在開始之前,我認(rèn)為你已經(jīng)完成了,jenkins的搭建已經(jīng)jenkins pipeline的編寫,并能正常的發(fā)布項目。
實現(xiàn)的成果

原理介紹
首先企業(yè)中使用的開發(fā)語言大部分都是java,既然已經(jīng)使用上k8s了,相信業(yè)務(wù)架構(gòu)是微服務(wù)類型的。這種微服務(wù)我們部署在k8s中時,最常用的一種資源類型時deployment.
我們來看一下deployment資源
我創(chuàng)建了一個deployment
$ kubectl create deploy web --image=nginx -n gebangfeng-test
deployment.apps/web created
而deployment會使用rs去管理我們的pod
$ kubectl get rs -n gebangfeng-test
NAME DESIRED CURRENT READY AGE
web-96d5df5c8 1 1 1 4m25s
接著我們查一下啟動后的pod
$ kubectl get pod -n gebangfeng-test
NAME READY STATUS RESTARTS AGE
web-96d5df5c8-fmk7r 1/1 Running 0 118s
可以看到pod的名字是由deployment名字+rs名字+隨機字符組成的。
我們的目的是想實現(xiàn)服務(wù)的部署通知,首先我們要知道怎么才能判斷服務(wù)有沒有部署完成。
首先還是那個deployment

可以看到在deployment一些信息中有Available 即可用的副本數(shù)。本來想著這個值可以利用一下,但是deployment的更新策略是滾動更新,deployment不太好判斷服務(wù)有沒有更新完成。deployment的更新操作都是rs來實現(xiàn)的。
deployment的每次更新操作都會創(chuàng)建一個rs完成更新,比如一個應(yīng)用的需要的副本數(shù)是1 準(zhǔn)備好了的副本數(shù)為1 這樣我們就可以認(rèn)為更新成功了。
方案如下
給rs增加版本標(biāo)簽,每次更新這個標(biāo)簽都會變更,然后我們通過標(biāo)簽去篩選出這次更新的rs,并取出rs需要副本數(shù),和準(zhǔn)備好了的副本數(shù)。然后進行循環(huán)的判斷就可以知道服務(wù)有沒有更新完成了。
準(zhǔn)備服務(wù)更新文件
cat nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
generation: 1
labels:
app: web
name: web
namespace: gebangfeng-test
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: web
template:
metadata:
creationTimestamp: null
labels:
app: web
spec:
containers:
- image: nginx:1.22.1
imagePullPolicy: Always
name: nginx
restartPolicy: Always
創(chuàng)建資源
kubectl apply -f nginx-deployment.yaml
模擬更新
添加標(biāo)簽
增加標(biāo)簽 version:1

獲取需要的副本數(shù)和準(zhǔn)備好了的副本數(shù)
kubectl get rs -l appversion=1 -n gebangfeng-test

-l指定新增加的標(biāo)簽,后面通過這個標(biāo)簽去篩選rs,就能準(zhǔn)確獲取到這次更新的rs了。
- 獲取需要的副本數(shù):
kubectl get rs -l appversion=1 -n gebangfeng-test -o jsonpath='{.items[0].status.replicas}'
- 獲取ready狀態(tài)的副本數(shù)
kubectl get rs -l appversion=1 -n gebangfeng-test -o jsonpath='{.items[0].status.readyReplicas}'

