这里所讲的自动化发布是指代码从提交到仓库,到发布到目标服务器的整个过程。
主要涉及到两个工具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
4.实际效果
开发人员自动化git工具
#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
动静分离发布:为了满足前端和后端的不同的发布需求,提高发布效率,采用了动静分离发布。纯静态文件直接同步到各服务器,只需几秒钟。动态文件发布则需编译,调用负载均衡,重启tomca等,需1-2分钟。
代码质量分析: 发布完,sonar自动分析开发人员代码仓库的代码质量,作为后期改进。
代码同步:采用rsync ssh模式进行代码同步到目标服务器
调用负载均衡api:通过python sdk调用腾讯云负载均衡api,来上下线服务器。
日志记录: 每条命令的执行结果进行记录。
定义代码配置中心:一套代码需要放在几个环境中运行,所以定义了不同的配置区分不同的环境, 发布时拷贝相对应的配置文件到目标服务器,这样就达到了只需管理一套代码,运行在不同的环境。
代码回滚:有时发布上去的代码有问题,这时可以紧急回滚,此处提供了按commitid来回滚和按文件回滚两种方式
发布过程中会对网站状态进行判断,如果打开不是200,则不上线。
9.消息通知:采用qqbot机器人自动发消息通知到群,让团队了解发布状态。qqbot采用smartqq协议,由于腾讯已下线,这里可以采用其他机器人插件(如酷Q),原理一样。