转载

Docker在英雄联盟游戏中的实践探索(三)

【编者的话】这篇博客是Riot的Docker实践系列博客的第三篇,主要讨论了Docker中的数据持久化,并详细介绍了如何使用数据卷容器来持久化Jenkins的日志文件。

Docker在英雄联盟游戏中的实践探索(三)

在 上一篇博客 中,我们讨论了如何基于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开始。

FROM jenkins:1.609.1

MAINTAINER Maxfield Stewart

USER root

RUN mkdir /var/log/jenkins

RUN chown -R jenkins:jenkins /var/log/jenkins

USER jenkins

ENV JAVA_OPTS="-Xmx8192m"

ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300 --logfile=/var/log/jenkins/jenkins.log"

对于初学者来说,我们需要创建一个镜像来保存日志文件。我们可以在一个根目录中创建两个Dockerfile,但是,我更喜欢在它们自己的目录中创建不同的Dockerfile。

mkdir jenkins-master

mkdir jenkins-data

将原来的Jenkins Dockerfile放在jenkins-master目录中:

mv Dockerfile jenkins-master

为了确定它仍能工作,我们先构建jenkins-master Dockerfile。

docker build -t myjenkins jenkins-master/.

Docker在英雄联盟游戏中的实践探索(三)

你可以使用相对路径来指定Dockerfile的位置。如果在单一的根目录中管理多个Dockerfile,使用相对路径非常有用。现在,我们需要为jenkins-data创建一个新的Dockerfile。

  1. 使用你喜欢的编辑器,在 jenkins-data 目录中创建 Dockerfile
  2. 在文件头添加以下内容:
    FROM debian:jessie
    MAINTAINER yourname
    • 注意:我使用了Debian基础镜像,原因是 Cloudbees Jenkins镜像 也使用了同样的基础镜像。因为容器之间会共享文件系统以及UID是跨容器的,所以操作系统需要相互匹配。
  3. 添加以下内容创建Jenkins用户:
    RUN useradd -d "/var/jenkins_home" -u 1000 -m -s /bin/bash jenkins
    • 注意:我们需要设置UID为Cloudbees Jenkins镜像中的值,从而在容器之间匹配UID。如果你想在不同的容器中保持一致的文件权限,那么UID匹配是必要的。我们也会使用相同的家目录和bash设置。
  4. 我们需要重新创建Jenkins的日志目录:
    RUN mkdir -p /var/log/jenkins
    RUN chown -R jenkins:jenkins /var/log/jenkins
  5. 我们要施展Docker的卷“魔法”了,来挂载日志目录:
    VOLUME ["/var/log/jenkins"]
  6. 为了保持一致性,我们将容器用户设置为 Jenkins
    USER jenkins
  7. 最后,尽管这个镜像并不真正运行应用,但是我喜欢让它启动时输出一条消息,来表示这个镜像的目的。
    CMD ["echo", "Data container for Jenkins"]

以下是完整的Dockerfile:

FROM debian:jessie

MAINTAINER yourname

RUN useradd -d "/var/jenkins_home" -u 1000 -m -s /bin/bash jenkins

RUN mkdir -p /var/log/jenkins

RUN chown -R jenkins:jenkins /var/log/jenkins

VOLUME ["/var/log/jenkins"]

USER jenkins

CMD ["echo", "Data container for Jenkins"]

保存文件,并构建之:

docker build -t myjenkinsdata jenkins-data/.

这样一来,基础镜像已经包含了Jenkins数据卷。然而,我们需要调整现有的镜像来使用它。

Docker在英雄联盟游戏中的实践探索(三)

准备数据卷容器

首先,我们启动新的数据卷容器。

docker run --name=jenkins-data myjenkinsdata

你将会看到我们加到CMD中的输出消息。如果你运行:

docker ps

你将会看到没有任何运行中的容器。如果你运行:

docker ps -a

你将会看到新的数据卷容器已经停止了。这是正常的,这就是数据卷容器的运行方式。只要这个容器还在那,那么 /var/log/jenkins 中的数据就会是持久化的,原因是我们将这个目录定义成了数据卷。我们可以将Jenkins主容器使用该数据卷容器,即使删除了主容器,我们的日志仍然会被保存下来。

Docker在英雄联盟游戏中的实践探索(三)

使用数据卷容器

这个部分是比较容易的。所有困难的工作已经在设置数据卷时做掉了。为了使用它,我们只需要在调用 docker run 时添加 volumes-from 参数即可:

docker run -p 8080:8080 -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins

从上面的命令可以看到,我加了一个新的端口映射 50000:50000 ,从而能够处理来自基于JNLP的从节点的连接。在未来的博客中,我将会详细说明这一点。

注意我们使用了 jenkins-data 这个容器名。Docker很聪明,可以引用这些名字。你可以通过日志文件内容验证一切正常:

docker exec jenkins-master tail -f /var/log/jenkins/jenkins.log

Docker在英雄联盟游戏中的实践探索(三)

但是我们怎么知道数据卷挂载是否成功呢?很简单,因为默认情况下Jenkins会以追加模式(append)写入日志文件——一个简单的 start/clean/restart 可以验证挂载是否成功:

docker stop jenkins-master

docker rm jenkins-master

docker run -p 8080:8080 -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins

docker exec jenkins-master cat /var/log/jenkins/jenkins.log

在日志文件中,你可以看到第一条和第二条Jenkins启动消息。Jenkins可以崩溃,或者升级,我们总可以保存原来的日志。当然,这也意味着你不得不清理日志和日志目录,就像通用的Jenkins主机一样。

