Docker新手入门之六:Docker在生产环境中的使用实践

转载过程中,图片丢失,代码显示错乱。

为了更好的学习内容,请访问原创版本:

https://www.missshi.cn/api/view/blog/5a6328110a745f6335000006

Ps:初次访问由于js文件较大,请耐心等候(5s左右)

 

 

在之前的文章中,我们简要介绍的Docker的一些简单实践。

接下来,我们将在本文中进行类似内容的讲解,不过相比而言,使用Docker部署的服务将会更加的复杂。

搭建Jekyll框架的网站

本文中我们将要构建的第一个应用是一个使用Jekyll框架的自定义网站。 
该网站会包括以下两个镜像:

  • 一个镜像安装了Jekyll及其相关的软件包
  • 一个镜像安装了Apache服务用于使网站正常运行。

则以后开发过程可以转化如下:

  1. 创建Jekyll基础镜像和Apache镜像
  2. 利用Jekyll镜像创建一个容器并通过卷挂载网站的源代码。
  3. 使用Apache镜像创建一个容器,这个容器利用包含编译后的网站的卷并为其服务。
  4. 网站更新时跳转至步骤2重复执行。

Jekyll基础镜像

首先,我们需要创建第一个Jekyll基础镜像。 
我们需要创建一个新的目录并创建该镜像所需要的Dockerfile文件。


  1. mkdir jekyll
  2. cd jekyll
  3. touch Dockerfile

修改Dockerfile文件如下:


  1. FROM ubuntu:16.04
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2018-02-17
  4. RUN apt-get -qq update
  5. RUN apt-get -qq install ruby ruby-dev build-essential nodejs libffi-dev
  6. RUN gem install --no-rdoc --no-ri jekyll
  7. VOLUME /data
  8. VOLUME /var/www/html
  9. WORKDIR /data
  10. ENTRYPOINT [ "jekyll", "build", "--destination=/var/www/html" ]

在该Dockerfile文件中,我们首先安装了Ruby和相关的包,此外,我们使用VOLUME指令创建了两个卷:/data/用于存放网站的源代码,/var/www/html用于存放编译后的Jekyll文件。

构建Jekyll基础镜像

创建好Dockerfile文件后,我们可以执行如下命令来构建镜像:


  1. docker build -t nianshi/jekyll .

命令执行完成后,我们则创建完成了一个jekyll的基础镜像。

Apache基础镜像

接下来,我们需要继续创建一个新的文件夹用于构建Apache镜像。


  1. mkdir apache
  2. cd apache
  3. touch Dockerfile

修改Dockerfile文件如下:


  1. FROM ubuntu:16.04
  2. MAINTAINER nianshi <[email protected].com>
  3. RUN apt-get -qq update
  4. RUN apt-get -qq install apache2
  5. VOLUME [ "/var/www/html" ]
  6. WORKDIR /var/www/html
  7. ENV APACHE_RUN_USER www-data
  8. ENV APACHE_RUN_GROUP www-data
  9. ENV APACHE_LOG_DIR /var/log/apache2
  10. ENV APACHE_PID_FILE /var/run/apache2.pid
  11. ENV APACHE_RUN_DIR /var/run/apache2
  12. ENV APACHE_LOCK_DIR /var/lock/apache2
  13. RUN mkdir -p $APACHE_RUN_DIR $APACHE_LOCK_DIR $APACHE_LOG_DIR
  14. EXPOSE 80
  15. ENTRYPOINT [ "/usr/sbin/apache2" ]
  16. CMD ["-D", "FOREGROUND"]

构建Apache镜像

创建好Dockerfile文件后,我们可以执行如下命令来构建镜像:


  1. docker build -t nianshi/apache .

命令执行完成后,我们则创建完成了一个apache的基础镜像。

启动Jekyll网站

现在,我们已经完成了两个基础镜像的构建了。 
下面,我们需要启动容器来构建我们的网站了: 
首先需要下载网站所需要的源代码:http://www.missshi.cn/#/adminBook?_k=95xjmw 
访问资源下载页面,搜索Jekyll示例服务依赖代码下载并解压即可,此处我们假设解压目录是/home/nianshi/blog_demo。 
此时,我们可以执行如下命令来启动Jekyll容器:


  1. docker run -v /home/nianshi/blog_demo:/data/ --name blog_demo nianshi/jekyll

