在开始之前,我认为你已经完成了,jenkins的搭建已经jenkins pipeline的编写,并能正常的发布项目。
实现的成果
原理介绍
首先企业中使用的开发语言大部分都是java,既然已经使用上k8s了,相信业务架构是微服务类型的。这种微服务我们部署在k8s中时,最常用的一种资源类型时deployment.
我们来看一下deployment资源
我创建了一个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名字+随机字符组成的。
我们的目的是想实现服务的部署通知,首先我们要知道怎么才能判断服务有没有部署完成。
首先还是那个deployment
可以看到在deployment一些信息中有Available 即可用的副本数。本来想着这个值可以利用一下,但是deployment的更新策略是滚动更新,deployment不太好判断服务有没有更新完成。deployment的更新操作都是rs来实现的。
deployment的每次更新操作都会创建一个rs完成更新,比如一个应用的需要的副本数是1 准备好了的副本数为1 这样我们就可以认为更新成功了。
方案如下
给rs增加版本标签,每次更新这个标签都会变更,然后我们通过标签去筛选出这次更新的rs,并取出rs需要副本数,和准备好了的副本数。然后进行循环的判断就可以知道服务有没有更新完成了。
准备服务更新文件
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
创建资源
kubectl apply -f nginx-deployment.yaml
模拟更新
添加标签
增加标签 version:1
获取需要的副本数和准备好了的副本数
kubectl get rs -l appversion=1 -n gebangfeng-test
-l指定新增加的标签,后面通过这个标签去筛选rs,就能准确获取到这次更新的rs了。
- 获取需要的副本数:
kubectl get rs -l appversion=1 -n gebangfeng-test -o jsonpath='{.items[0].status.replicas}'
- 获取ready状态的副本数
kubectl get rs -l appversion=1 -n gebangfeng-test -o jsonpath='{.items[0].status.readyReplicas}'
我们获取到了这两个值,然后写个脚本定期去查询,进行判断,就能知道服务是否更新完成了。
脚本检查
我这里写好了一个脚本
- 首先是发送到微信的脚本
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使用时需要替换成自己的企业微信群的地址
- 检查状态的脚本
[root@base4 ~]# cat /usr/local/scripts/jenkins-check.sh
#!/bin/bash
#!/bin/bash
#项目名
#构建版本号
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 "服务:${app_name} 发布成功 需要的副本数量:${replicas} 准备好了的副本数: ${readyReplicas}"
AccessApp[${#AccessApp[*]}]=$app_name
unset APP_LIST[$app_id]
#sh /usr/local/scripts/jenkins-weixin.sh ${JOB_NAME} ${ITEM_NAME} "构建成功 ?" ${BUILD_NUM} ${BUILD_USER} ${BUILD_URL}
#exit 0
else
if [ $count -gt 100 ];then
echo "服务:${app_name} 发布未成功, 需要的副本数量:${replicas} 准备好了的副本数: ${readyReplicas}"
EerorApp[${#EerorApp[*]}]=$app_name
#sh /usr/local/scripts/jenkins-weixin.sh ${JOB_NAME} ${ITEM_NAME} "构建失败 ?" ${BUILD_NUM} ${BUILD_USER} ${BUILD_URL}
#exit 1
else
echo "服务:${app_name} 第${count} 次检查,发布未成功,继续检查 需要的副本数量:${replicas} 准备好了的副本数: ${readyReplicas}"
fi
fi
done
if [ $count -gt 100 ];then
echo "检查周期结束退出循环"
echo "发布成功的 ${AccessApp[@]} 发布失败的 ${EerorApp[@]}"
message="**jenkins构建状态通知**
> 任务名称: **${JOB_NAME}**
> 应用名称: **${ITEM_NAME}**
> 构建环境: **${K8S_NAMESPACE}**
> 构建结果: **部分构建失败**
> 构建成功: **${AccessApp[@]}**
> 构建失败: **${EerorApp[@]}**
> 当前版本: **${BUILD_NUM}**
> 构建发起: **${BUILD_USER}**
> 构建日志: ${BUILD_URL}console
<@${userlist["$BUILD_USER"]}><@gebangfeng>
"
echo $message
sh ${SendMessage} "${message}"
exit 0
fi
if [ ${#APP_LIST[*]} -eq '0' ];then
echo "所有服务发布完成"
echo "发布成功的 ${AccessApp[@]} 发布失败的 ${EerorApp[@]}"
message="**jenkins构建状态通知**
> 任务名称: **${JOB_NAME}**
> 应用名称: **${ITEM_NAME}**
> 构建环境: **${K8S_NAMESPACE}**
> 构建结果: **构建成功 ✅**
> 当前版本: **${BUILD_NUM}**
> 更新内容: **${COMMENT}**
> 构建发起: **${BUILD_USER}**
<@${userlist["$BUILD_USER"]}>
"
echo $message
sh ${SendMessage} "${message}"
exit 0
fi
sleep 5
let count++
done
}
#判断状态
if [ ${JOB_STATUS} -eq '0' ];then
echo "开始更新"
message="**jenkins构建状态通知**
> 任务名称: **${JOB_NAME}**
> 应用名称: **${ITEM_NAME}**
> 构建环境: **${K8S_NAMESPACE}**
> 构建结果: **开始更新...**
> 当前版本: **${BUILD_NUM}**
> 构建发起: **${BUILD_USER}**
> 构建日志: ${BUILD_URL}console
<@${userlist["$BUILD_USER"]}>
"
echo "${message}"
#sh ${SendMessage} "${message}"
Check
elif [ ${JOB_STATUS} -eq '1' ];then
echo "更新失败"
message="**jenkins构建状态通知**
> 任务名称: **${JOB_NAME}**
> 应用名称: **${ITEM_NAME}**
> 构建环境: **${K8S_NAMESPACE}**
> 构建结果: **构建失败 ❌**
> 当前版本: **${BUILD_NUM}**
> 构建发起: **${BUILD_USER}**
> 构建日志: ${BUILD_URL}console
<@${userlist["$BUILD_USER"]}>
"
echo "${message}"
sh ${SendMessage} "${message}"
exit 1
else
usage
exit 1
fi
需要注意的内容SendMessage 替换 微信脚本的路径
userlist时存放企业微信 userid的数组
执行脚本需要传几个参数
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 脚本检查状态0是直接发失败消息,1是执行检查操作,
BUILD_NUM是对应jenkns 的BUILD_NUM,用于识别每个更新任务的,这个id也会以标签的方式写入到deployment部署文件中,保证这个标签每次更新服务都能对于的变更
BUILD_USER 更新者的名字 比如gebangfeng 用户@更新的人和消息展示
BUILD_URL 主要用户展示构建通知,一般填写jenkins任务的url,COMMENT填写这次更新的内容
接下来我们来模拟一次完整的过程。更新服务并给服务添加版本的标签,更新后调用脚本去检查更新状态。
删除之前的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的构建id
kubectl apply -f nginx-deployment.yaml
调用脚本去检查
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构建状态通知**
> 任务名称: **test**
> 应用名称: **web**
> 构建环境: **gebangfeng-test**
> 构建结果: **开始更新...**
> 当前版本: **1**
> 构建发起: **gebangfeng**
> 构建日志: https://baidu.comconsole
<@GeBangFeng>
服务:web 发布成功 需要的副本数量:1 准备好了的副本数: 1
所有服务发布完成
发布成功的 web 发布失败的
**jenkins构建状态通知** > 任务名称: **test** > 应用名称: **web** > 构建环境: **gebangfeng-test** > 构建结果: **构建成功 ✅** > 当前版本: **1** > 更新内容: **测试** > 构建发起: **gebangfeng** <@GeBangFeng>
content : {"msgtype": "markdown","markdown": {"content": "**jenkins构建状态通知**
> 任务名称: **test**
> 应用名称: **web**
> 构建环境: **gebangfeng-test**
> 构建结果: **构建成功 ✅**
> 当前版本: **1**
> 更新内容: **测试**
> 构建发起: **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}"
'''
}
}
失败和成功传递的参数区别在于第四个参数,如果说在发布流水线已经报失败异常了也就没有必要再去调用脚本执行检查操作,传递1说明直接发送是被消息。