0%

Jenkins Pipelines流水线实践

基于Jenkins Piplines script实现企业持续集成、持续交付。

demo项目地址:

https://github.com/GeekSRE/jenkins-demo

概述

基于Jenkins Piplines script实现企业持续集成、持续交付。

可自定义发版流程,制定标准发版模版后可通用于所有应用。

基于应用的不同变量实现兼容性。

架构图

jenkins01

效果图

jenkins02

文件
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
├── app-info                                # 服务信息
│   ├── app-java.sh # 包含gitlab代码库地址、启动命令、端口、Docker基础镜像
│   ├── app-node.sh # 是否开启APM监控、代码语言等信息
│   └── app-php.sh
├── app-k8s-yaml # 特别应用的k8s yaml文件
│   ├── api-gateway-v2-vcg-com.sh # 比如挂载nas存储、本地存储
│   ├── api-php-v2-vcg-com.sh # 指定日志收集到日志服务
│   ├── api-quick-v2-vcg-com.sh # 自定义hosts配置
│   ├── api-wps-photo-from-url-vcg-com.sh
│   ├── com-veer-cms.sh
│   ├── com-veer-veerservice.sh
│   ├── downloadservice-vcg-com.sh
│   ├── editservice-vcg-com.sh
│   ├── eneditservice-vcg-com.sh
│   ├── instock2service.sh
│   ├── instock-single-dam-vcg-com.sh
│   ├── logservice-vcg-com.sh
│   ├── node-vcg-api-b.sh
│   ├── node-visualchina-web3-b.sh
│   ├── node-visualchina-web3.sh
│   ├── node-visualchina-web-b.sh
│   ├── portalservice-vcg-com.sh
│   ├── vcg-gateway.sh
│   └── veer-gateway.sh
├── build # CICD的Shell脚本
│   ├── build-ali-k8s-yaml.sh # 生成k8s yaml
│   ├── build-ali-test-k8s-yaml.sh
│   ├── build-ali-usa-k8s-yaml.sh
│   ├── build-dockerfile.sh # 生成Dockerfile
│   ├── build-package.sh # 编译脚本,比如yarn install 或 maven
│   ├── build-tx-k8s-yaml.sh
│   ├── checkout.sh # 克隆代码
│   ├── ConsulServerList.java
│   ├── DBInstanceId.txt # 敏感数据替换
│   └── docker-build-push.sh # push镜像
├── Jenkinsfile # Jenkins 流水线文件
├── jenkins.sh # 主Shell文件
└── README.md

