第6章 使用Docker构建服务–小铁笔记

2年前 (2016-02-16) admin 《第一本Docker书》 1评论 已收录 6070℃

第6章 使用Docker构建服务

6.1 构建第一个应用

要构建的第一个应用是使用Jekyll框架的自定义网站,会构建以下两个镜像:

。一个镜像安装了Jekyll及其它用于构建Jekyll网站的必要的软件包

。一个镜像通过Apache来让Jekyll网站工作起来。

在启动容器时,通过创建一个新的Jekyll网站来实现自服务,工作流程如下:

。创建Jekyll基础镜像和Apache镜像(只需要构建一次)

。从Jekyll镜像创建一个容器,这个容器存放通过卷挂载的网站源代码。

。从Apache镜像创建一个容器,这个容器利用包含编译后的网站的卷,并为其服务。

。在网站需要更新时,清理并重复上面的步骤

6.1.1 Jekyll基础镜像

-->mkdir -p jekyll/jekyll

-->cd jekyll

-->vim Dockerfile

FROM ubuntu:14.04

MAINTAINER James Turnbull <james@example.com>

ENV REFRESHED_AT 2015-09-09

RUN apt-get -y update

RUN apt-get -y install ruby ruby-dev make nodejs

RUN gem install --no-rdoc --no-ri jekyll

RUN mkdir /data /var/www/html

VOLUME /data/

VOLUME /var/www/html

WORKDIR /data

ENTRYPOINT [ "jekyll", "build", "--destination=/var/www/html" ]

这个Dockerfile使用了第三章的模板作为基础,镜像基于ubuntu 14.04,并且安装了Ruby

和用于支持Jekyll的包,然后使用VOLUME指令创建了以下两个卷。

。/data/ ,用来存放网站的源代码。

。/var/www/html,用来存放编译后的Jekyll网站码。

设置工作目录为/data,并通过ENTRYPOINT指令指定自动构建的命令。这个命令会将工作目录

/data/中的所有的Jekyll网站代码构建到/var/www/html目录。

6.1.2 构建Jekyll基础镜像

-->docker build -t jamtur01/jekyll .

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 3f126a502d09
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Running in 92ee0cb8c5b8
---> 10b317f5826d
Removing intermediate container 92ee0cb8c5b8
Step 2 : RUN apt-get -y update
---> Running in e0713c8a7d05
...........................................................

---> 3f99ce4730de
Step 12 : RUN mkdir -p $APACHE_RUN_DIR $APACHE_LOCK_DIR $APACHE_LOG_DIR
---> Using cache
---> 059d48b82c99
Step 13 : EXPOSE 80
---> Using cache
---> bec93e4be569
Step 14 : ENTRYPOINT /usr/sbin/apache2
---> Using cache
---> f88f7925d442
Step 15 : CMD -D FOREGROUND
---> Using cache
---> 5020558062a8
Successfully built 5020558062a8

这样就构建了名为jamtur01/jekyll、ID为5020558062a8的新镜像。这就是将要使用的新的Jekyll镜像。

。查看镜像:

-->docker images

REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
jamtur01/jekyll latest 5020558062a8 12 minutes ago 265.5 MB

6.1.3 Apache镜像

构建Apache镜像,一个用来架构新网站的Apache服务器,先创建一个新目录和一个空的Dockerfile。

-->cd jekyll/

-->mkdir apache

-->cd apache

-->vim Dockerfile

FROM ubuntu:14.04
MAINTAINER James Turnbull <james@example.com>

RUN apt-get -y update
RUN apt-get -y 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"]

这个镜像是基于Ubuntu 14.04的,并安装了Apache,然后使用VOLUME指令创建了一个卷,即

/var/www/html,用来存放编译后的Jekyll网站,然后将/var/www/html设为工作目录。

然后使用了ENV指令设置了一些必要的环境变量,创建了必要的目录,并且使用EXPOSE公开了

80端口,最后指定了ENTRYPOINT和CMD指令组合来在容器启动时默认运行Apache。

6.1.4 构建Jekylll Apache镜像

-->docker build -t jamtur01/apache .

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 3f126a502d09
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 10b317f5826d
Step 2 : RUN apt-get update
---> Using cache
---> 8bca2ed2a6d7
Step 3 : RUN apt-get -y install apache2
---> Running in be9390af55b5

