jenkins 初体验

  |  

全部的 K8S学习笔记总目录,请点击查看。

CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。作为一种面向开发和运维团队的解决方案,CI/CD 主要针对在集成新代码时所引发的问题。这里主要介绍jenkins的使用。

ci/cd常用的持续集成工具:

  • travis-ci
  • circleci
  • bamboo
  • teamcity
  • gitlabci
  • jenkins
  • tekton
  • argo
  • spinnaker
  • drone
  • concourse
  • 等等……

本次要实现的效果是基于k8s集群部署gitlab、sonarQube、Jenkins等工具,并把上述一些工具集成到Jenkins中,以Django项目和SpringBoot项目为例,通过多分支流水线及Jenkinsfile实现项目代码提交到不同的仓库分支,实现自动代码扫描、单元测试、docker容器构建、k8s服务的自动部署。

其中包含的内容有:

  • DevOps、CI、CD介绍
  • Jenkins、sonarQube、gitlab的快速部署
  • Jenkins初体验
  • 流水线入门及Jenkinsfile使用
  • Jenkins与Kubernetes的集成
  • sonarQube代码扫描与Jenkins的集成
  • 实践Django项目的基于Jenkinsfile实现开发、测试环境的CI/CD

DevOps、CI、CD介绍

CI/CD就是持续集成 Continuous Integration (CI) / 持续交付Continuous Delivery (CD)

一般的软件交付流程如下:

devops-roles

软件交付发展历程

一个软件从零开始到最终交付,大概包括以下几个阶段:规划、编码、构建、测试、发布、部署和维护,基于这些阶段,我们的软件交付模型大致经历了几个阶段:

瀑布式流程

软件交付的早期阶段,大家都是采用瀑布式流程,如下图所示:

devops-waterfall

前期需求确立之后,软件开发人员花费数周和数月编写代码,把所有需求一次性开发完,然后将代码交给QA(质量保障)团队进行测试,然后将最终的发布版交给运维团队去部署。

瀑布模型,简单来说,就是等一个阶段所有工作完成之后,再进入下一个阶段。这种模式的问题也很明显,产品迭代周期长,灵活性差。一个周期动辄几周几个月,适应不了当下产品需要快速迭代的场景。

敏捷开发

为了解决瀑布模型的问题,以及更好地适应当下快速迭代的场景,敏捷开发模式越来越多地被采用。敏捷开发模式的核心是任务由大拆小,开发、测试协同工作,注重开发敏捷,不重视交付敏捷。

devops-agile

DevOps

敏捷开发模式注重开发敏捷,不重视交付敏捷。那么如何解决交付敏捷呢?DevOps就是为了解决这个问题而诞生的。

devops-compire

开发、测试、运维协同工作, 持续开发+持续交付。

DevOps的工具链

我们是否可以认为DevOps = 提倡开发、测试、运维协同工作来实现持续开发、持续交付的一种软件交付模式?为什么最初的开发模式没有直接进入DevOps的时代?

原因是:沟通成本。

各角色人员去沟通协作的时候都是手动去做,交流靠嘴,靠人去指挥,很显然会出大问题。所以说不能认为DevOps就是一种交付模式,因为解决不了沟通协作成本,这种模式就不具备可落地性。

那DevOps时代如何解决角色之间的成本问题?DevOps的核心就是自动化。自动化的能力靠什么来支撑,工具和技术。

DevOps工具链

devops-tools

靠这些工具和技术,才实现了自动化流程,进而解决了协作成本,使得devops具备了可落地性。因此我们可以大致给devops一个定义:

devops = 提倡开发、测试、运维协同工作来实现持续开发、持续交付的一种软件交付模式 + 基于工具和技术支撑的自动化流程的落地实践。

因此devops不是某一个具体的技术,而是一种思想+自动化能力,来使得构建、测试、发布软件能够更加地便捷、频繁和可靠的落地实践。本次核心内容就是要教会大家如何利用工具和技术来实现完整的DevOps平台的建设。我们主要使用的工具有:

  1. gitlab,代码仓库,企业内部使用最多的代码版本管理工具。
  2. Jenkins, 一个可扩展的持续集成引擎,用于自动化各种任务,包括构建、测试和部署软件。
  3. robotFramework, 基于Python的自动化测试框架
  4. sonarqube,代码质量管理平台
  5. maven,java包构建管理工具
  6. Kubernetes
  7. Docker

Kubernetes环境中部署jenkins

本次部署使用helm部署的方式,也可以使用yaml文件部署,参考其他部署方式

注意点:

  • 第一次启动很慢
  • 因为后面Jenkins会与kubernetes集群进行集成,会需要调用kubernetes集群的api,因此安装的时候创建了ServiceAccount并赋予了cluster-admin的权限
  • 初始化容器来设置权限
  • ingress来外部访问
  • 数据存储通过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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 新建chart仓库
$ helm repo add jenkins https://charts.jenkins.io
$ helm repo update
# 搜索jenkins
$ helm search repo jenkins
# 下载chart包
$ helm pull jenkins/jenkins
# 解压
$ tar -zxvf jenkins-4.9.1.tgz
$ vim jenkins/values.yaml

......
ingress:
# 启用ingress
enabled: true
......
# 配置ingressClassName
ingressClassName: nginx
# 配置ingress的hosts
hostName: jenkins.test.com
......
# 不进行测试
testEnabled: false
......
persistence:
enabled: true
# 配置storageClass
storageClass: "nfs-client"
annotations: {}
labels: {}
# 设置pvc的模式
accessMode: "ReadWriteMany"
# 设置pvc的大小
size: "200Gi"
......
......

# 安装
$ helm -n jenkins upgrade --create-namespace -i jenkins ./jenkins
# 等待安装完成
$ kubectl -n jenkins get all

# 获取登录密码
$ kubectl exec --namespace jenkins -it svc/jenkins -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password && echo

# 使用浏览器访问jenkins,输入账号密码就可以登录了

k8s devops jenkins index

由于默认的插件地址安装非常慢,我们可以替换成国内清华的源,进入 jenkins 工作目录,目录下面有一个 updates 的目录,下面有一个 default.json 文件,我们执行下面的命令替换插件地址:

1
2
3
4
$ kubectl -n jenkins exec -it jenkins-0 -c jenkins -- bash
jenkins@jenkins-0:/$ cd /var/jenkins_home/updates
jenkins@jenkins-0:~/updates$ sed -i 's/https:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json
jenkins@jenkins-0:~/updates$ sed -i 's/https:\/\/updates.jenkins.io\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json

