一“脚”到位-淋漓尽致的自动化部署


文/洪晓欢

1 利用自动部署平台遇到的困境

自从使用了自动部署平台,环境部署变得简单很多,在尝到甜头的同时也有问题随之产生。开发修改了代码或修复了bug后告知QA需要部署环境,常用部署方式是:
选择环境-定义部署版本-一键部署-等待环境和实例部署完成。
由于项目较特殊,需要自定义一键部署会覆盖的config文件内容,因此适用于项目的部署方式:
选择环境-定义构建版本-构建版本-等待构建完成-部署实例-等待实例部署完成-到服务器上执行脚本来替换与环境相关的一些配置文件。
可以想象,一次两次的手工触发平台还能忍受,如果一天多次或还需要切换部署分支又或者代码编译不通过,QA的效率会受到较大影响。

于是想到调用自动部署平台现有的API,但手工调用API后还是需要人工干预:如校验是否构建和部署成功、需要更新token。所以进行了以下实践来彻底解放人力进行真正的自动化部署。

几种利用自动部署平台进行部署的对比:

表1 几种利用自动部署平台进行部署的对比

自动部署平台使用方式 是否需要认为检查 能完美结合Jenkins
浏览器点击 不能
手工调用API 不能
下文实践的方式

2 解决问题的思路和工具的选型

2.1 选择收发http请求的方式

调用自动部署平台的API,需要知道如何在服务器内发送请求并获取到返回值,这样才能完成你预期的操作。比较通用的有两种方法: 1)通过编程语言实现 比如Java程序:各自有http请求的相关方法,编写完成后编译执行达到目的,这种方式虽然较为熟悉但过于重量级,执行一个源文件还需要引入依赖包等,最致命的是太笨拙,分支等参数写死在代码中,牵一发而动全身,即使变化的参数写在一个配置文件中,在需要修改如分支的值还要编辑文件,放弃之。 比如Python、Perl:对熟悉编程的人来说Python、Perl这类标准的编程语言可能更快,但shell脚本的学习成本更低,代码也更简约。考虑到仅在linux里实现,遵循“KEEP IT SIMPLE,STUPID”的原则,未采用Python或Perl。 2)使用命令行工具 尝试了两种工具:curl和wget。curl基于libcurl库,这个库是一个稳定的跨平台的类库,任何人可以免费使用其API进行开发,wget仅支持命令行方式运行,没有类库,不提供API,两者都支持http协议,都能抓取网页信息,具体的使用方法请--help。 curl命令调用接口:

图1 curl发送http请求


wget调用接口后的文件:

图2 wget发送http请求


图3 wget返回存入文件


前者直接返回结果:一个json object。后者会生成一个文件,文件内容为请求返回的结果。不选择wget的两个原因:其一,太多的文件生成,每次执行wget就生成一个文件,假设一次完整部署需要调用N个接口,一天需要部署M次,当前文件夹下会产生N*M个文件,当然你可以每次调用前或后删除文件,这又多了一次操作;其二,需要读取文件内容,仅一个json对象没必要花费如此大的精力。

不同收发请求方式的对比:

表2 不同收发请求方式的对比

服务器收发请求方式 是否有依赖包 返回结果的解析成本 学习成本 传参是否灵活
Java程序 较高
curl工具
wget工具 较高

2.2 选择解析json文本的方式

既然已经获取到接口返回,下一步就是对返回值进行提取来作为具体操作的判断,使用Java的json包解析json是非常熟悉和简单的,但抛弃Java的情况会怎样,使用sed、awk解析和拼接?如果只是键和值的简单列表,又有较强的操纵能力,可以尝试,但从开放的API返回值来看,部分具有数组和复杂的对象,硬上的话花费的时间还不如选择编程语言了,json这类具有特定格式的文本不会被linux忽视,jq工具解决了解析的问题。

jq是一个轻量级和灵活的命令行json处理器,由C语言编写并且运行时无依赖,适用于几乎所有平台,你可以把它想象成专门处理json数据的sed命令,用它来切割和筛选、映射和转换结构化数据。更多的jq介绍可访问官网:http://stedolan.github.io/jq/,不仅提供了各种平台能直接运行的二进制文件:

图4 jq工具的多平台支持


还有完整的使用手册:

图5 jq使用手册


除了常用的文本输出格式化、json查询,更有运算、内置函数、条件比较、变量声明、自定义函数等高级功能。

json解析方式的对比:

表3 json解析方式的对比

服务器上解析json方式 学习成本 功能是否强大 复杂度
awk、sed工具 较高
jq工具

注意:debian系统需要下载并安装deb文件,如果没有一定的权限是无法安装的,需要提交任务请SA安装。

3 项目实践


3.1 脚本编写思路


发送请求和解析返回值的方式都已确定,接下来就开始编写shell脚本。整个脚本的设计思路简单清晰:每发送一次请求后获取指定返回值,对比期望值来判断是否符合预期。项目中涉及到几个接口的调用和判断:


获取token—>判断返回码—>指定部署分支—>判断返回码—>构建版本—>判断构建状态—>部署实例—>判断实例状态。


其中构建版本和部署实例需要循环调用,循环体内判断构建和部署状态,一旦成功跳出循环,超过循环次数还未成功视为失败则退出程序。正常情况下构建和部署在一定时间内能成功,不需要循环等待和判断状态,但如果出现偶尔获取源码失败或部署平台本身偶发问题而引起的响应滞后,还是允许多等待几次的。一开始在循环体内判断有异常跳出循环,几次执行后发现异常值无法穷举的,改成了判断成功状态。用户执行脚本需定义一个参数:分支名。


if [ $estatus = "build_succ" ];
then
        DEPLOY_URL1=http://omad.hz.netease.com/api/cli/deploy?token=
        DEPLOY_URL2="&moduleId=真实ID&envId=真实ID&instanceId=真实ID"
        DEPLOY_URL="$DEPLOY_URL1$token$DEPLOY_URL2"        
        DEPLOY_CMD=`curl $DEPLOY_URL`
        deploy_code=`echo $DEPLOY_CMD | jq .code`
        #判断接口返回值
        if [ $deploy_code -eq 200 ];
    then
        sleep 3
        istatus_count=0
        #循环并判断环境状态和实例状态
                while [ $istatus_count -ne 10 ]
                do
                    ISTATUS_URL1=http://omad.hz.netease.com/api/cli/istatus?token=
          ISTATUS_URL2="&envId=真实ID&instanceId=真实ID"
          ISTATUS_URL="$ISTATUS_URL1$token$ISTATUS_URL2"
                    ISTATUS_CMD=`curl $ISTATUS_URL`
                    istatus=`echo $ISTATUS_CMD | jq .status | sed 's/^"//g' | sed 's/\"//g'`
                    ideployStatus=`echo $ISTATUS_CMD | jq .deployStatus | sed 's/^"//g' | sed 's/\"//g'`
                    if [ $istatus != "running" -o $ideployStatus != "success" ];
                    then
                            let istatus_count++
                            sleep 5        
                    else
                            break
                    fi
                done    
        else
                echo "deploy error:$deploy_code"                                
                exit 1
        fi        
else
        echo "estatus error:$estatus"                                
        exit 1                    
fi

脚本调试中遇到的问题多是语法和格式错误,比如使用变量时缺少变量符、if判断的格式书写错误、拼接url出错等。

3.2 结合Jenkins完成代码自动检测和部署


至此,符合现有环境的自动部署脚本完成,接下来就是利用Jenkins进行自动触发。推荐使用参数化构建过程来确定分支名,在一个环境需要切换多个分支时比较灵活和降低输入错误:

图6 Jenkins上配置branch_name参数


图7 Jenkins上配置部署和重启脚本


构建后发现job构建成功,服务器上确实部署成功但没有应用进程启动,脚本单独在服务器上执行并没有问题,询问了大牛和搜索一些资料后知道了还需要设置一个jenkins的环境变量,部署后重启的是后台进程,jenkins每次构建结束会默认kill这类进程。避免进程被杀掉的简单方法就是修改jenkins的环境变量BUILD_ID的值,从而让Jenkins认为此进程不是由Job的构建过程衍生的。官方说明:https://wiki.jenkins-ci.org/display/JENKINS/ProcessTreeKiller

 

图8 设置Jenkins环境变量


符合项目的自动部署操作已集成在Jenkins中,如果能定义部署的触发条件就让“自动”变得更有意义,配置源码管理和构建触发器让Jenkins定时查询分支是否有更新,如果有更新就触发构建的shell进行环境部署:

图9 Jenkins上配置源码管理


一个job完成了根据定义的分支进行代码检测+触发自动部署+自定义脚本。


4 总结

这次实践一定程度上提升了QA的工作效率,部署一次环境再也不用在浏览器、服务器上花费至少3分钟的时间,也节省了开发和QA的沟通成本,开发不用通知QA代码是否更新,在项目频繁有代码提交的时候效果更为突出。


对于自动部署脚本本身还存在一些值得改进的地方,比如适用于多个应用、环境和实例构建部署的情况,这需要编写更复杂的脚本将三种值都作为参数传入。


自动部署只是项目持续集成中的一个环节,自动部署后需要根据不同情况触发冒烟和回归测试,再进行静态代码检查及测试覆盖率统计,这些工作后续都需要完善。

作者介绍


洪晓欢

2011年加入杭研质量保障部,参与了杭研后台SDFS、
DataStream、大数据平台、web易信等的测试,
现在平台二组参与URS的测试,希望尝试更多的测试
工具和不同的测试手段来加强质量保障。


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者洪晓欢授权发布。