Jenkins CI&CD

Jenkins 版本:2.204.4
kubernetes版本 1.17

需要提前准备应用:Harbor、测试springboot git项目地址

jenkins默认拉取插件地址为外网地址,所以部署后可能出现插件无法下载或者下载很慢的问题,这里是直接使用国内修改版本的镜像。

建议修改镜像地址认证文件(参考https://blog.csdn.net/weixin_40046357/article/details/104489497) 也可以换用国人自己打包好修改的镜像或者war包来进行部署(推荐)

1
2
3
4
5
cd $JENKINS_HOME/war/WEB-INF/update-center-rootCAs

rm -fr jenkins-update-center-root-ca jenkins-update-center-root-ca.txt

curl 'https://raw.githubusercontent.com/jenkins-zh/docker-zh/master/mirror-adapter.crt' -o mirror-adapter.crt

修改文件 vim /var/lib/jenkins/hudson.model.UpdateCenter.xml

替换国内镜像地址https://updates.jenkins-zh.cn/update-center.json

1 Kubernetes 部署 Jenkins

1.1 采用NFS作为jenkins底层存储

Jenkins使用NFS作为底层存储,部署前需要在nfs服务器上创建对应的文件夹,并且确保目录对其它用户有读写权限。
mkdir /data/nfsshare/default/jenkins

1.2 创建 Jenkins 用于存储的 PV、PVC

jenkins-storage.yaml

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
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins
labels:
app: jenkins
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
mountOptions: #NFS挂载选项
- hard
- nfsvers=4.1
nfs: #NFS配置
path: /data/nfsshare/default/jenkins
server: 127.0.0.1
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jenkins
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi #存储空间大小
selector:
matchLabels:
app: jenkins

1.3 创建 ServiceAccount & ClusterRoleBinding

jenkins-rbac.yaml 给予jenkins权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-admin #ServiceAccount名
namespace: default #指定namespace,一定要修改成你自己的namespace
labels:
name: jenkins
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: jenkins-admin
labels:
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io

1.4 创建 Service & Deployment

jenkins-deployment.yaml

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
apiVersion: v1
kind: Service
metadata:
name: jenkins
namespace: default
labels:
app: jenkins
spec:
type: NodePort
ports:
- name: http
port: 8080 #服务端口
targetPort: 8080
- name: jnlp
port: 50000 #代理端口
targetPort: 50000
selector:
app: jenkins
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: default
labels:
app: jenkins
spec:
selector:
matchLabels:
app: jenkins
replicas: 1
template:
metadata:
labels:
app: jenkins
spec:
serviceAccountName: jenkins-admin
containers:
- name: jenkins
image: jenkinszh/jenkins-zh:2.204.4
securityContext:
runAsUser: 0 #设置以ROOT用户运行容器
privileged: true #拥有特权
ports:
- name: http
containerPort: 8080
- name: jnlp
containerPort: 50000
resources:
limits:
memory: 2Gi
cpu: "2000m"
requests:
memory: 2Gi
cpu: "1000m"
env:
- name: LIMITS_MEMORY
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Mi
- name: "JAVA_OPTS" #设置变量,指定时区和 jenkins slave 执行者设置生成一个执行器为每个构建队列立即不等待
value: "
-Xmx$(LIMITS_MEMORY)m
-XshowSettings:vm
-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
-Duser.timezone=Asia/Shanghai
"
- name: "JENKINS_OPTS"
value: "--prefix=/jenkins" #设置路径前缀加上 Jenkins
volumeMounts: #设置要挂在的目录
- name: data
mountPath: /var/jenkins_home
volumes:
- name: data
persistentVolumeClaim:
claimName: jenkins

1.5 获取 Jenkins 生成的 Token

在安装 Jenkins 时候,它默认生成一段随机字符串在控制台日志中,用于安装时验证。这里需要获取它输出在控制台中的日志信息,来获取 Token 字符串。

如果日志中没有看到也可以到容器中的/var/jenkins_home/secrets/initialAdminPassword位置文件进行查询

1.6 启动 Jenkins 进行初始化

输入 Kubernetes 集群地址和 Jenkins Service 设置的 NodePort 端口号,访问 Jenkins UI 界面进行初始化

  • 安装插件
    安装插件,选择推荐安装方式进行安装即可,后续再安装需要的插件。

等待一段时间让jenkins自己进行插件的下载和同步,如果出现错误可以直接进行后续操作,遇到插件无法下载可以从官网下载对应插件的jpi文件,推送到jenkins持久化的plugin目录中,重启jenkins就可以完成手动安装插件。

jenkin插件包地址(https://mutoulazy-public.oss-cn-shenzhen.aliyuncs.com/properties/plugins.rar)

完成设置用户名、密码和设置jenkins地址后,初始化完成

  • 推荐插件
1
2
3
4
5
6
7
8
9
10
11
12
13
Git: Jenkins 安装中默认安装 Git 插件,所以不需要单独安装。利用 git 工具可以将 github、gitlab 等等的地址下载源码。

Docker: Jenkins 安装中默认安装 Docker 插件,所以不需要单独安装。利用 Docker 插件可以设置 Docker 环境,运行 Docker 命令,配置远程 Docker 仓库凭据等。

Kubernetes: Kubernetes 插件的目的是能够使用 Kubernetes 集群动态配置 Jenkins 代理(使用Kubernetes调度机制来优化负载),运行单个构建,等构建完成后删除该代理。这里我们需要用到这个插件来启动 Jenkins Slave 代理镜像,让代理执行 Jenkins 要执行的 Job。

Kubernetes Cli: Kubernetes Cli 插件作用是在执行 Jenkins Job 时候提供 kubectl 与 Kubernetes 集群交互环境。可以在 Pipeline 或自由式项目中允许执行 kubectl 相关命令。它的主要作用是提供 kubectl 运行环境,当然也可以提供 helm 运行环境。

Config File Provider: Config File Provider 插件作用就是提供在 Jenkins 中存储 properties、xml、json、settings.xml 等信息,可以在执行 Pipeline 过程中可以写入存储的配置。例如,存入一个 Maven 全局 Settings.xml 文件,在执行 Pipeline Job 时候引入该 Settings.xml ,这样 Maven 编译用的就是该全局的 Settings.xml。

Pipeline Utility Steps: 这是一个操作文件的插件,例如读写 json、yaml、pom.xml、Properties 等等。在这里主要用这个插件读取 pom.xml 文件的参数设置,获取变量,方便构建 Docker 镜像。

Git Parameter: 能够与 Git 插件结合使用,动态获取 Git 项目中分支信息,在 Jenkins Job 构建前提供分支选项,来让项目执行人员选择拉取对应分支的代码。

2 Jenkins进行配置

配置过程可以参考这个帖子进行配置
http://www.mydlq.club/article/47/#wow8

2.1 导入导出job配置

除了像上面一样进行手动配置外,还可以直接导入已经做好的配置。

Jenkins上,打开Manage Jenkins,打开Jenkins-CLI。

下载jenkins-cli.jar,按照Jenkins-CLI页面的指引来操作(在jenkins中安全设置任何用户可以做任何事(没有任何限制)):

1
2
3
4
# 导出一个job
java -jar jenkins-cli.jar -s http://192.168.37.131:8080/ get-job myjob > myjob.xml
# 导入一个jobs
java -jar jenkins-cli.jar -s http://192.168.37.131:8080/ get-job myjob < myjob.xml

然后在目标Jenkins上,打开Manage Jenkins,选择Reload Configuration from Disk。

不需要重启目标Jenkins。

2.2 jenkins进行备份与恢复

使用thinbackup插件
https://www.cnblogs.com/paul8339/p/10559248.html

3 流水线脚本

pipeline.groovy

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
def label = "jnlp-agent"
timeout(time: 900, unit: 'SECONDS') {
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
// 执行 Git 命令进行 Clone 项目
git changelog: true,
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}",
url: "${GIT_PROJECT_URL}"
}
stage('Maven阶段'){
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
stage('Docker阶段'){
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
// 设置 Docker 镜像名称
dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
if ("${params.DOCKER_HUB_GROUP}" == '') {
dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("https://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
def customImage = docker.build("${dockerImageName}")
customImage.push()
}
}
}
}
stage('Kubernetes 阶段'){
// kubectl 镜像
container('kubectl') {
// 使用 Kubectl Cli 插件的方法,提供 Kubernetes 环境,在其方法块内部能够执行 kubectl 命令
withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
// 使用 configFile 插件,创建 Kubernetes 部署文件 deployment.yaml
configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
// 读取 Kubernetes 部署文件
deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
// 替换部署文件中的变量,并将替换后的文本赋予 deployfile 变量
deployfile = deploy.replaceAll("#APP_NAME","${appName}")
.replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
.replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
// 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// 输出新创建的部署 yaml 文件内容
sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
}
}
}
}
stage('应用启动检查'){
// 设置检测延迟时间 10s,10s 后再开始检测
sleep 10
// 健康检查地址
httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
// 循环使用 httpRequest 请求,检测服务是否启动
for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
try{
// 输出请求信息和请求次数
print "访问服务:${appName} \n" +
"访问地址:${httpRequestUrl} \n" +
"访问次数:${n}"
// 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
if(n > 1){
sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
}
// 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
result = httpRequest "${httpRequestUrl}"
// 判断是否返回 200
if ("${result.status}" == "200") {
print "Http 请求成功,流水线结束"
break
}
}catch(Exception e){
print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
// 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
currentBuild.result = "FAILURE"
}
}
}
}
}
}
}

4 配置项目推送自动触发构建

https://www.cnblogs.com/chenchen-tester/p/10025420.html

5 参考

Jenkins 与 Kubernetes 的 CI 与 CD & Git + Maven + Docker+Kubectl
https://www.cnblogs.com/chenchen-tester/p/10025420.html
https://www.cnblogs.com/paul8339/p/10559248.html