。。。。。。。。。。。。。。。。。。。。。。。。。。。。
Removing intermediate container 78425417dac4
Step 13 : EXPOSE 80
---> Running in 67d4bf9153c3
---> bec93e4be569
Removing intermediate container 67d4bf9153c3
Step 14 : ENTRYPOINT /usr/sbin/apache2
---> Running in 6c48e1b8aea8
---> f88f7925d442
Removing intermediate container 6c48e1b8aea8
Step 15 : CMD -D FOREGROUND
---> Running in cedefba8bcd8
---> 5020558062a8
Removing intermediate container cedefba8bcd8
Successfully built 5020558062a8

这样,就构建了名为jamtur01/apache,ID为5020558062a8的新镜像,这就是要使用的Apache镜像。

。查看新镜像

-->docker images

REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
jamtur01/apache latest 5020558062a8 42 seconds ago 265.5 MB

6.1.5 启动jekylll网站

现在已经有了两个镜像:

。Jekyll:安装了Ruby及其它必备软件包的Jekyll镜像。

。Apache:通过Apache Web服务器来让Jekyll网站工作起来的镜像。

使用docker run命令创建一个新的Jekyll容器开始我们的网站,启动容器,并构建网站。

然后需要一些博客的源代码,先把示例Jekyll博客复制到$HOME中(/home/james)。

-->cd $HOME

-->git clone https://github.com/jamtur01/james_blog.git

正克隆到 'james_blog'...
remote: Counting objects: 83, done.
remote: Total 83 (delta 0), reused 0 (delta 0), pack-reused 83
Unpacking objects: 100% (83/83), done.
检查连接... 完成。

在整个目录下可以看到一个启用了Twitter Bootstrap的最基础的Jekyll博客。如果想使用这个博客,

可以修改_config.xml文件和主题,以符合你的要求。

-->ls james_blog/

404.html atom.xml _config.yml _includes pages.html Rakefile sitemap.txt
archive.html categories.html _drafts index.md _plugins README.md tags.html
assets changelog.md History.markdown _layouts _posts rss.xml

-->cd james_blog/

-->docker run -v /root/james_blog:/data/ --name james_blog jamtur01/jekyll

提示:

AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2.
Set the 'ServerName' directive globally to suppress this message

解决方法:
将 apache 的配置文件httpd.conf中的 ServerName 改成可用域名或如下配置:ServerName localhost:80

===================================================================================================

现在启动了一个叫做james_blog的新容器,把本地的james_blog目录作为/data卷挂载到容器里。容器

已经拿到网站的源代码,并将其构建到已编译的网站,存放到/var/www/html/目录。

卷是在一个或多个容器中特殊指定的目录,卷会绕过联合文件系统,为持久化数据和共享数据提供几个

有用的特性。

。卷可以在容器间共享和重用

。共享卷时不一定要运行相应的容器

。对卷的修改会直接在卷上反应出来。

。更新镜像时不会包含对卷的修改。

。卷会一直存在,直到没有容器使用它们。

利用卷,可以在不用提交镜像修改的情况下,向镜像内加入数据(如源代码、数据或者其他内容),并且

可以在容器间共享这些数据。

卷在Docker宿主机的/var/lib/docker/volumes目录中。可以通过docker inspect命令查看某个卷的挂载位置。

-->docker inspect -f "{{ .Volumes }}"

所以,如果想在另一个容器里使用/var/www/html 卷里编译好的网站,可以创建一个新的连接到这个卷的容器。

-->docker run -d -P --volumes-from james_blog jamtur01/apache

参数:--volume-from,把指定容器里的所有卷都加入新创建的容器里。这意味着,Apache容器可以访问之前创建

的james_blog容器里/var/www/html卷存放的编译后的Jekyll网站。即便james_blog容器没有运行,Apache容器也可以

访问这个卷。如果用docker rm命令删除了james_blog容器,那么这个卷和卷里的内容也就不存在了。

。查看端口映射

-->docker port 09a570cc2267 80

。Docker宿主机上浏览该网站:

-->

6.1.6 更新Jekyll网站

如果要更新网站的数据,假设要修改Jekyll网站,将通过编辑james_blog/_config.yml 文件来修改博客的名字。

-->vim james_blog/_config.yml

将title域改为James' Dynamic Docker-driven Blog。

。更新博客

-->docker start james_blog #再次启动Docker容器即可。

。查看容器日志:

-->docker logs james_blog

可以看到,Jekyll编译过程第二次被执行,并且网站已经被更新。这次更新已经写入了对应的卷。

。浏览Jekyll网站:

-->

