这里所讲的自动化发布是指代码从提交到仓库,到发布到目标服务器的整个过程。
主要涉及到两个工具Gitlab,Jenkins,要完成自动化还需要rsync,qqbot,log,ant、shell脚本,python等。
Gitlab:我们主要用它来做代码的仓库
Jenkins:用来执行任务的持续集成,构建等。
一、大体的自动化思路:
开发人员push代码到gitlab,触发webhook,调用jenkins job。 jenkins job 执行拉取代码,编译,调用loadblance,下架部分服务器更新代码,验证更新后的可用性,上线;再下架另一部分服务器,更新代码,再上线。 更新完后,将本次发布的状态信息推送给项目组。
二、实际工作中,我们遇到的比以上要复杂。
服务器环境包括:测试环境,开发环境,预发布环境,生产环境等。 代码仓库又分为多个分支:master分支,开发分支,项目分支,本地分支等。
因此要完成整个过程自动化,还需要整合多分支,多环境的情况。
三、测试环境的自动化思路:
1.建一个dev分支用来专门发布测试环境,此分支只允许开发人员合并代码和push,不能直接在上面改代码。 2.开发人员开发一个功能,先在本地建一个本地分支,写完后合并到dev,然后push到gitlab,gitlab触发钩子事件,调用jenkins完成项目的自动化部署。
以上2点看似已经实现了自动化发布,但实践发现,开发人员仍需花不少时间在代码的提交 ,切换分支,合并分支,push代码等重复而繁琐的工作上。于是这里我做了对GIT操作的自动化,将提交、切换、合并、push整合整合起来成工具,后面会列出工具代码。
四、预发布环境的自动化思路:
1.预发布的自动化采用和测试环境一样的思路,只是dev分支换成了master分支。 2.master分支:我们用它来发布预发布和生产环境,对没错两套环境用同一分支,此分支只有项目经理有权限push,普通开发没权限操作。
以上2点看似也实现了代码的自动化发布,但实际工作中项目经理同样也要花不少时间在代码的提交 ,切换分支,合并分支,push代码等重复繁琐的工作,因此这里也要解决项目经理Git操作的自动化,后面列出工具。
五、生产环境的自动化发布:
生产环境的发布其实只是取消了webhook的自动触发jenkins job,改为手动点击发布,主要是为发布安全考虑。
六、实操:
1.设置webhook,对测试环境job和预发布环境job设置相应的钩子事件:
2.在jenkins中配置git插件
3.配置jenkins job,这里我用shell脚本做一系列的job,不需要像网上安装各种繁琐的插件。
#!/bin/bash
#本案例中WORKSPACE=/root/.jenkins/workspace/dev_test
#源目录
src="$WORKSPACE/WebRoot/"
#发布的目标目录
dest="/usr/local/apache-tomcat-6.0.39/webapps/xiangmu/"
#目标主机用户
user="root"
#目标主机,测试机1、测试机2
host1="10.111.111.1"
host2="10.111.111.2"
# 需要发布的文件列表
cd $WORKSPACE
change_file_list=`git diff --name-only HEAD~ HEAD`
echo "需要发布的文件列表:$change_file_list"
#定义机器人发布消息功能函数
function qqbot_deploy(){
proj_name=`git show $commitid --pretty=format:"%s" |sed -n 1p`
author=`git show $commitid --pretty=format:"%an" |sed -n 1p`
now_time=`date "+%Y-%m-%d %H:%M:%S"`
qq send group IT群 "
QQ消息机器人:
【测试环境】 发布时间:$now_time 自动化发布完成
项目:$proj_name 状态:$status $solution
发布的文件列表:$change_file_list
发布人:$author" &
}
# 定义执行命令成功或失败日志记录功能函数
function log(){
if [ $? -eq 0 ];then
echo "执行'$arg'成功"
else
echo "执行'$arg'失败"
status="发布失败"
qqbot_deploy
exit 1
fi
}
# 定义java编译功能函数
function ant_shell(){
#jdk需要基于1.7,ant低于1.9编译
cd $WORKSPACE
arg="编译"
#ant >/dev/null 2>&1
ant
log
# 一套代码,定义配置文件中心,拷贝不同的配置文件到不同的环境。这里拷贝测试环境配置文件到测试环境
\cp -rf conf/xiangmu/dev/system_dev.properties WebRoot/WEB-INF/classes/system.properties
\cp -rf conf/xiangmu/dev/ApplicationContext_dev.xml WebRoot/WEB-INF/classes/spring/ApplicationContext.xml
\cp -rf conf/xiangmu/dev//ApplicationContext-service_dev.xml WebRoot/WEB-INF/classes/spring/ApplicationContext-service.xml
}
# 定义java发布功能函数
function deploy_java(){
#1.编译
ant_shell
#2.对e互助测试1的操作
#2.1修改测试机1的负载均衡的值为0
arg="修改负载均衡的值"
python ModifyLoadBalancerBackends_test1_value0.py
log
sleep 3
#2.2发布测试机1的代码,rsync安静模式同步
arg="发布$host1代码"
rsync -e 'ssh -o stricthostkeychecking=no -p22' -qapgolr --progress --delete $src $user@$host1:$dest
log
#2.3重启测试机1的tomcat
ssh $host1 /home/tomcat/ver/restart_tomcat.sh&
sleep 18
status_code1=`curl -I -m 10 -o /dev/null -s -w %{http_code} http://$host1/planweb/index.do`
if [ $status_code1 -eq 200 ]
then
echo "http://$host1/planweb/index.do 打开正常"
else
echo "http://$host1/planweb/index.do 打开失败"
#发布失败通知消息到qq群
solution="原因和方案:可能tomcat启动时间过长的误报,延长sleep时间,重新发布一次"
status="发布失败"
qqbot_deploy
exit 1
fi
#3.上线测试机1,并下线测试机2,修改测试机1的负载均衡值为10,修改测试机2的值为0
arg="修改负载均衡的值"
python ModifyLoadBalancerBackends_test1_value10.py
log
#4.对测试机2的操作
#4.1发布代码到测试机2
arg="发布$host2代码"
rsync -e 'ssh -o stricthostkeychecking=no -p22' -aqpgolr --progress --delete $src $user@$host2:$dest
log
#4.2重启测试机2的tomcat
ssh $host2 /home/tomcat/ver/restart_tomcat.sh&
sleep 22
status_code2=`curl -I -m 10 -o /dev/null -s -w %{http_code} http://$host2/planweb/index.do`
if [ $status_code2 -eq 200 ]
then
echo "http://test.xiangmu.com 打开正常"
#4.3上线测试机2,修改测试1的值为10,全部上线
arg="修改负载均衡的值"
python ModifyLoadBalancerBackends_test2_value10.py
log
echo "测试机$host1,$host2上线java代码完成"
#动态文件,发布成功消息通知到qq群
status="发布成功"
qqbot_deploy
#退出循环
exit 0
else
echo "http://test.xiangmu.com打开失败"
#发布失败通知消息到qq群
status="发布失败"
solution="原因和方案:可能tomcat启动时间过长,重置clb,查看tomcat启动log"
qqbot_deploy
exit 1
fi
}
# 定义静态文件发布功能函数,无需重启tomcat
function deploy_static(){
arg="发布$host1的静态代码"
rsync -e 'ssh -o stricthostkeychecking=no -p22' -qapgolr --progress --delete $src $user@$host1:$dest
log
arg="发布$host2的静态代码"
rsync -e 'ssh -o stricthostkeychecking=no -p22' -qapgolr --progress --delete $src $user@$host2:$dest
log
}
#异步执行:代码质量分析。
function code_quality_analysis(){
if [ ! -f "sonar-project.properties" ];then
echo -e "sonar.projectKey=dev_test \nsonar.host.url=http://localhost:9000/sonar \nsonar.projectName=dev_test \nsonar.projectVersion=1.0 \nsonar.sources=src \nsonar.java.binaries=build/WEB-INF/classes" >sonar-project.properties
fi
BUILD_ID=
/usr/local/sonar-scanner/bin/sonar-scanner &
echo "开始异步运行代码质量分析"
}
#定义代码发布功能函数
function deploy(){
#标记静态文件出现的次数,解决遇到静态文件重复同步多次的问题
count=0
#遍历发布的文件列表
for i in $change_file_list;
do
# 遇到有java等后缀需要编译的文件,则编译发布。
if [ "${i##*.}"x = "java"x ] || [ "${i##*.}"x = "xml"x ] || [ "${i##*.}"x = "properties"x ];then
#发布编译的代码
deploy_java
# 如果是前端静态文件,则无需编译直接发布。第一次遇到静态发布一次,再遇到静态则不发布。
elif [ ! "${i##*.}"x = "java"x ] || [ ! "${i##*.}"x = "xml"x ] || [ ! "${i##*.}"x = "properties"x ] && [ $count -eq 0 ];then
#发布静态文件
deploy_static
#标记为1,记已同步一次代码
count+=1
fi
done
#全静态文件,发送消息通知到qq群
status="发布成功"
echo "全静态文件发布完成"
qqbot_deploy
}
#紧急回滚措施,只是发布回滚,实际git上面没回滚,事后得修改原先bug重新提交发布。
function rollback(){
cd ${WORKSPACE}
echo "commitid:$commitid"
if [ ! $commitid ] && [ ! $file ];then
echo "commitid和file至少填一个"
exit 1
elif [ ! $commitid ] && [ $file ];then
#回滚某个文件到上一次的更改
num=2
commitid=`git log -n $num --pretty=format:"%H" $file |sed -n ${num}p`
git checkout $commitid $file
deploy
else
arg="回滚"
# 回滚到指定的commit版本,或者回滚指定版本的某个文件
# git log WebRoot/campaign/daily/share.js 可以查看share.js最近修改的记录
git checkout $commitid $file
log
#发布回滚的版本
deploy
fi
}
case $select in
Deploy)
echo "select:$select"
commitid=`git rev-parse remotes/origin/dev`
#发布
deploy
;;
Rollback)
echo "select:$select"
#回滚
rollback
;;
*)
echo "*select:$select"
commitid=`git rev-parse remotes/origin/dev`
deploy
;;
esac
#coding:utf-8
#author:laocao
#date: 20181225
#测试环境运行在python3.6 windows上
import os
from time import sleep
code_dir = "D:\\proj\\xiangmu"
#code_dir = "D:\git\xiangmu-git"
print("------------提×××并本地分支的代码到dev----------")
print("注意:本程序运行的的前提,代码必须在D:\proj\xiangmu中")
print("")
print("")
os.chdir(code_dir)
print("当前工作目录:" + os.getcwd())
#输入分支名称,拉取远端dev最新代码到本地
my_branch=input('请输入您本地的分支名称:')
os.system("git checkout %s" % my_branch)
os.system("git pull origin dev")
#输入项目描述,并提交到本地
desc=input('请输入项目描述:')
os.system("git add -A")
os.system("git commit -am '%s'" % desc)
# 切换到dev分支,拉取dev最新代码
os.system("git checkout dev")
os.system("git pull")
#合并本地分支到dev,并推送
os.system("git merge %s" % my_branch)
os.system("git push")
#返回本地分支
os.system("git checkout %s" % my_branch)
print("提交成功,返回本地分支%s" % my_branch)
input("按任意键退出")
6.项目经理自动化git工具,根据commitid合并
#coding:utf-8
#author:jorden
#date: 20181225
#测试环境运行在python3.6 windows上
import os
from time import sleep
code_dir = "D:\\proj\\xiangmu"
#code_dir = "D:\git\xiangmu-git"
print("------------合并dev的代码到master----------")
print("注意:本程序运行的的前提,代码必须在D:\proj\xiangmu中")
print("")
print("")
os.chdir(code_dir)
print("当前工作目录:" + os.getcwd())
# 切换到dev分支,拉取dev最新代码
os.system("git checkout dev")
os.system("git pull")
# 切换到master分支,拉取master最新代码
os.system("git checkout master")
os.system("git pull")
dev_commitid=input('请输入dev中需要合并的commitid:')
print(dev_commitid)
os.system("git cherry-pick " + dev_commitid)
print("dev_commitid: %s" % dev_commitid)
os.system("git push")
print("git自动合并完成")
input("按任意键退出")
7.项目经理自动化工具,根据文件合并版本。
#coding:utf-8
#author:laocao
#date: 20181225
#测试环境运行在python3.6 windows上
import os
from time import sleep
code_dir = "D:\\proj\\xiangmu"
#code_dir = "D:\git\xiangmu-git"
print("------------合并dev的代码到master,按文件合并----------")
print("注意:本程序运行的的前提,代码必须在D:\proj\xiangmu中")
print("")
print("")
os.chdir(code_dir)
print("当前工作目录:" + os.getcwd())
# 切换到dev分支,拉取dev最新代码
os.system("git checkout dev")
os.system("git pull")
# 切换到master分支,拉取master最新代码
os.system("git checkout master")
os.system("git pull")
#dev_commitid=input('请输入dev中需要合并的commitid:')
file_list = input('请输入dev中需要合并的文件列表:')
os.system("git checkout dev " + file_list)
print("file_list: %s" % file_list)
#输入项目描述,并提交到本地master
desc=input('请输入项目描述:')
os.system("git add -A")
os.system("git commit -am '%s'" % desc)
os.system("git push")
print("git自动合并完成")
input("按任意键退出")
8.脚本完成了如下功能:
编译:根据实际项目,这里用的ant。也可以maven
动静分离发布:为了满足前端和后端的不同的发布需求,提高发布效率,采用了动静分离发布。纯静态文件直接同步到各服务器,只需几秒钟。动态文件发布则需编译,调用负载均衡,重启tomcat等,需1-2分钟。
代码质量分析: 发布完,sonar自动分析开发人员代码仓库的代码质量,作为后期改进。
代码同步:采用rsync ssh模式进行代码同步到目标服务器
调用负载均衡api:通过python sdk调用腾讯云负载均衡api,来上下线服务器。
日志记录: 每条命令的执行结果进行记录。
定义代码配置中心:一套代码需要放在几个环境中运行,所以定义了不同的配置区分不同的环境, 发布时拷贝相对应的配置文件到目标服务器,这样就达到了只需管理一套代码,运行在不同的环境。
代码回滚:有时发布上去的代码有问题,这时可以紧急回滚,此处提供了按commitid来回滚和按文件回滚两种方式
发布过程中会对网站状态进行判断,如果打开不是200,则不上线。
9.消息通知:采用qqbot机器人自动发消息通知到群,让团队了解发布状态。qqbot采用smartqq协议,由于腾讯已下线,这里可以采用其他机器人插件(如酷Q),原理一样。