The Settings Of Jenkins Pipeline

声明式pipeline:

  1. 先检查harbor仓库中是否有对应的项目分支,如果不存在则自动创建并配置好相应的tag保留机制与机器人配置(api格式为:http://do-web.pikaqiu.com/dev-pkq,cosign和notary暂不开启,前者在devops阶段验签麻烦,后者harbor目前似乎不支持开启)
  2. 拉取svn代码拉取
  3. 进行docker容器镜像的构建(以时间戳为tag),并删除本地所缓存的刚构建完的镜像(此处可配置Cosign和notary验签,但目前暂不配置)
  4. 因为仓库均是私有化配置,远程机器需要使用机器人账号登陆才可以拉取相应tag的镜像,与此同时,应用服务是通过docker swarm进行部署,所以需要把机器人账号保存到远程机器的secret中等待后续的部署操作(Cosign与Notation基于摘要的验签也补上)验证之后才能进行容器的部署
  • pipeline-script:
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
pipeline {
agent {
label 'slave-mix'
}
environment {
// 因为使用了node,需要指定node的bin的路径
PATH = "PATH+EXTRA=/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin"
}
parameters {
// 此处配置项目具体的参数,不展示......
}
stages {
stage('perpare') {
steps {
echo "check harbor program"
script {
def DeployEnvs = params.DestEnvs.split(',')
for (DeployEnv in DeployEnvs) {
retry(3) {
response = httpRequest httpMode: 'GET', url: "http://${params.autoUrl}/create/${DeployEnv}", validResponseCodes: "100:511", ignoreSslErrors: true, contentType: 'APPLICATION_JSON_UTF8'
if (response.status != 200) {
echo "访问失败,正在重试..."
sleep(5000)
sh "exit 1"
} else {
sh "exit 0"
}
}
}
}
echo "svn pulling"
script {
def scmVars = checkout([$class: 'SubversionSCM', additionalCredentials: [], excludedCommitMessages: '', excludedRegions: '', excludedRevprop: '', excludedUsers: '', filterChangelog: false, ignoreDirPropChanges: false, includedRegions: '', locations: [[cancelProcessOnExternalsFail: true, credentialsId: "${params.SvnCredentials}", depthOption: 'infinity', ignoreExternalsOption: true, local: "${params.SvnRootName}", remote: "${params.SvnRootUrl}/${params.SvnRootName}"]], quietOperation: true, workspaceUpdater: [$class: 'UpdateUpdater']])
def svnRevision = scmVars.SVN_REVISION
env.SVN_REVISION = svnRevision
}
echo 'Env prepared .'
}
}
stage('buildAndPush') {
steps {
echo "clean up history image"
sh "/bin/bash /code/cleanImage.sh ${params.harborUrl}"
script {
def DeployEnvs = params.DestEnvs.split(',')
def DeployPrograms = params.PublishPrograms.split(',')
for (DeployEnv in DeployEnvs) {
for (DeployProgram in DeployPrograms) {
def IMAGE_NAME = "${params.harborUrl}/${DeployEnv}/${DeployProgram}:${params.BUILD_TIMESTAMP_1}"
echo "start build ${env.IMAGE_NAME} image"
docker.build("${IMAGE_NAME}", "-f ./${params.SvnRootName}/${params.DockerFileName} ./${params.SvnRootName}")
echo "start push ${env.IMAGE_NAME} image"
try {
docker.withRegistry("https://${params.harborUrl}", "${DeployEnv}") {
docker.image("${IMAGE_NAME}").push()
}
} catch (Exception e) {
echo "An error occurred: ${e.getMessage()}"
retry(3) {
response = httpRequest httpMode: 'GET', url: "http://${params.autoUrl}/update/${DeployEnv}/robot", validResponseCodes: "100:511", ignoreSslErrors: true, contentType: 'APPLICATION_JSON_UTF8'
if (response.status != 200) {
echo "访问失败,正在重试..."
sleep(5000)
sh "exit 1"
} else {
sh "exit 0"
}
}
docker.withRegistry("https://${params.harborUrl}", "${DeployEnv}") {
docker.image("${IMAGE_NAME}").push()
}
}
}
}
}
// echo "start signature ${env.IMAGE_NAME} image"
// script {
// docker.withRegistry("https://${params.harborUrl}", "${params.DeployEnv}") {
// 线上验签 echo -n '${env.cosign_passwd}' | COSIGN_DOCKER_MEDIA_TYPES=1 cosign verify --allow-insecure-registry --key=/code/cosign.pub --slot 0 ${env.IMAGE_NAME}
// sh "echo -n '${env.cosign_passwd}' | COSIGN_DOCKER_MEDIA_TYPES=1 cosign sign -y --allow-insecure-registry --key=/code/cosign.key --slot 0 ${env.IMAGE_NAME}"
// harbor无法使用notaion,因为没有开启,但install.sh中也已经把notation给去掉了,所以只能这样
// sh "notation sign --signature-format cose ${env.IMAGE_NAME}"
// sh "notation sign --signature-format cose ${env.IMAGE_NAME}"
// sh "notation verify ${env.IMAGE_NAME}"
// }
// }
// 此处变更为利用节点的脚本进行镜像的清理,不然每次都清理就无法使用缓存层
// echo "start prune ${env.IMAGE_NAME} image"
// script {
// sh "docker rmi -f ${IMAGE_NAME}"
// }
}
}
stage('Publish') {
environment {
// IMAGE_NAME = "${params.harborUrl}/${params.DeployEnv}/${params.ApplicationName}:${params.BUILD_TIMESTAMP_1}"
// robot_filename = sh(script: "ls /code/secrets | grep ${params.DeployEnv}", returnStdout: true).trim()
// robot_name = sh(script: "ls /code/secrets | grep ${params.DeployEnv} | awk -F \".\" '{print \$1}' | sed 's/\\\$/\\\\\$/g'", returnStdout: true).trim()
// robot_passwd = sh(script: "cat /code/secrets/${robot_name}.log | grep 'robotSecret' | awk -F \":\" '{print \$2}'", returnStdout: true).trim()
// cosign_passwd = sh(script: "cat /code/cosign_passwd.yaml | grep 'cosign_passwd' | awk '{print \$2}'", returnStdout: true).trim()
AllBotstudioAdmins = "admin2,admin3,admin4"
AllBotstudioExecutors = "executor"
}
steps {
wrap([$class: 'BuildUser']) {
script {
def DeployEnvs = params.DestEnvs.split(',')
def DeployPrograms = params.PublishPrograms.split(',')
for (DeployEnv in DeployEnvs) {
for (DeployProgram in DeployPrograms) {
def IMAGE_NAME = "${params.harborUrl}/${DeployEnv}/${DeployProgram}:${params.BUILD_TIMESTAMP_1}"
def robot_name = sh(script: "ls /code/secrets | grep ${DeployEnv} | awk -F \".\" '{print \$1}' | sed 's/\\\$/\\\\\$/g'", returnStdout: true).trim()
def robot_passwd = sh(script: "cat /code/secrets/${robot_name}.log | grep 'robotSecret' | awk -F \":\" '{print \$2}'", returnStdout: true).trim()
def ChoicedWorkspaces = []
def workspaceList = params.DestWorkspaces.split(',')
def adminList = env.AllBotstudioAdmins.split(',')
def executorList = env.AllBotstudioExecutors.split(',')
for (workspace in workspaceList) {
workspace = workspace.trim()
if (workspace == "all_admin") {
ChoicedWorkspaces = (ChoicedWorkspaces + adminList).flatten()
} else if (workspace == "all") {
ChoicedWorkspaces = (ChoicedWorkspaces + adminList).flatten()
ChoicedWorkspaces = (ChoicedWorkspaces + executorList).flatten()
} else {
ChoicedWorkspaces.add(workspace)
}
}
env.ChoicedWorkspaces = ChoicedWorkspaces.toSet().toList().join(',')
sh "/bin/bash /code/publish-git3.sh ${env.WORKSPACE}/${params.SvnRootName} ${DeployEnv} ${env.BUILD_USER} ${env.SVN_REVISION} ${params.BUILD_TIMESTAMP_1} ${params.GitRootUrl} ${params.harborUrl} ${env.robot_name} ${env.robot_passwd} ${env.IMAGE_NAME} ${DeployProgram}"
}
}
}
}
sshPublisher failOnError: true, publishers: [sshPublisherDesc(configName: "${params.DeployEnv}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/bin/bash /data/home/code/git_pull.sh ${params.DeployEnv} ${params.BUILD_TIMESTAMP_1} ${params.ApplicationName};source /etc/profile;source ~/.bash_profile;${params.ApplicationName}.sh --deploy ${env.ChoicedWorkspaces} --password '000000'", execTimeout: 1200000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: "", sourceFiles: "")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)]
echo "work finished ..."
}
}
}
}

  • cleanImage.sh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# /code/cleanImage.sh
