这一节我们继续探索如何利用 Docker 来运行生产环境的服务。首先构建一个 Jekyll 静态Blog站(可以搭建在github.io上);接着构建一个无聊的 Java Web应用;最后再构建一个支持 Redis 集群的 Node.js 应用。
基本流程:首先创建一个 Jekyll 基础镜像和一个 Apache 镜像;然后从 Jekyll 镜像创建一个容器,存放通过卷挂载的源码;然后再从 Apache 镜像创建一个容器,利用包含编译后的网站的卷并为其服务;最后在网站需要更新时,清理并重复上面的步骤;
# Jekyll Dockerfile FROM registry.docker-cn.com/library/ubuntu:16.04 MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 RUN apt-get -yqq update && apt-get -yqq install ruby ruby-dev make nodejs RUN apt-get -yqq install build-essential RUN gem install --no-rdoc --no-ri jekyll VOLUME [ "/data", "/var/www/html" ] WORKDIR /data ENTRYPOINT [ "jekyll", "build", "--destination=/var/www/html" ] # sudo docker build -t leon/jekyll .
# Apache2 Dockerfile FROM registry.docker-cn.com/library/ubuntu:16.04 MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 RUN apt-get -yqq update && apt-get -yqq install apache2 VOLUME [ "/var/www/html" ] WORKDIR /var/www/html ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_LOG_DIR /var/log/apache2 ENV APACHE_PID_FILE /var/run/apache2.pid ENV APACHE_RUN_DIR /var/run/apache2 ENV APACHE_LOCK_DIR /var/lock/apache2 RUN mkdir -p $APACHE_RUN_DIR $APACHE_LOCK_DIR $APACHE_LOG_DIR EXPOSE 80 ENTRYPOINT [ "/usr/sbin/apache2" ] CMD ["-D", "FOREGROUND"] # sudo docker build -t leon/apache .
现在依次构建上面这2个镜像。接着我们在镜像同级目录 clone 一份源码:
$ git clone https://github.com/jamtur01/james_blog.git iblog
现在我们基于这个源码创建 Jekyll 容器和 Apache 容器:
$ sudo docker run -v /home/.../iblog:/data/ --name iblog leon/jekyll
$ sudo docker run -d -P --volumes-from iblog leon/apache
注:--volumes-from 把指定容器里的所有卷都加入新创建的容器里(指定卷可以不运行,但必须存在)。由于 apache 和 jekyll 使用的是同一个卷,现在我们可以直接通过 apache 映射的端口浏览网页了。
首先我们在宿主 iblog 里修改一些基本信息,如 _config.yml 中的 title;然后我们只需要再次启动 iblog 容器即可完成更新:$ sudo docker start iblog
$ sudo docker run --rm --volumes-from iblog -v $(pwd):/backup ubuntu tar cvf /backup/iblog_backup.tar /var/www/html
注:--rm 容器执行完毕后自动删除。
在静态 Jekyll 的基础上,我们可以进行一些简单的扩展,比如:
运行多个Apache容器,然后在这些Apache前加一个负载均衡器,这样就是一个简单的Web集群;
简单的可迁移通用方案:进一步构建一个镜像,把源数据复制(git clone)到卷里,再把这个卷挂载到leon/jekyll创建的容器;
在上一个扩展上我们可以构建一个Web前端,从指定的源自动构建和部署网站。
# fetcher Dockerfile FROM registry.docker-cn.com/library/ubuntu:16.04 MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 RUN apt-get -yqq update RUN apt-get -yqq install wget VOLUME [ "/var/lib/tomcat7/webapps/" ] WORKDIR /var/lib/tomcat7/webapps/ ENTRYPOINT [ "wget" ] CMD [ "-?" ] # sudo docker build -t leon/fetcher . # sudo docker run -t -i --name sample leon/fetcher https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war
首先构建一个用于下载 war 文件的镜像,然后启动容器,并指定下载一个 war 示例文件! 接着我们构建一个 Tomcat 应用服务器来运行这个 war 文件:
# tomcat7 Dockerfile FROM registry.docker-cn.com/library/ubuntu:16.04 MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 RUN apt-get -yqq update RUN apt-get -yqq install tomcat7 default-jdk ENV CATALINA_HOME /usr/share/tomcat7 ENV CATALINA_BASE /var/lib/tomcat7 ENV CATALINA_PID /var/run/tomcat7.pid ENV CATALINA_SH /usr/share/tomcat7/bin/catalina/sh ENV CATALINA_TMPDIR /tmp/tomcat7-tomcat7-tmp RUN mkdir -p $CATALINA_TMPDIR VOLUME [ "/var/lib/tomcat7/webapps/" ] EXPOSE 8080 ENTRYPOINT [ "/usr/share/tomcat7/bin/catalina.sh", "run" ] # sudo docker build -t leon/tomcat7 . # sudo docker run --name sample_app --volumes-from sample -d -P leon/tomcat7
构建并启动 Tomcat 容器运行 war 文件,现在我们可以浏览这个Web应用了:http://ip:port/sample
我们首先在宿主上安装一个已经构建好的基于Sinatra的Web应用:TProv(源码:https://github.com/turnbullpress/dockerbook-code/blob/master/code/6/tomcat/tprov/lib/tprov/app.rb),这个应用可以通过网页自动展示Tomcat应用。
$ sudo apt-get install ruby make ruby-dev
$ sudo gem install --no-rdoc --no-ri tprov
$ sudo tprov
现在我们可以浏览TProv网站了,我们可以在网页中直接提交并运行一个url.war应用;
这个示例会把一个使用Express框架、带有Redis后端的Node.js应用Docker化,我们将会如下部署:
一个Node容器,用来服务于Node应用,这个容器会链接到:
一个Redis主容器,用于保存和集群化应用状态,这个容器会链接到:
两个Redis副本容器,用于集群化应用状态;
一个日志容器,用于捕获应用日志;
先构建一个Node.js服务镜像,且包含Express应用和必要软件包:
$ mkdir nodeapp $ wget -P ./nodeapp https://raw.githubusercontent.com/oooline/dockerbook-code/master/code/6/node/nodejs/nodeapp/package.json $ wget -P ./nodeapp https://raw.githubusercontent.com/oooline/dockerbook-code/master/code/6/node/nodejs/nodeapp/server.js
# nodeapp Dockerfile FROM registry.docker-cn.com/library/ubuntu:16.04 MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 RUN apt-get -yqq update && apt-get -yqq install nodejs npm RUN ln -s /usr/bin/nodejs /usr/bin/node RUN mkdir -p /var/log/nodeapp ADD nodeapp /opt/nodeapp/ WORKDIR /opt/nodeapp RUN npm install VOLUME [ "/var/log/nodeapp" ] EXPOSE 3000 ENTRYPOINT [ "nodejs", "server.js" ] # sudo docker build -t leon/nodejs .
构建这个镜像。接下来我们准备构建一个 Redis 基础镜像,然后使用这个镜像构建 Redis 主副镜像:
# Redis Dockerfile FROM registry.docker-cn.com/library/ubuntu:16.04 MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 RUN apt-get -yqq update && apt-get -yqq install software-properties-common python-software-properties RUN add-apt-repository ppa:chris-lea/redis-server RUN apt-get -yqq update && apt-get -yqq install redis-server redis-tools VOLUME [ "/var/lib/redis", "/var/log/redis/" ] EXPOSE 6379 CMD [] # $ sudo docker build -t leon/redis .
先构建好 Redis 基础镜像!
# Redis Primary Dockerfile FROM leon/redis MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 ENTRYPOINT [ "redis-server", "--logfile /var/log/redis/redis-server.log" ] # sudo docker build -t leon/redis_primary . # Redis Replica Dockerfile FROM leon/redis MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 ENTRYPOINT [ "redis-server", "--logfile /var/log/redis/redis-replica.log", "--slaveof redis_primary 6379" ] # sudo docker build -t leon/redis_replica .
接着构建上述2个镜像:Redis 主镜像、Redis 副镜像。
首先创建一个网络:
$ sudo docker network create express
然后运行主 Redis (-h 主机名):
$ sudo docker run -d -h redisPrimary --net express --name redisPrimary leon/redis_primary
接下来我们创建一个 Redis 副本容器:$ sudo docker run -d -h redisReplica1 --name redisReplica1 --net express leon/redis_replica
现在我们创建第二个 Redis 副本容器:
$ sudo docker run -d -h redisReplica2 --name redisReplica2 --net express leon/redis_replica
现在我们可以启动 Node 容器了:
$ sudo docker run -d --name nodeapp -p 3000:3000 --net express leon/nodejs
现在我们可以使用浏览器访问这个应用了,正常会返回:{ "status": "ok" }
在上述 Redis 实例中,因为使用--logfile 指定了日志输出到 log 文件,所以如果想查看日志,我们可以通过 /var/log/redis 卷查看:
$ sudo docker run -ti --rm --volumes-from redisPrimary ubuntu cat /var/log/redis/redis-server.log
我们还可以搭建专业的日志服务器来集中管理,这里我们仅搭建 Logstash 日志服务器来捕获生产环境的日志;
# Logstash Dockerfile FROM registry.docker-cn.com/library/ubuntu:16.04 MAINTAINER Leon "test@weippt.com" ENV REFRESHED_AT 2017-07-15 RUN apt-get -yqq update && apt-get -yqq install wget RUN wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add - RUN sudo apt-get -yqq install apt-transport-https RUN echo "deb https://artifacts.elastic.co/packages/6.x/apt stable main" > /etc/apt/sources.list.d/elastic-6.x.list RUN apt-get -yqq update && apt-get -yqq install logstash ADD logstash.conf /etc/ WORKDIR /opt/logstash ENTRYPOINT [ "/usr/share/logstash/bin/logstash" ] CMD [ "--config=/etc/logstash.conf" ]
# logstash.conf input { file { type => "syslog" path => ["/var/log/nodeapp/nodeapp.log", "/var/log/redis/redis-server.log"] } } output { stdout { codec => rubydebug } }
构建日志镜像:$ sudo docker build -t leon/logstash .
启动容器:$ sudo docker run -d --name logstash --volumes-from redisPrimary --volumes-from nodeapp leon/logstash
在这个容器中我们挂载了 nodeapp 和 redis 的卷并监控了 path 指定的2个log文件。配置中的 output 为了演示设置输出为标准输出,实际应用中,一般会将起配置输出到 Elasticsearch 集群或其他目的地。
现在我们可以刷新刚刚nodeapp应用,然后就能看到这个动作日志:$ sudo docker logs -f logstash (注:如果Logstash容器总是退出,重新build镜像(不执行logstash),然后bash进去手动执行logstash,这里应该可以正常监听。)