暂时先不用重新启动pod,汉化后一起重启。

配置升级站点的URL:

  1. 进入 web ui 的界面,点击 Manage Jenkins -> Plugins 按钮,进入插件管理页面:http://jenkins.test.com/manage/pluginManager/advanced
  2. 选择最后一项 Advanced settings
  3. 在 Update Site 中将原有url替换为:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
  4. 点击 Submit 按钮,保存设置

接下来我们就可以重启服务了。重启jenkins服务有两种方式:

  1. 删除pod,kubernetes会自动重启
  2. 访问下面这个url重启:http://jenkins.test.com/restart/ 这样重启的话,pod不会重建

安装插件

分别点击以下菜单 Jenkins -> Manage Jenkins -> Plugins -> Avaliable plugins

主要安装以下插件:

  • GitLab
  • Pipeline: Multibranch
  • Blue Ocean
  • Localization: Chinese (Simplified)
  • Gogs (太老了,没有维护,但是gitlab比较重,这里先安装gogs,仅实验使用,之后webhook和gitlab用法相同)
  • gitea (gogs的继承者,安装gogs已经很久没有维护了,这里安装gitea)
  • Generic Webhook Trigger

如果安装超时,需要查看之前更改的数据源是否生效,如果没有生效,需要重新配置。

选中后,选择[Install],等待下载完成,然后点击[ Restart Jenkins when installation is complete and no jobs are running ],让Jenkins自动重启

启动后,界面默认变成中文。

Kubernetes环境中部署gogs

gogs已经很久没有维护了,这里仅记录一下安装过程,下一节我们会使用gitea。

这里我们需要一个git仓库,因为实验使用的环境比较轻量,因此选用gogs来部署。

安装gogs

gogs的helm仓库已经很老了,因此整理了以下的资源清单,使用k8s部署gogs。

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# postgres secret
apiVersion: v1
kind: Secret
metadata:
name: postgres
namespace: gogs
type: Opaque
data:
password: cG9zdGdyZXM=
user: cG9zdGdyZXM=
dbname: Z29ncw==
---
# postgres service
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: gogs
spec:
ports:
- name: postgres
port: 5432
targetPort: 5432
protocol: TCP
type: ClusterIP
selector:
app: postgres
---
# postgres persistent volume claim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres
namespace: gogs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-client
---
# postgres deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: gogs
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres
key: password
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres
key: user
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: postgres
key: dbname
volumeMounts:
- name: postgres
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres
persistentVolumeClaim:
claimName: postgres
---
# memcache service
apiVersion: v1
kind: Service
metadata:
name: memcache
namespace: gogs
spec:
ports:
- name: memcache
port: 11211
targetPort: 11211
protocol: TCP
type: ClusterIP
selector:
app: memcache
---
# memcache deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: memcache
namespace: gogs
labels:
app: memcache
spec:
replicas: 1
selector:
matchLabels:
app: memcached
template:
metadata:
labels:
app: memcached
spec:
containers:
- name: memcached
image: memcached:1.6.22
imagePullPolicy: IfNotPresent
command: ["memcached", "-m", "64", "-p", "11211", "-u", "memcache", "-v", "-c", "1024", "-o", "modern"]
ports:
- containerPort: 11211
name: memcached
livenessProbe:
tcpSocket:
port: memcached
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
readinessProbe:
tcpSocket:
port: memcached
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
securityContext:
runAsUser: 1000
---
# gogs persistent volume claim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gogs
namespace: gogs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-client
---
# gogs configmap
apiVersion: v1
kind: ConfigMap
metadata:
name: gogs-config
namespace: gogs
data:
app.ini: |-
BRAND_NAME = TestGogs

[server]
EXTERNAL_URL = http://gogs.test.com/
DOMAIN = gogs.test.com
HTTP_PORT = 3000

[database]
TYPE = postgres
HOST = postgres:5432
NAME = gogs
USER = postgres
PASSWORD = postgres

[cache]
ADAPTER = memcache
INTERVAL = 60
HOST = memcached:11211

[ui]
EXPLORE_PAGING_NUM = 50
ISSUE_PAGING_NUM = 50

[ui.user]
REPO_PAGING_NUM = 50
COMMITS_PAGING_NUM = 50

[prometheus]
ENABLED = false

[other]
SHOW_FOOTER_BRANDING = true

[security]
INSTALL_LOCK = true
SECRET_KEY = sHyaED0SMrLCR7x
LOCAL_NETWORK_ALLOWLIST = *
---
# gogs service
apiVersion: v1
kind: Service
metadata:
name: gogs
namespace: gogs
spec:
ports:
- name: http
port: 3000
targetPort: 3000
protocol: TCP
- name: ssh
port: 22
targetPort: 22
protocol: TCP
type: ClusterIP
selector:
app: gogs
---
# gogs deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: gogs
namespace: gogs
labels:
app: gogs
spec:
replicas: 1
selector:
matchLabels:
app: gogs
template:
metadata:
labels:
app: gogs
spec:
containers:
- name: gogs
image: gogs/gogs:0.13.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 3000
- name: ssh
containerPort: 22
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
volumeMounts:
- name: gogs
mountPath: /data
- name: gogs-config
mountPath: /data/gogs/conf/app.ini
subPath: app.ini
volumes:
- name: gogs
persistentVolumeClaim:
claimName: gogs
- name: gogs-config
configMap:
name: gogs-config
---
# ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gogs
namespace: gogs
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
ingressClassName: nginx
rules:
- host: gogs.test.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gogs
port:
number: 3000

有了资源配置清单,我们就可以部署gogs了。

1
2
3
4
5
6
7
# 创建命名空间
$ kubectl create namespace gogs
# 部署gogs
$ kubectl apply -f gogs.yaml

# 查看pod
$ kubectl get pod -n gogs

全部容器启动成功后,我们就可以配置host然后通过浏览器访问gogs了。

k8s devops gogs index

这时可以点击右上角的注册按钮,注册一个账户,在gogs中id=0,也就是第一个注册的账户,是管理员账户,可以创建组织、创建仓库等操作。

k8s devops gogs register

注册完成后,我们就可以使用这个账户登录gogs了。

k8s devops gogs login

创建仓库

安装完成gogs我们就可以创建一个仓库了。这里创建一个django项目的仓库,仓库名称为moonlight。创建方法与github类似,这里就不再赘述。

创建完成后的仓库如下图所示:

k8s devops gogs repo

然后就可以创建django项目了,然后将远程仓库地址添加到django项目中,合并后提交到远程仓库。