#!/bin/bash

registryUrl="${1}"
imagesInfo=$(docker images | grep "${registryUrl}")
envs=$(echo "${imagesInfo}" | awk '{print $1}' | awk -F "/" '{print $2}' | uniq)
for env in ${envs[@]}
do
programs=$(docker images | grep "${registryUrl}/${env}" | awk '{print $1}' | awk -F "/" '{print $3}' | uniq)
for program in ${programs[@]}
do
tags=$(docker images | grep "${registryUrl}/${env}/${program}" | awk '{print $2}')
lastTag=$(echo "${tags}" | tail -n 1)
tags=$(echo "${tags}" | grep -v "${lastTag}")
for tag in ${tags[@]}
do
docker rmi "${registryUrl}/${env}/${program}:${tag}"
done
done
done

# 0 */12 * * * /bin/bash /root/cleanImage.sh 2>&1 # 每半天清理一次镜像,或者放到pipeline上也可以

  • publish-git.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
#!/bin/bash
# python3 /code/thread_jenkins_pjc3.py -w ${WORKSPACE}/${ProjectName}/${ApplicationDirName} -b ${DeployEnv} -f ${ApplicationFileName} -owner ${BUILD_USER} -url "${SVN_URL}" -num ${SVN_REVISION} -c "${git_commit}" -u "${git_url}" -t "${BUILD_TIMESTAMP_1}" -Fs "${FilePathProject}" -userId "${BUILD_USER_ID}" -node "${NODE_NAME}"

