在Docker容器中运行Jenkins

jopen 9年前


【编者的话】这篇博客是Riot的Docker实践系列博客的第二篇,主要内容是:基于Cloudbees镜像创建新的Dockerfile,设置了一个日志目录,并介绍了如何使用docker exec命令查看日志文件。

在Docker容器中运行Jenkins

当我一年前开始学习Docker的时候,发现很难找到好的文档和实例,即使是今天,也只能找到一些简单的用例,完全不能用作真正的产品。使用Docker容器来产品化应用,需要适应容器的短暂性和单一进程的特性。这对于需要数据持久化和多进程的应用来说,是一个挑战。

就像我 上一篇博客 中提到的,我们在Jenkins这样一个开源软件的基础上构建自己的自动化系统。Jenkins也是一种容器化应用的一种方式。我们使用以下的体系结构组件来部署Jenkins:

  • Jenkins主服务器(Java进程)
  • Jenkins master data (插件,任务定义等)
  • NGINX web代理(使用SSL证书)
  • 构建slave代理(可以通过SSH、JNLP或Jenkins Master连接)

这是一个很好的开端。通过这一系列博客,我们考虑将上述提到的组件容器化,并使用Docker容器来构建从节点(slaves)。对于初学者,我们将在Docker容器中创建Jenkins主服务器,然后转向处理数据持久化,并通过NGINX添加web代理。

整个系列博客将涉及以下几个Docker概念:

  • 创建你自己的Dockerfiles
  • 最小化对于公共镜像的依赖
  • 创建和使用数据卷(Data-Volumes),包括备份
  • 利用容器创建容器化的“构建环境”
  • 使用镜像和Jenkins来处理“机密”数据

如果你还没有看过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容器。现在,我们将集中在现有的技术上。

要求

  • 你需要Windows 7(或更高版本)或者Mac OSX 10.7(或更高版本)。
  • 你需要开启BIOS中的虚拟化选项来运行VirtualBox。
  • 如果你已经安装了Virtualbox和Docker Toolbox,你就可以跳过步骤1。这篇博客使用的是Docker Toolbox 1.8和Virtualbox 5.0。
    • 请注意:如果你事先安装了Virtualbox,那么在安装Docker Toolbox时可能会遇到一些有趣的问题。当我这么做时,Virtualbox并没有完全升级,现有的镜像配置信息也遭到了破坏。我建议先卸载 Virtualbox,并将之前的镜像导出为OVA文件或ISO文件。
    • * 如果你在老版本的boot2docker的基础上安装新版本的Docker Toolbox,也会有问题。因此,最好首先卸载boot2docker和ISO镜像。

KITEMATIC简介

在Docker 1.8和Docker Toolbox中,已经包含了“Kitematic”,它是一个漂亮的GUI工具,来管理和可视化Docker镜像和容器。这篇教程大部分专注在使用命令 行来控制Docker。这是为了更好地暴露给读者底层的机制。同样地,后续博客将涉及Compose的用法,同时开启和关闭多个容器。然 而,Kitematic的确是一个很酷的工具,未来我会专门写一篇博客介绍如何在开发生命周期中使用它。

步骤1:安装Docker Toolbox

  1. 点击 https://www.docker.com/toolbox
  2. 下载和安装Docker Toolbox。请注意,在此过程中将安装VirtualBox和一些其他工具(Compose, Kitematic, Docker Machine)。
  3. 按照步骤安装。
  4. 运行“Docker Quickstart Terminal”,来验证安装是否成功。在Windows上,在桌面上有一个快捷方式;在Max OSX上,它在你的Docker文件夹中。然后,运行以下命令,并确保没有发生任何错误。
    docker ps
    docker info

在Docker容器中运行Jenkins

步骤2:查询你的IP地址

我们需要访问Jenkins web服务器(最终是一个NGINX服务器)。最简单的方式,就是在浏览器中直接输入Docker宿主机的IP地址。

你可以使用ifconfig或ipconfig来查询IP地址,但是Docker Toolbox通过docker-machine提供了一个方便的命令:

docker-machine ip default

在Docker容器中运行Jenkins

这就是你的宿主机的IP地址,你的web服务就监听在上面。当然,这个IP是在你本地的机器上,并不能被外部访问。如果你希望从外部访问你的机 器,你需要在Virtualbox上设置端口转发(port forwarding)。我把这个留给你作为一个练习,可以参考Virtualbox的文档。