此时,我们启动了一个blog_demo的容器,并对卷进行了挂载,其中,本地的/home/nianshi/blog_demo目录挂载到的容器中的/data/文件夹下。另外容器对源代码编译后生成的代码会存放在/var/www/html/卷下。

接下来,我们期望启动一个Apache容器,同时希望Apache容器可以连接到Jekyll容器中/var/www/html/卷下获取编译后的文件。 
我们可以执行如下命令:


  1. docker run -d -P --name apache --volumes-from blog_demo nianshi/apache

在上述命令中,出现了一个新的参数--volumes-from,该参数用于连接一个卷,从而可以访问到指定容器中的所有的卷。 
Ps:当所有使用某个卷的容器全部都被rm命令删除后,该卷会被自动释放。

下面我们来查看一下我们的容器映射到了机器的哪个端口吧:


  1. docker port apache 80
  2. # 0.0.0.0:32782

接下来,我们访问32782端口,可以看到如下页面: 
Docker新手入门之六:Docker在生产环境中的使用实践

更新Jekyll网站

如果需要更新网站的数据,假设要修改Jekyll网站的博客名字,我们只需要通过编辑宿主机上james_blog/_config.yml文件:并将title域改为Nianshi Blog。 
然后通过使用docker start命令启动Docker容器即可。 
可以看到,Jekyll编译过程第二次被运行,并且往网站已经被更新。这次更新已经写入了对应的卷。现在浏览Jekyll网站,就能看到变化了。 
由于共享的卷会自动更新,这一切都不要更新或者重启Apache容器。这个流程非常简单,可以将其扩展到更复杂的部署环境。

备份Jekyll卷

如果担心一不小心删除卷。 
由于卷的优点之一就是可以挂载到任意的容器,因此可以轻松备份它们。 
现在创建一个新容器,用来备份/var/www/html卷。


  1. docker run --rm --volumes-from blog_demo -v $(pwd):/backup ubuntu tar cvf /backup/blog_demo_backup.tar /var/www/html

上述代码描述了一个最简单的备份方式,基于基础的ubuntu镜像,通过执行tar cvf命令,将卷/var/www/html中的内容压缩为blog_demo_backup.tar文件,并存储到机器当前目录中。

服务扩展方式

  • 首先,我们可以通过运行多个Apache容器,这些容器同时共享blog_demo*享的卷,同时在多个Apache容器前使用一个负载均衡器从而构成一个Web集群。
  • 进一步构建一个镜像,用于把拉取源代码(如git clone)过程打包入卷中,在将该卷挂载到jekyll容器中,从而可以生成一个快速迁移的通用解决方案。

使用Docker构建一个Java项目

接下来,我们考虑用Docker构建一个Java项目。 
具体来说,主要分为两步:

  1. 从指定URL中拉取指定的WAR文件并将其保存到卷中。
  2. 利用一个含有Tomcat服务器的镜像运行拉取的WAR文件。

WAR文件获取器及其构建

首先创建一个新的目录用于准备构建WAR文件获取器镜像:


  1. mkdir fetcher
  2. cd fetcher
  3. touch Dockerfile

接下来,修改Dockerfile文件如下:


  1. FROM ubuntu:16.04
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2018-02-18
  4. RUN apt-get -qq update
  5. RUN apt-get -qq install wget
  6. VOLUME [ "/var/lib/tomcat7/webapps/" ]
  7. WORKDIR /var/lib/tomcat7/webapps/
  8. ENTRYPOINT [ "wget" ]
  9. CMD [ "--help" ]

该镜像只完成一件非常简单的事,使用wget从指定的url中拉取文件并保存在/var/lib/tomcat7/webapps/卷中。后续我们会将该卷共享到Tomcat服务器中。

下面,我们可以执行如下命令来构建镜像:


  1. docker build -t nianshi/fetcher .

获取WAR文件

