k8s中部署jenkins并利用master-slave模式实现CICD

1、Jenkins概念

1.1 前言

Jenkins 是一个开源的自动化构建工具,用于持续集成和持续交付。它可以帮助开发团队自动化构建、测试和部署软件,提高软件开发的效率和质量。Jenkins 具有以下特点:

  • 开源:Jenkins 是一个开源软件,可以免费使用。
  • 易用:Jenkins 提供了丰富的插件和模板,可以快速地搭建自动化构建和部署流程。
  • 可扩展:Jenkins 支持插件扩展,可以满足不同项目的需求。
  • 支持多种编程语言:Jenkins 支持多种编程语言,如 Java、Python、Ruby 等。
  • 支持多种构建工具:Jenkins 支持多种构建工具,如 Maven、Gradle、Ansible 等。
  • 支持多种部署方式:Jenkins 支持多种部署方式,如 SCM 仓库、Git 仓库、SVN 仓库等。
  • 支持多种测试工具:Jenkins 支持多种测试工具,如 Selenium、JUnit、SonarQube 等。

总之,Jenkins 是一个功能强大的自动化构建工具,可以帮助开发团队提高软件开发的效率和质量。

在生产环境中我们往往会在物理机或者虚拟机上部署jenkins,但是这种部署方式会有一些痛点,如下:

  • 主 Master 发生单点故障时,整个流程都不可用了
  • 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲
  • 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态
  • 资源有浪费,每台 Slave 可能是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会完全释放掉资源。
  • 扩展性差:在物理机或虚拟机上部署 Jenkins,当 Jenkins 需要扩展时,需要购买更多的服务器或虚拟机,扩展性较差。

正因为上面的这些种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面能够更好来解决上面的问题。

  • 优势
    • 基于云原生现有K8s集群来解决问题,充分的利用现有资源,无需再申请新虚机;
    • Slave可在构建任务来之时动态创建,工作结束后自动销毁,释放资源;
    • 可通过K8s原生来管理Jenkins的调度策略,防止Slave调度分配不均匀;
    • 通过云原生控制器来管理Jenkins配置,后期比较利于维护、扩展;
    • Jenkins 小概率意外宕机场景,通过K8s的机制可以自愈;
  • 劣势
    • 增加了系统复杂度;
    • 有一定技术壁垒;
    • 实现需要时间;

1.2 Master-Slave 工作原理

下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图:

从图中可以看出,Jenkins Master和Jenkins Slave以Pod的形式运行在Kubernetes集群的节点上。Master运行在其中的一个节点上,并将其配置数据存储到一个Volume中,而Slave运行在各个节点上,并且它并不总是处于运行状态,它会根据需求动态地创建并自动删除。

这种方法的工作流程大致如下:当Jenkins Master收到一个构建请求时,它会根据配置的Label动态地创建一个运行在Pod中的Jenkins Slave并将其注册到Master上。在运行完Job之后,这个Slave会被注销,Pod也会自动删除,恢复到原始状态。

设计优势

  • 动态伸缩

    合理的使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后, Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高, 还排队等待在该节点的情况。

  • 服务高可用

    当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。

  • 扩展性好

    当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一 个 Kubernetes Node 到集群中,从而实现扩展。

2、Jenkins Master部署

在kubernetes中我们可以通过helm或者自定义控制器文件部署,本文我们介绍自定义控制器文件部署方式。

Jenkins使用NFS做数据持久化

首先部署NFS服务器

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
# 安装NFS软件包:
# 对于Ubuntu系统,可以使用以下命令安装NFS软件包:
$ sudo apt-get update
$ sudo apt-get install nfs-kernel-server

# 对于CentOS系统,可以使用以下命令安装NFS软件包:
$ sudo yum install nfs-utils

# 配置NFS共享目录:
# 在NFS服务器上创建共享目录,例如:
sudo mkdir /nfsshare

# 将该目录设置为可共享:
$ sudo chmod 777 /nfsshare

# 配置NFS服务器:
# 编辑NFS服务器的配置文件:
$ sudo nano /etc/exports

# 在该文件中添加共享目录的配置:
$ /nfsshare *(rw,sync,no_root_squash)

# 保存并关闭文件。

# 启动NFS服务:
$ sudo systemctl start nfs-kernel-server

# 设置NFS服务开机自启动:
sudo systemctl enable nfs-kernel-server

