基于Kubernetes的Jenkins实现动态slave

前言

一般情况下都是在物理机或者虚拟机上部署Jenkins,但是这种部署方式会存在一些缺点,比如:

  1. master节点故障,整体不可用
  2. 每个slave都需要维护,较为繁琐
  3. 资源分配不均衡,且资源利用率低

因此需要更可靠和高效的方式来完成CI/CD流程。

1649815494764.png

如图所示,master和slave以Pod的形式运行在kubernetes集群中,并且将配置数据存储到PV中。但是其中slave运行在多个节点,但是不是一直运行的状态,会根据需求动态创建并自动删除。

工作流程:

  1. master接收到build请求,会根据配置的label动态创建一个slave的Pod并注册到master
  2. slave运行完job,该Pod自动注销并且删除
  3. 恢复到最初的状态

架构优点:

  1. 高可用。当master故障时,kubernetes 会自动创建一个新的Pod,并且将之前的PV分配给新的Pod,保证数据的完整
  2. 动态伸缩。根据job来动态创建slave的Pod,并且完成后自动释放资源,提供资源利用率
  3. 可扩展。当资源不足时,可以通过增加node的方式快速增加slave

部署

编排文件

  1. PV/PVC 提供数据持久化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Delete
nfs:
server: x.x.x.x
path: /Public/jenkins

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: devops
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 500Gi

  1. 角色授权
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-sa
namespace: devops

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: jenkins-cr
rules:
- apiGroups: ["extensions", "apps"]
resources: ["deployments"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["services"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: jenkins-crd
roleRef:
kind: ClusterRole
name: jenkins-cr
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: jenkins-sa
namespace: devops
  1. 创建deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jenkins
namespace: devops
spec:
template:
metadata:
labels:
app: jenkins
spec:
terminationGracePeriodSeconds: 10
serviceAccount: jenkins-sa
containers:
- name: jenkins
image: jenkins/jenkins:lts
imagePullPolicy: IfNotPresent
env:
- name:JAVA_OPTS
value: -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai -Dhudson.model.DownloadService.noSignatureCheck=true
ports:
- containerPort: 8080
name: web
protocol: TCP
- containerPort: 50000
name: agent
protocol: TCP
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
volumeMounts:
- name: jenkinshome
mountPath: /var/jenkins_home
securityContext:
fsGroup: 1000
volumes:
- name: jenkinshome
persistentVolumeClaim:
claimName: jenkins-pvc

---
apiVersion: v1
kind: Service
metadata:
name: jenkins
namespace: devops
labels:
app: jenkins
spec:
selector:
app: jenkins
type: NodePort
ports:
- name: web
port: 8080
targetPort: web
nodePort: 30002
- name: agent
port: 50000
targetPort: agent

依次执行上述编排文件即可。需要注意PV的文件目录权限是否需要单独修改。

配置

初始化

Jenkins启动后先进行初步配置,此处不在详细描述。需要注意的是:

  • 访问的地址是使用的nodePort:30002端口
  • 初始化的密码文件可以从nfs目录上获取
  • 必要插件Pipline和kubernetes

配置kubernetes

节点管理-》Configure Clouds -》kubernetes

1649817346433.png

Kubernetes Cloud details

1649817406288.png

  1. 注意此处填写的是kubernetes 集群6443的地址。
  2. 由于之前创建了account,因此此处无需填写凭据
  3. 命名空间可以独立
  4. 信息填写完成后注意测试一下。如果测试失败,很可能是权限问题,需要把ServiceAccount的凭证jenkins-sa添加进来。

1649817519819.png

注意此处的Jenkins地址,要填写 http://jenkins.devops.svc.cluster.local:8080 。因为之后slave的Pod是需要访问master节点的,只有这种域名的形式才能更灵活的满足需求。如果想不明白为什么是这样的域名,可以思考一下域名解析的过程。

1649817671870.png

1649817705400.png

这两部分是配置slave的Pod的,重点部分在于镜像。jnlp是Jenkins的slave访问master的一种方式。

验证

1649817799601.png

1
2
3
4
~ kubectl get pods -n devops
NAME READY STATUS RESTARTS AGE
jenkins-666657ffdd-vvdx8 1/1 Running 0 16h
test-6-5gh46-qfs4k-jz13z 0/2 Terminating 0 61s

此时slave的Pod已经运行完成,自动终止了,之后会自动释放。

调试

使用podTemplate

根据标签,调度到其他节点,且使用Jenkins-slave的label

1
2
3
4
5
6
7
8
9
10
11
12
13
14
podTemplate(cloud: 'kubernetes', inheritFrom: 'jenkins-slave', label: 'jenkins-slave', name: 'test-label', namespace: 'devops') {
// some block
}
node('jenkins-slave') {
stage('get code') {
git credentialsId: '04bfb304-3573-4876-ad8c-52e43d047d6a', url: 'http://192.168.1.110/SystemProject/joyshebao/DiscoveryServer.git'
}
stage('build') {
container('maven381') {
sh 'mvn -Dmaven.test.skip=true clean package -f pom.xml'
archiveArtifacts '**/target/*.jar'
}
}
}

调度后的Pod

1
2
3
NAME                       READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
jenkins-666657ffdd-vvdx8 1/1 Running 0 2d17h 10.200.128.174 uat-master <none> <none>
jenkins-slave-6pz0c 2/2 Running 0 19s 10.200.229.150 pre-205 <none> <none>

使用变量

总结

纠结了好几天,总算调试出一些结果。

  1. 事实证明,label的作用是决定是否使用模板,和kubernetes中的label毫无关系;
  2. 脚本式的pipeline和声明式的pipeline存在很大的不同,至少语法上就有很大差别;
  3. 解决了maven项目重复下载依赖的问题,挂载.m2目录即可;

目前仍存在的问题:

  1. 如何决定让slave不运行在某些机器上?
  2. 哪种pipeline才是主流呢?