下面,我们以获取示例文件来启动我们刚才构建好的镜像:


  1. docker run -it --name fetcher nianshi/fetcher https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war
  2. # --2018-02-18 01:55:13-- https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war
  3. # Resolving tomcat.apache.org (tomcat.apache.org)... 140.211.11.105, 195.154.151.36, 2001:bc8:2142:300::
  4. # Connecting to tomcat.apache.org (tomcat.apache.org)|140.211.11.105|:443... connected.
  5. # HTTP request sent, awaiting response... 200 OK
  6. # Length: 4606 (4.5K)
  7. # Saving to: 'sample.war'
  8. #
  9. # sample.war # 100%[===================================================================>] 4.50K --.-KB/s in 0s
  10. #
  11. # 2018-02-18 01:55:15 (217 MB/s) - 'sample.war' saved [4606/4606]

我们可以在Docker容器中切换到/var/lib/tomcat7/webapps/目录下,我们可以找到刚刚下载得到的sample.war文件。 
同时,我们也可以在宿主机上找到该文件:


  1. docker inspect -f "{{ .Mounts }}" fetcher
  2. # [{642e56f6be0bb520661678cfa61f8c0182d3426a1b44092801bfcc20a154025f /mnt/docker_hub/volumes/642e56f6be0bb520661678cfa61f8c0182d3426a1b44092801bfcc20a154025f/_data /var/lib/tomcat7/webapps local true }]

该命令可以查询fetcher容器的Mounts所在的文件夹。其中,/mnt/docker_hub/volumes/642e56f6be0bb520661678cfa61f8c0182d3426a1b44092801bfcc20a154025f/_data就是该卷在我们实际宿主机的位置。

Tomcat7服务器构建

下面,我们需要继续构建一个Tomcat7服务器镜像。


  1. mkdir tomcat7
  2. cd tomcat7
  3. touch Dockerfile

修改Dockerfile如下:


  1. FROM ubuntu:16.04
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2018-02-18
  4. RUN apt-get -qq update
  5. RUN apt-get -qq install tomcat7 default-jdk
  6. ENV CATALINA_HOME /usr/share/tomcat7
  7. ENV CATALINA_BASE /var/lib/tomcat7
  8. ENV CATALINA_PID /var/run/tomcat7.pid
  9. ENV CATALINA_SH /usr/share/tomcat7/bin/catalina.sh
  10. ENV CATALINA_TMPDIR /tmp/tomcat7-tomcat7-tmp
  11. RUN mkdir -p $CATALINA_TMPDIR
  12. VOLUME [ "/var/lib/tomcat7/webapps/" ]
  13. EXPOSE 8080
  14. ENTRYPOINT [ "/usr/share/tomcat7/bin/catalina.sh", "run" ]

下面,我们来构建一下该镜像:


  1. docker build -t nianshi/tomcat7 .

运行WAR文件

下面,我们来启动Tomcat容器来运行一下示例应用吧:


  1. docker run --name tomcat --volumes-from fetcher -d -P nianshi/tomcat7

该命令将会创建一个tomcat容器,并关联fetcher容器*享的卷。 
下面我们来查看一下映射在宿主机的端口


  1. docker port tomcat 8080
  2. # 0.0.0.0:32768

接下来,我们在浏览器中打开该页面,页面显示如下: 
Docker新手入门之六:Docker在生产环境中的使用实践

多容器应用栈

接下来,我们将分享一个使用Express框架的,带有Redis后端的Node.js的应用。 
在本应用中,我们需要构建如下一系列镜像:

  1. 一个Node容器,服务于Node应用
  2. 一个Redis主容器,用于保存和集群化状态管理
  3. 两个Redis备份容器,用于集群化应用状态
  4. 一个日志容器,用于收集日志。

Node.js镜像

首先,同样是需要创建一个新的文件夹用于构建Node.js镜像。


  1. mkdir nodejs
  2. cd nodejs
  3. mkdir -p nodeapp
  4. touch Dockerfile

修改Dockerfile文件如下:


  1. FROM ubuntu:16.04
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2018-02-18
  4. RUN apt-get -qq update
  5. RUN apt-get -qq install nodejs npm
  6. RUN ln -s /usr/bin/nodejs /usr/bin/node
  7. RUN mkdir -p /var/log/nodeapp
  8. ADD nodeapp /opt/nodeapp/
  9. WORKDIR /opt/nodeapp
  10. RUN npm install
  11. VOLUME [ "/var/log/nodeapp" ]
  12. EXPOSE 3000
  13. ENTRYPOINT [ "nodejs", "server.js" ]