由于共享的卷会自动更新,这一切都不需要更新或者重启Apache容器,这个流程非常简单,可以将其扩展到更复杂的

部署环境。

6.1.7 备份Jekyll卷

。创建新容器,用来备份/var/www/html卷

-->docker run --rm --volumes james_blog -v $(pwd):/backup ubuntu tar cvf /backup/james_blog_backup.tar /var/www/html

参数--rm:表示使用一次就删除容器。

此处运行一个已有的Ubuntu容器,并把james_blog的卷挂载到该容器里。这会在该容器里创建/var/www/html目录。然后使用-v标志把

当前目录(通过$pwd命令获得),挂载到容器的/backup目录,最后容器运行这一备份命令。

提示:这个例子对卷中存储的数据库或者其它类似的数据也适用,只要简单地把卷挂载到新容器,完成备份,然后废弃这个用于备份的

容器就可以了。

6.1.8 扩展Jekyll示例网站

下面几种扩展Jekyll网站的方法

。运行多个Apache容器,这些容器都使用来自james_blob容器的卷。在这些Apache容器前面加一个负载均衡器,就拥有了一个Web集群。

。进一步构建一个镜像,这个镜像把用户提供的源数据复制到卷里,再把这个卷挂载到从jamtur01/jekyll镜像创建的容器。这就是一个

可迁移的通用方案,而且不需要宿主机本地包含任何源代码。

。在上一个扩展基础上可以很容易为服务构建一个Web前端,这个服务用于从指定的源自动构建和部署网站。这样就有一个完全属于自己

的GitHub Pages了。

http://aws.amazon.com/s3

http://www.amanda.org

6.2 使用Docker构建一个Java应用服务

工作目标:获取Tomcat服务器上的WAR文件

2个步骤:

。一个镜像从ULR拉取指定的WAR文件并将其保存到卷里。

。一个含有Tomcat服务器的镜像运行这些下载的WAR文件。

6.2.1 WAR文件的获取器

构建一个镜像,这个镜像会下载WAR文件并将其挂载在卷里:

-->mkdir fetcher

-->cd fetcher

-->vim Dockerfile

FROM ubuntu:14.04

MAINTAINER James Turnbull <james@example.com>

ENV REFRESHED_AT 2015-09-09

RUN apt-get update

RUN apt-get -y install wget

RUN mkdir -p /var/lib/tomcat7/webapps

VOLUME [ "/var/lib/tomcat7/webapps/" ]

WORKDIR /var/lib/tomcat7/webapps/

ENTRYPOINT [ "wget" ]

CMD [ "-?" ]

此Dockerfile使用wget从指定的URL获取文件并把文件保存在/var/lib/tomcat7/webapps/目录。

这个目录也是卷,并且是所有容器的工作目录,然后把这个卷共享给Tomcat服务器容器运行里面的内容。

最后,如果没有指定URL,ENTRYPOINT和CMD指令会让容器运行,在容器不带URL运行的时候,这两条指令

通过返回wget帮助来做到这一点。

。构建这个镜像

-->docker build -t jamtur01/fetcher .

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 3f126a502d09
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 10b317f5826d
Step 2 : ENV REFRESHED_AT 2015-09-09
---> Running in d57d943c8c70
---> a1927b3157eb
Removing intermediate container d57d943c8c70
Step 3 : RUN apt-get update
---> Running in ca9a01a11714

.....................................................

Removing intermediate container 4e1c1bf66ff4
Step 8 : ENTRYPOINT wget
---> Running in 22a1e5aa0ae6
---> 51338e1bfb9e
Removing intermediate container 22a1e5aa0ae6
Step 9 : CMD -?
---> Running in 1150cf32f97f
---> 452a3a720b32
Removing intermediate container 1150cf32f97f
Successfully built 452a3a720b32

6.2.2 获取WAR文件

获取一个示例文件来启动新镜像。从Tomcat官网下载Apache Tomcat示例应用:

-->docker run -t -i --name sample jamtur01/fetcher https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war

可以看到,容器通过下载了sample.war文件。从输出结果看不出最终的保存路径,但是因为设置了容器的工作目录,sample.war文件最终

会保存到/var/lib/tomcat7/webapps/ 目录下。

。查找卷的存储位置

-->docker inspect -f "{{ .Volumes }}" sample

map[/var/lib/tomcat7/webapps:/var/lib/docker/volumes/58c33fe4944609cb2a086a39f3541df81c1beeca6fe0faeb3056895d56c9be18/_data]

。查看这个目录