1
2
3
4
5
6
7
8
$ django-admin startproject moonlight
$ cd moonlight
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin http://gogs.test.com/administrator/moonlight.git
$ git pull --rebase origin master
$ git push origin master

通过gogs的网页,可以看到代码已经提交到了仓库中。

k8s devops gogs repo commit

配置jenkins

在gogs中,我们可以配置webhook,当代码提交到仓库后,会触发webhook,然后调用jenkins的接口,从而触发jenkins的流水线。

webhook触发的是jenkins的流水线,因此我们需要先创建一个流水线,然后再配置webhook。

因此我们先创建一个流水线

k8s devops jenkins create a job

然后选择自由风格的软件项目,名字叫free-style-demo,然后点击确定。

k8s devops jenkins create free style job

然后我们就可以配置流水线了。首先我们先配置源码管理。选择git,然后填写仓库地址,这里填写的是gogs的仓库地址。然后当焦点离开仓库地址的时候,会发现git报错了,提示指定一个git仓库。

k8s devops jenkins git repo error

这是因为我们使用的是url地址,而k8s内部无法解析这个地址,要想解决这个问题,有两种方式:

1. 在容器内配置hosts
2. 配置coredns的静态解析

这里我们使用第二种方式,配置coredns的静态解析。

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
$ kubectl -n kube-system edit configmap coredns

......
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
# hosts字段为新加内容,将解析地址填入。我们将git仓库和jenkins的地址都做了解析,是因为之后使用git的webhook的时候还得用jenkins的url,因此也需要解析。
hosts {
192.168.100.1 gogs.test.com jenkins.test.com
fallthrough
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
......

然后保存即可。保存后我们再回到jenkins的配置页面,重新刷新配置,发现git的报错已经消失了。

k8s devops jenkins git repo success

因为这里我们的仓库是公有的,因此不需要配置认证信息,如果是私有仓库,需要配置认证信息。

这里我们直接配置认证信息,做一个演示。点击添加,选择用户名密码,然后填写用户名密码,id保证内部唯一即可,然后点击添加。

k8s devops jenkins git auth username

k8s devops jenkins git auth password

然后认证选择刚刚添加的认证信息。

k8s devops jenkins git auth

往下走,构建触发器。这里我们选择”Build when a change is pushed to GoGS”,也就是当代码提交到gogs的时候,触发构建。

k8s devops jenkins build trigger

再往下,Build Steps,我们选择执行shell命令。这个就是说,当代码提交到gogs的时候,jenkins会下载代码,然后执行Buile Steps中的命令。

k8s devops jenkins build steps

这里我们主要是测试,先写一个简单点的echo success,点击”可用的环境变量”,可以看到jenkins提供了很多环境变量,这些环境变量可以在shell中使用,这里也打印一个环境变量。

k8s devops jenkins build steps env

然后点击保存即可。

k8s devops jenkins build steps save

配置webhook

jenkins的流水线配置完成后,我们就可以配置webhook了。点击仓库的设置按钮,然后选择管理Web钩子,点击添加Web钩子,选择Gogs

k8s devops gogs repo webhook

然后填写推送地址,推送地址为 http://jenkins.test.com/gogs-webhook/?job=free-style-demo ,这里的free-style-demo为jenkins的流水线名称。

gogs 配置的推送地址格式为 http(s)://<Jenkins地址>/gogs-webhook/?job=<Jenkins任务名>

k8s devops gogs repo webhook url

然后点击添加Web钩子即可。

k8s devops gogs repo webhook add

添加成功后能看到Web钩子列表中多了一个Web钩子。

k8s devops gogs repo webhook list

然后我们点进去,点击测试按钮,测试一下是否能够触发jenkins的流水线。然后就能看到测试已经推送了。

k8s devops gogs repo webhook test

测试按钮底下也有一个成功的推送记录。

k8s devops gogs repo webhook test success

此时,去jenkins的流水线页面,可以看到流水线已经触发了。

k8s devops jenkins build success

触发构建

moonlight项目中,创建一个test.md文件,然后提交到远程仓库,触发jenkins的流水线。

1
2
3
4
$ echo "test" > test.md
$ git add .
$ git commit -m "test"
$ git push origin master

然后我们可以看到jenkins的流水线已经触发了。

k8s devops jenkins gogs push build success

等待build完成后,点击序号,可以看到build的日志。

k8s devops jenkins gogs push build info

点击控制台输出,可以看到整个流水线的执行过程以及日志。

k8s devops jenkins gogs push build console

拉倒最后可以看到整体是先把git仓库拉取到本地,然后执行了我们配置的shell命令。

k8s devops jenkins gogs push build console success

使用 helm 部署 gitea

Gitea 是一款轻量级的自托管Git服务,它提供了与Github、Gitlab、Gitee等类似的功能,可以在你自己的服务器上部署和运行,且占用资源较低。同时它提供了代码仓库管理、问题跟踪、团队协作、持续集成等功能,适用于小型团队和个人开发者,具有简单的界面和丰富的定制选项。 更重要的是它是开源的,允许你完全掌握自己的代码和数据。

他的文档地址是:https://docs.gitea.cn/,功能及安装方式都有详细的介绍。

安装 gitea

安装步骤参考官方文档

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
# 添加gitea的helm仓库
$ helm repo add gitea-charts https://dl.gitea.io/charts/
$ helm repo update
# 下载gitea的chart包
$ helm pull gitea-charts/gitea
# 解压
$ tar -zxvf gitea-10.5.0.tgz
$ cd gitea
$ vim values.yaml

......
# 全局配置
global:
imageRegistry: ""
imagePullSecrets: []
storageClass: "nfs-client"
hostAliases: []
......
# 配置ingress
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
hosts:
- host: gitea.test.com
paths:
- path: /
pathType: Prefix
tls: []
......
# 配置持久化存储
persistence:
enabled: true
create: true
mount: true
claimName: gitea-shared-storage
size: 10Gi
accessModes:
- ReadWriteMany
labels: {}
storageClass: "nfs-client"
subPath:
volumeName: ""
annotations:
helm.sh/resource-policy: keep
......
# 设置管理员账号密码
gitea:
admin:
existingSecret:
username: gitea_admin
password: r8sA8CPHD9!bt6d
email: "gitea@local.domain"
passwordMode: keepUpdated
......

编辑完成后,我们就可以安装gitea了。

1
2
3
4
5
6
# 创建命名空间
$ kubectl create namespace gitea
# 安装
$ helm -n gitea upgrade --create-namespace -i gitea ./gitea
# 查看安装结果
$ kubectl -n gitea get all

安装完成后,我们就可以配置host通过浏览器访问gitea了。

k8s devops gitea index

此时我们可以点击右上角的注册按钮,注册一个账户,在gitea中id=0,也就是第一个注册的账户,是管理员账户,可以创建组织、创建仓库等操作。

k8s devops gitea register

注册完成后,我们就可以使用这个账户登录gitea了。

k8s devops gitea login

创建仓库

安装完成gitea我们就可以创建一个仓库了。这里创建一个django项目的仓库,仓库名称为moonlight。创建方法这里就不再赘述。

创建完成后的仓库如下图所示:

k8s devops gitea repo

然后就可以创建django项目了,然后将远程仓库地址添加到django项目中,合并后提交到远程仓库。

1
2
3
4
5
6
7
8
$ django-admin startproject moonlight
$ cd moonlight
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin http://gitea.test.com/admin/moonlight.git
$ git pull --rebase origin master
$ git push origin master

通过gitea的网页,可以看到代码已经提交到了仓库中。

k8s devops gitea repo commit

生成访问令牌

为了能让jenkins访问gitea,我们需要生成一个访问令牌。点击右上角的头像,然后选择设置,然后选择应用,然后点击生成新的访问令牌。

k8s devops gitea access token

点击之后就会生成一个访问令牌,这个访问令牌是用来让jenkins访问gitea的。注意:这个访问令牌只会显示一次,所以一定要保存好。

k8s devops gitea access token create

配置jenkins

在gitea中,我们可以配置webhook,当代码提交到仓库后,会触发webhook,然后调用jenkins的接口,从而触发jenkins的流水线。

webhook触发的是jenkins的流水线,因此我们需要先创建一个流水线,然后再配置webhook。

配置访问令牌及gitea地址

但是创建流水线之前,我们需要先配置jenkins访问gitea的访问令牌。点击系统管理,然后点击凭据,在凭据页面点击系统,然后点击全局凭据,然后点击添加凭据。

类型选择Gitea Personal Access Token,然后填写访问令牌,id保证内部唯一即可,然后点击添加。

k8s devops jenkins credential

然后打开系统管理,点击系统设置,然后找到Gitea,然后填写Gitea的地址,然后选择刚刚添加的凭据,然后点击保存。

k8s devops jenkins gitea config

上面图里配置的Server URL是http://gitea-http:3000,这个是因为我们使用的是k8s集群,因此使用的是k8s集群内部的服务地址,gitea-http是gitea的service名称,3000是gitea的端口。

如果是外部地址,可以填写http://gitea.test.com,对gitea.test.com做解析即可。

填写完成后,jenkins会主动去gitea中获取信息,如果获取成功,会显示gitea版本,如果获取失败,会显示错误信息。因此需要我们最好等jenkins获取成功后再保存。

创建简单任务

这样前置的准备工作就完成了,接下来我们就可以创建一个最简单的任务了。

k8s devops jenkins create a job

然后选择自由风格的软件项目,名字叫free-style-demo,然后点击确定。

k8s devops jenkins create free style job

然后我们就可以配置流水线了。首先我们先配置源码管理。选择git,然后填写仓库地址,这里填写的是gitea的仓库地址。

k8s devops jenkins git repo

再往下走,构建触发器。这里我们选择”Generic Webhook Trigger”,这个是插件Generic Webhook Trigger提供的,可以通过webhook触发jenkins的流水线。

首先需要记下来http的请求url http://JENKINS_URL/generic-webhook-trigger/invoke, 这个是jenkins的webhook触发地址,一会儿需要填写到gitea的webhook中,这样当代码提交到gitea的时候,gitea会调用这个地址,从而触发jenkins的流水线。

同时配置提取git结构中的ref值。

一个大概的结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"ref": "refs/heads/main",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"repository": {
"id": 1296269,
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
"name": "Hello-World",
"full_name": "octocat/Hello-World",
"owner": {
// ... owner details ...
},
// ... more repository details ...
},
// ... more event details ...
}