在该Dockerfile文件中,我们使用ADD命令将nodeapp文件夹添加到了容器的/usr/bin/node目录下。其中nodeapp文件夹存储的是配置文件和实际代码。 
Ps:代码可以访问http://www.missshi.cn/#/books?_k=xmjul9,搜索Node.js示例服务依赖代码,下载并解压至nodeapp文件夹即可。 
其中,server.js文件如下:


  1. var fs = require('fs');
  2. var express = require('express'),
  3. app = express(),
  4. redis = require('redis'),
  5. RedisStore = require('connect-redis')(express),
  6. server = require('http').createServer(app);
  7. var logFile = fs.createWriteStream('/var/log/nodeapp/nodeapp.log', {flags: 'a'});
  8. app.configure(function() {
  9. app.use(express.logger({stream: logFile}));
  10. app.use(express.cookieParser('keyboard-cat'));
  11. app.use(express.session({
  12. store: new RedisStore({
  13. host: process.env.REDIS_HOST || 'redis_primary',
  14. port: process.env.REDIS_PORT || 6379,
  15. db: process.env.REDIS_DB || 0
  16. }),
  17. cookie: {
  18. expires: false,
  19. maxAge: 30 * 24 * 60 * 60 * 1000
  20. }
  21. }));
  22. });
  23. app.get('/', function(req, res) {
  24. res.json({
  25. status: "ok"
  26. });
  27. });
  28. app.get('/hello/:name', function(req, res) {
  29. res.json({
  30. hello: req.params.name
  31. });
  32. });
  33. var port = process.env.HTTP_PORT || 3000;
  34. server.listen(port);
  35. console.log('Listening on port ' + port);

下面,我们来构建容器


  1. docker build -t nianshi/nodejs .

Redis基础镜像

下面,我们需要继续构建Redis基础镜像。后续会根据该基础镜像来构建主从镜像。


  1. mkdir redis_base
  2. cd redis_base
  3. touch Dockerfile

编写Dockerfile文件如下:


  1. FROM ubuntu:16.04
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2018-02-18
  4. RUN apt-get -qq update
  5. RUN apt-get install -qq software-properties-common python-software-properties
  6. RUN add-apt-repository ppa:chris-lea/redis-server
  7. RUN apt-get -qq update
  8. RUN apt-get -qq install redis-server redis-tools
  9. VOLUME [ "/var/lib/redis", "/var/log/redis" ]
  10. EXPOSE 6379
  11. CMD []

下面我们来构建Redis基础镜像:


  1. docker build -t nianshi/redis_base .

Redis主镜像

接下来,我们基于Redis基础镜像来构建我们的Redis主镜像。


  1. mkdir redis_primary
  2. cd redis_primary
  3. touch Dockerfile

编辑Dockerfile文件如下:


  1. FROM nianshi/redis_base
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2016-06-01
  4. ENTRYPOINT [ "redis-server", "--protected-mode no", "--logfile /var/log/redis/redis-server.log" ]

构建该镜像:


  1. docker build -t nianshi/redis_primary .

Redis从镜像

同样,我们来继续构建构建从镜像:


  1. mkdir redis_slave
  2. cd redis_slave
  3. touch Dockerfile

编辑Dockerfile文件如下:


  1. FROM nianshi/redis_base
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2018-02-18
  4. ENTRYPOINT [ "redis-server", "--protected-mode no", "--logfile /var/log/redis/redis-replica.log", "--slaveof redis_primary 6379" ]

构建该镜像:


  1. docker build -t nianshi/redis_slave .

启动Redis后端集群

现在Redis的主从镜像都已经准备好了,我们来启动Redis后端集群。 
首先启动主容器:


  1. docker run -d -h redis_primary --name redis_primary nianshi/redis_primary

在该命令中,我们使用了-h命令,-h命令用于设置容器的主机名,从而保证本地DNS可以正确解析。 
下面,我们来查看一下容器的日志吧,由于此处我们将容器日志定向到了指定的日志文件夹,而不是标准输出,因此我们不能通过之前的docker logs命令来查看。 
而是可以通过如下命令来查看日志:


  1. docker run -it --rm --volumes-from redis_primary ubuntu cat /var/log/redis/redis-server.log

