2017-07-15 23:15

(七)Docker 应用实例 - 卷、集群、日志

这一节我们继续探索如何利用 Docker 来运行生产环境的服务。首先构建一个 Jekyll 静态Blog站(可以搭建在github.io上);接着构建一个无聊的 Java Web应用;最后再构建一个支持 Redis 集群的 Node.js 应用。

 构建 Jekyll 应用

基本流程:首先创建一个 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 映射的端口浏览网页了。

更新 Jekyll

首先我们在宿主 iblog 里修改一些基本信息,如 _config.yml 中的 title;然后我们只需要再次启动 iblog 容器即可完成更新:$ sudo docker start iblog

备份 Jekyll 卷

$ sudo docker run --rm --volumes-from iblog -v $(pwd):/backup ubuntu tar cvf /backup/iblog_backup.tar /var/www/html

注:--rm 容器执行完毕后自动删除。

扩展 Jekyll

在静态 Jekyll 的基础上,我们可以进行一些简单的扩展,比如:

  • 运行多个Apache容器,然后在这些Apache前加一个负载均衡器,这样就是一个简单的Web集群;

  • 简单的可迁移通用方案:进一步构建一个镜像,把源数据复制(git clone)到卷里,再把这个卷挂载到leon/jekyll创建的容器;

  • 在上一个扩展上我们可以构建一个Web前端,从指定的源自动构建和部署网站。

构建一个Java应用

# 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应用;

多容器的 Node.js 应用

这个示例会把一个使用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 副镜像。

创建 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" }

日志服务器(Logstash、ElasticSearch、Kibana)

在上述 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,这里应该可以正常监听。)