此时,ref的值为refs/heads/main,这个值就是分支名,我们将他提取出来,然后就可以在接下来的任务中使用了。

k8s devops jenkins build trigger

k8s devops jenkins build trigger ref

在trigger中,我们还需要配置Token,这个Token是用来验证webhook请求的,只有带有这个Token的请求才会触发jenkins的流水线,这个是选填的。

因为我们已经获取到了分支信息,所有这里嗯Cause中填写Triggered by ${ref},这样就可以看到触发的分支。

k8s devops jenkins build trigger token

Print post content勾选上,这样可以在jenkins的日志中看到post的内容,同时勾上Print contributed variables,这样可以在jenkins的日志中看到所有的变量。

如果只想对特定的分支进行触发,可以在Optional Filter中填写TextExpression,这样只有符合条件的请求才会触发jenkins的流水线。

图中的含义是匹配Text(也就是$ref分支名),然后匹配Expression中的正则表达式(^refs/heads/develop$,也就是develop分支),如果匹配成功,才会触发jenkins的流水线。

这里只是举个例子,Optional Filter实验时是不填写的。

k8s devops jenkins build trigger filter

然后我们主要是测试,点击执行shell,然后填写一个简单点的echo success,点击”可用的环境变量”,可以看到jenkins提供了很多环境变量,这些环境变量可以在shell中使用,这里也打印了一个环境变量$JOB_URL

k8s devops jenkins build steps env

然后点击保存即可。

k8s devops jenkins build steps save

配置webhook

jenkins的job配置完成后,我们就可以配置gitea的webhook了。进入代码仓库,点击设置。

k8s devops gitea repo setting

在设置中选择”Web Hooks”(Web钩子),然后点击”添加web钩子”按钮,选择”Gitea”选项。

k8s devops gitea repo webhook

然后输入添加钩子所需要的信息。

  • URL:这个是jenkins的webhook触发地址,也就是jenkins触发器中的URL,这里填写 http://jenkins:8080/generic-webhook-trigger/invoke?token=moonlight ,注意token的填写,token已经在jenkins的触发器中填写了,这里需要保持一致。
  • 触发条件:这个是指什么时候触发webhook,这里使用默认项。如果有特殊需求,可以选择自定义事件,然后选择需要的事件。
  • 激活:这个是指是否激活webhook,这里选择激活。添加完成后就能立即使用。

k8s devops gitea repo webhook add

添加成功后能看到Web钩子列表中多了一个Web钩子。

k8s devops gitea repo webhook list

再次点击进去,就可以发现多出了一个测试按钮,此时还不能点击测试,如果点击测试,会报错,因为gitea对于webhook的地址有限制。

k8s devops gitea repo webhook test error

