Docker和Jenkins像DevOps界的巧克力和花生酱那样,它们的组合产生了无数的机会,当然也产生了很多难题,笔者将提及这两个方面。
本文中,我假定读者已经熟悉Jenkins和Docker,我将把焦点放在特定的配置上而不是把笔墨花费在许多博文已经介绍过的入门概念上。
设定目标
我所要达成的目标其实非常简单:在一个容器中搭建Jenkins主节点,并且在多个主机上搭建多个JNLP代理容器。这些代理节点可以运行在不同的AWS VPC或者ECS上。
我的目标是得到一个能在任何主机上部署的通用配置,而每个项目分别定义各自的构建环境。这样各个开发团队就可以掌控这份配置,而不用经由Jenkins的构建团队。我会尽量避免构建一个特定工具集的代理节点。容器技术能实现这样的构建环境,但是要真正把每个细节都做好绝对是一个挑战。
为了实现这个目标,我还使用了Jenkins Pipeline / Workflow插件。这个插件让你能非常优雅地使用DSL语言描述构建过程,例如这样简单地定义:
```js
node('test-agent') {
stage "Container Prep"
// do the thing in the container
docker.image('maven:3.3.3-jdk-8').inside {
// get the codez
stage 'Checkout'
git url: 'https://github.com/damnhandy/Handy-URI-Templates.git'
stage 'Build'
// Do the build
sh "./mvnw clean install"
}
}
```
这个pipeline会在一个名为"test-agent"的Jenkins代理上执行,它会基于“maven”3.3.3-jdk-8”镜像构建一个容器。这个pipeline在物理节点上能正常运行,但是在容器中运行则会报错。
运行在Docker中的Docker
在容器中运行Jenkins的主或从节点,可能有人会以为我需要特权模式来使用"Docker in Docker",但是我并没有,Jérôme Petazzoni发表了一篇文章[《用Docker-in-Docker来构建持续集成环境?请三思》](https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/),你应该参考一下这篇文章。
如果你还在使用wrapdocker的脚本,你应该问问自己为什么,因为这样用起来更简单:
```sh
docker run -v ${JENKINS_HOME}:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 8080:8080 -p 50000:50000 \
index.csphere.cn/microimages/jenkins
```
这个命令会启动Jenkins并且可以拥有所有的容器操作功能,所以并不需要特权模式来启动容器,也不需要"Docker-in-Docker"模式。
有个地方需要注意:在这里你不能用官方的Jenkins镜像,因为jenkins用户需要属于docker用户组,这样才能使用socket,从而能在容器中的Jenkins调用docker,最终实现通过Jenkins构建和运行其他容器。
Jenkins JNLP代理容器
在“系统管理”=>“管理节点”页面,点击“新建节点”,可以添加slave:
Jenkins从节点的启动方式与主节点类似,它也需要连接docker的socket接口,你可以这样启动:
```sh
docker run -v ${JENKINS_HOME}:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
--name=jenkins-slave \
-d index.csphere.cn/microimages/jenkins-slave \
-url http://jenkins-master:8080/ \
a0a1b92971030d5f5dd69bd972c6cd899f705ddd3699ca3c5e92f937d860be7e \
test-agent
```
与Jenkins主节点一样,你需要确保jenkins用户有权限访问docker socket接口,我使用的是Jenkins jnlp从节点容器,这样,这个slave容器就可以执行构建操作了,注意secret参数需要从master上的某个slave里查看。
准备就绪,开始构建
在容器中开始一个构建过程不难,问题是你必需让这个代理容器绑定一个宿主机上的路径<code>${JENKINS_HOME}:/var/jenkins_home</code>,而且被构建的容器也需要这个目录的访问权限。
```sh
docker run -t -d -u 1000:1000 -w /var/jenkins_home/workspace/uri-templates-in-docker \
-v /var/jenkins_home/workspace/uri-templates-in-docker:/var/jenkins_home/workspace/uri-templates-in-docker:rw \
-e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** \
-e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** \
maven:3.3.3-jdk-8 cat
```
这个容器会把宿主机上的 /var/jenkins_home/workspace/uri-templates-in-docker目录挂载到容器化环境以供Maven使用,并且会把这个路径设置成当前工作路径,这些在物理机上都能正常运行,但是要在容器中执行,我需要尝试这样做:
这样明显不行,因为我把docker socket端口映射到了Jenkins代理容器上,挂载到Jenkins agent容器的所有卷实际上都是引用宿主上的路径,假定宿主上的<code>${JENKINS_HOME}</code>是<code> /opt/jenkins_home</code>,以下的命令应该生效:
```sh
docker run -t -d -u 1000:1000 -w /opt/jenkins_home/workspace/uri-templates-in-docker \
-v /opt/jenkins_home:/var/jenkins_home/workspace/uri-templates-in-docker:rw \
-e ********
maven:3.3.3-jdk-8 cat
```
总结
把构建环境容器化是一个非常好的主意,这样节省了很多时间。
注意,这份代码可能不正正满足你的需求,但是起码是一个demo,我希望本文能帮助更多人用上Jenkins的容器来构建应用。
关于希云cSphere
希云cSphere是一个高度集成、功能强大的Docker私有云平台和类PaaS解决方案,其架构设计借鉴了VMWare vSphere的思想。系统健壮性比肩VMWare这样的商业产品,产品经过一年多十多个版本的迭代更新,在内部更是经历了1000次以上的破坏性测试,目前已经在金融、制造、游戏、安全、电商、教育等多个领域落地。
cSphere的亮点:
- 平台适应应用,不需要应用适应平台。
- 多应用架构多场景支持,希云承诺不论是5年前的应用、现在的甚至5年后的应用架构都可以在希云上完美支持
- 搬迁现有业务,代码、架构无需任何修改
- 希云产品以自研发为主,抛弃了“拼凑”模式,有力保证了工程质量
- 真正企业级的PaaS,可满足高复杂项目需求
欢迎联系我们:
- 电话 400-686-1560
- 邮箱 [email protected]