[!jenkins文章分为三部分]

第一部分为docker安装Jenkins与常用插件使用,此文章 Jenkins安装

第二部分为运行pipeline并通过k8s pod打包 ,此文章

第三部分为,pipeline高阶使用,如map,共享库,参考 Jenkins-pipeline进阶使用

1 准备

Info

提前安装好插件
参考 Jenkins安装主要是 Config File Provider

1.1 dockerfile模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# global-dockerfile-file
FROM ccr.ccs.tencentyun.com/ccops/all:jdk-17
EXPOSE 18100
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV TZ=Asia/Shanghai\
# jvm配置,按需选择
JAVA_OPTS="-Xmx4096m -Xms4096m -Xmn1536m -Xss256k -XX:+UseConcMarkSweepGC -XX: +UseCompressedClassPointers -XX:+PrintGCDetails -XX:+PrintGC -XX:+PrintGCTimeStamps -Xloggc:/var/ app/logs/gc.log -verbose:gc -Djava.awt.headless=true" \
# skywalking agent,如果不需要刻意删除
JAVA_TOOL_OPTIONS="-javaagent:/skywalking/agent/skywalking-agent.jar" \
SW_AGENT_NAMESPACE="#PROJECT" \
SW_AGENT_NAME="#PROJECT::#APP_NAME" \
SW_AGENT_COLLECTOR_BACKEND_SERVICES="#SW_OAP"
# jar路径不统一,配合pipeline jarPath map使用
ADD #jarPath /#APP_NAME.jar
ENTRYPOINT java ${JAVA_OPTS} -jar /#APP_NAME.jar --spring.profiles.active=prod

1.2 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
# global-kubernetes-deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: #APP_NAME
spec:
replicas: #APP_REPLICAS
selector:
matchLabels:
app: #APP_NAME
service: #APP_NAME
strategy:
type: Recreate
template:
metadata:
labels:
app: #APP_NAME
service: #APP_NAME
spec:
volumes:
- name: skywalking-agent
emptyDir: { }
containers:
- image: #APP_IMAGE_NAME
name: #APP_NAME
# 探针相关,按需修改
startupProbe:
tcpSocket:
port: 8080
failureThreshold: 30
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
ports:
- containerPort: 8080
name: server
# 下面这部分是集成skywalking-agent,按需开启
volumeMounts:
- name: skywalking-agent
mountPath: /skywalking
initContainers:
- name: agent-container
image: ccr.ccs.tencentyun.com/ccops/all:skywalking-java-agent-9.4.0
volumeMounts:
- name: skywalking-agent
mountPath: /agent
command: [ "/bin/sh" ]
args: [ "-c", "cp -R /skywalking/agent /agent/" ]
---
apiVersion: v1
kind: Service
metadata:
name: #APP_NAME
labels:
app: #APP_NAME
spec:
type: ClusterIP
ports:
- name: server
port: 8080
targetPort: 8080
selector:
app: #APP_NAME