此时,我们进入gitea的容器中,修改/data/gitea/conf/app.ini文件。添加如下内容(如果有的话,修改即可):

1
2
3
4
......

[webhook]
ALLOWED_HOST_LIST = '*'

然后重启gitea的容器,重新测试webhook,就可以看到测试成功了。

k8s devops gitea repo webhook test success

从jenkins的中,我们也可以看到任务已经触发了。

k8s devops jenkins build success

点击#1,进入任务详情页面,选择控制台输出,可以看到任务的执行过程。

开头就是我们配置的输出信息Triggered by refs/heads/main,然后是基本信息,然后是工作pod的配置。

k8s devops jenkins build console

后面就是打印数据,变量信息,拉取代码过程以及执行shell的过程。

k8s devops jenkins build console success

Jenkins 的 Master-Slave 模式

工作节点

在 Jenkins 页面中,点击系统管理, 然后点击节点和云管理,可以看到 Jenkins 的工作节点列表。

k8s devops jenkins node list button

可以看到在 Jenkins 中,只有一个节点,这个节点就是 Jenkins 的 Master 节点,也就是 Jenkins 的主节点。

k8s devops jenkins node list

刚才的任务都是在master节点执行的,为了验证我们可以登录到master节点上查看任务的执行情况。

1
2
$ kubectl -n jenkins exec -it jenkins-0 -- /bin/bash
$ cat /var/jenkins_home/jobs/free-style-demo/builds/1/log

默认jenkens的工作目录是/var/jenkins_home,一般任务的执行日志都在/var/jenkins_home/jobs/任务名称/builds/任务编号/log中,而工作目录是/var/jenkins_home/workspace/任务名称

但是不同的是,我们的jenkins是通过helm安装的,因此jenkins的默认会添加云配置,而有云配置的话jenkins就会通过建立pod来执行任务,pod中的工作目录默认是/home/jenkins/agent/workspace/任务名称,导致不会留下工作目录内容,当然这个可以配置,这个后面再说。

可以看到如果只是几个任务,直接在节点上执行是没有问题的,但是如果有多个任务都在master节点执行,对master节点的性能就会造成一定影响。因此我们可以添加多个工作节点,让工作节点来执行任务,这样就可以减轻master节点的压力。

当然,我们有集群配置,可以直接使用k8s集群中的节点作为jenkins的工作节点,这样就可以更好的利用资源。

Jenkins 的 Master-Slave 模式是指 Jenkins 的 Master 节点负责分发任务,Slave 节点负责执行任务。这样做的好处是可以将任务分发到不同的节点上执行,从而提高任务的执行效率。

添加工作节点

因为目前这个环境已经接入了k8s集群,因此我们使用k8s集群中的节点作为jenkins的工作节点。以下内容仅作为演示,最后还是会切换回k8s集群中的节点。
多个k8s管理和多个slave节点的添加方式大致相同,这里不再赘述,可以尝试添加基本就会了。
以下内容是旧的截图,但是方法是一样的,只是界面有所不同,可以参考。

点击新建节点,进入新建节点页面。

k8s devops jenkins node create

定义一个节点名称,这里我们定义为slave,然后选择固定节点,然后点击确定。

k8s devops jenkins node create name

之后是填写更详细的信息

  • Number of executors:这个是指这个节点可以同时执行多少个任务,这里我们填写5,也就是这个节点可以同时执行5个任务。
  • 远程工作目录:这个是指这个节点的工作目录,这里我们填写/home/jenkins,也就是这个节点的工作目录为/home/jenkins
  • 标签:这个是指这个节点的标签,这里我们填写slave,也就是这个节点的标签为slave。之后我们可以在任务中指定任务执行在哪个节点上,就是通过标签来进行控制。
  • 启动方法:这个是指这个节点的启动方式,也就是通过什么方式实现Master-Slave的通信。这里我们选择通过Java Web启动代理,也就是通过Java Web启动代理的方式来实现Master-Slave的通信。
    • 自定义工作目录:这个是指这个节点的工作目录,这里我们填写/home/jenkins,也就是和上面的远程工作目录一样。
    • Tunnel连接位置:点开”高级”按钮就可以看到了。
      • 这个是指这个节点连接到Master节点的时候,通过什么方式来连接。这里我们填入10.99.223.156:50000
      • Jenkins 默认就会开放50000端口供Slave节点连接、通信。
      • 因为我们使用的node节点,因此ClusterIP就可以在集群内部访问,因此填写Jenkins的ClusterIP即可。

k8s devops jenkins node create info

k8s devops jenkins node create jave web

填写完成后,点击保存即可。

保存后,我们可以看到节点列表中多了一个节点。但是节点的状态是离线的,因为我们还没有启动这个节点。

k8s devops jenkins node create success

启动工作节点

点击节点列表中的节点名称,进入节点详情页面。可以看到连接节点的命令,这个命令就是启动节点的命令。

k8s devops jenkins slave node command

我们在node节点上执行这个命令,启动节点。

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
# 首先配置hosts确保能够解析jenkins的地址
$ sudo vim /etc/hosts

192.168.100.1 jenkins.test.com gogs.test.com

# 因为命令是java的,因此需要先安装java,注意java的版本,要和jenkins的版本一致,jenkins的pod启动的镜像名称会包含java的版本。比如本次启动的镜像是jenkins/jenkins:2.426.1-jdk17,因此我们安装的java版本也要是jdk17。
$ sudo yum install java-17-openjdk.x86_64 -y

# 保证工作目录存在
$ sudo mkdir -p /home/jenkins
# 修改工作目录的权限,这里修改的用户就是下面执行java命令的用户
$ sudo chown -R jenkins:jenkins /home/jenkins

# 执行命令启动节点
$ curl -sO http://jenkins.test.com/jnlpJars/agent.jar
$ java -jar agent.jar -jnlpUrl http://jenkins.test.com/computer/slave/jenkins-agent.jnlp -secret e74b242a6a58da754a7bdecb0e0511a0fa70f305321974b052a79d6aa8d9c56d -workDir "/home/jenkins"