-->ls -l /var/lib/docker/volumes/

总用量 96
drwxr-xr-x 3 root root 4096 9月 10 10:24 0d8e6e87b4251396833e6e19f9078cea93668fb8e278697f24c9d324c17e11fd

6.2.3 Tomcat7应用服务器

-->mkdir tomcat7

-->cd tomcat7

-->vim Dockerfile

FROM ubuntu:14.04

MAINTAINER James Turnbull <james@example.com>

ENV REFRESHED_AT 2015-09-10

RUN apt-get update

RUN apt-get -y 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" ]

这个镜像需要安装Java JDK和Tomcat服务器,首先制定一些启动Tomcat需要的环境,然后创建一个临时目录,

还创建了/var/lib/tomcat7/webapps/卷,公开了tomcat 8080端口,最后使用ENTRYPOINT指令来启动Tomcat。

。构建Toomcat 7镜像

-->docker build -t jamtur01/tomcat7 .

Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 3f126a502d09
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 10b317f5826d
Step 2 : ENV REFRESHED_AT 2015-09-10
---> Running in f210401179c4
---> a5cad6395685
Removing intermediate container f210401179c4
Step 3 : RUN apt-get update
---> Running in 7e6ce2ab52d9

6.2.4 运行WAR文件

创建一个新的Tomcat实例,运行示例应用。

-->docker run --name sample_app --volumes-from sample -d -P jamtur01/tomcat7

表示创建一个名为sample_app的容器,这个容器会复用sample容器里的卷。意味着存储在/var/lib/tomcat7/webapps/

卷里的WAR文件会从sample容器挂载到sample_app容器,最终被Tomcat加载并执行。

。查看被公开的端口

-->docker port sample_app 8080

。浏览这个应用

-->http://ip:49154/sample/

6.2.5 基于Tomcat应用服务器的构建服务

现在有了自服务Web的基础模块,看看怎么基于这些基础模块做扩展。

已经构建好了一个简单的基于Sinatra的Web应用,这个应用可以通过网页自动展示Tomcat应用。这个应用叫TProv。

下载地址:http://dockerbook.com/code/6/tomcat/tprov/

https://github.com/jamtur01/dockerbook-code/tree/master/code/6/tomcat/tprov

提示:可以把TProv安装在Docker容器里。

。安装:

-->apt-get -y install ruby make ruby-dev

。通过Ruby gem安装tprov

-->gem install --no-rdoc --no-ri tprov

。启动应用

-->tprov

从输出可知,端口是:

。浏览TProv网站

-->http://ip:

可以指定Tomcat应用的名字和指向Tomcat WAR文件的URL。

从https://gwt-example.googlecode.com/files/Calendar.war下载示例日历应用,并将其称作Calendar。

-->Enter name of the Tomcat application : Calendar

-->Enter the URL of a WAR file you wish to run: https://gwt-example.googlecode.com/files/Calendar.war

-->Submit

单击Submit下载WAR文件,将其放入卷里,运行Tomcat服务器,加载卷例的WAR文件。

可以通过List instances(展示实例)连接来查看实例的运行状态。

展示了:

。容器的ID;

。容器的内部IP地址;

。服务器映射到接口和端口

利用这些信息,可以通过浏览映射端口来查看应用的运行状态。还可以使用Delete(是否删除)单选框来删除正在

运行的实例。

可以通过查看TProv应用的源代码,看看程序是如何实现这些功能的。这个应用很简单,只是通过shell执行docker

程序,再捕获输出,来运行或者删除容器。

https://github.com/jamtur01/dockerbook-code/blob/master/code/6/tomcat/tprovlib/tprov/app.rb

6.3 多容器的应用栈

构建一系列镜像来支持部署多容器的应用:

。一个Node容器,用来服务于Node应用,这个容器会连接到。

。一个Redis主容器,用于保存和集群化应用状态,这个容器会连接到。

。两个Redis备份容器,用于集群化应用状态。

。一个日志容器,用于捕获应用日志。

Node应用会运行在一个容器里,后面会挂载以主从模式配置在多个容器里的Redis集群。

6.3.1 Node.js镜像

-->mkdir nodejs

-->cd nodejs

-->mkdir -p nodeapp

-->cd nodeapp

-->wget https://raw.githubusercontent.com/jamtur01/dockerbook-code/master/code/6/node/nodejs/nodeapp/package.json

-->wget https://raw.githubusercontent.com/jamtur01/dockerbook-code/master/code/6/node/nodejs/nodeapp/server.js