2 不集成通知与k8s

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
pipeline {
parameters {
choice choices: ['app1', 'app2'], description: '选择发布程序', name: 'APP_NAME'
}
options { buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '5')) }
environment {
PROJECT = '项目名'
BRANCH_NAME = '环境名(分支名)'
HARBOR_NAMESPACE = 'docker仓库凭据名称'
GIT_ID = 'git凭据名称'
GIT_URL = "https://git.xxx.com/${APP_NAME}.git(git的url)"
REGISTRY = '镜像仓库地址'
KUBECONFIG_ID = 'kubeconfig id'
KUBERNETES_APP_REPLICAS = 'deployment副本数'
KUBERNETES_DEPLOYMENT_ID = 'global-kubernetes-deployment' # deployment模板名称
DOCKER_DOCKERFILE_ID = 'global-dockerfile-file' # dockerfile模板
SW_OAP = 'skywalking OPA地址'
}
agent any
tools {
maven 'maven'
jdk 'jdk17'
}
stages {
stage('拉取代码') {
steps {
git(credentialsId: "${GIT_ID}", url: "$GIT_URL", branch: "${BRANCH_NAME}", changelog: true, poll: false)
}
}
stage('编译打包') {
steps {
sh 'mvn clean package install -Dmaven.test.skip=true -DarchetypeCatalog=local'
}
}
stage('镜像制作') {
steps {
// 设置 Docker 镜像名称
def dockerImageName = "${REGISTRY}/${APP_NAME}:1.${BUILD_NUMBER}"
// 使用 configFileProvider 插件提供 Dockerfile 文件
configFileProvider([configFile(fileId: "${DOCKER_DOCKERFILE_ID}", targetLocation: "dockerfile")]){
// 替换dockerfile文件中的变量
def docker = readFile encoding: "UTF-8", file: "dockerfile"
def dockerfile = docker.replaceAll("#APP_NAME","${APP_NAME}")
.replaceAll("#PROJECT","${PROJECT}")
.replaceAll("#SW_OAP","${SW_OAP}")
.replaceAll("#jarPath","${jarPath}")
// 生成新的 dockerfile 部署文件
writeFile encoding: 'UTF-8', file: './Dockerfile', text: "${dockerfile}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("http://${REGISTRY}", "${HARBOR_NAMESPACE}") {
def customImage = docker.build("${dockerImageName}")
customImage.push()
}
}
}
stage('更新版本') {
steps {
// 使用 Kubectl Cli 插件的方法,提供 Kubeconfig
withKubeConfig([credentialsId: "${KUBECONFIG_ID}"]) {
// 读取 Kubernetes 部署文件
configFileProvider([configFile(fileId: "${KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
script {
// 替换deployment文件中的变量
def deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
def deployfile = deploy.replaceAll("#APP_NAME","${APP_NAME}")
.replaceAll("#APP_REPLICAS","${KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
// 生成新的 Kubernetes 部署文件
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${PROJECT} -f deploy.yaml"
}
}
}
}
}
}
}

3 集成k8s

没写的直接复用不集成k8s的配置就可以

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
pipeline {
......
agent {
kubernetes {
inheritFrom 'mypod'
}
}
stages {
stage('拉取代码') {
steps { # 不指定container默认使用jnlp
git(credentialsId: "${GIT_ID}", url: "$GIT_URL", branch: "${BRANCH_NAME}", changelog: true, poll: false)
}
}
stage('镜像制作') {
steps {
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${DOCKER_DOCKERFILE_ID}", targetLocation: "dockerfile")]){
script {
// 设置 Docker 镜像名称
def dockerImageName = "${REGISTRY}/${PROJECT}/${APP_NAME}:1.${BUILD_NUMBER}"
// 替换dockerfile文件中的变量
def docker = readFile encoding: "UTF-8", file: "dockerfile"
def dockerfile = docker.replaceAll("#APP_NAME","${APP_NAME}")
.replaceAll("#PROJECT","${PROJECT}")
.replaceAll("#SW_OAP","${SW_OAP}")
// 生成新的 dockerfile 部署文件
writeFile encoding: 'UTF-8', file: './Dockerfile', text: "${dockerfile}"
// sh "cat ./Dockerfile"
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("http://${REGISTRY}", "${HARBOR_NAMESPACE}") {
def customImage = docker.build("${dockerImageName}")
customImage.push()
}
}
}
}
}
}
......
}
}

4 集成飞书

需要安装此插件

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

pipeline {
......
  environment {
  ROBOT_ID = '告警机器人id'
  }
agent {
kubernetes {
inheritFrom 'mypod'
}
}
stages {
stage('拉取代码') {
steps {
git(credentialsId: "${GIT_ID}", url: "$GIT_URL", branch: "${BRANCH_NAME}", changelog: true, poll: false)
}
post {
failure {
echo "❌ 代码拉取失败"
// 发送失败通知
sendNotification("拉取代码", "❌失败")
}
}
}
}
post {
success {
lark (
robot: "${ROBOT_ID}",
type: "CARD",
title: "📢 ${PROJECT} ${APP_NAME} 构建成功通知 ",
text: [
"📋 **任务名称**:[${JOB_NAME}](${JOB_URL})",
"🔢 **任务编号**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"🌟 **构建状态**: <font color='green'>成功</font>",
"🕐 **构建用时**: ${currentBuild.duration / 1000} 秒",
"📜 **更新内容**: $changeString",
"<at id=all></at>"
],
buttons: [
[
title: "更改记录",
url: "${BUILD_URL}changes"
],
[
title: "控制台",
type: "danger",
url: "${BUILD_URL}console"
]
]
)
}
}
}
# 告警失败函数
def sendNotification(String stageName, String status) {
lark(
robot: "${ROBOT_ID}",
type: "CARD",
title: "❌ ${PROJECT} ${APP_NAME} ${stageName}失败通知",
text: [
"📌 **阶段**: ${stageName}",
"🔢 **状态**: <font color='red'>${status}</font>",
"<at id=all></at>"
]
)
}
......
# 此部分为了获取commit信息,如果不需要可以不加,但需要删除更新内容
@NonCPS
def getChangeString() {
MAX_MSG_LEN = 100
def changeString = ""

echo "Gathering SCM changes"
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += " - ${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - No new changes"
}
return changeString
}