Docker在英雄联盟游戏中的实践探索(三)

不要忘记 docker cp 。你可以将日志文件从数据卷容器中拷贝出来,即使你丢失了主容器:

docker cp jenkins-data:/var/log/jenkins/jenkins.log jenkins.log

保存日志文件只是一个次要的优势——我们可以在容器重启时,使用它来保存Jenkins数据,例如插件和任务。保存日志文件是展示数据卷容器如何工作的很好的一个例子。

保存Jenkins家目录

首先,我们在数据卷中添加Jenkins家目录。编辑 jenkins-data/Dockerfile ,更新 VOLUME 命令:

VOLUME ["/var/log/jenkins", "/var/jenkins_home"]

因为已经创建了属于Jenkins用户的目录,我们不需要做任何事,除了添加它为容器挂载点。不要忘记重建新的数据镜像,并在重启前清理原来的容器。

docker rm jenkins-data

docker build -t myjenkinsdata jenkins-data/.

docker run --name=jenkins-data myjenkinsdata

Docker在英雄联盟游戏中的实践探索(三)

在使用之前,还有一件事情需要处理。CloudBees默认的Docker镜像中,在 jenkins_home 中存储了未压缩的Jenkins war文件,这意味着我们需要在Jenkins运行时保存数据。这不是理想的,因为我们不需要保存这些数据,而且当Jenkins版本变化时这也会引起混淆。因此,我们使用另一个Jenkins启动选项,来把war包移到 /var/cache/jenkins

编辑Jenkins-Master Dockerfile,更新 JENKINS_OPTS

ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300 --logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war"

这个选项将设置Jenkins的webroot目录。然而,我们需要保证这个目录存在,并给予Jenkins用户合适的权限。

USER root

RUN mkdir /var/log/jenkins

RUN mkdir /var/cache/jenkins

RUN chown -R jenkins:jenkins /var/log/jenkins

RUN chown -R jenkins:jenkins /var/cache/jenkins

USER jenkins

保存Dockerfile,重建jenkins-master镜像,重启它。使用完之后,我们需要删除数据卷,请注意需要使用 rm -v 。Docker默认情况下不会删除它们,因为你可能希望保留它们,以备不时之需。

docker stop jenkins-master

docker rm -v jenkins-master

docker build -t myjenkins jenkins-master/.

docker run -p 8080:8080 -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins

你的容器将会启动。通过运行以下指令,你可以验证WAR文件已经移动了:

docker exec jenkins-master ls /var/cache/jenkins/war

你可以看到未压缩的内容。但是我们怎么知道这种新布局真的保存了Jenkins数据呢?

Docker在英雄联盟游戏中的实践探索(三)

测试是否保存了任务

我们可以容易地测试这一点。当jenkins-master运行时,我们创建一个Jenkins构建任务。

  1. 访问 http://yourdockermachineip:8080 (通过“docker-machine ip default”查询yourdockermachineip)
  2. 点击“New Item”,创建一个新任务
  3. 输入“testjob”
  4. 选择Freestyle software project
  5. 点击“ok”
  6. 点击“save”

Docker在英雄联盟游戏中的实践探索(三)

“无用的、仅供测试的(useless for anything but testing)”新任务应该出现在主任务列表中。现在,停止并删除Jenkins容器。

docker stop jenkins-master

docker rm jenkins-master

注意我们并未使用“-v”,只有在希望完全删除数据卷的时候才应该使用“-v”。记住,数据卷就像指针一样。Docker所做的只是在磁盘上创建一个虚拟文件系统——只有一个容器指向它,它就会存在。当jenkins-master容器被删除了,jenkins-data数据卷仍然指向了虚拟文件系统。如果我们使用了“-v”,那么Docker将删除该虚拟文件系统。这会删除jenkins-data对它的引用。

在原来的镜像里,上述操作会删除我们的任务。然而,对于新的镜像,当我们重新创建容器时:

docker run -p 8080:8080 -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins

刷新浏览器地址 http://yourdockermachineip:8080 ,等待Jenkins启动。我们发现测试任务仍然存在。

总结

如同之前的博客一样,你可以在我的 Github仓库 中找到更新和示例文件。你会注意到makefile又更新了,包含了一个“clean-data”的新命令,可以彻底清理你的数据容器。

至此,我们有了一个具备全功能的Jenkins镜像。我们可以保存日志、任务和插件因为我们把jenkins_home放置在数据卷容器中。还有一个有益的副作用,即便Docker守护进程崩溃,或者宿主机重启,数据都会被持久化。原因是容器会保留停止的容器。

尽管我们可以以此起步,但是在实践中,仍然有很多地方可以优化。例如:

  • 在Jenkins容器之前,使用NGINX设置代理服务器
  • 管理多个镜像和容器开始变得越来越复杂,即使是使用makefile。有没有更简单的方法?
  • 我们需要一个备份Jenkins环境的方法,特别是任务。
  • 如果我们不想使用D恶变作为基础操作系统呢?如果我们不想依赖于外部镜像呢?
  • 我们还未构建从节点。尽管我们的容器允许来自任何标准的从节点的连接,但是如果将从节点创建成容器,不是更酷吗?

每个优化点都可以写成一篇独立的博客。之后,我们将要设置一个web代理,并讨论如何处理三个容器,这意味着我们将引入Docker Compose。其他的优化点,将在未来的博客中逐一介绍。敬请期待!

原文链接: DOCKER & JENKINS: DATA THAT PERSISTS (翻译:夏彬 校对:)

正文到此结束
Loading...