3 directories, 35 files
详细讲解
Jenkinsfile
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
pipeline {
agent any
options {
timestamps()
}
environment {
TimeStamp="${currentBuild.startTimeInMillis}"
Service="${JOB_BASE_NAME}"
Branch="${env.gitlabTargetBranch}"
}
parameters {
choice(name: 'Action',choices: '程序发版\n程序回滚',description: '请选择操作')
choice(name: 'Scope',choices: '测试环境\n预发环境\n生产环境\n美西环境',description: '请选择部署环境')
string(name: 'JenkinsApi', defaultValue: 'false', description: '是否是JenkinsAPI触发,默认请不要填写。')
string(name: 'BranchOrTag', defaultValue: '', description: '指定分支或tag发版,默认请不要填写。')
}
stages {
stage('PrintEnv') {
steps {
sh "printenv"
}
}
stage('Check Out') {
when {
anyOf {
environment name: 'Branch',value:'master';
environment name: 'Branch',value:'test';
environment name: 'Scope',value:'测试环境';
environment name: 'Scope',value:'美西环境';
environment name: 'Scope',value:'预发环境'
}
}
steps {
sh "sh jenkins.sh 'CheckOut' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${BranchOrTag}'"
}
}
stage('Build Package') {
when {
anyOf {
environment name: 'Branch',value:'master';
environment name: 'Branch',value:'test';
environment name: 'Scope',value:'测试环境';
environment name: 'Scope',value:'美西环境';
environment name: 'Scope',value:'预发环境'
}
}
steps {
sh "sh -x jenkins.sh 'BuildPackage' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}'"
}
}
stage('Build Dockerfile') {
when {
anyOf {
environment name: 'Branch',value:'master';
environment name: 'Branch',value:'test';
environment name: 'Scope',value:'测试环境';
environment name: 'Scope',value:'预发环境';
environment name: 'Scope',value:'美西环境';
environment name: 'Scope',value:'生产环境'
}
}
steps {
sh "sh jenkins.sh 'BuildDockerfile' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}'"
}
}
stage('Build K8S Yaml') {
when {
anyOf {
environment name: 'Branch',value:'master';
environment name: 'Branch',value:'test';
environment name: 'Scope',value:'测试环境';
environment name: 'Scope',value:'预发环境';
environment name: 'Scope',value:'美西环境';
environment name: 'Scope',value:'生产环境'
}
}
steps {
sh "sh jenkins.sh 'BuildK8SYaml' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'"
}
}
stage('Deploy') {
steps {
script {
if ("${Scope}" == "测试环境") {
echo "测试环境发版"
sh "sh jenkins.sh 'DockerBuildPush' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'"
sh "sh jenkins.sh 'Deploy' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}'"
}
if ("${Scope}" == "预发环境") {
echo "预发环境发版"
sh "sh jenkins.sh 'DockerBuildPush' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'"
sh "sh jenkins.sh 'Deploy' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}'"
}
if ("${Scope}" == "美西环境") {
echo "预发环境发版"
sh "sh jenkins.sh 'DockerBuildPush' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'"
sh "sh jenkins.sh 'Deploy' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}'"
}
if ("${Scope}" == "生产环境") {
script {
if ("${JenkinsApi}" == "true") {
sh "sh jenkins.sh 'DockerBuildPush' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'"
sh "sh jenkins.sh 'Deploy' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}'"
}
else {
script {
if ("${env.Action}" == "程序回滚") {
echo "生产环境回滚,等待领导确认"
script {
input message: "请确认是否回滚 ${Scope}: ",ok : '确认',submitter: "admin"
}
echo '已确认,即将回滚'
sh "sh jenkins.sh 'Deploy' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'"
}
else {
echo "生产环境发版,等待领导确认"
script {
input message: "请确认是否部署 ${Scope}: ",ok : '确认',submitter: "admin"
}
echo '已确认,即将发布'
sh "sh jenkins.sh 'DockerBuildPush' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'"
sh "sh jenkins.sh 'Deploy' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}'"
}
}
}
}
}
}
}
}
}
}

特点:参数化构建,根据不同部署环境,会走不同的部署流程,生产环境需要admin确认。

流水线语法、示例、变量请参考https://www.jenkins.io/zh/doc/book/pipeline/jenkinsfile/

jenkins.sh
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
#!/bin/bash

set -e

Service=$2
Branch=$3
Scope=$4
DateTmp=$(printf "%.3f" `echo "scale=3;$5/1000"|bc`)
Date=`date -d @${DateTmp} "+%Y%m%d%H%M%S"`
BranchOrTag=$6

echo "传递的参数为:$*";

source ./app-info/app-java.sh
source ./app-info/app-node.sh
source ./app-info/app-php.sh

# 环境判断
# 当Branch 为 test 或 master 时,代表是 git webhook触发
# 当Branch 为 null,代表是 手动触发

if [[ ${Branch} == "null" ]]; then
if [[ ${Scope} == "测试环境" ]]; then
Branch="test"
fi
if [[ ${Scope} == "预发环境" ]]; then
Branch="master"
fi
if [[ ${Scope} == "美西环境" ]]; then
Branch="master"
fi
if [[ ${Scope} == "生产环境" ]]; then
Branch="master"
fi
echo "+++ 手动触发 +++ Git分支为 ${Branch} ;部署环境为 ${Scope}"
else
if [[ ${Branch} == "test" ]]; then
Scope="测试环境"
fi
if [[ ${Branch} == "master" ]]; then
Scope="预发环境"
fi
if [[ ${Branch} != "master" ]] && [[ ${Branch} != "test" ]]; then
echo "非 master 或者 test 分支的 WebHook 不做任何操作 "
exit 1
fi
echo "+++ Git WebHook触发 +++ 触发分支为 ${Branch} ;部署环境为 ${Scope} "
fi

