相比传统的在多台主机上部署Jenkins和slave,K8s环境下有诸多好处,比如:
- CI的部署流程脚本化,方便重用和跟踪改动
- master节点出现故障后自动重启
- slave节点繁忙时自动扩容,空闲时自动释放
部署Master到K8s
为了方便跟踪所有的改动,可以把Jenkins相关的文件放在一个代码库里,并启用Git。
首先在K8s下创建一个namspace:
1 | kubectl create namespace ci |
创建Deployment
创建一个K8s的deployment(deployment.yml
)脚本:
1 | --- |
- 在metadata中指定了namespace为刚创建的
ci
- 使用了一个名为
jenkins2
的service account,创建方式如下。 - 在containers中指定了镜像为Jenkins的官方镜像
jenkins/jenkins:lts
。如果需要在官方镜像上加一些定制化的功能,也可以使用自己的镜像,但是注意如果镜像改动频繁的话要把imagePullPolicy
设置为Always
。 - 8080端口用来访问UI,50000端口用来和Slave通信。
- 这里挂载了两个volume,其中一个以PVC和PV的方式创建,用来保存Jenkins的各种配置,这样的话运行Jenkins的pods重新创建后就不会丢失配置信息。创建方式如下。
securityContext
,以及名为dockersock
的volume都是用来解决所谓的docker-in-docker的问题,简单来说就是在jenkins里使用K8s master的docker,下面有详细说明。
创建Service Account
Jenkins部署上K8s之后会对K8s进行一些操作,比如部署时需要创建Pod,这些操作需要权限才能完成,这里使用了Role-based access control,创建方式如下(rbac.yml
):
1 | apiVersion: v1 |
- 第一部分声明了
service account
,metadata
里指定了service account的name
,这个name
也就是前面deployment.yml
里使用到的serviceAccountName
。 - 第二部分声明了
RoleBinding
,也就是给service account
赋权,其中:ci-self-rolebinding
是这个RoleBinding
的名字roleRef
里引用了admin
这个角色,这是K8s内置的一个角色,拥有大部分的权限。这一步声明了这个RoleBinding
要用到的权限。subjects
描述了要将上面的权限绑定到什么地方,这里绑定到了之前创建的service account
上。
最后apply一下:
1 | kubectl create -f rbac.yaml |
至此,给service account
赋权的操作就完成了。
创建PV及PVC
Persist Volume
和Persis Volume Claim
是K8s里的概念,其中前者就像一块云盘,需要使用时通过后者来申请其中的一小块。
首先创建一个Persist Volume
(pv.yml
):
1 | apiVersion: v1 |
- 这里使用了AWS的EBS作为
Persist Volume
的载体,需要注意的是不同载体的accessModes
会不同,EBS就只支持ReadWriteOnce
(只能被一个node读写)。详细的列表可以在这里找到。
然后创建一个Persis Volume Claim
(pcv.yml
):
1 | --- |
最后apply一下:
1 | kubectl create -f pv.yml |
一切准备好之后,就可以创建Deployment了:
1 | kubectl create -f deployment.yml |
这时候运行kubectl get all -n ci
,就能看到Jenkins已经起起来了。
创建Service
这时候Jenkins虽然已经起起来了,但是并不能通过浏览器访问,因为还没对外暴露,所以需要创建一个service
(service.yml):
1 | --- |
- 使用了AWS ELB来暴露端口,端口配置和
deployment
里的一样。
apply一下:
1 | kubectl create -f service.yml |
这时候运行kubectl get all -n ci
应该就能看到类似的界面:
1 | ➜ ~ kubectl get service -n ci |
然后就可以通过external IP来访问了:
Docker-in-Docker
Jenkins是以Docker的方式部署到K8s里去的,而在使用Jenkins的时候,又可能会需要运行Docker来打包或者运行镜像,这就产生了所谓的Dcoker-in-Docker的问题,如果在Jenkins里再安装一个Docker,就可能会遇到一些非常底层的问题(详细资料可以看这篇文章),所以推荐的方式是将K8s master机器上的Docker socket绑定进Jenkins容器中来解决。简单来说就是在Jenkins里使用宿主的Docker。
我们在Deployment脚本中有这么一个volume:
1 | volumes: |
然后又有这么一个mount:
1 | volumeMounts: |
可以看到我们将宿主的/var/run/docker.sock
绑定到了Jenkins的/var/run/docker.sock
,比较理想的情况下这样就够了,但是往往还会遇到权限的问题,这时候如果进入到jenkins的pod里运行docker ps
,应该就会出现权限的错误。
解决的方法就是将docker的group ID加入到Deployment的fsGroup
字段里,一开始的值是995,但是这个值每个人都可能会不同,可以通过以下命令查看group ID。
首先通过kubectl get pods -n ci
拿到pod的ID,然后运行:
1 | kubectl exec -it pod/jenkins2-6c68468fd4-ccqz2 /bin/bash -n ci |
这时候就能看到如下输出:
1 | jenkins@jenkins2-6c68468fd4-ccqz2:/$ ls -l /var/run/docker.sock |
这个995就是所需要的fsGroup
的值,将其写入deployment脚本后重新apply以下就可以了。
动态创建Slave
至此Jenkins就已经可以使用了,但是build时用的slave还是master自己,这并不是一个很好的选择,原因在于官方的Jenkins镜像内置的东西非常少,而且默认不启用root账户,所以在上面装东西非常困难,其次是随着要build的任务种类的增加,master可能会变得越来越臃肿,并且build比较繁忙时也会产生排队很久的现象。
所以利用K8s的特性来动态的创建和释放slave是一个更好的选择。
首先,在Jenkins->系统管理->插件管理里,安装Jenkins的Plugin,这个插件能让Jenkins和K8s实现集成:
然后,在Jenkins->系统管理->系统设置里,对插件进行配置:
这一步里填写K8s的信息:
- 命名空间就是一开始创建的ci
- Jenkins地址的格式为:service-name.namespace.svc.cluster.local:8080,创建service时名字为jenkins2,所以这里用这个名字
下一步填写Slave的信息:
- 标签列表非常重要,build脚本里就是用这个名字来指定slave
container的name必须为jnlp,否则会报错
这里使用了
cnych/jenkins:jnlp6
来作为slave的镜像,包含了kubectl等实用功能
最后一步做了一些额外配置:
- 把K8s的docker.sock挂载到slave容器里面去,解决docker-in-docker的问题
- 把K8s的.kube挂载到slave容器里面去,slave就能通过kubectl来访问K8s集群
- 这里代理的空闲存活时间要注意,如果用默认值的话slave用完后就会立即销毁,这里为了查看一些build后的产物,设置成了一个月后才会销毁
测试
至此Jenkins就已经完全配置好了,跑个简单的build测试一下。
首先创建一个freestyle的project:
然后在限制项目的运行节点里,写入之前配置的标签列表的值。
最后在构建里新建一个shell:
命令如下:
1 | echo "************test docker in docker************" |
创建完成后点立即构建,可以看到如下输出:
同时可以看到,slave已经被自动创建出来了。
评论(需梯子)