李加庆 分布式实验室
最近我开始尝试在Kubernetes中使用Jenkins,其实我也是想看看如何可以更好地运行 Docker Workflow插件。
我的思路是有一个可以运行Jenkins的Pod,并用它来运行通过Docker Workflow插件定义的构建。经过大量阅读和多次反复的实验,我发现有很多种方法来实现这一想法,当然这些方法各有不同的利弊。
在我阐述所有可能的配置之前,我觉得先来描述一下所有的这些插件是什么会对大家很有帮助。
一个使用了Docker的Jenkins插件,用以创建和使用slaves,它使用HTTP协议来与Docker通信并生成新的容器。这些容器只需支持Java并且可以运行SSHD,以便master可以通过SSH连接到容器内并施展它的魔力。网络上有许多slave容器的镜像,在我重新连接时最流行的镜像是evarga jenkins slave。
这个插件很不错但是让人感觉有点不可靠,虽然它可以创建Docker容器,但是有时候它却无法连接到slave并且会重试(往往会重试两三次),它会以相似的方法,使用很多不同的身份验证方法(密码、密钥验证等)尝试连接很多不同的slave镜像。
生成slaves的一种方法是使用插件。另外一种方法是“建立自己的slaves”,这几乎就是Swarm的思想所在。这种思想指的是Jenkins master来运行Swarm插件,用户负责启动Swarm客户端(它仅仅是个Java进程)。
java -jar /path/to/swarm-client.jar http://jenkins.master:8080
客户端连接到master机器并且让master知道客户端已经启动并且运行。然后master就能够在客户端开始构建。
这个插件允许你在工作流脚本中使用Docker镜像和容器,或者换句话说,此插件允许你在Docker容器中执行工作流步骤并且可以从工作流脚本中生成Docker。
为了概括一下在Docker镜像中你的构建的所有需求并且让你无须担心如何安装和配置它们,下面给出一个Docker Workflow脚本的例子:
node('docker') { docker.image('maven').inside { git 'https://github.com/fabric8io/example-camel-cdi' sh 'mvn clean install' } }
注意:你无需在Docker Workflow Plugin中使用Docker Plugin。
另外:Docker Workflow Plugin使用的是Docker二进制,这就意味着无论你在何处想要使用Docker Workflow插件,你都必须安装Docker客户端。
差点忘了:构建的“执行器”和参与工作流的容器需要共享项目工作空间,这里我就不赘述了。只需记住,在Docker主机(或是一些缺少共享文件系统的主机)中,通常需要访问Docker host上的特定路径。无法满足这个需求会导致一些“难以监测”的问题比如构建永久悬挂等。
现在我们已经准备好了,让我们来一探究竟这些可能的设置是什么。
这是最简单的方法,它不会涉及Jenkins slaves,通过配置一系列固定的执行器,构建就可以直接运行在master上。
因为没有slaves,所以运行Jenkins的容器自身需要安装和配置Docker二进制文件来指向真正的Docker host。
如何在Kubernetes中使用Docker Host?
这里给出两种方法:
使用Kubernetes API
通过挂载 /var/run/docker.sock
你可以(1)通过使用一个简单的shell脚本,如下:
(2)通过指定一个挂载到Jenkins POD上的hostDir卷
一个像这样设置的实例可以在这里(https://raw.githubusercontent.com/iocanel/jenkins-poc/master/single-docker-daemon/kubernetes.json)找到。
优点:
缺点:
不具规模性
直接连接了Docker Daemon
需要获取主机上的具体路径(参见Docker Workflow Plugin)
由于显而易见的原因,先前的方法都不具规模性。因为Docker和Kubernetes早已准备就绪,所以将它们当做丰富的资源来使用似乎是一个非常好的主意。
所以,我们可以为每一个我们想要运行的构建增加Docker Plugin并用它生成一个slave容器。这就意味着我们需要一个Docker容器,这个容器有权访问Docker二进制文件(需要docker workflow支持)并且也可以从master上挂载项目的工作空间。
正如上文所述,master需要通过SSH连接到slave。为了使得连接成功,需要配置证书或是正确的SSH密钥。在这两种情况中,docker plugin的xml配置文件都需要更新以适用于Jenkins证书配置的编号(例子参见config.xml)。
那么,确切的说,这个编号是什么呢?
Jenkins使用Credential Plugin来存储和检索证书。每一套证书都有唯一的编号,其他插件可以根据这个编号找到一套适用的证书。考虑到安全因素,密码、口令等并不是以纯文本的方式存储,而是通过SHA256加密后存储。用来加密的密钥同样也被加密以确保更加安全。关于这个主题的更多详细的资料,你可以在这篇优秀的文章“Credential storage in Jenkins(http://xn--thibaud-dya.fr/jenkins_credentials.html)”中找到。
我希望您注意的是,鉴于证书在Jenkins的存储方式,建立一个在没有人为干扰下可以相互通信的master镜像和slave镜像是十分重要的。可以尝试使用像这样的脚本:
为了生成密钥和master key,并用他们来加密密码,你可以使用像下面这样的脚本:
若要真正地加密密码,我不会向任何人推荐这种做法,我展示上述脚本仅仅是为了强调这种加密是多么的复杂。当然了,像这样的脚本也会充分利用Credential Plugin内部的一些细节并且给人感觉很hack。我发现在配置证书时一个更加优雅的方法是将下面的groovy脚本写进Jenkins中的init.groovy.d文件:
上面的片段展示了如何使用一个空的口令来生成用户名/密码证书以及SSH私钥。
优点:
足够简单
缺点:
Docker Plugin现阶段还未发展到这个地步
直接连接到Docker daemon
需要访问主机的特定路径(参见Docker Workflow Plugin)
即使我们抛开与Docker Plugin相关的问题不谈,我仍然对于不与Docker daemon通信而是基于Kubernetes运行的方法非常感兴趣。
对于我们而言, 为了避免落后于Kubernetes。
可能的情况数量有所增加,既可以在Kubernetes master上直接使用dind,也可以结合Docker Plugin ,从而使得每一个salve都能够运行自己的demon并且实现100%隔离。
无论哪种方式,构建过程中发生的事情与环境之外完全隔离,另一方面它确实需要使用特权模式,这可能是一个问题,因为该模式可能无法在某些环境中使用(比如我上次检查时,它就无法在Google容器引擎中使用)。
注:通过在slave中托管docker daemon,我们可以从在外部docker中挂载卷的需求中解脱出来(请记住,只有执行程序和工作流步骤中需要共享工作空间)。
优点:
100%隔离
无需访问外部Docker的特定路径
缺点:
复杂性
需要特权模式
Docker 镜像不能被缓存
D.I.N.D.或者说还没未出现的方法仍需要给出一个用于缩放的解决方案。到目前为止,Docke Plugin似乎并不是一个理想的解决方案。此外,在Kubernetes中等同于Docker Plugin的插件(Kubernetes Plugin)似乎需要多一点的关注。所以,现在让我们来谈谈Swarm。
使用Swarm似乎是一个不错的选择,因为我们使用的是Kubernetes,它可以非常容易的开启N多个运行Swarm客户端的容器。我们可以使用带有合适容器的replication controller。
优点:
迅速
可扩展性
健壮性
缺点:
Slaves需要在外部管理
需要访问主机的特定路径(参见Docker Workflow Plugin)
在这种使用情况下,关于D.I.N.D的主要问题是,事实上,在Docker中的Docker,里面的镜像无法被缓存。可以去尝试做一个分享Docker Registry的实验,不过我不确定有没有可能实现。
另一方面,对于大多数其他方法,我们需要使用hostPath挂载,在一些环境中这可能无法奏效。
解决上述问题的方案是将Swarm与D.I.N.D结合。
保留Swarm客户端(而不是在每次构建之后都消除它),这样就解决了镜像缓存问题。
另外,对于D.I.N.D,我们也不再需要通过Kubernetes来使用hostPath挂载。
因此,对于我们来说,这是个两全之计。
优点:
迅速
可扩展性
健壮性
100%隔离
镜像可以被缓存
缺点:
Slave需要在外部管理
我尝试了上述所有的设置,并把它作为我正在做的poc的一部分:“Jenkins for Docker Workflow on Kubernetes(https://github.com/iocanel/jenkins-poc)”。
我觉得我应该分享出来。我还想要尝试的一些东西比如:
欢迎在评论中回复您的经验、建议或是指正。我希望这篇文章对您有用。