if [[ ${Scope} == "测试环境" ]]; then
ServiceNum=${ServiceNumPre}
NameSpace="default"
ServiceCommand=${ServiceCommandTest}
ScopeEnv="su - cicd-test -c"
ScopeName='test'
Cloud="ali"
fi

if [[ ${Scope} == "预发环境" ]]; then
ServiceNum=${ServiceNumPre}
NameSpace="default"
ServiceCommand=${ServiceCommandPre}
ScopeEnv="su - cicd-pre -c"
ScopeName='pre'
Cloud="ali"
fi

if [[ ${Scope} == "美西环境" ]]; then
ServiceNum=${ServiceNumUsa}
NameSpace="default"
ServiceCommand=${ServiceCommandUsa}
ScopeEnv="su - cicd-usa -c"
ScopeName='usa'
Cloud="ali"
fi

if [[ ${Scope} == "生产环境" ]]; then
ServiceNum=${ServiceNumPro}
NameSpace="default"
ServiceCommand=${ServiceCommandPro}
ScopeEnv="su - cicd-pro -c"
ScopeName='pro'
Cloud="ali"
fi

# 当前项目 /jenkins/workspace/....
WorkDir=`pwd`
ServiceDir=/jenkins/vcgapp/${Service}/${ScopeName}
mkdir -p $ServiceDir
echo "工作目录为 : ${WorkDir} , 制品仓库目录为 : ${ServiceDir} "

CheckOut()
{
source ./build/checkout.sh
}
BuildPackage()
{
source ./build/build-package.sh
}

BuildDockerfile()
{
source ./build/build-dockerfile.sh
}
DockerBuildPush()
{
if [[ ${Scope} == "生产环境" ]]; then
if [[ ${ServiceType} == "java" ]]; then
rm -f ${ServiceDir}/${Service}.jar
cp /jenkins/vcgapp/${Service}/pre/${Service}.jar ${ServiceDir}/${Service}.jar
echo "拷贝预发布的应用包到 ${ServiceDir} "
ls -l ${ServiceDir}/${Service}.jar
# cp ${ServiceDir}/${Service}.jar ${ServiceDir}/${Service}.jar-bak-${Date}
# echo "应用包加时间戳,用于备份"
# ls -l ${ServiceDir}/${Service}.jar-bak-${Date}
else
rm -f ${ServiceDir}/${Service}.tar.gz
cp /jenkins/vcgapp/${Service}/pre/${Service}.tar.gz ${ServiceDir}/${Service}.tar.gz
echo "拷贝预发布的应用包到 ${ServiceDir} "
ls -l ${ServiceDir}/${Service}.tar.gz
# cp ${ServiceDir}/${Service}.tar.gz ${ServiceDir}/${Service}.tar.gz-bak-${Date}
# echo "应用包加时间戳,用于备份"
# ls -l ${ServiceDir}/${Service}.tar.gz-bak-${Date}
fi
fi
source ./build/docker-build-push.sh
}