其中,我们启动了另外一个容器,该容器连接至Redis容器的卷用于查询日志,其中--rm命令表示该容器运行结束后自动删除。 
日志观察到我们的Redis服务已经正常启动了,下面我们可以继续创建Redis从服务了。


  1. docker run -d -h redis_slave --name redis_slave --link redis_primary:redis_primary nianshi/redis_slave

其中,--link参数用于将redis_primary容器以别名redis_primary连接到Redis从容器。 
下面,我们来查看一下新容器的日志:


  1. docker run -it --rm --volumes-from redis_slave ubuntu cat /var/log/redis/redis-replica.log

日志显示我们新启动的容器已经成功连接至Redis主容器。 
下面,我们可以再启动第二个Redis从容器来提高服务的可靠性:


  1. docker run -d -h redis_slave2 --name redis_slave2 --link redis_primary:redis_primary nianshi/redis_slave

启动Node容器

目前,我们的后台Redis集群已经可以正常运行了,下面,我们可以启动Node容器了:


  1. docker run -d --name nodeapp -p 4000:3000 --link redis_primary:redis_primary nianshi/nodejs

其中,-p 4000:3000表示将容器的3000端口映射宿主机的4000端口。 
此时,使用浏览器打开该页面即可: 
Docker新手入门之六:Docker在生产环境中的使用实践 
输出为


  1. {
  2. "status": "ok"
  3. }

表示服务目前正常工作,浏览器的会话状态被先记录到Redis的主容器后同步到了两个Redis从服务器上。

捕获应用日志

在生产环境中,我们需要确保可以捕获日志并保存到日志服务器中。 
此处,我们以Logstash为例进行讲解,首先需要创建一个Logstash镜像:


  1. mkdir logstash
  2. cd logstash
  3. touch Dockerfile

修改Dockerfile如下:


  1. FROM ubuntu:16.04
  2. MAINTAINER nianshi <[email protected].com>
  3. ENV REFRESHED_AT 2018-02-18
  4. RUN apt-get -qq update
  5. RUN apt-get -qq install wget
  6. RUN wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add -
  7. RUN echo 'deb http://packages.elasticsearch.org/logstash/1.5/debian stable main' > /etc/apt/sources.list.d/logstash.list
  8. RUN apt-get -qq update
  9. RUN apt-get -qq install logstash default-jdk
  10. ADD logstash.conf /etc/
  11. WORKDIR /opt/logstash
  12. ENTRYPOINT [ "bin/logstash" ]
  13. CMD [ "--config=/etc/logstash.conf" ]

此时,创建镜像的过程中用到了一个配置文件logstash.conf,内容如下:


  1. input {
  2. file {
  3. type => "syslog"
  4. path => ["/var/log/nodeapp/nodeapp.log", "/var/log/redis/redis-server.log"]
  5. }
  6. }
  7. output {
  8. stdout {
  9. codec => rubydebug
  10. }
  11. }

该配置文件表示logstash会监控两个文件:/var/log/nodeapp/nodeapp.log/var/log/redis/redis-server.log,将其中新的内容发送给logstash。 
下面,我们来构建logstash镜像:


  1. docker build -t nianshi/logstash .

镜像构造完成后,我们可以执行如下命令来启动容器:


  1. docker run -d --name logstash --volumes-from redis_primary --volumes-from nodeapp nianshi/logstash

容器启动后,我们可以执行如下命令来查看logstash容器的输出日志:


  1. docker logs -f logstash

在之前的配置文件中,我们选择将日志进行标准输出,实际在生产环境中,我们更多的选择是会存储到Elasticsearch里。

管理Docker容器的推荐方式

传统上,我们管理机器的方式通常是通过SSH登入运行环境中来管理服务。 
但是对于Docker而言,大部分容器内部仅仅只是运行一个进程,因此不推荐这种方式。 
如果其中在Docker容器中执行某些命令,更推荐的方式如下:


  1. docker exec [参数] 容器名称 [执行命令]

 

更多更详细的内容,请访问原创网站:

https://www.missshi.cn/api/view/blog/5a6328110a745f6335000006

Ps:初次访问由于js文件较大,请耐心等候(5s左右)