步骤3:Pull并运行Cloudbees的Jenkins容器

  1. 登录到Docker的终端窗口。
  2. 从公共仓库中pull Jenkins镜像:
    docker pull jenkins
    docker run -p 8080:8080 --name=jenkins-master jenkins
  3. 打开浏览器,并访问http://yourdockermachineiphere:8080。

在Docker容器中运行Jenkins

如果Jenkins没有显示在浏览器中,但是容器正在运行,那么请再次确认是否正确执行了步骤2,并获得了正确的IP地址。

在Docker容器中运行Jenkins

请注意,你可能注意到我使用了--name这个参数,并将这个容器命名为jenkins-master。命名容器是一个最佳实践,有三个优点:

1. 更容易记忆

2. Docker不允许两个容器使用同一个名字,可以防止开启两个同名容器。

3. 很多通用的Docker工具(如docker-compose)需要使用特定的容器名字,因此命名容器是一个最佳实践。

步骤4:更实用一点

之前的步骤是以最基本的参数来启动Jenkins。就像Riot一样,你是不太可能直接用默认配置来运行Jenkins的。让我们加上一些有用的参数,并解释为什么这么加。

步骤4A: 守护进程

你可能不希望直接将Jenkins的日志打到标准输出中。因此使用Docker的daemon标志(-d)来开启容器。

  1. 用Ctrl-c关闭你的容器
  2. 运行以下命令:
    docker rm jenkins-master
    docker run -p 8080:8080 --name=jenkins-master -d jenkins

在Docker容器中运行Jenkins

现在你应该可以获得一个hash字符串。如果你是刚接触Docker的话,那个hash值就是你的容器的唯一ID。

步骤4B: 内存设置

在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。

步骤4C: 升级连接池(Connection Pool)

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。

步骤5: 将这些组合起来

我把这些学到的内容放到一个简单的makefile中,你就可以用make命令来控制运行Jenkins Docker容器。你可以在这里找到:

你可以运行以下命令:

  • make build - 下拉Jenkins镜像
  • make run - 运行容器
  • make stop - 关闭容器
  • make clean - 关闭和删除已有容器

你不是必须使用makefile,只是使用它可以不用输入太多命令。你可以将这些命令放到一个脚本中。

COMMENTS

希望你能看到运行Docker和Jenkins是多么容易。我已经提供了一些基本的选项,来运行默认的Cloudbees Jenkins容器,并使其更实用。Cloudbees提供了很多有用的建议,例如如何预安装插件和存储Jenkins数据。

这个镜像也有一些缺点:没有一致的日志记录、没有持久化、没有web服务器代理、不能保证使用正确版本的Jenkins。这带来了一个问题:如果你想使用一个老版本,怎么办?或者你想使用最新版本?

在下一课中,我将介绍如何增强容器的鲁棒性。特别是:

  • 以Cloudbees为基础,创建自己的Dockerfile
  • 将部分环境变量移到这个新的镜像中
  • 创建一个日志目录,设置权限和其他有用的Jenkins目录,以及如何获得Jenkins实时的运行日志

第2课 - 基于Jenkins的镜像

在前面的课程中,我们讨论了如何准备开发环境,来运行Docker和Cloudbees提供的Jenkins镜像。我们发现这很简单,也易于使用,也有很多不错的特性。为了进一步进行优化,在本课程中将涉及以下几个概念:

  • 创建自己的Dockerfile
  • 在Dockerfile中设置环境变量
  • 在Dockerfile中创建目录,并设置权限
  • 使用docker exec执行命令

创建基础Dockerfile

我们希望改变Jenkins启动的默认行为。在上一篇博客中,我们的方法是通过makefile传递了一些参数作为环境变量。考虑到我们每次启动容器都需要这么做,因此我们可以把这些环境变量放到Dockerfile中。另外,我们自己的Dockerfile可以锁定Jenkins的版本,以防我们还没准备好升级Jenkins版本。

我们通过以下四步完成这一点:

  1. 创建工作路径。
  2. 使用你最喜爱的文本编辑器,创建一个新文件“Dockerfile”。
  3. 添加以下内容,并保存。
    FROM jenkins:1.609.1
    MAINTAINER yourname
  4. 在命令行中,输入以下命令:
    docker build -t myjenkins .

在Docker容器中运行Jenkins

我们所做的是,从公共的Docker仓库中下拉一个特定版本的Jenkins镜像。你可以找到所有可用的版本:

你总是可以使用FROM语句来设置任意版本的可用镜像。然而,你却不能设置成Jenkins的任意版本。因为这个版本实际上指的是镜像版本 (“tag”或“lable”),Cloudbees很友善地将它设置为Jenkins版本。但是Cloudbees并没有为每个Jenkins版本构建 镜像,只支持长期稳定版本。如果你希望使用一个特定版本,那么请关注后续博客,我将介绍不使用Cloudbees镜像,创建自己的镜像。

测试新的Dockerfile

我们可以运行以下命令,很容易地切换至新镜像:

docker run -p 8080:8080 --name=jenkins-master -d --env JAVA_OPTS="-Xmx8192m" --env JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300" myjenkins

我们可以将那些环境变量放在Dockerfile中。

添加环境变量到Dockerfile中

添加像环境变量这样的默认配置,是很容易的。这也提供了一种自文档化(self-documentation)的方式。而且,你总是可以在运行Docker容器的时候重写这些环境变量。

  1. 在Dockerfile的“MAINTAINER”一行下,添加以下内容:
    ENV JAVA_OPTS="-Xmx8192m"
    ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300"
  2. 保存并重建镜像:
    docker build -t myjenkins .

相当简单!你可以输入以下三条命令来测试镜像是否成功:

docker stop jenkins-master
docker rm jenkins-master
docker run -p 8080:8080 --name=jenkins-master -d myjenkins

在Docker容器中运行Jenkins

你的镜像应该可以立即启动!但是如何知道那些环境变量是否生效了呢?这很简单:可以使用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容器中运行Jenkins

你可以看到我们的设置已经生效。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

在Docker容器中运行Jenkins

不用担心,这是因为Cloudbees默认容器使用了“Jenkins”用户。你可以从Dockerfile( https://github.com/ Jenkinsci/docker/blob/master/Dockerfile)的底部看到:

USER jenkins

这对于创建/var/log/jenkins是不方便的,通常情况下,需要使用SUDO或者其他方式来创建目录(/var/log是root用户所有的)。幸运的是,Docker运行我们切换用户。在Dockerfile中添加以下内容:

  1. 在RUN mkdir line之前添加:
    USER root
  2. 在RUN mkdir line之后添加:
    RUN chown -R jenkins:jenkins /var/log/jenkins
  3. 在RUN chown line之后添加:
    USER jenkins

注意:我们需要添加chown命令,因为我们想要Jenkins用户写这个目录。然后,我们将用户重置为Jenkins,从而限制Dockerfile的行为。

再次构建镜像:

docker build -t myjenkins .

在Docker容器中运行Jenkins

之前的错误消失了。

通过设置日志目录(请注意:你可以设置成任意目录,为了一致性,我们选择了/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

在Docker容器中运行Jenkins

Jenkin崩溃后的日志恢复

bonus时间到了。如果Jenkins崩溃了,那么容器将停止,docker exec将停止工作。那么怎么办呢?

之后,我们将提供几种高级方法来持久化日志文件。现在,因为容器停止了,我们可以使用docker cp命令将文件拷贝出来。让我们通过停止容器来模拟一次崩溃,然后恢复日志:

  1. ctrl-c终止日志文件的查看
  2. 运行以下命令:
    docker stop jenkins-master
    docker cp jenkins-master:/var/log/jenkins/jenkins.log jenkins.log
    cat jenkins.log

在Docker容器中运行Jenkins

总结

你可以找到这篇教程中所有的代码:

https://github.com/maxfields2000/docker Jenkins_tutorial/tree/master/tutorial_02

为了更为简单一些,我们基于Cloudbees镜像创建了自己的Dockerfile。我们设置了一个更合适的目录来存储日志文件,并学习了如何使用docker exec命令查看它们。我们将默认设置放进Dockerfile中,从而可以将其纳入源码控制中。

我们仍然还面临着数据持久化的挑战。我们已经学会了如何从已停止的容器(Jenkins崩溃)中获取日志。但是,一旦容器停止,我们就失去了所有的工作。因此,如果没有持久化,那么这个Jenkins镜像就只能作为本地的开发和测试。

通过Dockerfile,我们可以解决持久化问题。在下一篇博客中,我们将讨论以下问题:

  • 保存Jenkins的任务和插件数据(Plugin data)
  • 通过卷(Volumes)实现数据持久化
  • 制作一个数据卷容器(Data-Volume container)
  • 与其他容器分享卷中的数据

原文链接: PUTTING JENKINS IN A DOCKER CONTAINER (翻译:夏彬 校对:李颖杰)