-->cd ..

-->vim Dockerfile

FROM ubuntu:14.04

MAINTAINER James Turnbull <james@example.com>

ENV refreshed_at 2015-09-09

RUN apt-get update

RUN apt-get -y 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" ]

Node.js镜像安装了Node,然后把二进制文件nodejs连接到node,解决了Ubuntu上原有的一些无法

向后兼容的问题。

然后将nodeapp的源代码通过ADD指令添加到/opt/nodeapp目录。这个Node.js是一个简单的Express

服务器,包括一个存放应用依赖信息的package.json文件和包含实际应用代码的server.js文件,查看该

应用的子集。

server.js文件引入了所有的依赖,并启动了Express应用。Express应用把会话(session)信息保存到

Redis例,并创建了一个以JSON格式返回状态信息的节点。这个应用默认使用redis_primary作为主机名去

连接Redis,如有必要,可以通过环境变量覆盖这个默认的主机名。

这个应用会把日志记录到/var/log/nodeapp/nodeapp.log文件里,并监听3000端口。

源码位置:http://dockerbook.com/code/6/node/

https://github.com/jamtur01/dockerbook-code/tree/master/6/node

接着将工作目录设置为/opt/nodeapp,并且安装了Node应用的必要软件包,创建了用于存放Node应用日志的

卷/var/log/nodeapp。

最后公开了3000端口,并使用ENTRYPOINT指定了运行Node应用的命令nodejs、server.js。

。构建镜像:

-->mkdir nodeapp

-->mv package.json server.js nodeapp/

-->docker build -t jamtur01/nodejs .

Sending build context to Docker daemon 5.12 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 3f126a502d09
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 10b317f5826d
Step 2 : ENV REFRESHED_AT 2014-06-01
---> Running in 6241d5a71909
---> 1874b2ef0850
Removing intermediate container 6241d5a71909
Step 3 : RUN apt-get -y update
---> Running in ce199f84c613
........................................................

├── fresh@0.2.0
├── buffer-crc32@0.2.1
├── cookie@0.1.0
├── mkdirp@0.3.5
├── debug@2.2.0 (ms@0.7.1)
├── send@0.1.4 (mime@1.2.11)
├── commander@1.2.0 (keypress@0.1.0)
└── connect@2.8.8 (uid2@0.0.2, pause@0.0.1, qs@0.6.5, bytes@0.2.0, formidable@1.0.14)
---> 7d7fbe7eb9f6
Removing intermediate container fa9b8f1d81e4
Step 11 : VOLUME /var/log/nodeapp
---> Running in b0c204e52ef3
---> 072d101fc7a3
Removing intermediate container b0c204e52ef3
Step 12 : EXPOSE 3000
---> Running in 35e5beb8e397
---> 9512bf275d3c
Removing intermediate container 35e5beb8e397
Step 13 : ENTRYPOINT nodejs server.js
---> Running in f62e2a176be0
---> a2873cb43b1e
Removing intermediate container f62e2a176be0
Successfully built a2873cb43b1e

6.3.2 Redis基础镜像

构建第一个Redis镜像:安装Redis基础镜像,然后使用这个镜像构建主从Redis镜像。

-->mkdir redis_base

-->cd redis_base

-->vim Dockerfile

FROM ubuntu:14.04
MAINTAINER James Turnbull <james@example.com>
ENV REFRESHED_AT 2014-06-01

RUN apt-get update
RUN apt-get install -y software-properties-common python-software-properties
RUN add-apt-repository ppa:chris-lea/redis-server
RUN apt-get -y update
RUN apt-get -y install redis-server redis-tools

VOLUME [ "/var/lib/redis", "/var/log/redis" ]

EXPOSE 6379

CMD []

这个Redis基础镜像安装了最新版本的Redis(从PPA库安装,而不是使用Ubuntu自带的),

指定了两个卷(/var/lib/redis和/var/log/redis),公开了Redis的默认端口6379,因为不会

执行这个镜像,所以没有包含ENTRYPOINT或CMD指令,然后将只是基于这个镜像构建别的镜像。

。构建镜像:

