Jenkins中自由风格回滚方案的最佳实践
上周在 Jenkins 中文社区做了一次关于回滚那些事儿的分享,因为要准备这样一次分享,所以很多以前放下的事情,又都捡拾起来了,瞬间又有了不少奇思妙想出来,这种收获,是不进行这次经历无法获取的,分享常常使我更加成长,尽管分享的东西,已经是自己所熟悉的,熟悉的东西多加巩固,就会有意外收获。
曾经我一直以为,Jenkins 对比所谓 Gitlab 的 cicd,以及 drone 等的发布工具,可能在对发布的历史版本回滚的操作上,有天生不足(前边提到的两者都是直接基于 git 自身的版本控制进行发布,回滚的时候也直接基于历史版本重新发布即可),我一度以为这是 Jenkins 无尽美好中的唯一缺憾。
后来工作中接触到 walle 的部署,当我了解了 walle 的发布思路之后,就像忽然打开了一扇大门一样,忽然觉得 Jenkins 同样可以借鉴这样一个思路来进行发布的工作,于是,在工作之余,我做了五大实验:
- 基于 freestyle 的定制化单机版本发布回滚配置管理
- 基于 pipeline 的定制化单机版本发布回滚配置管理
- 基于 freestyle 结合 ansible 的多主机批量发布回滚配置管理
- 基于 pipeline 结合 ansible 的多主机批量发布回滚配置管理
- 基于 pipeline 结合优化版 ansible 的多主机批量发布回滚配置管理
今天将针对这些方案一一解析与分享,本文将分享 基于freestyle的定制化单机版本发布回滚配置管理
和 基于freestyle结合ansible的多主机批量发布回滚配置管理
这两种。
现在,进入正题。
# 1,思路整理
如果是发布 PHP 项目:
- 同步到远端的版本目录,目录使用
时间 + build id
命名 - 将新的应用版本软链到项目 root 目录
- 确保远程目录只保留五次(可自定义次数)历史
如果是回滚:
- 构建参数当中添加一个回滚 ID,默认情况下,将回滚到上次构建
- 如果输入自定义 ID,则回滚到对应构建
- 支持灵活在五次构建内回滚
这一思路的流程图大概如下:
现在通过实际例子来实现一下上边这个思路,大概将进行四个实验,或者更多,首先是类前端项目发布,静态部署,PHP 项目等都可采用这种方案。
# 2,自由风格单机部署回滚示例
创建一个自由风格的测试项目,核心当然是在参数化的配置,以及脚本的灵活定义,因此这里就展示这两个部分,先看参数化部分配置:
定义三个参数,分别对应上边流程图中的三个节点,首先通过分支 branch 进行下发,接着通过 mode 进行部署或者回滚,如果是回滚,则再针对版本 ID 进行一下判断即可。
脚本内容:
#!/bin/bash
##set color##
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
##set color##
source /etc/profile
project="admin"
remote_port="22"
remote_user="root"
remote_ip="10.3.0.42"
Date=`date "+%Y%m%d%H%M%S"`
project_dir="/data/www/${project}"
version_dir="/release/$project/${Date}_${BUILD_ID}"
cat > keepfive.sh << 'EOF'
file_path="/release/admin"
while true;do
A=`ls ${file_path} | wc -l`
B=`ls -lrX ${file_path} | tail -n 1 | awk '{print $9}'`
if [ $A -gt 5 ];then rm -rf ${file_path}/$B;else break;fi;done
EOF
if [ $mode == "deploy" ];then
echoYellow "创建远程主机上的版本目录 :${version_dir}"
ssh -p $remote_port $remote_user@$remote_ip "mkdir -p ${version_dir}"
[ $? != 0 ] && echoRed "请注意,在创建远程主机上的版本目录时出错,故而退出构建,可联系运维同学处理!" && exit 1
echoYellow "将代码同步到远程主机版本目录"
rsync -az -e "ssh -p $remote_port" --exclude='Jenkinsfile' --exclude='keepfive.sh' --delete ${WORKSPACE}/ $remote_user@$remote_ip:$version_dir/
[ $? != 0 ] && echoRed "请注意,在执行同步代码到版本目录时出错,故而退出构建,可联系运维同学处理!" && exit 1
echoGreen "将代码同步到远程主机版本目录成功!"
echoYellow "将项目部署到生产目录"
ssh -p $remote_port $remote_user@$remote_ip "ln -snf $version_dir $project_dir"
[ $? != 0 ] && echoRed "请注意,在将项目部署到生产目录时出错,故而退出构建,可联系运维同学处理!" && exit 1
echoGreen "将项目部署到生产目录成功!"
echoYellow "使版本目录保持五个版本历史"
ssh -p $remote_port $remote_user@$remote_ip sh < keepfive.sh
[ $? != 0 ] && echoRed "请注意,在执行版本清理时出错,将会影响回滚,故而退出构建,可联系运维同学处理!" && exit 1
echoGreen "执行版本清理成功!"
echoYellow "同步版本号到本地"
[ ! -d /root/.jenkins/version/$project ] && mkdir -p /root/.jenkins/version/$project
ssh -p $remote_port $remote_user@$remote_ip "ls /release/$project" > /root/.jenkins/version/$project/version.log
echoGreen "============"
echoGreen "上线部署完成!"
echoGreen "============"
else
if [ ${version_id} == "0" ];then
echoYellow "选择回滚的版本是默认,将回滚到上次制品,回滚即将进行..."
Version="/release/$project/`tail -n2 /root/.jenkins/version/$project/version.log | head -n1`"
ssh -p $remote_port $remote_user@$remote_ip "ln -snf $Version $project_dir"
[ $? != 0 ] && echoRed "请注意,在执行回滚时出错,故而退出构建,可立即联系运维同学处理!" && exit 1
echoGreen "=============="
echoGreen "项目已回滚完成!"
echoGreen "=============="
else
echoYellow "选择回滚的版本是:${version_id},将回滚到 ${version_id} 的制品,回滚即将进行..."
Version="/release/$project/`grep "_$version_id" /root/.jenkins/version/$project/version.log`"
ssh -p $remote_port $remote_user@$remote_ip "ln -snf $Version $project_dir"
[ $? != 0 ] && echoRed "请注意,在执行回滚时出错,故而退出构建,可立即联系运维同学处理!" && exit 1
echoGreen "项目回滚到 ${version_id} 完成!"
fi
fi
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
核心部分也就是脚本内容了,以上即是脚本内容,采用的逻辑也都比较容易理解,只要读一下就能理解,可用于生产,可能只需要调整一下几个变量,或者路径就可以直接投入使用。
# 3,自由风格结合 ansible 多机批量部署回滚示例
自由风格之下,同样首先创建对应的参数用于传递,然后这里又新增了一个参数用于传递远程主机,以实现单机或者批量发布都可的功能。
依旧是先定于参数如下:
然后定义脚本如下:
#!/bin/bash
##set color##
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
##set color##
source /etc/profile
project="tale"
remote_port="22"
remote_user="root"
project_dir="/data/www/${project}"
version_dir="/release/$project/${Date}_${BUILD_ID}"
cat > keepfive.sh << 'EOF'
file_path="/release/tale"
while true;do
A=`ls ${file_path} | wc -l`
B=`ls -lrX ${file_path} | tail -n 1 | awk '{print $9}'`
if [ $A -gt 5 ];then rm -rf ${file_path}/$B;else break;fi;done
EOF
cat > excludefile << EOF
hosts.ini
deploy.yml
Jenkinsfile
keepfive.sh
excludefile
EOF
Rs(){
if [ $remote_ip == "all" ];then
cat > hosts.ini << EOF
[remote]
10.3.9.32 ansible_port=34222
10.3.20.4 ansible_port=34222
EOF
else
cat > hosts.ini << EOF
[remote]
$remote_ip ansible_port=34222
EOF
fi
}
Rb(){
cat > rollback.yml << EOF
---
- hosts: "remote"
remote_user: root
tasks:
- name: "将项目回滚到对应期望的构建"
file: path=/data/www/${project} state=link src=${Version}
EOF
}
Rd(){
cat > deploy.yml << EOF
---
- hosts: "remote"
remote_user: root
tasks:
- name: "创建远程主机上的版本目录"
file: path=/release/${project}/${project}_${BUILD_ID} state=directory
- name: "将代码同步到远程主机版本目录"
synchronize:
src: ${WORKSPACE}/
dest: /release/${project}/${project}_${BUILD_ID}/
rsync_opts: --exclude-from=excludefile
- name: "将项目部署到生产目录"
file: path=/data/www/${project} state=link src=/release/${project}/${project}_${BUILD_ID}
- name: "使版本目录保持五个版本历史"
script: keepfive.sh
- name: "生成远程版本号"
shell: "ls /release/${project} > /release/version.log"
- name: "同步版本号到本地"
synchronize: "src=/release/version.log dest=/root/.jenkins/version/${project}/version.log mode=pull"
EOF
}
if [ $mode == "deploy" ];then
Rs && Rd
ansible-playbook -i hosts.ini deploy.yml
else
if [ ${version_id} == "0" ];then
Rs
echo "选择回滚的版本是默认,将回滚到上次制品,回滚即将进行..."
Version="/release/$project/`tail -n2 /root/.jenkins/version/$project/version.log | head -n1`"
Rb
ansible-playbook -i hosts.ini rollback.yml
echo "=============="
echo "项目已回滚完成!"
echo "=============="
else
Rs
echo "选择回滚的版本是:${version_id},将回滚到 ${version_id} 的制品,回滚即将进行..."
Version="/release/$project/`grep "_$version_id" /root/.jenkins/version/$project/version.log`"
Rb
ansible-playbook -i hosts.ini rollback.yml
echo "项目回滚到 ${version_id} 完成!"
fi
fi
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
这里把所有内容都集成到一个脚本,便于维护管理。这是我现在比较推崇的一种配置习惯,日常使用过程中,可能我们经常需要调试一些东西,如果一个构建调用的脚本要放好几个地方,那么维护起来是非常痛苦的,莫不如直接全部都集成在 Jenkins 当中,任何时候需要排查某个项目,直接到 Jenkins 中打开配置查看即可,而不需要再找几个地方。
如上两种方式都是紧贴合文章开头的流程图所为,也就几个大佬口中简单的 if/else 而已,但是就已经足够满足并实现我们的需求了。