重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
目前很多企业应用都已经容器化,版本发布比较多,构建的次数也比较多,相对于之前单台 jenkins 有了很大的挑战
,传统的 Jenkins Slave 一主多从方式会存在一些痛点:
创新互联建站是一家以网络技术公司,为中小企业提供网站维护、成都网站建设、成都网站制作、网站备案、服务器租用、域名注册、软件开发、成都小程序开发等企业互联网相关业务,是一家有着丰富的互联网运营推广经验的科技公司,有着多年的网站建站经验,致力于帮助中小企业在互联网让打出自已的品牌和口碑,让企业在互联网上打开一个面向全国乃至全球的业务窗口:建站咨询电话:13518219792
从图上可以看到 Jenkins Master 和 Jenkins Slave 以 Docker Container 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。
这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Docker Container 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且 Docker Container 也会自动删除,恢复到最初状态。
这种方式带来的好处有很多:
我们把 master 节点部署到 k8s 集群中,大家可以参照 官方 github 文档进行配置,我这里进行了一点简化,我这里使用的是 nfs 来存储 jenkins 的数据,用于进行持久存储。
kubectl apply -f https://raw.githubusercontent.com/wangzan18/jenkins-agent-k8s-cicd/master/master/jenkins.yaml
说明一下:这里 Service 我们暴漏了端口 8080 和 50000,8080 为访问 Jenkins Server 页面端口,50000 为创建的 Jenkins Slave 与 Master 建立连接进行通信的默认端口,如果不暴露的话,Slave 无法跟 Master 建立连接。这里使用 NodePort 方式暴漏端口,并未指定其端口号,由 Kubernetes 系统默认分配,当然也可以指定不重复的端口号(范围在 30000~32767)。
Jenkins 的配置过程我这里不再掩饰,我们直接配置 kubernetes plugin。
管理员账户登录 Jenkins Master 页面,点击 “系统管理” —> “管理插件” —> “可选插件” —> “Kubernetes plugin” 勾选安装即可。
安装完毕后,点击 “系统管理” —> “系统设置” —> “新增一个云” —> 选择 “Kubernetes”,然后填写 Kubernetes 和 Jenkins 配置信息。
说明一下:
<svc_name>.<namespace_name>.svc.cluster.local
的命名方式,或者直接填写外部 Kubernetes 的地址 https://<ClusterIP>:<Ports>
。http://<ClusterIP>:<Node_Port>
方式。配置完毕,可以点击 “Test Connection” 按钮测试是否能够连接的到 Kubernetes,如果显示 Connection test successful 则表示连接成功,配置没有问题。
因为我们的 jenkins 是集群内部的 pod,所以它是可以直接和 kubernetes api 进行通信,并且我们也赋予了相应的权限,如果说 master 是创建在集群外部的,我们需要提前为 jenkins agent 创建一个 service account,然后把相应的 token 赋予到凭据的 sercet text。
创建一个 Pipeline 类型 Job 并命名为 jenkins-pipeline
,然后在 Pipeline 脚本处填写一个简单的测试脚本如下:
podTemplate {
node(POD_LABEL) {
stage('Run shell') {
sh 'echo hello world'
sh 'sleep 60'
}
}
}
创建还 job 之后,点击构建,我们会在构建队列中发现一个待执行的 job,因为我们在 pipeline 中要求 jenkins agent 节点的名称为 POD_LABEL,没有发现这个 agent,所以会去请求 kubernetes 去创建 agent 节点。
jenkins agent 节点创建好了之后,会去 jenkins master 注册,并去执行队列中的 job,完成之后取消注册,并自行销毁。
我们还可以去 console 查看构建日志。
也可以在 k8s 上面看到启动的 agent 容器。
wangzan:~/k8s $ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
jenkins-5df4dff655-f4gk8 1/1 Running 0 25m app=jenkins,pod-template-hash=5df4dff655
jenkins-pipeline-5-lbs5j-b2jl6-0mk2g 1/1 Running 0 7s jenkins/label=jenkins-pipeline_5-lbs5j,jenkins=slave
myapp1 1/1 Running 0 21h app=myapp1
The podTemplate
is a template of a pod that will be used to create agents. It can be either configured via the user interface, or via pipeline.
Either way it provides access to the following fields:
kubernetes
POD_LABEL
will be defined inside the step.merge()
or override()
. Controls whether the yaml definition overrides or is merged with the yaml definition inherited from pod templates declared with inheritFrom
. Defaults to override()
.activeDeadlineSeconds
has passed.podRetention
is set to 'never()' or 'onFailure()', pod is deleted after this deadline is passed.true
前面的 pipeline 中的 agent 是使用的默认的镜像 jenkins/jnlp-slave:3.35-5-alpine,我们也可以添加其他的一些镜像到 pod 里面。
创建一个 Pipeline 类型 Job 并命名为 jenkins-pipeline-container
,然后在 Pipeline 脚本处填写一个简单的测试脚本如下:
podTemplate(containers: [
containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'golang', image: 'golang:1.8.0', ttyEnabled: true, command: 'cat')
]) {
node(POD_LABEL) {
stage('Get a Maven project') {
git 'https://github.com/jenkinsci/kubernetes-plugin.git'
container('maven') {
stage('Build a Maven project') {
sh 'mvn -B clean install'
}
}
}
stage('Get a Golang project') {
git url: 'https://github.com/hashicorp/terraform.git'
container('golang') {
stage('Build a Go project') {
sh """
mkdir -p /go/src/github.com/hashicorp
ln -s `pwd` /go/src/github.com/hashicorp/terraform
cd /go/src/github.com/hashicorp/terraform && make core-dev
"""
}
}
}
}
}
从 k8s 中也可以看到 pod 中存在三个容器。
wangzan:~/k8s $ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
jenkins-5df4dff655-f4gk8 1/1 Running 0 42m app=jenkins,pod-template-hash=5df4dff655
jenkins-pipeline-container-1-6zf73-chltq-b0rjt 3/3 Running 0 70s jenkins/label=jenkins-pipeline-container_1-6zf73,jenkins=slave
myapp1
The containerTemplate
is a template of container that will be added to the pod. Again, its configurable via the user interface or via pipeline and allows you to set the following fields:
使用 SCM 可以有很多好处:
使用 SCM ,就需要我们把上面所写的 pipeline 代码放到 Jenkinsfile,一般是这个名字,当然也可以自定义名称,我们把上面第一个案例使用 SCM 运行一下,首先就是修改我们的 job。
我的 jenkinsfile 地址为 https://github.com/wangzan18/jenkins-agent-k8s-cicd/blob/master/jenkinsfile/jenkins-pipeline-podtemplate.jenkinsfile 。
然后在控制台查看运行日志。
其他参数大家可以根据自己的情况进行设定。
Jenkins 中除了使用 Pipeline 方式运行 Job 外,通常我们也会使用普通类型 Job,如果也要想使用 kubernetes plugin 来构建任务
那么就需要点击 “系统管理” —> “系统设置” —> “云” —> “Kubernetes” —> “Add Pod Template” 进行配置 “Kubernetes Pod Template” 信息。
Labels 名:在配置非 pipeline 类型 Job 时,用来指定任务运行的节点。
Containers Name: 这里要注意的是,如果 Name 配置为 jnlp,那么 Kubernetes 会用下边指定的 Docker Image 代替默认的 jenkinsci/jnlp-slave 镜像,否则,Kubernetes plugin 还是会用默认的 jenkinsci/jnlp-slave 镜像与 Jenkins Server 建立连接,即使我们指定其他 Docker Image。这里我配置为 jenkins-slave,意思就是使用 plugin 默认的镜像与 jenkins server 建立连接,当我选择 jnlp 的时候,发现镜像无法与 jenkins server 建立连接,具体情况我也不太清楚,也有可能是镜像的问题。
新建一个自由风格的 Job 名称为 jenkins-simple
,配置 “Restrict where this project can be run” 勾选,在 “Label Expression” 后边输出我们上边创建模板是指定的 Labels 名称 jnlp-agent,意思是指定该 Job 匹配 jenkins-slave
标签的 Slave 上运行。
效果如我们预期所示:
前面我随便在 https://hub.docker.com/r/jenkins/jnlp-slave 中选择了一个镜像,发现无法与 jenkins server 建立连接,那我们就自己制作一个镜像。
通过 kubernetest plugin 默认提供的镜像 jenkinsci/jnlp-slave 可以完成一些基本的操作,它是基于 openjdk:8-jdk 镜像来扩展的,但是对于我们来说这个镜像功能过于简单,比如我们想执行 Maven 编译或者其他命令时,就有问题了,那么可以通过制作自己的镜像来预安装一些软件,既能实现 jenkins-slave 功能,又可以完成自己个性化需求,那就比较不错了。如果我们从头开始制作镜像的话,会稍微麻烦些,不过可以参考 jenkinsci/jnlp-slave 和 jenkinsci/docker-slave 这两个官方镜像来做,注意:jenkinsci/jnlp-slave 镜像是基于 jenkinsci/docker-slave 来做的。这里我简单演示下,基于 jenkinsci/jnlp-slave:latest 镜像,在其基础上做扩展,安装 Maven 到镜像内,然后运行验证是否可行吧,大家可以查看我的镜像:https://hub.docker.com/r/wangzan18/jenkins-slave-maven 。
podTemplate(containers: [
containerTemplate(
name: 'jnlp',
image: 'wangzan18/jenkins-agent:maven-3.6.3',
alwaysPullImage: false,
args: '${computer.jnlpmac} ${computer.name}'),
]) {
node(POD_LABEL) {
stage('git pull') {
echo "hello git"
}
stage('build') {
sh 'mvn -version'
}
stage('test') {
echo "hello test"
}
stage('deploy') {
echo "hello deploy"
sleep 10
}
}
}
这里 containerTemplate 的 name 属性必须叫 jnlp
,Kubernetes 才能用自定义 images 指定的镜像替换默认的 jenkinsci/jnlp-slave 镜像。此外,args 参数传递两个 jenkins-slave 运行需要的参数。还有一点就是这里并不需要指定 container('jnlp'){...} 了,因为它被 Kubernetes 指定了要被执行的容器,所以直接执行 Stage 就可以了。
可以看到已经达到我们想要的效果,确实也是使用我们自定义的 jenkins-slave 镜像。
我测试的过程中,使用自由风格的 job,不管使用什么镜像,镜像就是无法自主连接 jenkins server,目前我也不清楚是哪里的原因,如果有知道的小伙伴可以留言回复。
参考文档:https://github.com/jenkinsci/kubernetes-plugin