我們獲取到了這兩個值,然后寫個腳本定期去查詢,進行判斷,就能知道服務(wù)是否更新完成了。
腳本檢查
我這里寫好了一個腳本
- 首先是發(fā)送到微信的腳本
cat /usr/local/scripts/jenkins-weixin.sh
#!/bin/bash
message=$1
date=$(date +%Y-%m-%d)
time=$(date "+%H:%M:%S")
if [ _"${message}" = _"" ]; then
message='this is test message...'
fi
webHookUrl="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=*************"
content='{"msgtype": "markdown","markdown": {"content": "'$message'"}}'
echo "content : $content"
curl --data-ascii "$content" $webHookUrl
echo "over!"
webHookUrl使用時需要替換成自己的企業(yè)微信群的地址
- 檢查狀態(tài)的腳本
[root@base4 ~]# cat /usr/local/scripts/jenkins-check.sh
#!/bin/bash
#!/bin/bash
#項目名
#構(gòu)建版本號
usage(){
echo "usage: $0 JOB_NAME ITEM_NAME K8S_NAMESPACE JOB_STATUS BUILD_NUM BUILD_USER BUILD_URL COMMENT"
}
JOB_NAME=$1
ITEM_NAME=$2
K8S_NAMESPACE=$3
JOB_STATUS=$4
BUILD_NUM=$5
BUILD_USER=$6
BUILD_URL=$7
COMMENT=$8
if [ $# -lt 8 ]; then
usage
exit 1
fi
declare -A userlist='([gebangfeng]='GeBangFeng')'
SendMessage='/usr/local/scripts/jenkins-weixin.sh'
count=1
APP_LIST=(`echo ${ITEM_NAME//,/\ }`)
AccessApp=()
EerorApp=()
Check() {
while true :
do
for app_id in ${!APP_LIST[@]}
do
app_name=${APP_LIST[$app_id]}
replicas=`kubectl get rs -n ${K8S_NAMESPACE} -l jenkins-build-id=${BUILD_NUM},k8s-app=${app_name} -o jsonpath='{.items[0].status.replicas}'`
readyReplicas=`kubectl get rs -n ${K8S_NAMESPACE} -l jenkins-build-id=${BUILD_NUM},k8s-app=${app_name} -o jsonpath='{.items[0].status.readyReplicas}'`
if [ _"${readyReplicas}" = _"${replicas}" ]; then
echo "服務(wù):${app_name} 發(fā)布成功 需要的副本數(shù)量:${replicas} 準(zhǔn)備好了的副本數(shù): ${readyReplicas}"
AccessApp[${#AccessApp[*]}]=$app_name
unset APP_LIST[$app_id]
#sh /usr/local/scripts/jenkins-weixin.sh ${JOB_NAME} ${ITEM_NAME} "構(gòu)建成功 ?" ${BUILD_NUM} ${BUILD_USER} ${BUILD_URL}
#exit 0
else
if [ $count -gt 100 ];then
echo "服務(wù):${app_name} 發(fā)布未成功, 需要的副本數(shù)量:${replicas} 準(zhǔn)備好了的副本數(shù): ${readyReplicas}"
EerorApp[${#EerorApp[*]}]=$app_name
#sh /usr/local/scripts/jenkins-weixin.sh ${JOB_NAME} ${ITEM_NAME} "構(gòu)建失敗 ?" ${BUILD_NUM} ${BUILD_USER} ${BUILD_URL}
#exit 1
else
echo "服務(wù):${app_name} 第${count} 次檢查,發(fā)布未成功,繼續(xù)檢查 需要的副本數(shù)量:${replicas} 準(zhǔn)備好了的副本數(shù): ${readyReplicas}"
fi
fi
done
if [ $count -gt 100 ];then
echo "檢查周期結(jié)束退出循環(huán)"
echo "發(fā)布成功的 ${AccessApp[@]} 發(fā)布失敗的 ${EerorApp[@]}"
message="**jenkins構(gòu)建狀態(tài)通知**
> 任務(wù)名稱: **${JOB_NAME}**
> 應(yīng)用名稱: **${ITEM_NAME}**
> 構(gòu)建環(huán)境: **${K8S_NAMESPACE}**
> 構(gòu)建結(jié)果: **部分構(gòu)建失敗**
> 構(gòu)建成功: **${AccessApp[@]}**
> 構(gòu)建失敗: **${EerorApp[@]}**
> 當(dāng)前版本: **${BUILD_NUM}**
> 構(gòu)建發(fā)起: **${BUILD_USER}**
> 構(gòu)建日志: ${BUILD_URL}console
<@${userlist["$BUILD_USER"]}><@gebangfeng>
"
echo $message
sh ${SendMessage} "${message}"
exit 0
fi
if [ ${#APP_LIST[*]} -eq '0' ];then
echo "所有服務(wù)發(fā)布完成"
echo "發(fā)布成功的 ${AccessApp[@]} 發(fā)布失敗的 ${EerorApp[@]}"
message="**jenkins構(gòu)建狀態(tài)通知**
> 任務(wù)名稱: **${JOB_NAME}**
> 應(yīng)用名稱: **${ITEM_NAME}**
> 構(gòu)建環(huán)境: **${K8S_NAMESPACE}**
> 構(gòu)建結(jié)果: **構(gòu)建成功 ?**
> 當(dāng)前版本: **${BUILD_NUM}**
> 更新內(nèi)容: **${COMMENT}**
> 構(gòu)建發(fā)起: **${BUILD_USER}**
<@${userlist["$BUILD_USER"]}>
"
echo $message
sh ${SendMessage} "${message}"
exit 0
fi
sleep 5
let count++
done
}
#判斷狀態(tài)
if [ ${JOB_STATUS} -eq '0' ];then
echo "開始更新"
message="**jenkins構(gòu)建狀態(tài)通知**
> 任務(wù)名稱: **${JOB_NAME}**
> 應(yīng)用名稱: **${ITEM_NAME}**
> 構(gòu)建環(huán)境: **${K8S_NAMESPACE}**
> 構(gòu)建結(jié)果: **開始更新...**
> 當(dāng)前版本: **${BUILD_NUM}**
> 構(gòu)建發(fā)起: **${BUILD_USER}**
> 構(gòu)建日志: ${BUILD_URL}console
<@${userlist["$BUILD_USER"]}>
"
echo "${message}"
#sh ${SendMessage} "${message}"
Check
elif [ ${JOB_STATUS} -eq '1' ];then
echo "更新失敗"
message="**jenkins構(gòu)建狀態(tài)通知**
> 任務(wù)名稱: **${JOB_NAME}**
> 應(yīng)用名稱: **${ITEM_NAME}**
> 構(gòu)建環(huán)境: **${K8S_NAMESPACE}**
> 構(gòu)建結(jié)果: **構(gòu)建失敗 ?**
> 當(dāng)前版本: **${BUILD_NUM}**
> 構(gòu)建發(fā)起: **${BUILD_USER}**
> 構(gòu)建日志: ${BUILD_URL}console
<@${userlist["$BUILD_USER"]}>
"
echo "${message}"
sh ${SendMessage} "${message}"
exit 1
else
usage
exit 1
fi
需要注意的內(nèi)容SendMessage 替換 微信腳本的路徑
userlist時存放企業(yè)微信 userid的數(shù)組
執(zhí)行腳本需要傳幾個參數(shù)
JOB_NAME ITEM_NAME K8S_NAMESPACE JOB_STATUS BUILD_NUM BUILD_USER BUILD_URL COMMENT
JOB_NAME是jenkins項目的名字
ITEM_NAME是項目的名字比如我們例子web
K8S_NAMESPACE 是k8s的命名空間
JOB_STATUS 腳本檢查狀態(tài)0是直接發(fā)失敗消息,1是執(zhí)行檢查操作,
BUILD_NUM是對應(yīng)jenkns 的BUILD_NUM,用于識別每個更新任務(wù)的,這個id也會以標(biāo)簽的方式寫入到deployment部署文件中,保證這個標(biāo)簽每次更新服務(wù)都能對于的變更
BUILD_USER 更新者的名字 比如gebangfeng 用戶@更新的人和消息展示
BUILD_URL 主要用戶展示構(gòu)建通知,一般填寫jenkins任務(wù)的url,COMMENT填寫這次更新的內(nèi)容
接下來我們來模擬一次完整的過程。更新服務(wù)并給服務(wù)添加版本的標(biāo)簽,更新后調(diào)用腳本去檢查更新狀態(tài)。
刪除之前的web項目
kubeclt delete -f nginx-deployment.yaml
編寫更新deployment文件
cat nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
generation: 1
labels:
k8s-app: web
jenkins-job: gebangfeng-test-web-demo
jenkins-build-id: "1"
name: web
namespace: gebangfeng-test
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: web
template:
metadata:
creationTimestamp: null
labels:
k8s-app: web
jenkins-job: gebangfeng-test-web-demo
jenkins-build-id: "1"
spec:
containers:
- image: nginx:1.22.1
imagePullPolicy: Always
name: nginx
restartPolicy: Always
jenkins-job 代表了jenkins job的名稱
jenkins-build-id 代表了jenkins的構(gòu)建id

kubectl apply -f nginx-deployment.yaml
調(diào)用腳本去檢查
sh jenkins-check.sh test web gebangfeng-test 0 1 gebangfeng https://baidu.com '測試'
$ sh jenkins-check.sh test web gebangfeng-test 0 1 gebangfeng https://baidu.com '測試'
開始更新
**jenkins構(gòu)建狀態(tài)通知**
> 任務(wù)名稱: **test**
> 應(yīng)用名稱: **web**
> 構(gòu)建環(huán)境: **gebangfeng-test**
> 構(gòu)建結(jié)果: **開始更新...**
> 當(dāng)前版本: **1**
> 構(gòu)建發(fā)起: **gebangfeng**
> 構(gòu)建日志: https://baidu.comconsole
<@GeBangFeng>
服務(wù):web 發(fā)布成功 需要的副本數(shù)量:1 準(zhǔn)備好了的副本數(shù): 1
所有服務(wù)發(fā)布完成
發(fā)布成功的 web 發(fā)布失敗的
**jenkins構(gòu)建狀態(tài)通知** > 任務(wù)名稱: **test** > 應(yīng)用名稱: **web** > 構(gòu)建環(huán)境: **gebangfeng-test** > 構(gòu)建結(jié)果: **構(gòu)建成功 ?** > 當(dāng)前版本: **1** > 更新內(nèi)容: **測試** > 構(gòu)建發(fā)起: **gebangfeng** <@GeBangFeng>
content : {"msgtype": "markdown","markdown": {"content": "**jenkins構(gòu)建狀態(tài)通知**
> 任務(wù)名稱: **test**
> 應(yīng)用名稱: **web**
> 構(gòu)建環(huán)境: **gebangfeng-test**
> 構(gòu)建結(jié)果: **構(gòu)建成功 ?**
> 當(dāng)前版本: **1**
> 更新內(nèi)容: **測試**
> 構(gòu)建發(fā)起: **gebangfeng**
<@GeBangFeng>
"}}
{"errcode":0,"errmsg":"ok"}over!

和jenkins集成
post{
success{
sh '''
/usr/local/scripts/jenkins-check.sh ${JOB_NAME} ${ITEMS} ${K8S_NAMESPACE} '0' ${BUILD_NUMBER} ${BUILD_USER_ID} ${BUILD_URL} "${COMMENT}"
'''
}
failure{
sh '''
/usr/local/scripts/jenkins-check.sh ${JOB_NAME} ${ITEMS} ${K8S_NAMESPACE} '1' ${BUILD_NUMBER} ${BUILD_USER_ID} ${BUILD_URL} "${COMMENT}"
'''
}
}
失敗和成功傳遞的參數(shù)區(qū)別在于第四個參數(shù),如果說在發(fā)布流水線已經(jīng)報失敗異常了也就沒有必要再去調(diào)用腳本執(zhí)行檢查操作,傳遞1說明直接發(fā)送是被消息。