前言

发布是持续交付的最后一公里。
传统上,软件的最终发布是个充满压力的过程,需要大量的手工配置、操作和团队配合。为了发布的可靠性,开发人员需要准备详尽的部署文档,然后再把相关信息同步给运维人员执行部署,由运维人员执行一系列个性化的发布脚本,部署完后还需要测试人员做详尽的手工验证。
每个步骤里都有很多需要人为判断和信息沟通的事情,稍有不慎就很会产生人为错误造成系统故障,发布时间和结果都不可预测,发布之后忙活到凌晨,绞尽脑汁想着怎么让刚刚部署的应用程序能够正常工作,最后常常不得不回滚,类似这样的场景都很常见。
要解决这样的痛点,除了在软件研发时采用小步迭代的方式,降低交付复杂度外,一套易用、快速、稳定、容错力强,必要时有能力快速回滚的发布系统必不可少,本文将重点建设微医在发布平台建设过程中的一些优秀实践。

核心特性

整体架构

发布前质量卡点

质量是持续交付的内置特性。在发布这最后一公里,如何通过发布平台自动化做好质量红线的卡点,是发布平台的一个重要特性。
在 Pipeline 流水线中,开发代码提交后自动触发单元测试,、静态代码扫描、安全测试、集成测试, 构成了开发和测试共同参与的一套流水线。
发布卡点是用于保障交互质量的重要手段,为了达到持续交付的目标,我们把研发 pipeline 执行结果作为质量红线(也可以增加人工的发布检查表结果),以此方式来保障整个持续交付的顺利进行。

常用的自动化质量卡点策略:

发布中质量监控

为保障系统的稳定性,我们每个应用在监控平台都配置有对应的拨测监控点。
如何减少发布时的告警误报,或者避免发布过程中出现重大故障,这里需要发布平台和监控平台配合做一系列精密的控制策略。

发布后质效度量

质效度量是研发协作平台的一个重要组成部分,主要质效指标将按照研发质量、研发效率、研发成本三方面进行细分。

其中在发布过程中产生的数据,将会输送给质效度量系统进行质效分析,重点包括

所有团队和研发成员可以结合这些发布数据指标,发现自己存在的问题和短板,并进行有效改进。

分批发布

分批发布是批次进行应用部署,每次仅对应用的一部分实例进行升级。分批发布过程中如果出现故障,则终止回退,待问题修复后重新发布。
这里我们采用了比较简洁的批次分配算法,因为公司目前使用的是双机房 IDC,当应用进行分批发布时,首批发布会在两个机房中随机各选择一个实例执行,其他的实例则放到了第二批发布。

选择发布暂停,则可在首批发布完后暂停发布,等人工确认首批发布的实例没有问题后,再执行后续其他实例的发布,如此可有效保障发布的稳定性。
发布过程中,可在发布平台中实时查看运行日志,若发现问题,可随时执行暂停、取消或者回滚等操作。

每个实例进行部署时,需要保证没有请求会派发到该实例,否则用户就会看到 502 的错误。所以需要有一个 “下线” 的操作,把当前机器从负载均衡中摘除,然后在部署完成之后,再把自己挂回到负载均衡中,这个过程称为 “上线”。
为了实现该目的,可基于 OpenResty 自研 Nginx 网关对实例上下线进行实时调度,基于 OpenResty 的 Nginx 网关的实现过程比较复杂,这里不再详尽展开。

问题响应

在发布过程中,如果出现了一些意料之外的情况,发布平台也提供了一些常用的功能,满足开发人员定位和处理问题的需要,同时也尽量避免开发人员直接登录服务器操作。

主要对接了公司统一的日志平台系统,可实时查看应用日志,并且聚合了多实例的日志信息,减少几个实例不停切换寻找问题的痛苦。

某个实例故障时,可快速重启或停用实例。

每个发布的版本发布平台都会有备份,当发布新版本发现问题时,可快速回滚到历史版本

Jenkins Pipeline

在整套发布平台中,Jenkins Pipeline 提供了核心的构建、打包、部署以及分布式调度的底层基础能力,只不过为了更灵活的调度发布操作、管理应用与发布任务之间关系等,我们摒弃了 Jenkins 自身的 UI 界面,而通过发布平台调用 Jenkins API 的方式将其定位为基础引擎。
其中 Jenkins Pipeline 的共享库特性,让我们通过 groovy 编程的方式,很好的实现了发布脚本的版本管理,再也不用发愁怎么管理那堆凌乱的 shell 脚本了。
这里只截取一部分结构代码,Jenkins 共享库的具体使用可参见之前的系列文章。