5 完整配置

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
pipeline {
parameters {
choice choices: ['app1', 'app2'], description: '选择发布程序', name: 'APP_NAME'
}
options { buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '5')) }
environment {
PROJECT = '项目名'
BRANCH_NAME = '环境名(分支名)'
HARBOR_NAMESPACE = 'docker仓库凭据名称'
GIT_ID = 'git凭据名称'
GIT_URL = "https://git.xxx.com/${APP_NAME}.git(git的url)"
REGISTRY = '镜像仓库地址'
KUBECONFIG_ID = 'kubeconfig id'
KUBERNETES_APP_REPLICAS = 'deployment副本数'
KUBERNETES_DEPLOYMENT_ID = 'global-kubernetes-deployment' # deployment模板名称
DOCKER_DOCKERFILE_ID = 'global-dockerfile-file' # dockerfile模板
SW_OAP = 'skywalking OPA地址'
}

agent {
kubernetes {
inheritFrom 'mypod'
}
}
stages {
stage('拉取代码') {
steps {
git(credentialsId: "${GIT_ID}", url: "$GIT_URL", branch: "${BRANCH_NAME}", changelog: true, poll: false)
}
post {
failure {
echo "❌ 代码拉取失败"
sendNotification("拉取代码", "❌失败")
}
}
}
stage('编译打包') {
steps {
sh 'mvn clean package install -Dmaven.test.skip=true -DarchetypeCatalog=local'
}
post {
failure {
echo "❌ 打包失败"
sendNotification("maven打包", "❌失败")
}
}
}
stage('镜像制作') {
steps {
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${DOCKER_DOCKERFILE_ID}", targetLocation: "dockerfile")]){
script {
// 设置 Docker 镜像名称
def dockerImageName = "${REGISTRY}/${PROJECT}/${APP_NAME}:1.${BUILD_NUMBER}"
// 替换dockerfile文件中的变量
def dockerfile = readFile encoding: "UTF-8", file: "dockerfile"
def dockerfile = dockerfile.replaceAll("#APP_NAME","${APP_NAME}")
.replaceAll("#PROJECT","${PROJECT}")
.replaceAll("#SW_OAP","${SW_OAP}")
// 生成新的 dockerfile 部署文件
writeFile encoding: 'UTF-8', file: './Dockerfile', text: "${dockerfile}"
// sh "cat ./Dockerfile"
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("http://${REGISTRY}", "${HARBOR_NAMESPACE}") {
def customImage = docker.build("${dockerImageName}")
customImage.push()
}
}
}
}
}
post {
failure {
echo " 镜像制作"
sendNotification("镜像制作", "❌失败")
}
}
}
stage('更新版本') {
steps {
// 使用 Kubectl Cli 插件的方法,提供 Kubeconfig
withKubeConfig([credentialsId: "${KUBECONFIG_ID}"]) {
// 读取 Kubernetes 部署文件
configFileProvider([configFile(fileId: "${KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
script {
// 替换deployment文件中的变量
def deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
def deployfile = deploy.replaceAll("#APP_NAME","${APP_NAME}")
.replaceAll("#APP_REPLICAS","${KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
// 生成新的 Kubernetes 部署文件
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${PROJECT} -f deploy.yaml"
}
}
}
}
post {
failure {
echo "❌ 更新版本失败"
sendNotification("更新版本", "❌失败")
}
}
}
}
post {
success {
lark (
robot: "${ROBOT_ID}",
type: "CARD",
title: "📢 ${PROJECT} ${APP_NAME} 构建成功通知 ",
text: [
"📋 **任务名称**:[${JOB_NAME}](${JOB_URL})",
"🔢 **任务编号**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"🌟 **构建状态**: <font color='green'>成功</font>",
"🕐 **构建用时**: ${currentBuild.duration / 1000} 秒",
"📜 **更新内容**: $changeString",
"<at id=all></at>"
],
buttons: [
[
title: "更改记录",
url: "${BUILD_URL}changes"
],
[
title: "控制台",
type: "danger",
url: "${BUILD_URL}console"
]
]
)
}
}
}

def sendNotification(String stageName, String status) {
lark(
robot: "${ROBOT_ID}",
type: "CARD",
title: "❌ ${PROJECT} ${APP_NAME} ${stageName}失败通知",
text: [
"📌 **阶段**: ${stageName}",
"🔢 **状态**: <font color='red'>${status}</font>",
"<at id=all></at>"
]
)
}

@NonCPS
def getChangeString() {
MAX_MSG_LEN = 100
def changeString = ""

echo "Gathering SCM changes"
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += " - ${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - No new changes"
}
return changeString
}