-->docker build -t jamtur01/redis .

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 3f126a502d09
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 10b317f5826d
Step 2 : ENV REFRESHED_AT 2014-06-01
---> Using cache
---> 1874b2ef0850
Step 3 : RUN apt-get update
---> Running in f290189afd97
........................................................
---> 6a1b963c8b23
Removing intermediate container 4fd036d34eab
Step 8 : VOLUME /var/lib/redis /var/log/redis
---> Running in bc2cf7a83732
---> 6555902a8c8a
Removing intermediate container bc2cf7a83732
Step 9 : EXPOSE 6379
---> Running in 28aa6097a48b
---> e4165aa80779
Removing intermediate container 28aa6097a48b
Step 10 : CMD
---> Running in 6e3443df3c29
---> 5d07afc9555e
Removing intermediate container 6e3443df3c29
Successfully built 5d07afc9555e

6.3.3 Redis主镜像

继续构建第一个Redis镜像,即Redis主服务器:

-->mkdir redis_primary

-->cd redis_primary

-->vim Dockerfile

FROM jamtur01/redis

MAINTAINER James Turnbull <james@example.com>

ENV REFRESHED_AT 2015-09-10

RUN touch /var/log/redis/redis-server.log

ENTRYPOINT [ "redis-server", "--logfile /var/log/redis/redis-server.log" ]

说明:Redis镜像基于之前的jamtur01/redis镜像,并通过ENTRYPOINT指令指定了Redis服务启动命令,

Redis服务的日志文件保存到/var/log/redis/redis-server.log。

。构建主镜像

-->docker build -t jamtur01/redis_primary .

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM jamtur01/redis
---> a0a58e52218b
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 86066df5d38e
Step 2 : ENV REFRESHED_AT 2015-09-10
---> Using cache
---> c580e7e933dc
Step 3 : RUN touch /var/log/redis/redis-server.log
---> Running in 798023d1f7b5
---> 53289991d80b
Removing intermediate container 798023d1f7b5
Step 4 : ENTRYPOINT redis-server --logfile /var/log/redis/redis-server.log
---> Running in c30cf1f2ca9f
---> 2a3431938713
Removing intermediate container c30cf1f2ca9f
Successfully built 2a3431938713

6.3.4 Redis从镜像

为了配合Redis主镜像,会创建Redis从镜像,保证为Node.js应用提供Redis服务的冗余度。

-->mkdir redis_replica

-->cd redis_replica

-->vim Dockerfile

FROM jamtur01/redis

MAINTAINER James Turnbull <james@example.com>

ENV REFRESHED_AT 2015-09-10

RUN touch /var/log/redis/redis-repa.log

ENTRYPOINT [ "redis-server","--logfile /var/log/redis/redis-repa.log","--slaveof redis_primary 6379" ]

Redis从镜像也是基于Jamtur01/redis构建的,并且通过ENTRYPOINT指令指定了运行Redis服务器的命令,设置了

日志文件和slaveof选项。这就把Redis配置为从模式,从这个镜像构建的任何容器都会将redis_primary主机的Redis

作为主服务,连接其6379端口,成为其对应的从服务器。

。构建Redis从镜像:

-->docker build -t jamtur01/redis_replica .

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM jamtur01/redis
---> a0a58e52218b
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 86066df5d38e
Step 2 : ENV REFRESHED_AT 2015-09-10
---> Using cache
---> c580e7e933dc
Step 3 : RUN touch /var/log/redis/redis-repa.log
---> Running in 83316e880c86
---> b7c1db6682dd
Removing intermediate container 83316e880c86
Step 4 : ENTRYPOINT redis-server --logfile /var/log/redis/redis-replica.log --slaveof redis_primary 6379
---> Running in b1e1e5847fac
---> 64734d6d4f56
Removing intermediate container b1e1e5847fac
Successfully built 64734d6d4f56

6.3.5 创建Redis后端集群

开始构建Redis集群环境,从构建Redis主容器开始:

-->docker run -d -h redis_primary --name redis_primary jamtur01/redis_primary

c293a244ea1ff0408b33caf3252b622319a25dbd873d10b179a4f05d0b7cc4b6

这里使用docker run命令从jamtur01/redis_primary 镜像创建了一个容器。

-h: 用来设置容器的主机名。这会覆盖默认的行为(默认以容器ID作为主机名),并允许指定自己的主机名。使用这个

标志可以确保容器使用redis_primary作为主机名,并被本地的DNS服务正确解析。

--name:确保容器的名字也是redis_primary。使用这个名字来连接容器。

。查看日志了解Redis主容器的运行状况

-->docker logs redis_primary

没有输出,是因为Redis将日志记录到一个文件而不是记录到标准输出。可以使用之前创建的/var/log/redis卷查看。

。查看日志

-->docker run -it --rm --volumes-from redis_primary jamtur01/redis_primary cat /var/log/redis/redis-server.log