import groovy.json.JsonSlurper
def call(Map map) {
    pipeline {
        agent any
        parameters {          
            //java应用参数
            string(name: 'BUILD_TOOL', defaultValue: 'maven', description: '构建工具')
            string(name: 'MAVEN_VERSION', defaultValue: 'maven3', description: 'maven构建工具版本')
            string(name: 'GRADLE_VERSION', defaultValue: 'Gradle3.3', description: 'gradle构建工具版本')
            string(name: 'WAR_RELATIVE_PATH', defaultValue: '', description: 'war包地址')
            string(name: 'WAR_STD_NAME', defaultValue: '', description: 'war包地址')
            string(name: 'POM_RELATIVE_PATH', defaultValue: '/pom.xml', description: 'pom文件地址')
            string(name: 'HAS_TEMPLATES', defaultValue: 'false', description: '是否有模板文件')
            string(name: 'TEMPLATES_RELATIVE_PATH', defaultValue: '', description: '模板文件路径')
            string(name: 'JETTY_VERSION', defaultValue: '', description: 'jetty版本')
            string(name: 'GRADLE_TASK', defaultValue: 'war', description: 'gradleTask打包方式')
            ......
        }
        tools {
            gradle "${params.GRADLE_VERSION}"
            jdk "${params.LANGUAGE_VERSION}"
            maven "${params.MAVEN_VERSION}"
        }
        stages {
            stage('部署正式环境') {
                steps {
                    script {
                        def pmap = [:]

                        try {
                            //应用参数传递
                            pmap.put('BUILD_TOOL', BUILD_TOOL.trim())
                            pmap.put('WAR_RELATIVE_PATH', WAR_RELATIVE_PATH.trim())
                            pmap.put('WAR_STD_NAME', WAR_STD_NAME.trim())
                            pmap.put('POM_RELATIVE_PATH', POM_RELATIVE_PATH.trim())
                            pmap.put('HAS_TEMPLATES', HAS_TEMPLATES.trim())
                            pmap.put("TEMPLATES_RELATIVE_PATH", TEMPLATES_RELATIVE_PATH.trim())
                            pmap.put('JETTY_VERSION', JETTY_VERSION.trim())
                            pmap.put('GRADLE_TASK', GRADLE_TASK.trim())
                            ......

                        } catch (MissingPropertyException ex) {
                            println("Catching the MissingPropertyException " + ex.messageWithoutLocationText)
                            ......
                        }
                        pmap = Utils_EnvConfig(pmap)
                        //发布前监控调度
                        Utils_Monitor(pmap.isRestartMonitor,monitorApiDomain,appIpName,monitorTimeOut,true)
                        switch (ACTION) {
                            case "package":
                                java_package(pmap)
                                break
                            case "copy":
                                java_copy(pmap)
                                break
                            case "start":
                                java_start(pmap)
                                break
                            case "rollback":
                                java_rollback(pmap)
                                break
                            case "restart":
                                java_start(pmap)
                                break
                            case "stop":
                                java_start(pmap)
                                break
                            case "kill":
                                java_start(pmap)
                                break
                            case "backup":
                                java_backup(pmap)
                                break
                            default:
                                echo "pipeline do nothing please choose one step (package,copy,start,rollback,restart,stop,kill)"
                        }
                        //发布后监控调度
                        Utils_Monitor(pmap.isRestartMonitor, monitorApiDomain, appIpName, monitorTimeOut, false)
                    }
                }
            }
        }
    }
}

结语

一套好的发布平台可以充当最后的守卫角色,对交付给线上用户的产品进行最后的检查,将未达到要求的软件版本挡于门外,一套完善的自动化发布平台也往往会比制订各类书面上的发布制度更为有效。
这套发布平台从 19 年年初开始重构,从原有一个单纯驱动 shell 脚本操作的发布工具,逐渐进化成内嵌大量质量和效率特性的发布平台,过程收获良多。这里一方面得益于持续交付的先进工程理念,另一方面也是站在了 Jenkins Pipeline 以及内部积累的大量成熟基础设施之上,让我们在开发时事半功倍。
当然这套平台也还有不少可以继续加强的功能,比如灰度发布的能力、基于更多质量指标对发布前后智能分析的能力等等,这些都在规划和进行之中。
中秋节将至,有闲余时间码篇文章分享给大家,祝中秋节快乐吧。

主帖直达:https://testerhome.com/topics/9977


↙↙↙阅读原文可查看相关链接,并与作者交流