......
12月 29, 2023 11:56:51 下午 hudson.remoting.jnlp.Main$CuiListener status
信息: Agent discovery successful
Agent address: 10.99.223.156
Agent port: 50000
Identity: de:25:39:3d:94:4e:ca:0b:9b:c7:e8:fb:19:e2:92:70
12月 29, 2023 11:56:51 下午 hudson.remoting.jnlp.Main$CuiListener status
信息: Handshaking
12月 29, 2023 11:56:51 下午 hudson.remoting.jnlp.Main$CuiListener status
信息: Connecting to 10.99.223.156:50000
12月 29, 2023 11:56:51 下午 hudson.remoting.jnlp.Main$CuiListener status
信息: Trying protocol: JNLP4-connect
12月 29, 2023 11:56:51 下午 org.jenkinsci.remoting.protocol.impl.BIONetworkLayer$Reader run
信息: Waiting for ProtocolStack to start.
12月 29, 2023 11:56:52 下午 hudson.remoting.jnlp.Main$CuiListener status
信息: Remote identity confirmed: de:25:39:3d:94:4e:ca:0b:9b:c7:e8:fb:19:e2:92:70
12月 29, 2023 11:56:52 下午 hudson.remoting.jnlp.Main$CuiListener status
信息: Connected

此时我们再回到jenkins的节点列表中,可以看到节点的状态已经变成在线了。

k8s devops jenkins slave node online

测试工作节点

此时我们进入到free-style-demo任务中,然后点击配置,在Gogs Webhook中,选择限制项目的运行节点,然后填入slave,也就是这个任务只能在slave节点上执行。

k8s devops jenkins slave node test

然后点击保存,保存成功后,我们再次提交代码,触发jenkins的流水线。

1
2
3
$ echo "test again" > test.md
$ git commit -am "test again"
$ git push origin master

然后我们可以看到jenkins的流水线已经触发了。

k8s devops jenkins slave node test build

等待build完成后,点击序号,查看build的日志。可以看到build的时候,是在slave节点上执行的。

k8s devops jenkins slave node test build info

登录到slave节点上,可以看到slave节点上已经有了我们的代码。

1
2
3
4
5
$ ls /home/jenkins/workspace/free-style-demo
LICENSE manage.py moonlight README.md test.md
$ cd /home/jenkins/workspace/free-style-demo
$ cat test.md
test again

Jenkins定制化容器

由于每次新部署Jenkins环境,都需要安装很多必要的插件,因此考虑把插件提前做到镜像中,这样再部署的时候就不需要再在线安装插件了。

首先得知道插件都有哪些,我们可以访问Jenkins的url,然后生成插件列表文件。

1
2
3
# admin:admin@jenkins.test.com 需要替换成Jenkins的用户名、密码及访问地址

curl -sSL "http://admin:admin@jenkins.test.com/pluginManager/api/xml?depth=1&xpath=/*/*/shortName|/*/*/version&wrapper=plugins" | perl -pe 's/.*?<shortName>([\w-]+).*?<version>([^<]+)()(<\/\w+>)+/\1:\2\n/g'|sed 's/ /:/' > plugins.txt

插件列表文件大致格式如下,这个文件中包含了我们需要安装的插件。

1
2
3
4
5
6
7
ace-editor:1.1
allure-jenkins-plugin:2.28.1
ant:1.10
antisamy-markup-formatter:1.6
apache-httpcomponents-client-4-api:4.5.10-1.0
authentication-tokens:1.3
...

下面是一个定制化的Jenkins镜像的Dockerfile文件,主要为了构建一个包含插件的镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM jenkinsci/blueocean:1.25.2
LABEL maintainer="me@example.com"

ENV JENKINS_UC https://updates.jenkins-zh.cn
ENV JENKINS_UC_DOWNLOAD https://mirrors.tuna.tsinghua.edu.cn/jenkins
ENV JENKINS_OPTS="-Dhudson.model.UpdateCenter.updateCenterUrl=https://updates.jenkins-zh.cn/update-center.json"
ENV JENKINS_OPTS="-Djenkins.install.runSetupWizard=false"

## 用最新的插件列表文件替换默认插件文件
COPY plugins.txt /usr/share/jenkins/ref/

## 执行插件安装
RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt

执行构建,定制jenkins容器

1
2
$ docker build . -t harbor.test.com/jenkins:v1 -f Dockerfile
$ docker push harbor.test.com/jenkins:v1

至此,我们已经有了定制化的Jenkins镜像,接下来我们就可以使用这个镜像来部署Jenkins了。

1
2
# 编辑现有jenkins的statefulset,将镜像替换成我们定制化的镜像,保存即可
$ kubectl -n jenkins edit statefulset jenkins

Jenkins 流水线

之前我们已经创建了一个简单的任务,这个任务是通过webhook触发的,这个任务是一个自由风格的任务,也就是我们可以在任务中执行任何命令。

但是这种一步一步选择的方式十分麻烦,而且不利于维护与移植,因此我们可以使用流水线的方式来执行任务。

在 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
30
31
32
33
34
35
36
37
38
39
40
41
pipeline {
agent any
environment {
BUILD_VERSION = '1.0.0'
}
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
stage('Test') {
steps {
echo 'Testing...'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
}
}
}
post {
always {
echo 'This will always run'
}
success {
echo 'This will run only if successful'
}
failure {
echo 'This will run only if failed'
}
unstable {
echo 'This will run only if the run was marked as unstable'
}
changed {
echo 'This will run only if the state of the Pipeline has changed'
echo 'For example, if the Pipeline was previously failing but is now successful'
}
}
}
  • pipeline:定义流水线,固定写法,这个是必须的。
    • agent any:指定流水线的执行环境,这里是任意节点,也就是可以在任意节点上执行。如果想要指定节点,可以写成agent { label 'slave' },也就是指定在slave节点上执行。主要有以下几种:
      • any:可以在任意可用的 agent上执行pipeline
      • none:pipeline将不分配全局agent,每个 stage分配自己的agent
      • label:指定运行节点agent的 Label
      • node:自定义运行节点配置,
        • 指定 label
        • 指定 customWorkspace
      • docker:使用给定的容器执行流水线。
        1
        2
        3
        4
        5
        6
        7
        agent {
        docker {
        image 'node:7-alpine'
        args '-v /tmp:/tmp'
        label 'my-defined-label'
        }
        }
      • dockerfile:使用源码库中包含的Dockerfile构建的容器来执行Pipeline。
      • kubernetes:在kubernetes集群执行Pipeline
  • environment:定义流水线的环境变量,这里定义了一个BUILD_VERSION的环境变量,只在流水线中有效,一个构建周期内有效。
  • stages:定义流水线的阶段,这里定义了三个阶段,分别是BuildTestDeploy,他们是按顺序执行的。agent的参数也可以用在这里。
    • 包含一系列一个或多个 stage指令
    • stages 部分是流水线描述的大部分”work” 的位置。
    • 建议 stages 至少包含一个 stage 指令用于连续交付过程的每个离散部分,比如构建, 测试, 和部署。
  • steps:定义阶段的执行步骤,这里定义了每个阶段的执行步骤,这里只是简单的打印了一句话,可以是一个或多个steps,顺序执行。
  • post:定义流水线的后置条件,支持 post-condition 块中的其中之一: always, changed, failure, success, unstable, 和 aborted
    • always, 无论流水线或阶段的完成状态如何,都允许在 post 部分运行该步骤
    • changed, 当前流水线或阶段的完成状态与它之前的运行不同时,才允许在 post 部分运行该步骤
    • failure, 当前流水线或阶段的完成状态为”failure”,才允许在 post 部分运行该步骤, 通常web UI是红色
    • success, 当前流水线或阶段的完成状态为”success”,才允许在 post 部分运行该步骤, 通常web UI是蓝色或绿色
    • unstable, 当前流水线或阶段的完成状态为”unstable”,才允许在 post 部分运行该步骤, 通常由于测试失败,代码违规等造成。通常web UI是黄色
    • aborted, 只有当前流水线或阶段的完成状态为”aborted”,才允许在 post 部分运行该步骤, 通常由于流水线被手动的aborted。通常web UI是灰色
  • options:定义流水线的选项,这里没有列出,可以定义很多选项,比如超时时间等。
    • timeout:定义流水线的超时时间,如果超过这个时间,流水线就会被终止。如 options { timeout(time: 1, unit: 'HOURS') },这个是定义流水线的超时时间为1小时。
    • retry:定义流水线的重试次数,如果流水线执行失败,会重试这个次数。如 options { retry(3) },这个是定义流水线的重试次数为3次。
    • buildDiscarder:定义流水线的构建丢弃策略,这个是定义流水线的构建丢弃策略,比如保留构建的天数等。比如 options { buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '1')) },这个是定义流水线的构建丢弃策略,保留构建的天数为10天,保留构建的数量为1个。
    • disableConcurrentBuilds:定义流水线的不能同时执行,防止同时访问共享资源。如 options { disableConcurrentBuilds() },这个是定义流水线的不能同时执行。

