【编者的话】这篇博客是Riot的Docker实践系列博客的第二篇,主要内容是:基于Cloudbees镜像创建新的Dockerfile,设置了一个日志目录,并介绍了如何使用 docker exec
命令查看日志文件。
当我一年前开始学习Docker的时候,发现很难找到好的文档和实例,即使是今天,也只能找到一些简单的用例,完全不能用作真正的产品。使用Docker容器来产品化应用,需要适应容器的短暂性和单一进程的特性。这对于需要数据持久化和多进程的应用来说,是一个挑战。
就像我 上一篇博客 中提到的,我们在Jenkins这样一个开源软件的基础上构建自己的自动化系统。Jenkins也是一种容器化应用的一种方式。我们使用以下的体系结构组件来部署Jenkins:
这是一个很好的开端。通过这一系列博客,我们考虑将上述提到的组件容器化,并使用Docker容器来构建从节点(slaves)。对于初学者,我们将在Docker容器中创建Jenkins主服务器,然后转向处理数据持久化,并通过NGINX添加web代理。
整个系列博客将涉及以下几个Docker概念:
如果你还没有看过Cloudbees的Jenkins Docker镜像,就从它开始吧,这是一个相当不错的开端。我刚开始就参考了它,在Docker容器中运行Jenkins,对于很多人来说,这就足够了。你可以找到相关的 文档 和 Git仓库 。
这篇博客分为了两课,每一课需要30分钟来完成。第一课是准备好你的开发环境,并学习如何使用Cloudbees提供的Jenkins Docker容器。第二课是奠定基础,定义自己的Dockerfile,并更优雅地控制镜像。这些课程是设计来起步的,尤其是当你从未用过Docker,尽管我们是假设你已经熟悉Jenkins的用法。如果你已经有Docker的使用经验,那么第一课中的某些内容,你可能已经了解了。
让我们从准备环境开始。在Riot,我们在Windows、Mac OSX和Linux上运行Docker和Jenkins。尽管1.6版本之后,Docker就可以完美地运行在Windows上了,但是,我个人比较喜欢在OSX上运行Docker。无论哪一种方式,你的Docker宿主机服务器都是运行在一台Linux服务器上的。我最喜欢的一个工具Docker-Compose也不兼容Windows(Docker 1.8和Compose 1.4)。在每个方式(OSX或Windows)中,你都需要安装Docker Toolbox(之前叫Boot2Docker)。
顺便说一下,微软正在计划在2016年发布一个支持原生Docker的Windows服务器,我们非常期待Windows Docker容器。现在,我们将集中在现有的技术上。
* 如果你在老版本的boot2docker的基础上安装新版本的Docker Toolbox,也会有问题。因此,最好首先卸载boot2docker和ISO镜像。
在Docker 1.8和Docker Toolbox中,已经包含了“Kitematic”,它是一个漂亮的GUI工具,来管理和可视化Docker镜像和容器。这篇教程大部分专注在使用命令行来控制Docker。这是为了更好地暴露给读者底层的机制。同样地,后续博客将涉及Compose的用法,同时开启和关闭多个容器。然而,Kitematic的确是一个很酷的工具,未来我会专门写一篇博客介绍如何在开发生命周期中使用它。
docker ps
docker info
我们需要访问Jenkins web服务器(最终是一个NGINX服务器)。最简单的方式,就是在浏览器中直接输入Docker宿主机的IP地址。
你可以使用ifconfig或ipconfig来查询IP地址,但是Docker Toolbox通过docker-machine提供了一个方便的命令:
docker-machine ip default
这就是你的宿主机的IP地址,你的web服务就监听在上面。当然,这个IP是在你本地的机器上,并不能被外部访问。如果你希望从外部访问你的机器,你需要在Virtualbox上设置端口转发(port forwarding)。我把这个留给你作为一个练习,可以参考Virtualbox的文档。
docker pull jenkins
docker run -p 8080:8080 --name=jenkins-master jenkins
http://yourdockermachineiphere:8080
。
如果Jenkins没有显示在浏览器中,但是容器正在运行,那么请再次确认是否正确执行了步骤2,并获得了正确的IP地址。
请注意,你可能注意到我使用了 --name
这个参数,并将这个容器命名为 jenkins-master
。命名容器是一个最佳实践,有三个优点:
1. 更容易记忆
2. Docker不允许两个容器使用同一个名字,可以防止开启两个同名容器。
3. 很多通用的Docker工具(如docker-compose)需要使用特定的容器名字,因此命名容器是一个最佳实践。
之前的步骤是以最基本的参数来启动Jenkins。就像Riot一样,你是不太可能直接用默认配置来运行Jenkins的。让我们加上一些有用的参数,并解释为什么这么加。
你可能不希望直接将Jenkins的日志打到标准输出中。因此使用Docker的daemon标志(-d)来开启容器。
Ctrl-c
关闭你的容器 docker rm jenkins-master
docker run -p 8080:8080 --name=jenkins-master -d jenkins
现在你应该可以获得一个hash字符串。如果你是刚接触Docker的话,那个hash值就是你的容器的唯一ID。
在Riot,我们需要一些健壮的设置来运行Jenkins。运行以下命令:
docker stop jenkins-master
docker rm jenkins-master
docker run -p 8080:8080 --name=jenkins-master -d --env JAVA_OPTS="-Xmx8192m" jenkins
这就会给Jenkins 8GB的内存空间来做GC(garbage collection)。请注意,如果你使用的是Java 1.7或者更早的版本,请使用一下指令:
docker run -p 8080:8080 --name=jenkins-master -d --env JAVA_OPTS=”-Xmx8192m -XX:PermSize=256m -XX:MaxPermSize=1024m” jenkins
Cloudbees的容器使用的是Java 1.8,我们不需要设置 PermSize
,因为 Java 1.8
没有 PermGen
。
STEP 4C: UPPING THE CONNECTION POOL
在Riot,大量的流量发往Jenkins服务器,因此我们需要给Jenkins设置更大的连接池。运行以下命令:
docker stop jenkins-master
docker rm jenkins-master
docker run -p 8080:8080 --name=jenkins-master -d --env JAVA_OPTS="-Xmx8192m" --env JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300" jenkins
你已经学会了如何使用JAVA OPTS和JENKINS OPTS作为环境变量。请注意,这是因为Cloudbees方便地组织了Dockerfile。
我把这些学到的内容放到一个简单的makefile中,你就可以用 make
命令来控制运行Jenkins Docker容器。你可以在这里找到:
你可以运行以下命令:
你不是必须使用 makefile
,只是使用它可以不用输入太多命令。你可以将这些命令放到一个脚本中。
希望你能看到运行Docker和Jenkins是多么容易。我已经提供了一些基本的选项,来运行默认的Cloudbees Jenkins容器,并使其更实用。Cloudbees提供了很多有用的建议,例如如何预安装插件和存储Jenkins数据。
这个镜像也有一些缺点:没有一致的日志记录、没有持久化、没有web服务器代理、不能保证使用正确版本的Jenkins。这带来了一个问题:如果你想使用一个老版本,怎么办?或者你想使用最新版本?
在下一课中,我将介绍如何增强容器的鲁棒性。特别是:
在前面的课程中,我们讨论了如何准备开发环境,来运行Docker和Cloudbees提供的Jenkins镜像。我们发现这很简单,也易于使用,也有很多不错的特性。为了进一步进行优化,在本课程中将涉及以下几个概念:
我们希望改变Jenkins启动的默认行为。在上一篇博客中,我们的方法是通过 makefile
传递了一些参数作为环境变量。考虑到我们每次启动容器都需要这么做,因此我们可以把这些环境变量放到Dockerfile中。另外,我们自己的Dockerfile可以锁定Jenkins的版本,以防我们还没准备好升级Jenkins版本。
我们通过以下四步完成这一点:
FROM jenkins:1.609.1
MAINTAINER yourname
docker build -t myjenkins .
我们所做的是,从公共的Docker仓库中下拉一个特定版本的Jenkins镜像。你可以找到所有可用的版本:
你总是可以使用FROM语句来设置任意版本的可用镜像。然而,你却不能设置成Jenkins的任意版本。因为这个版本实际上指的是镜像版本(“tag”或“lable”),Cloudbees很友善地将它设置为Jenkins版本。但是Cloudbees并没有为每个Jenkins版本构建镜像,只支持长期稳定版本。如果你希望使用一个特定版本,那么请关注后续博客,我将介绍不使用Cloudbees镜像,创建自己的镜像。
我们可以运行以下命令,很容易地切换至新镜像:
docker run -p 8080:8080 --name=jenkins-master -d --env JAVA_OPTS="-Xmx8192m" --env JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300" myjenkins
我们可以将那些环境变量放在Dockerfile中。
添加像环境变量这样的默认配置,是很容易的。这也提供了一种自文档化(self-documentation)的方式。而且,你总是可以在运行Docker容器的时候重写这些环境变量。
ENV JAVA_OPTS="-Xmx8192m"
ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300"
docker build -t myjenkins .
相当简单!你可以输入以下三条命令来测试镜像是否成功:
docker stop jenkins-master
docker rm jenkins-master
docker run -p 8080:8080 --name=jenkins-master -d myjenkins
你的镜像应该可以立即启动!但是如何知道那些环境变量是否生效了呢?这很简单:可以使用 ps
命令来查看Jenkins的启动参数,即使在Windows中这也适用。
为了验证我们设置的Java和Jenkins选项,我们可以运行 Docker exec
来查看Jenkins进程的运行情况:
docker exec jenkins-master ps -ef | grep java
你应该可以看到类似的输出:
jenkins 1 0 99 21:28 ? 00:00:35 java -Xmx8192m -jar /usr/share/jenkins/jenkins.war --handlerCountStartup=100 --handlerCountMax=300
你可以看到我们的设置已经生效。 docker exec
是一个在容器中执行shell命令的简单方式。这个方法甚至在Windows中也适用,原因是“exec”执行的命令是运行在容器中的,它是由容器使用的镜像决定的。
在上一篇博客中,当我们使用守护进程参数(-d)运行容器时,我们看不到Jenkins的日志。我们想要使用Jenkins的内置特性来设置一个日志目录。我们需要在Dockerfile中创建它,并以参数形式传递给Jenkins。
让我们再次编辑Dockerfile,在“MAINTAINER”和第一行ENV命令之间,加入以下内容:
RUN mkdir /var/log/jenkins
之所以将这条指令放在这个位置,是遵循了最佳实践。相对于创建的目录而言,我们更有可能修改环境变量。Dockerfile中的每一行都是镜像的一层,因此,把经常变化的指令尽可能放在文件尾部,可以最大程度地重用这些层。
再次创建镜像:
docker build -t myjenkins .
你将会遇到类似的错误:
---> Running in 0b5ac2bce13b
mkdir: cannot create directory ‘/var/log/jenkins’: Permission denied
不用担心,这是因为Cloudbees默认容器使用了“Jenkins”用户。你可以从Dockerfile( https://github.com/ Jenkinsci/docker/blob/master/Dockerfile)的底部看到:
USER jenkins
这对于创建 /var/log/jenkins
是不方便的,通常情况下,需要使用 SUDO
或者其他方式来创建目录( /var/log
是root用户所有的)。幸运的是,Docker运行我们切换用户。在Dockerfile中添加以下内容:
RUN mkdir line
之前添加: USER root
RUN mkdir line
之后添加: RUN chown -R jenkins:jenkins /var/log/jenkins
RUN chown line
之后添加: USER jenkins
注意:我们需要添加 chown
命令,因为我们想要Jenkins用户写这个目录。然后,我们将用户重置为Jenkins,从而限制Dockerfile的行为。
再次构建镜像:
docker build -t myjenkins .
之前的错误消失了。
通过设置日志目录(请注意:你可以设置成任意目录,为了一致性,我们选择了 /var/log
),我们可以修改JENKINS_OPTS环境变量,让Jenkins将日志写入该目录。
在Dockerfile中,设置 JENKINS_OPTS
环境变量:
ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300 --logfile=/var/log/jenkins/jenkins.log"
再次构建镜像:
docker build -t myjenkins .
让我们测试新镜像,来查看日志文件。运行以下命令:
docker stop jenkins-master
docker rm jenkins-master
docker run -p 8080:8080 --name=jenkins-master -d myjenkins
我们可以查看日志文件:
docker exec jenkins-master tail -f /var/log/jenkins/jenkins.log
bonus时间到了。如果Jenkins崩溃了,那么容器将停止, docker exec
将停止工作。那么怎么办呢?
之后,我们将提供几种高级方法来持久化日志文件。现在,因为容器停止了,我们可以使用 docker cp
命令将文件拷贝出来。让我们通过停止容器来模拟一次崩溃,然后恢复日志:
ctrl-c
终止日志文件的查看 docker stop jenkins-master
docker cp jenkins-master:/var/log/jenkins/jenkins.log jenkins.log
cat jenkins.log
你可以找到这篇教程中所有的代码:
https://github.com/maxfields2000/docker Jenkins_tutorial/tree/master/tutorial_02
为了更为简单一些,我们基于Cloudbees镜像创建了自己的Dockerfile。我们设置了一个更合适的目录来存储日志文件,并学习了如何使用 docker exec
命令查看它们。我们将默认设置放进Dockerfile中,从而可以将其纳入源码控制中。
我们仍然还面临着数据持久化的挑战。我们已经学会了如何从已停止的容器(Jenkins崩溃)中获取日志。但是,一旦容器停止,我们就失去了所有的工作。因此,如果没有持久化,那么这个Jenkins镜像就只能作为本地的开发和测试。
通过Dockerfile,我们可以解决持久化问题。在下一篇博客中,我们将讨论以下问题:
原文链接: PUTTING JENKINS IN A DOCKER CONTAINER (翻译:夏彬 校对:李颖杰)