# 配置NFS客户端:
# 在NFS客户端上安装NFS软件包:
$ sudo apt-get install nfs-kernel-client

# 或
$ sudo yum install nfs-utils

# 验证NFS共享:
$ showmount -e 192.168.1.0

创建PV、PVC,为Jenkins提供数据持久化

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
$ vim jenkins-pvc.yml

---
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Delete
nfs:
server: 10.0.1.2 # nfs服务器地址
path: /volume1/k8s/ha-nfs/jenkins # nfs共享目录

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

应用Jenkins-pvc.yaml时,需要确认没有默认的storageclass,否则不会直接使用文件内的nfs路径,而是使用默认storageclass进行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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$ jenkins-rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-sa
namespace: devops

---
apiVersion: rbac.authorization.k8s.io/v1
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/v1
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

在Kubernetes中部署Jenkins,新建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
$ vim jenkins-deploy.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: devops
spec:
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
terminationGracePeriodSeconds: 10
serviceAccount: jenkins-sa
containers:
- name: jenkins
image: jenkins/jenkins:lts
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: web
protocol: TCP
- containerPort: 50000
name: agent
protocol: TCP
resources:
limits:
cpu: 2000m
memory: 3Gi
requests:
cpu: 500m
memory: 1Gi
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
env:
- name: JAVA_OPTS
value: -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Duser.timezone=Asia/Shanghai -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
securityContext:
#ifsGroup: 1000
runAsUser: 0
volumes:
- name: jenkinshome
persistentVolumeClaim:
claimName: jenkins-pvc

默认情况下,Jenkins生成代理是保守的。

例如,如果队列中有两个构建,它不会立即生成两个执行器。它将生成一个执行器,并等待某个时间释放第一个执行器,然后再决定生成第二个执行器。Jenkins确保它生成的每个执行器都得到了最大限度的利用。

如果你想覆盖这个行为,并生成一个为每个构建队列不等待的执行器,所以在Jenkins启动时候添加这些参数:

  • -Dhudson.slaves.NodeProvisioner.initialDelay=0
  • -Dhudson.slaves.NodeProvisioner.MARGIN=50
  • -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85

创建集群访问入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ vim jenkins-svc.yaml

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

部署Jenkins

1
$ kubectl apply -f jenkins-rbac.yaml -f jenkins-pvc.yaml -f jenkins-deploy.yaml -f jenkins-svc.yaml

初始化的密码我们可以在 jenkins 的容器的日志中进行查看,也可以通过指定数据位置查看:

1
$ kubectl exec -it jenkins-xxxxx -ndevops -- cat /var/jenkins_home/secrets/initialAdminPassword

访问

在配置文件中,我们配置的端口类型为NodePort,所以我们使用k8s节点ip+端口即可访问

1
2
3
4
# 获取端口
$ kubectl get svc -n devops
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jenkins NodePort 10.15.151.20 <none> 8080:32000/TCP,50000:31510/TCP 16d

地址: http://nodeip:32000

3、Jenkins Master配置

3.1 插件安装

“系统管理” –> “插件管理” –> “可选插件”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 推荐插件
Chinese 中文
Gitlab
Git Parameter
Extended Choice Parameter 构建job时实用插件
Docker
Groovy
Kubernetes
Pipeline
Config File Provider
active choices
kubernetes Continuous Deploy
http request
build user vars
description setter
Describe With Params
Build Name and Description Setter
Pipeline Stage View

新版本jenkins中已弃用kubernetes Continuous Deploy,可以通过从本地系统中上传插件文件进行安装,下载地址:https://oss.zhoumx.net/kubernetes_Continuous_Deploy.hpi

3.2 全局凭据 (unrestricted)

  • 1、gitlab-auth-passwd,类型:Username with password
  • 2、Harbor,类型:Username with password
  • 3、kubeconfig,类型: Secret file

Kubeconfig: 如果是kubeadm部署的,配置文件:

1
$ cat /root/.kube/config

3.3 在Jenkins上添加k8s

系统管理 –> 节点管理 –> Configure Cloud

如果jenkins是直接部署在k8s之内的,就比较简单,不需要填写证书,直接这样,如图:

如果jenkins部署在集群外,新建一个按照3.2步骤新建一个凭据,通过ingress将k8s api接口暴露,配置DNS解析只想LoadBalancer。