创建流水线任务

点击新建任务,进入新建任务页面。

k8s devops jenkins create a job

然后选择流水线,然后填写任务名称,这里我们填写new-pipeline,然后点击确定。

k8s devops jenkins create pipeline job

然后我们就可以配置流水线了,基本配置和之前的任务是一样的,下面的四张图只是再展示一遍。

k8s devops jenkins pipeline config 01

k8s devops jenkins pipeline config 02

k8s devops jenkins pipeline config 03

k8s devops jenkins pipeline config 04

然后我们就可以在定义流水线中填写我们的流水线脚本。

k8s devops jenkins pipeline script

如果我们不会流水线的语法怎么办呢?我们可以点击流水线语法,然后就会跳转到流水线语法的页面。

我们先看片段生成器,这里提供了很多流水线的片段,我们可以根据自己的需求选择片段,然后生成流水线的语法。

比如我们想要对git进行checkout的操作,我们可以选择checkout,然后填写git的地址,分支等信息,然后点击生成流水线语法,就会生成流水线的语法。

k8s devops jenkins pipeline snippet 01

k8s devops jenkins pipeline snippet 02

注意,这里生成的只是流水线的语法,并不会实际执行任务,生成的语法可以直接复制到流水线的脚本文件中。

下面是一个完整的流水线脚本,我们还是监听moonlight仓库的webhook,当有代码提交的时候,就会触发流水线,执行docker镜像的构建。

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
pipeline {
agent any
environment {
BUILD_VERSION = '1.0.0'
}
stages {
stage('print env') {
steps {
echo 'Building...'
echo "BUILD_VERSION: ${BUILD_VERSION}"
sh 'printenv'
}
}
stage('Checkout') {
steps {
checkout([$class: 'GitSCM', branches: [[name: '*/main']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: 'http://gitea.test.com/admin/moonlight.git']]])
}
}
stage('Build') {
steps {
sh 'docker build -t harbor.test.com/moonlight:${BUILD_VERSION} .'
}
}
stage('Push') {
steps {
sh 'docker push harbor.test.com/moonlight:${BUILD_VERSION}'
}
}
}
post {
always {
echo 'This will always run'
}
success {
echo 'Congratulations! The pipeline has been successful!'
}
failure {
echo 'Oh no! The pipeline has failed!'
}
unstable {
echo 'The pipeline is unstable'
}
changed {
echo 'This will run only if the state of the Pipeline has changed'
echo 'For example, if the Pipeline was previously failing but is now successful'
}
}
}

将他复制到流水线的脚本中,然后点击保存即可。

回到Dashboard,我们可以看到新创建的流水线任务。

k8s devops jenkins pipeline job

然后我们提交代码,触发流水线,之后就能看到流水线的执行过程。

k8s devops jenkins pipeline build

因为我们已经安装了插件 Blue Ocean,因此我们可以在Blue Ocean中看到流水线的执行过程。Blue Ocean是一个Jenkins的插件,提供了一个更好的UI界面,更好的展示流水线的执行过程。

注:这里我们只是演示了功能,具体错误可以进行忽略

k8s devops jenkins pipeline blue ocean

可以看到我们可以看到每个stage的执行过程,每个stage的执行时间,每个stage的执行结果等信息,每一步也有对应的日志输出,可以看到每一步的执行过程。

k8s devops jenkins pipeline blue ocean stage

也可以点击其他节点查看其他节点的执行情况。

k8s devops jenkins pipeline blue ocean node

Jenkins 优化

可以看到,我们的Jenkins已经可以正常使用了,但是我们还可以对Jenkins进行一些优化。

Jenkinsfile

比如我们现在的脚本是直接配置在Jenkins中的,这样不利于维护,也不利于迁移。我们可以将脚本放到代码仓库中,然后通过Jenkinsfile来引用。

如果想要将脚本放到代码仓库中,首先要修改一下流水线的配置,选择Pipeline script from SCM,然后选择Git,然后填写仓库地址,分支等信息,然后点击保存。

k8s devops jenkins pipeline script scm

k8s devops jenkins pipeline script scm

然后我们就可以在代码仓库中创建一个Jenkinsfile文件,然后将流水线的脚本复制到Jenkinsfile中,脚本内容和之前的一样。

checkout scm

在 Jenkinsfile 中,我们需要拉取代码,但是拉取的代码通常和 Jenkinsfile 在同一个仓库中,因此我们可以使用 checkout scm 来拉取代码,这样就不需要再配置拉取代码的步骤了。

