这里所讲的自动化发布是指代码从提交到仓库,到发布到目标服务器的整个过程。

主要涉及到两个工具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设置相应的钩子事件:
基于Gitlab+Jenkins的代码自动化发布
2.在jenkins中配置git插件
基于Gitlab+Jenkins的代码自动化发布

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.实际效果
基于Gitlab+Jenkins的代码自动化发布
开发人员自动化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

动静分离发布:为了满足前端和后端的不同的发布需求,提高发布效率,采用了动静分离发布。纯静态文件直接同步到各服务器,只需几秒钟。动态文件发布则需编译,调用负载均衡,重启tomcat等,需1-2分钟。

代码质量分析: 发布完,sonar自动分析开发人员代码仓库的代码质量,作为后期改进。

代码同步:采用rsync ssh模式进行代码同步到目标服务器

调用负载均衡api:通过python sdk调用腾讯云负载均衡api,来上下线服务器。

日志记录: 每条命令的执行结果进行记录。

定义代码配置中心:一套代码需要放在几个环境中运行,所以定义了不同的配置区分不同的环境, 发布时拷贝相对应的配置文件到目标服务器,这样就达到了只需管理一套代码,运行在不同的环境。

代码回滚:有时发布上去的代码有问题,这时可以紧急回滚,此处提供了按commitid来回滚和按文件回滚两种方式

发布过程中会对网站状态进行判断,如果打开不是200,则不上线。
9.消息通知:采用qqbot机器人自动发消息通知到群,让团队了解发布状态。qqbot采用smartqq协议,由于腾讯已下线,这里可以采用其他机器人插件(如酷Q),原理一样。

更多精彩,关注公众号
基于Gitlab+Jenkins的代码自动化发布