这里以交互方式运行了另一个容器,这个命令指定了--rm标志,它会在进程运行完后自动删除容器。

参数:

--volumes-from:告诉它从redis_primary容器挂载了所有的卷。

ubuntu:指定的基础镜像,告诉执行cat /var/log/redis/redis-server.log来展示日志文件。

这种方法利用了卷的优点,可以直接从redis_primary容器挂载/var/log/redis目录并读取里面的日志文件。

。创建redis从服务

-->docker run -d -h redis_replical --name redis_replical --link redis_primary:redis_primary jamtur01/redis_replica

e90c86dde89a76fb2776b4a574c912d264440fae9a82c093cd9df413d879f463

这里从jamtur01/redis_replica镜像运行了另一个容器。命令里指定了主机名(通过-h标志)和容器名(通过--name标志)都是

redis_replical。使用了--link标志将redis_primary容器以别名redis_primary连接到了Redis从容器。

。查看容器日志

-->docker run -ti --rm --volumes-from redis_replical jamtur01/redis_replica cat /var/log/redis/redis-replica.log

这里通过交互的方式运行了一个新容器来查询日志。

--rm:它在命令执行完毕后自动删除容器。

--volumes-from:挂载了redis_replical 容器的所有卷

ubuntu:为指定的镜像

cat /var/log/redis..: 指定日志文件的内容。

最终成功运行了redis_primary容器和redis_replical容器,并让其互为备份。

。加入另一个从服务器redis_replica2来确保万无一失。

-->docker run -d -h redis_replica2 --name redis_replica2 --link redis_primary:redis_primary jamtur01/redis_replica

9fa16e8ba208c8193b8c78fa2c4be71d44748273084905ea6bf5215182ef06c7

。查看新容器的日志:

-->docker run -ti --rm --volumes-from redis_replica2 jamtur01/redis_replica cat /var/log/redis/redis-replica.log

6.3.6 创建Node容器

现在已经让Redis集群运行了,可以启动Node.js应用启动一个容器:

-->docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9fa16e8ba208 jamtur01/redis_replica "redis-server '--log About a minute ago Up About a minute 6379/tcp redis_replica2
e90c86dde89a jamtur01/redis_replica "redis-server '--log 5 minutes ago Up 5 minutes 6379/tcp redis_replical
c293a244ea1f jamtur01/redis_primary "redis-server '--log 35 minutes ago Up 35 minutes 6379/tcp redis_primary

-->docker run -d --name nodeapp -p 3000:3000 --link redis_primary:redis_primary jamtur01/nodejs

1a00ab7dc1601536df8d4eea3826eb1f16531662b99332aa99155d698de65310

从jamtur01/nodejs 镜像启动创建了一个新容器,命名为nodeapp,并将容器内的3000端口映射到宿主机的3000端口,

然后将redis_primary容器以别名redis_primary连接到新的nodeapp容器。

。查看容器日志

-->docker logs nodeapp

Listening on port 3000

。Web访问容器3000端口

-->http://192.168.1.72:3000/,显示如下:

{
"status": "ok"
}

这个输出表明应用正在工作。浏览器的会话状态会先被记录到Redis主容器redis_primary,然后同步到两个Redis从

服务容器redis_replical和redis_replica2。

6.37 捕获应用日志

现在应用已经可以运行了,需要把这个应用放到生成环境中。在生产环境里需要确保可以捕获日志将日志保存到日志

服务器。使用Logstash来完成这件事。

参考:https://www.elastic.co/products/logstash

-->wget https://download.elastic.co/logstash/logstash/packages/debian/logstash_1.4.5-1-a2bacae_all.deb

-->wget https://download.elastic.co/logstash/logstash/logstash-1.5.4.tar.gz

。创建Logstash镜像

-->mkdir logstash

-->cd logstash

-->vim Dockerfile

FROM ubuntu:14.04
MAINTAINER James Turnbull <james@example.com>
ENV REFRESHED_AT 2015-09-11

RUN apt-get update
RUN apt-get -y install wget
RUN wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add -
RUN echo 'deb http://packages.elasticsearch.org/logstash/1.4/debian stable main' > /etc/apt/sources.list.d/logstash.list
RUN apt-get update
RUN apt-get -y install logstash

ADD logstash.conf /etc/

RN mkdir /opt/logstash

WORKDIR /opt/logstash

ENTRYPOINT [ "bin/logstash" ]
CMD [ "--config=/etc/logstash.conf" ]