这两个单词都是Jenkins的关键字,checkout 是拉取代码的命令,scm 是源码管理的缩写,这两个单词组合在一起就是拉取代码的命令。

1
2
3
4
5
6
7
...
stage('Checkout') {
steps {
checkout scm
}
}
...

镜像版本

在流水线中,我们定义了一个环境变量 BUILD_VERSION,这个环境变量是用来定义镜像的版本的,这个版本是写死的,每次构建的时候都需要手动修改,这样不利于维护。

我们每次push代码的时候,都会生成一个commit id,这个commit id是唯一的,我们可以使用这个commit id 来定义镜像的版本。

下面是一种方法,这里直接配置环境变量 BUILD_VERSION

1
2
3
4
5
...
environment {
BUILD_VERSION = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
}
...

还有一种方法是直接使用jenkins提供的环墧变量 GIT_COMMIT

1
2
3
4
5
6
7
8
9
10
11
12
...
stage('Build') {
steps {
sh 'docker build -t harbor.test.com/moonlight:${GIT_COMMIT} .'
}
}
stage('Push') {
steps {
sh 'docker push harbor.test.com/moonlight:${GIT_COMMIT}'
}
}
...

钉钉通知

在流水线执行完成后,我们可以通过钉钉通知来通知我们流水线的执行结果,这样我们就不需要一直在 Jenkins 中查看流水线的执行结果了。

首先我们需要在钉钉中创建一个机器人,然后获取机器人的 webhook 地址,比如我们这里假设机器人的 webhook 地址为 https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx

然后我们可以在流水线的后置条件中添加一个步骤,来发送钉钉通知。

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
...
post {
always {
echo 'This will always run'
}
success {
echo 'Congratulations! The pipeline has been successful!'
script {
def payload = """
{
"msgtype": "text",
"text": {
"content": "Congratulations! The pipeline has been successful!"
}
}
"""
sh "curl -s -X POST -H 'Content-Type: application/json' -d '${payload}' https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx"
}
}
failure {
echo 'Oh no! The pipeline has failed!'
sh """
curl -s -X POST \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "text",
"text": {
"content": "构筑失败!\n 项目名称:${JOB_BASE_NAME}\n Commit ID:${GIT_COMMIT}\n 构筑地址:${RUN_DISPLAY_URL}"
}
}' \
https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx
"""
}
unstable {
echo 'The pipeline is unstable'
}
changed {
echo 'This will run only if the state of the Pipeline has changed'
echo 'For example, if the Pipeline was previously failing but is now successful'
}
}
...

上面我们在 successfailure 后面使用两种方法添加了发送钉钉通知的步骤,这样当流水线执行成功或者失败的时候,就会发送钉钉通知。

使用凭据管理敏感信息

在流水线中,我们可能会使用一些敏感信息,比如用户名、密码等,这些信息不能直接写在流水线中,因为流水线是可以查看的,这样就会泄露敏感信息。

比如上面配置的钉钉通知的token信息,我们就不能直接写在流水线中,我们可以使用凭据管理来管理这些敏感信息。

Jenkins 的声明式流水线语法有一个 credentials() 辅助方法(在environment 指令中使用),它支持 secret 文本带密码的用户名,以及 secret 文件凭据。

这里只是展示用户名和密码的方式,实际上我们可以使用其他方式,比如secret text

如果使用用户名密码的方式创建了一个凭证testCred,并且使用 TESTCRED = credentials('testCred') 辅助方法,我们可以在流水线中使用这个凭据的环境变量如下:

  • ${TESTCRED}:凭证的完整内容,格式为 username:password
  • ${TESTCRED_USR}:凭证的用户名部分,系统会额外进行添加
  • ${TESTCRED_PSW}:凭证的密码部分,系统会额外进行添加

首先我们需要在 Jenkins 中创建一个凭据,这里还是使用username with password类型的凭据,然后填写用户名和密码,因为只需要一个token,因此我们可以将token写在密码中,然后点击保存,名称我们可以填写Dingtalk

k8s devops jenkins credentials

然后我们可以在流水线中使用这个凭据,比如上面的钉钉通知的token信息,我们可以使用凭据管理来管理这个token信息。

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
...
environment {
DINGTALK_TOKEN = credentials('Dingtalk')
}
...
success {
echo 'Congratulations! The pipeline has been successful!'
script {
def token = credentials('dingtalk-token')
def payload = """
{
"msgtype": "text",
"text": {
"content": "Congratulations! The pipeline has been successful!"
}
}
"""
sh "curl -s -X POST -H 'Content-Type: application/json' -d '${payload}' https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_TOKEN_PSW}"
}
}
failure {
echo 'Oh no! The pipeline has failed!'
script {
def token = credentials('dingtalk-token')
sh """
curl -s -X POST \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "text",
"text": {
"content": "构筑失败!\n 项目名称:${JOB_BASE_NAME}\n Commit ID:${GIT_COMMIT}\n 构筑地址:${RUN_DISPLAY_URL}"
}
}' \
https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_TOKEN_PSW}
"""
}
}
...
文章目录
  1. 1. DevOps、CI、CD介绍
    1. 1.1. 软件交付发展历程
      1. 1.1.1. 瀑布式流程
      2. 1.1.2. 敏捷开发
      3. 1.1.3. DevOps
    2. 1.2. DevOps的工具链
  2. 2. Kubernetes环境中部署jenkins
    1. 2.1. 安装jenkins
    2. 2.2. 安装插件
  3. 3. Kubernetes环境中部署gogs
    1. 3.1. 安装gogs
    2. 3.2. 创建仓库
    3. 3.3. 配置jenkins
    4. 3.4. 配置webhook
    5. 3.5. 触发构建
  4. 4. 使用 helm 部署 gitea
    1. 4.1. 安装 gitea
    2. 4.2. 创建仓库
    3. 4.3. 生成访问令牌
    4. 4.4. 配置jenkins
      1. 4.4.1. 配置访问令牌及gitea地址
      2. 4.4.2. 创建简单任务
    5. 4.5. 配置webhook
  5. 5. Jenkins 的 Master-Slave 模式
    1. 5.1. 工作节点
    2. 5.2. 添加工作节点
    3. 5.3. 启动工作节点
    4. 5.4. 测试工作节点
  6. 6. Jenkins定制化容器
  7. 7. Jenkins 流水线
    1. 7.1. 创建流水线任务
  8. 8. Jenkins 优化
    1. 8.1. Jenkinsfile
    2. 8.2. checkout scm
    3. 8.3. 镜像版本
    4. 8.4. 钉钉通知
    5. 8.5. 使用凭据管理敏感信息