本文讲的是Docker在英雄联盟游戏中的实践探索(三),
【编者的话】这篇博客是Riot的Docker实践系列博客的第三篇,主要讨论了Docker中的数据持久化,并详细介绍了如何使用数据卷容器来持久化Jenkins的日志文件。
在 上一篇博客 中,我们讨论了如何基于Cloudbees镜像来编写自己的Dockerfile,从而更好地控制Jenkins Docker镜像。通过Dockerfile,我们可以设置一些基本的默认值,不需要每次将它们作为
docker run
的参数了。我们也可以定义Jenkins的日志目录,并用
docker exec
来查看运行中的容器的日志。
我也提到了,我们还需要数据持久化技术来使它更有用。容器和其中的数据是转瞬即逝的(ephemeral),只要重启容器,我们就会丢失Jenkins中所有的插件和任务数据。Cloudbees文档提到,需要使用卷(volumes)来保存数据。他们建议挂载宿主机上的目录到容器中,这是一种传统的保存数据的方式。
还有另一种方式,那就是Docker数据卷容器。你可以参考 Docker官方文档 来了解数据卷容器。
本文将涉及:
- 使用卷来持久化Docker数据
- 创建数据卷容器
- 在容器间通过数据卷共享数据
- 保存Jenkins任务和插件
主机挂载卷(HOST MOUNTED VOLUMES) VS 数据卷容器(DATA VOLUME CONTAINERS)
所谓“主机挂载卷”,指的是Docker宿主机在自己的文件系统中存储数据。此时,当你使用docker run
运行容器,Docker会将物理存储挂载到你的容器中。
这种方法有很多优点,其中最明显的就是易用性。在更复杂的环境中,存储可能是NAS(Network Attached Storage)或者SATA(Serial Advanced Technology Attachment),有着不错的空间和性能。
缺点就是需要预先设置Docker宿主机上的挂载点,这让Docker的两大优点黯然失色:容器的可移植性和应用的“run anywhere”。如果你需要一个可以运行在任意主机上的真正可移植的容器,那就不能指望宿主机是预先配置好的。
数据卷容器就可以解决这个问题。所谓数据卷容器,其实是一个定义了存储空间的Docker镜像。容器本身只是定义了Docker虚拟文件系统中数据的存储位置。容器中并不运行任何进程,事实上它会在调用
docker run
之后立即“停止”。因此,容器以停止状态退出,其中的数据也是。
优点是Docker容器可以共享数据,而不需要宿主机配置一个正确的挂载点。用户可以通过Docker命令来相互交互,不需要主机参与。
缺点是性能略差 ,原因是数据是存储在Docker的虚拟文件系统中。因此,如果应用需要非常好的IO性能的话,那么数据卷容器可能不是最理想的选择。然而,对于大多数应用来说,这种性能差异并不显著。另一方面,数据卷容器也造成了额外的复杂性,原因是你的应用至少需要两个镜像(即两个Dockerfile),一个是应用本身,另一个是存储。
需要说明的是,两种方法都是100%有效的,但是取决于你希望它如何工作。我自己的选择是应用应当是尽可能地保持独立性,因此,本文将展示如何使用数据卷容器。
开始
我们从 前一篇博客 中的Dockerfile开始。对于初学者来说,我们需要创建一个镜像来保存日志文件。我们可以在一个根目录中创建两个Dockerfile,但是,我更喜欢在它们自己的目录中创建不同的Dockerfile。
将原来的Jenkins Dockerfile放在jenkins-master目录中:
为了确定它仍能工作,我们先构建jenkins-master Dockerfile。
你可以使用相对路径来指定Dockerfile的位置。如果在单一的根目录中管理多个Dockerfile,使用相对路径非常有用。现在,我们需要为jenkins-data创建一个新的Dockerfile。
- 使用你喜欢的编辑器,在
jenkins-data
目录中创建Dockerfile
。 - 在文件头添加以下内容:
注意:我使用了Debian基础镜像,原因是 Cloudbees Jenkins镜像也使用了同样的基础镜像。因为容器之间会共享文件系统以及UID是跨容器的,所以操作系统需要相互匹配。
- 添加以下内容创建Jenkins用户:
注意:我们需要设置UID为Cloudbees Jenkins镜像中的值,从而在容器之间匹配UID。如果你想在不同的容器中保持一致的文件权限,那么UID匹配是必要的。我们也会使用相同的家目录和bash设置。
- 我们需要重新创建Jenkins的日志目录:
- 我们要施展Docker的卷“魔法”了,来挂载日志目录:
- 为了保持一致性,我们将容器用户设置为
Jenkins
:
- 最后,尽管这个镜像并不真正运行应用,但是我喜欢让它启动时输出一条消息,来表示这个镜像的目的。
以下是完整的Dockerfile:
保存文件,并构建之:
这样一来,基础镜像已经包含了Jenkins数据卷。然而,我们需要调整现有的镜像来使用它。
准备数据卷容器
首先,我们启动新的数据卷容器。你将会看到我们加到CMD中的输出消息。如果你运行:
你将会看到没有任何运行中的容器。如果你运行:
你将会看到新的数据卷容器已经停止了。这是正常的,这就是数据卷容器的运行方式。只要这个容器还在那,那么
/var/log/jenkins
中的数据就会是持久化的,原因是我们将这个目录定义成了数据卷。我们可以将Jenkins主容器使用该数据卷容器,即使删除了主容器,我们的日志仍然会被保存下来。
使用数据卷容器
这个部分是比较容易的。所有困难的工作已经在设置数据卷时做掉了。为了使用它,我们只需要在调用docker run
时添加
volumes-from
参数即可:
从上面的命令可以看到,我加了一个新的端口映射
50000:50000
,从而能够处理来自基于JNLP的从节点的连接。在未来的博客中,我将会详细说明这一点。
注意我们使用了
jenkins-data
这个容器名。Docker很聪明,可以引用这些名字。你可以通过日志文件内容验证一切正常:
但是我们怎么知道数据卷挂载是否成功呢?很简单,因为默认情况下Jenkins会以追加模式(append)写入日志文件——一个简单的
start/clean/restart
可以验证挂载是否成功:
在日志文件中,你可以看到第一条和第二条Jenkins启动消息。Jenkins可以崩溃,或者升级,我们总可以保存原来的日志。当然,这也意味着你不得不清理日志和日志目录,就像通用的Jenkins主机一样。
不要忘记
docker cp
。你可以将日志文件从数据卷容器中拷贝出来,即使你丢失了主容器:
保存日志文件只是一个次要的优势——我们可以在容器重启时,使用它来保存Jenkins数据,例如插件和任务。保存日志文件是展示数据卷容器如何工作的很好的一个例子。
保存Jenkins家目录
首先,我们在数据卷中添加Jenkins家目录。编辑jenkins-data/Dockerfile
,更新
VOLUME
命令:
因为已经创建了属于Jenkins用户的目录,我们不需要做任何事,除了添加它为容器挂载点。不要忘记重建新的数据镜像,并在重启前清理原来的容器。
在使用之前,还有一件事情需要处理。CloudBees默认的Docker镜像中,在
jenkins_home
中存储了未压缩的Jenkins war文件,这意味着我们需要在Jenkins运行时保存数据。这不是理想的,因为我们不需要保存这些数据,而且当Jenkins版本变化时这也会引起混淆。因此,我们使用另一个Jenkins启动选项,来把war包移到
/var/cache/jenkins
。
编辑Jenkins-Master Dockerfile,更新
JENKINS_OPTS
:
这个选项将设置Jenkins的webroot目录。然而,我们需要保证这个目录存在,并给予Jenkins用户合适的权限。
保存Dockerfile,重建jenkins-master镜像,重启它。使用完之后,我们需要删除数据卷,请注意需要使用
rm -v
。Docker默认情况下不会删除它们,因为你可能希望保留它们,以备不时之需。
你的容器将会启动。通过运行以下指令,你可以验证WAR文件已经移动了:
你可以看到未压缩的内容。但是我们怎么知道这种新布局真的保存了Jenkins数据呢?
测试是否保存了任务
我们可以容易地测试这一点。当jenkins-master运行时,我们创建一个Jenkins构建任务。- 访问http://yourdockermachineip:8080(通过“docker-machine ip default”查询yourdockermachineip)
- 点击“New Item”,创建一个新任务
- 输入“testjob”
- 选择Freestyle software project
- 点击“ok”
- 点击“save”
“无用的、仅供测试的(useless for anything but testing)”新任务应该出现在主任务列表中。现在,停止并删除Jenkins容器。
注意我们并未使用“-v”,只有在希望完全删除数据卷的时候才应该使用“-v”。记住,数据卷就像指针一样。Docker所做的只是在磁盘上创建一个虚拟文件系统——只有一个容器指向它,它就会存在。当jenkins-master容器被删除了,jenkins-data数据卷仍然指向了虚拟文件系统。如果我们使用了“-v”,那么Docker将删除该虚拟文件系统。这会删除jenkins-data对它的引用。
在原来的镜像里,上述操作会删除我们的任务。然而,对于新的镜像,当我们重新创建容器时:
刷新浏览器地址
http://yourdockermachineip:8080
,等待Jenkins启动。我们发现测试任务仍然存在。
总结
如同之前的博客一样,你可以在我的 Github仓库 中找到更新和示例文件。你会注意到makefile又更新了,包含了一个“clean-data”的新命令,可以彻底清理你的数据容器。至此,我们有了一个具备全功能的Jenkins镜像。我们可以保存日志、任务和插件因为我们把jenkins_home放置在数据卷容器中。还有一个有益的副作用,即便Docker守护进程崩溃,或者宿主机重启,数据都会被持久化。原因是容器会保留停止的容器。
尽管我们可以以此起步,但是在实践中,仍然有很多地方可以优化。例如:
- 在Jenkins容器之前,使用NGINX设置代理服务器
- 管理多个镜像和容器开始变得越来越复杂,即使是使用makefile。有没有更简单的方法?
- 我们需要一个备份Jenkins环境的方法,特别是任务。
- 如果我们不想使用Debian作为基础操作系统呢?如果我们不想依赖于外部镜像呢?
- 我们还未构建从节点。尽管我们的容器允许来自任何标准的从节点的连接,但是如果将从节点创建成容器,不是更酷吗?
每个优化点都可以写成一篇独立的博客。之后,我们将要设置一个web代理,并讨论如何处理三个容器,这意味着我们将引入Docker Compose。其它的优化点,将在未来的博客中逐一介绍。敬请期待!
原文链接:DOCKER & JENKINS: DATA THAT PERSISTS(翻译:夏彬 校对:李颖杰)
原文发布时间为:2015-10-24
本文作者:binsummer
本文来自云栖社区合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:Docker在英雄联盟游戏中的实践探索(三)