创建了镜像并安装了Logstash,然后将logstash.conf文件使用ADD指令添加到/etc/目录。

接着指定了工作目录为/opt/logstash,通过CMD指定了启动参数--config=/etc/logstash.conf,

这个启动命令会启动Logstash并载入/etc/logstash.conf配置文件。

。查看logstash.conf

-->cat logstash.conf

input {
file {
type => "syslog"
path => ["/var/log/nodeapp/nodeapp.log", "/var/log/redis/redis-server.log"]
}
}
output {
stdout {
codec => rubydebug
}
}

此配置文件监控两个文件,即/var/log/nodeapp/nodeapp.log和/var/log/redis/redis-server.log。

Logstash会一直监视这两个文件,将其中新的内容发送给Logstash。配置文件中的第二部分是output部分,

接受所有Logstash输入的内容并将其输出到标准输出上。现实中,一般会将Logstash配置为输出到Elasticsearch

集群或者其它目的地,不过这里只使用标准输出做演示,忽略现实的细节。

。构造镜像

-->docker build -t jamtur01/logstash .

Sending build context to Docker daemon 3.584 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 3f126a502d09
Step 1 : MAINTAINER James Turnbull <james@example.com>
---> Using cache
---> 10b317f5826d
Step 2 : ENV REFRESHED_AT 2015-09-11
---> Running in 88e21669df1d
---> a6f7faa5761f
Removing intermediate container 88e21669df1d
Step 3 : RUN apt-get update
---> Running in de46156bec80

。从镜像启动一个容器

-->docker run -d --name logstash --volumes-from redis_primary --volumes-from nodeapp jamtur01/logstash

这个容器使用--volume-from标志两次,挂载了redis_primary和nodeapp里的卷,这样就可以访问Redis和Node的日志

文件了。任何加到这些日志文件里的内容都会反应在logstash容器里的卷里,并传给Logstash做后续处理。

。查看输出日志

-->docker logs -f logstash

。浏览器访问容器Web

-->http://192.168.1.72:3000/ ,然后刷新页面,产生一个新的日志事件

-->docker logs -f logstash

现在Node和Redis容器都将日志输出到了Logstash。在生产环境中,这些事件会发到Logstash服务器并存储在Elasticsearch

里。如果要加入新的Redis从服务器容器或者其他组件,也可以很容易地将日志输出到日志容器里。

注意:如果需要,可以通过卷对Redis做备份。

6.3.8 Node程序栈的小结

6.4 不使用SSH管理Docker容器

可以使用卷或者链接完成大部分同样的管理操作,如果服务通过某个网络接口做管理,就可以在启动容器时公开这个接口,

如果服务通过Unix套接字(socket)来管理,就可以通过卷公开这个套接字。如果需要给容器发送信号,可以使用dcoker kill这样

发送信号。

docker kill -s

这个操作会发送指定的信号(如HUP信号)给容器,而不是杀掉容器。需要再次登入容器时,可以使用一个叫nsenter的小工作。

注意:nsenter一般适用于Docker 1.2或者更早的版本。docker exec命令是在Docker 1.3中引入的,替换了它的大部分功能。

工具nsenter可以进入Docker用来构建容器的内核命名空间。这个工具可以进入一个已经存在的命名空间,或者在新的一组命名

空间里执行一个进程。简单来说,使用nsenter可以进入一个已经存在的容器的shell,即便这个容器没有运行SSH或者任何类似目的

守护进程。可以通过Docker容器安装nsenter。

-->docker run -v /usr/local/bin:/target jpetazzo/nsenter

这会把nsenter安装到/usr/local/bin目录下,然后立刻就可以使用这个命令。

为了使用nsenter,首先要拿到要进入容器的进程ID(PID)。可以使用docker inspect命令获得PID。

PID=$(docker inspect --format {{.State.Pid}} )

然后就可以进入容器:

-->nsenter --target $PID --mount --uts --ipc --net --pid

这会在容器启动一个shell,而不需要SSH或者其他类似的守护进程或者进程。

还可以将想在容器内执行的命令添加到nsenter命令行的后面:

-->nsenter --target $PID --mount -uts -ipc --net --pid ls

这会在目标容器内执行ls命令。

博主

让学习成为习惯,坚持-共享-开源-自由! 成功者决不放弃,放弃者绝不成功!

相关推荐

1 条评论

  1. avatar
    -49#
    admin 于2016-03-25 下午5:39 评论 回复