workspace="${1}"
branch="${2}"
user="${3}"
svn_resivion="${4}"
git_timestamp="${5}"
git_url="${6}"
gitlab_path="/data/gitlab"
harbor_url="${7}"
robot_name="${8}"
robot_password="${9}"
image_name="${10}"
programName="${11}"
handle_interrupt(){
folder1=${gitlab_path}/"${branch}"/"${programName}"
echo "进行还原暂存区与工作目录"
cd ${folder1} || exit
git reset --hard HEAD
echo "恢复完毕"
exit 0
}
# 配置基于各种情况的中断保底机制
trap "handle_interrupt" SIGINT SIGQUIT SIGABRT SIGFPE SIGSEGV SIGTERM SIGTSTP SIGXCPU SIGXFSZ
echo "现在${user}正在操作:${branch} 的 ${programName} 版本号:${svn_resivion}"
# gitlab的缓存路径
[ -d "${gitlab_path}"/"${branch}" ] || {
mkdir -p ${gitlab_path}/"${branch}"
}
[ -d ${gitlab_path}/"${branch}"/"${programName}" ] || {
git clone -b "${branch}" "${git_url}" ${gitlab_path}/"${branch}"/"${programName}"
}
cd ${gitlab_path}/"${branch}"/"${programName}" || exit
git checkout "${branch}"
# 切换指定的分支,并保持最新的log
if [[ ! $? ]];then
# 创建一个没有历史的分支
git checkout --orphan "${branch}"
# 删除当前分支中没有被跟踪的文件和文件夹
git clean -f -d .
else
git pull origin "${branch}"
lastcommitLog=$(git log --pretty=format:"%s %H" | head -n 2 | tail -n 1 | awk '{print $NF}')
git reset --hard "${lastcommitLog}"
git pull origin "${branch}"
fi
folder1=${gitlab_path}/"${branch}"/"${programName}"
robot_name=$(echo -n "${robot_name}" | base64)
robot_password=$(echo -n "${robot_password}" | base64)
{
echo "one:${branch}@-@${svn_resivion}"
echo "two:${robot_name}@-@${robot_password}"
echo "three:${harbor_url}@-@${image_name}"
} > ${folder1}/deployment.log
folder2=${workspace}/
cp -f ${workspace}/swarm.env ${folder1}/
cp -f ${workspace}/docker-compose-swarm.yml ${folder1}/
git add .
git commit -m "${user}&&${git_timestamp}"
git push origin "${branch}":"${branch}"
exit 0


  • pipeline-mail:
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
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
env2Name = [
'prod-bot-pjcxa':'bot线上a环境'
]
publishMap = [
"botstudio":["description":"botstudio","nop":"<span>发布</span><span><font color=\"#FF0000\"><b>不启动</b></font></span>","op":"<span>发布</span><span><font color=\"#FF0000\"><b>启动</b></font></span>"]
]
projectName = "pjcx"
destEnvs = env.DestEnvs.tokenize(",")
publishDayOffset = env.PublishDayOffset
publishTime = env.PublishTime
maintainLongTime = "true".toUpperCase() == env.MaintainLongTime.toUpperCase()
publishPrograms = env.PublishPrograms.tokenize(",")
publishFiles_1 = []
publishPrograms.each{
publishFiles_1.add(it)
}
publishDate = getCurrentTimeAsyyyyMMdd(getDayOffset())
publishFile = env.DockerFileName
destWorkspaces = env.DestWorkspaces.tokenize(",")
extraPublishOps = env.ExtraPublishOps
publishFolderName = env.BUILD_TIMESTAMP_1
HarborUrl = env.harborUrl
adminList = env.AllBotstudioAdmins.split(',')
executorList = env.AllBotstudioExecutors.split(',')
def getCurrentTimeAsyyyyMMdd(dayOffset) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日")
Calendar calendar = Calendar.getInstance()
calendar.add(Calendar.DATE, dayOffset)
return sdf.format(calendar.getTime())
}
def getDayOffset() {
if(publishDayOffset == "今天") {
return 0
} else if(publishDayOffset == "明天") {
return 1
} else if(publishDayOffset == "后天") {
return 2
} else if(publishDayOffset == "大后天") {
return 3
} else {
return 0
}
}
def buildMailPublishOps() {
def mailPublishPaths = ""
mailPublishPaths += "<div><br /></div>"
mailPublishPaths += "<div><b>更新以下${destEnvs.size()}个环境:</b></div>"
def ChoicedWorkspaces = []
for (workspace in destWorkspaces) {
workspace = workspace.trim()
if (workspace == "all_admin") {
adminList.each { item ->
ChoicedWorkspaces << item
}
} else if (workspace == "all") {
adminList.each { item ->
ChoicedWorkspaces << item
}
executorList.each { item ->
ChoicedWorkspaces << item
}
} else {
ChoicedWorkspaces.add(workspace.toString())
}
}
ChoicedWorkspaces = ChoicedWorkspaces.toSet().join(',')
def envId = 1
for(destEnv in destEnvs) {
def destEnvName = "${env2Name[destEnv]}"
mailPublishPaths += "<div><span>${envId++}、</span>${destEnvName}(${destEnv})</div>"
//mailPublishPaths += "<div>${publishFilePath}</div>"
mailPublishPaths += "<div><br /></div>"
mailPublishPaths += "<div><b>步骤:</b></div>"
mailPublishPaths += "<div><br /></div>"
def publishOpId = 1
if (maintainLongTime) {
mailPublishPaths += "<div><span>${publishOpId++}、</span><b style=\"color: rgb(255, 0, 0);\"></b>准时暂停服务之后再部署</div>"
} else {
mailPublishPaths += "<div><span>${publishOpId++}、</span><b style=\"color: rgb(255, 0, 0);\"></b>开始直接部署,不用停服!</div>"
}
def publishFileNum = publishFiles_1.size()
mailPublishPaths += "<div><br /></div>"
def publishSubOpId = 1
for (publish in publishMap) {
def publishName = publish.key
def publishFile = publish.value["description"]
def publishOp = ""
if (maintainLongTime) {
publishOp = publish.value["nop"]
} else {
publishOp = publish.value["op"]
}
//if(publishFiles_1.contains(publishFile) || (publishName == "SQL脚本" && hasSQLFile)) {
if (publishFiles_1.contains(publishFile)) {
if (publishFileNum > 1) {
mailPublishPaths += "<div><span>${publishOpId}.${publishSubOpId++}、</span>${publishOp}"
} else {
mailPublishPaths += "<div><span>${publishOpId++}、</span>${publishOp}"
}
mailPublishPaths += "<span>${publishName},对应的镜像名称为:${HarborUrl}/${destEnv}/${publishName}:${publishFolderName},</span>"
mailPublishPaths += "<span>对应需要更新的应用进程名称为:${ChoicedWorkspaces}</span></div>"
}
}
if (extraPublishOps.trim() != "") {
mailPublishPaths += "<div><br /></div>"
mailPublishPaths += "<div><span>${publishOpId++}、</span><span><b>额外步骤——开始:</b></span></div>"
mailPublishPaths += "<div>${extraPublishOps.replace('\n', '<br />')}</div>"
mailPublishPaths += "<div><span><b>额外步骤——结束。</b></span></div>"
}
if (maintainLongTime) {
mailPublishPaths += "<div><span>${publishOpId++}、</span><span>启动所有应用进程,通知测试</span></div>"
} else {
mailPublishPaths += "<div><span>${publishOpId++}、</span><span>通知测试人员</span></div>"
}
}
return mailPublishPaths
}
node {
for(destEnv in destEnvs) {
stage("SendMail for ${destEnv}") {
def destEnvName = "${env2Name[destEnv]}"
def mailSubject = "【${projectName.toUpperCase()}】${destEnvName}(${destEnv})-发版"
def mailPublishOps = buildMailPublishOps()
def mailBody = """
<html>
<body>
<style>
body { line-height: 1.5; }body { font-size: 10.5pt; font-family: 微软雅黑; color: rgb(0, 0, 0); line-height: 1.5; }body {
font-size: 10.5pt; font-family: 微软雅黑; color: rgb(0, 0, 0); line-height: 1.5; }
</style>
<div>
<div><b>${mailSubject}</b></div>
<div><b>时间:${publishDate},<font color="#FF0000">${publishTime}</font>部署</b></div>
${mailPublishOps}
</div>
</body>
</html>
"""
echo mailBody
emailext body: mailBody, mimeType: 'text/html', recipientProviders: [requestor()], subject: mailSubject
}
}
}
  • git-pull:
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
#!/bin/bash

goal1="$1"
programName="$3"
emailcommitLog="$2"
gitPathName=$(grep "gitName" /usr/local/CGManage/${goal1}/bin/${programName}.sh | awk -F "=" '{print $2}')
git_path=/data/transfer/deploy/${goal1}/${programName}
if [ ! -d ${git_path} ];then
echo "当前还没拉取相关的镜像,请先手动拉取(因各环境的拉取ip方式不同,故不统一拉取,手动狗头)"
exit 1
fi
cd $git_path
git checkout -b $goal1
git pull origin $goal1
commitLog=$(git log --pretty=format:"%s %H" | head -1 | awk -F "&&" '{print $2}' | awk '{print $1}')
if [[ ${emailcommitLog} != ${commitLog} ]]
then
echo "New git commit is not right"
echo "go to git reset"
commitLog=$(git log --pretty=format:"%s %H" | grep ${emailcommitLog})
if [[ ${commitLog} != "" ]]
then
commitLog=$(git log --pretty=format:"%s %H" | grep ${emailcommitLog} | head -1 | awk '{print $NF}')
git reset --hard ${commitLog}
else
echo "gitlab上没有对应的version,请检查jenkins是否传达成功"
exit 1
fi
else
echo "可以开始执行相关的命令啦"
fi