BuildK8SYaml()
{
if [[ ${ServiceStartTime} ]]; then
echo "程序的ServiceStartTime启动时间变量为 ${ServiceStartTime} "
else
if [[ ${ServiceType} == "java" ]]; then
ServiceStartTime="100"
else
ServiceStartTime="30"
fi
echo "程序的ServiceStartTime启动时间变量为 ${ServiceStartTime} "
fi

if [[ ${Scope} == "测试环境" ]]; then
if [[ -f ./app-k8s-yaml/${Service}.sh ]] && [[ ${Service} == 'instock2service' ]]&& [[ ${Service} != 'instock-single-dam-vcg-com' ]]; then
echo "./app-k8s-yaml/${Service}.sh 存在,将生成 自定义(非模板)yaml文件。"
source ./app-k8s-yaml/${Service}.sh
else
source ./build/build-${Cloud}-test-k8s-yaml.sh
fi
fi

if [[ ${Scope} == "美西环境" ]]; then
limitsCpu="1"
limitsMem="4Gi"
requestsCpu="0.5"
requestsMem="1Gi"
if [[ -f ./app-k8s-yaml/${Service}.sh ]] && [[ ${Service} != 'com-veer-veerservice' ]]&& [[ ${Service} != 'com-veer-cms' ]]&& [[ ${Service} != 'instock2service' ]]&& [[ ${Service} != 'instock-single-dam-vcg-com' ]]; then
echo "./app-k8s-yaml/${Service}.sh 存在,将生成 自定义(非模板)yaml文件。"
source ./app-k8s-yaml/${Service}.sh
else
source ./build/build-${Cloud}-usa-k8s-yaml.sh
fi
fi

if [[ ${Scope} == "预发环境" ]]; then
limitsCpu="1"
limitsMem="4Gi"
requestsCpu="0.2"
requestsMem="1Gi"
if [[ -f ./app-k8s-yaml/${Service}.sh ]] && [[ ${Service} != 'com-veer-veerservice' ]]&& [[ ${Service} != 'com-veer-cms' ]]&& [[ ${Service} != 'instock2service' ]]&& [[ ${Service} != 'instock-single-dam-vcg-com' ]]; then
echo "./app-k8s-yaml/${Service}.sh 存在,将生成 自定义(非模板)yaml文件。"
source ./app-k8s-yaml/${Service}.sh
else
source ./build/build-${Cloud}-k8s-yaml.sh
fi
fi

if [[ ${Scope} == "生产环境" ]]; then
if [[ ${CustomResources} != "yes" ]]; then
limitsCpu="2"
limitsMem="8Gi"
requestsCpu="0.5"
requestsMem="2Gi"
fi
# if [[ ${Service} == 'com-veer-veerservice' ]]; then
# source ./app-k8s-yaml/com-veer-veerservice.sh
if [[ -f ./app-k8s-yaml/${Service}.sh ]]&& [[ ${Service} != 'instock2service' ]]; then
echo "./app-k8s-yaml/${Service}.sh 存在,将生成 自定义(非模板)yaml文件。"
source ./app-k8s-yaml/${Service}.sh
else
source ./build/build-${Cloud}-k8s-yaml.sh
fi
fi

echo "$ServiceDir/${Service}.yaml 文件内容:"
cat $ServiceDir/${Service}.yaml

}

Deploy()
{
$ScopeEnv "kubectl apply -f $ServiceDir/${Service}.yaml"
echo "Action:Deploy,ScopeName:${ScopeName},ServiceName:${Service},time:$Date,image:registry-vpc.cn-beijing.aliyuncs.com/vcg/${Service}:$date" >> /jenkins/vcgapp/${ScopeName}_deploy_history.txt
}

# 回滚命令 kubectl rollout undo deployment/node-vcg-web

case "$1" in
CheckOut)
CheckOut
;;
BuildPackage)
BuildPackage
;;
BuildDockerfile)
BuildDockerfile
;;
DockerBuildPush)
DockerBuildPush
;;
BuildK8SYaml)
BuildK8SYaml
;;
Deploy)
Deploy
;;
*)
echo $"Usage: $0 {CheckOut|BuildPackage|BuildDockerfile|DockerBuildPush|BuildK8SYaml|Deploy}"
exit 1
esac

脚本需传参来实现不同功能

例如:

1
sh jenkins.sh 'DockerBuildPush' '${Service}' '${Branch}' '${Scope}' '${TimeStamp}' '${env.Action}'
app-java.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if [[ ${Service} == "assetservice-vcg-com" ]]; then
ServiceType="java"
Consul="yes"
GitAddress="git@git.visualchina.com:structure/vcgasset.git"
GitDir=`echo ${GitAddress}|awk -F'/' '{print $2}'|awk -F'.git' '{print $1}'`
PomDir="./"
ServicePort="8080"
ServiceCommandPre="java -jar -Dserver.port=${ServicePort} -Dspring.profiles.active=production"
ServiceCommandPro="java -jar -Dserver.port=${ServicePort} -Dspring.profiles.active=production"
ServiceImageFrom="registry-vpc.cn-beijing.aliyuncs.com/vcgcs/jdk8"
ServiceStartTime="30"
TingyunApm="no"
ServiceNumPre="1"
ServiceNumPro="3"
fi

根据服务名称去确定相关变量信息,实现后面流程

Jenkins配置

jenkins03