点击Test Connection,如果出现 Connection test successful 或图中的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信

4、Jenkins Slave部署

4.1 Slave Container 设计思路

我们都知道,基于k8s拉起的服务,实际上都是以Pod形式存在的,而Pod是个容器组, 最终服务实际是以Pod内的Container来支撑运行的。那么针对Slave的应用场景, Container应该如何设计?

  • Jenkins-Master在构建Job的时候,Kubernetes会创建Jenkins-Slave的Pod来完成 Job的构建
  • 我们选择运行Jenkins-Slave的镜像为官方推荐镜像:jenkins/inbound-agent:latest,但是这个镜像里面并没有Maven 环境,为了方便使用,我们需要自定义一个新的镜像

这里我们使用docker进行slave的构建

Dockerfile文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM jenkins/inbound-agent:latest
# 切换到 root 账户进行操作
USER root
# 安装 maven
COPY apache-maven-3.3.9-bin.tar.gz .
RUN tar -zxf apache-maven-3.3.9-bin.tar.gz && \
mv apache-maven-3.3.9 /usr/local && \
rm -f apache-maven-3.3.9-bin.tar.gz && \
ln -s /usr/local/apache-maven-3.3.9/bin/mvn /usr/bin/mvn && \
ln -s /usr/local/apache-maven-3.3.9 /usr/local/apache-maven && \
mkdir -p /usr/local/apache-maven/repo
COPY settings.xml /usr/local/apache-maven/conf/settings.xml
USER jenkins

构建镜像

1
docker build -t jenkins-slave-maven:v1 .

上传harbor仓库(公共镜像全部放在/library/下)

1
docker tag jenkins-slave-maven:v1 harbor.zhoumx.cc/library/jenkins-slave-maven:v1

为了不侵入宿主机Node上的docker服务,那么目前最佳的思路是要引入Docker in Docker技术来完成:

1
docker tag docker:stable harbor.zhoumx.cc/library/docker:stable

4.2 测试验证

新建任务

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def git_address = "https://gitlab.zhoumx.net/kubernetes/springdemo.git" 
def git_auth = "d94c267d-20be-4778-bfad-2a26f568a508"

//创建一个Pod的模板,label为jenkins-slave
podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
containerTemplate(
name: 'jnlp',
image: "harbor.zhoumx.cc/library/jenkins-slave-maven:v1"
)
]
)
{
//引用jenkins-slave的pod模块来构建Jenkins-Slave的pod
node("jenkins-slave"){
stage('拉取代码'){
checkout([$class: 'GitSCM', branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
}
}
}

执行结果

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
Running on jenkins-slave-r5q0z-sx1s4 in /home/jenkins/agent/workspace/test-pipeline
[Pipeline] {
[Pipeline] stage
[Pipeline] { (拉取代码)
[Pipeline] checkout
The recommended git tool is: NONE
using credential d94c267d-20be-4778-bfad-2a26f568a508
Cloning the remote Git repository
Avoid second fetch
Cloning repository https://gitlab.zhoumx.net/kubernetes/springdemo.git
> git init /home/jenkins/agent/workspace/test-pipeline # timeout=10
Fetching upstream changes from https://gitlab.zhoumx.net/kubernetes/springdemo.git
> git --version # timeout=10
> git --version # 'git version 2.30.2'
using GIT_ASKPASS to set credentials Gitlab-user
> git fetch --tags --force --progress -- https://gitlab.zhoumx.net/kubernetes/springdemo.git +refs/heads/*:refs/remotes/origin/* # timeout=10
> git config remote.origin.url https://gitlab.zhoumx.net/kubernetes/springdemo.git # timeout=10
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
> git rev-parse refs/remotes/origin/main^{commit} # timeout=10
Checking out Revision 910acad93981159051ee0d095742162e1bae2ce5 (refs/remotes/origin/main)
Commit message: "更新.gitlab-ci.yml文件"
> git config core.sparsecheckout # timeout=10
> git checkout -f 910acad93981159051ee0d095742162e1bae2ce5 # timeout=10
> git rev-list --no-walk 910acad93981159051ee0d095742162e1bae2ce5 # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS

k8s中部署jenkins并利用master-slave模式实现CICD
https://www.zhoumx.net/k8s中部署jenkins并利用master-slave模式实现CICD.html
作者
阿星
发布于
2023年12月17日
许可协议