效果
代码提交后触发hook,通知jenkins构建,jenkins构建成功后发送镜像到服务器并运行容器,容器运行成功后shell修改nginx配置文件切换到新容器完成服务无感发布。
提交代码到github后,效果如下。可根据不同分支发布到不同环境
基础环境
1、GitHub新建项目、并设置webHook
2、搭建jenkins、并添加基础组件(Generic Webhook Trigger、Send files or execute commands over SSH)
3、nginx搭建并修改初始化配置文件
配置t1.docker.vip域名为所有服务的根域名
t1.docker.vip/saas/指向项目名为saas的服务(对应的.localhost和upstrem由jenkins生成)
t1.docker.vip/event/执行项目名为event的服务(对应的.localhost和upstrem由jenkins生成)
t1.docker.vip.conf文件的内容
#加载对应位置的所有upstream文件(jenkins会生成)
include /etc/nginx/conf.d/*.upstream;
server {
listen 80;
listen [::]:80;
server_name t1.docker.vip;
#加载对应位置的所有localhost文件(jenkins会生成)
include /etc/nginx/conf.d/*.localhost;
}
构建任务
1、创建自由风格任务,构建出发器勾选:Generic Webhook Trigger,GitHub提交代码后会触发代码构建。
2、GitHub事件参数获取
3、WebHooks过滤
4、执行shell
#!/bin/bash
# 判断是否webHook触发
if [ -z "$repository" ]; then
echo "非hook触发构建,退出....."
exit
fi
#set -x 开启调试模式
#set -x
#定义一个全局异常消息变量
shellErrMsg=""
#构建通知:企业微信机器人链接
noteUrl=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=XXXXXX
#发送企业微信通知方法
sendMsg(){
curl -X POST \
-H "Content-Type: application/json" \
-d "{
\"msgtype\": \"markdown\",
\"markdown\": {
\"content\": \"$1\"
}
}" \
${noteUrl}
}
#异常事件执行方法
error_handler() {
shellErrMsg="shell执行异常退出;行:$1."
echo $shellErrMsg
exit 1
}
#退出事件执行方法
exit_handler(){
if [ -n "$shellErrMsg" ]; then
sendMsg "发布失败:"$shellErrMsg
else
sendMsg ${repository}":"$(basename $branch)"."${BUILD_NUMBER}"发布成功"
fi
}
# 设置 trap 捕获错误
trap 'error_handler $LINENO' ERR
# 捕获退出事件
trap 'exit_handler' EXIT
msg="项目:${repository};分支:$(basename $branch);事件:${x_github_event}\n\n开始构建:\n"
mapfile -t commits_array < <(echo "$commits" | jq -c '.[]')
for obj in "${commits_array[@]}"; do
message=$(echo "$obj" | jq -r '.message')
committer=$(echo "$obj" | jq -r '.committer')
name=$(echo "$committer" | jq -r '.name')
username=$(echo "$committer" | jq -r '.username')
#msg+="$(echo "$message" | tr '\n' ' ')-${name}\n"
cleaned_message=$(echo "$message" | sed "s/\\'\''/ /g; s/'//g")
msg+=$cleaned_message"-"${name}"\n"
done
#发送构建通知到企业微信
escaped_msg=$(printf '%s' "$msg" | sed 's/"/\\\"/g; s/\n/\\n/g')
sendMsg "$escaped_msg"
pwd
#删除原构建信息
rm -rf $repository
ls -l
#git clone制定项目分支的代码
cloneResult=$(git clone -b $(basename $branch) https://[github-Token]@github.com/XXXX/${repository}.git 2>&1)
ls -l
#进入clone的代码
cd $repository
# node项目 运行build编译(不同代码换位不同逻辑即可)
yarn run tar
#打包位docker镜像并推送到阿里云(项目根目录有Dockerfile文件)
docker build -t registry.cn-shanghai.aliyuncs.com/namespace/${repository}:$(basename $branch).${BUILD_NUMBER} .
docker login -p docker仓库密码 --username=docker仓库账号 registry.cn-shanghai.aliyuncs.com
docker push registry.cn-shanghai.aliyuncs.com/namespace/${repository}:$(basename $branch).${BUILD_NUMBER}
docker logout registry.cn-shanghai.aliyuncs.com
#运行新构建的镜像
containerStr=""
for ((i=1; i<=2; i++)); do
# 保存容器名到变量,nginx配置文件后续会更新为代理新的容器
containerStr+="server "${repository}.${i}.${BUILD_NUMBER}":8000;"
# 容器需与nginx服务器再同一个网络: --network saas-docker-net,只有在同一个网络才可以通过容器名访问,新的容器名为:服务名.容器编号.镜像版本
docker run --name ${repository}.${i}.${BUILD_NUMBER} --restart always --network saas-docker-net -e RUN_ENV=t1 -d registry.cn-shanghai.aliyuncs.com/namespace/${repository}:$(basename $branch).${BUILD_NUMBER}
done
sleep 5
cd ../
# 将新的容器信息写入upstream文件,后续更新nginx 重新读取该文件
tee ${repository}.upstream <<EOF
upstream ${repository} {
${containerStr}
}
EOF
# 将新的容器信息写入location 文件,后续更新nginx 重新读取该文件
tee ${repository}.localhost <<EOF
location ~ ^/${repository}/(.*) {
proxy_pass http://${repository}/\$1;
}
EOF
#移动新的localhost文件和upstream文件到nginx配置文件下
mv -f ${repository}.upstream /var/lib/docker/volumes/nginx_config/_data/conf.d/
mv -f ${repository}.localhost /var/lib/docker/volumes/nginx_config/_data/conf.d/
#更新nginx指向新的容器
docker exec nginx nginx -t
docker exec nginx nginx -s reload
#删除老容器
# 遍历每行输出
docker ps -a|grep ${repository} | while read -r line; do
containerId=$(echo $line | awk '{print $1}')
image=$(echo $line | awk '{print $2}')
if [[ $image == *.${BUILD_NUMBER} ]]; then
echo "当前运行容器,不删除"
else
echo "正在停止并删除服务:"$containerId
docker stop $containerId
docker rm $containerId
echo $containerId" 停止 删除 成功"
fi
done
#删除老镜像
docker images |grep ${repository}|grep $(basename $branch) | while read -r line; do
image=$(echo $line | awk '{print $1}')
tag=$(echo $line | awk '{print $2}')
imageId=$(echo $line | awk '{print $3}')
if [[ $tag == *.${BUILD_NUMBER} ]]; then
echo "当前运行镜像,不删除"
else
echo "正在删除容器:"${image}':'${tag}
docker rmi ${image}':'${tag}
fi
done
#set +x