天天看點

最後一公裡,你需要一套具備品質思維的釋出平台!

前言

釋出是持續傳遞的最後一公裡。

傳統上,軟體的最終釋出是個充滿壓力的過程,需要大量的手工配置、操作和團隊配合。為了釋出的可靠性,開發人員需要準備詳盡的部署文檔,然後再把相關資訊同步給運維人員執行部署,由運維人員執行一系列個性化的釋出腳本,部署完後還需要測試人員做詳盡的手工驗證。

每個步驟裡都有很多需要人為判斷和資訊溝通的事情,稍有不慎就很會産生人為錯誤造成系統故障,釋出時間和結果都不可預測,釋出之後忙活到淩晨,絞盡腦汁想着怎麼讓剛剛部署的應用程式能夠正常工作,最後常常不得不復原,類似這樣的場景都很常見。

要解決這樣的痛點,除了在軟體研發時采用小步疊代的方式,降低傳遞複雜度外,一套易用、快速、穩定、容錯力強,必要時有能力快速復原的釋出系統必不可少,本文将重點建設微醫在釋出平台建設過程中的一些優秀實踐。

核心特性

  • 覆寫主流應用:

    Java、NodeJS、Python、PHP、Lua、Android、IOS等

  • 支援分批釋出和灰階釋出
  • 支援釋出暫停和恢複
  • 支援曆史版本的快速復原
  • 支援應用重新開機和停止等操作
  • 支援多執行個體應用日志聚合檢視
  • 支援分布式的釋出節點
  • 釋出前品質紅線卡點
  • 釋出中實時監控分析
  • 釋出後質效度量

整體架構

最後一公裡,你需要一套具備品質思維的釋出平台!
  • 典型應用釋出流程:
    • 系統立項後,在WCP-應用管理中申請建立應用,輸入包括開發語言、jdk版本、建構工具、部署方式、産物路徑、代碼位址、應用負責人等應用基礎資訊(應用資訊是所有研發協作的基礎)。
    • 應用建立後,在WCP-資源管理中申請申請資源,在CMDB中自動建立應用和IP/域名之間的關系(包括測試環境、預發環境、生産環境等)。
    • 應用建立後,釋出平台自動生成釋出job,釋出時擷取應用和資源等基礎資訊,可觸發灰階釋出或分批釋出的執行。
    • 釋出操作前,自動執行品質紅線檢查(主要包括pipeline執行結果以及釋出檢查表确認等),品質紅線未達标拒絕釋出。
    • 釋出操作中,自動暫停監控,灰階釋出或首批釋出後,自動觸發監控。

      若監控失敗,停止釋出;

      若監控通過,可繼續釋出。

    • 釋出操作後,采集存儲釋出資料,輸出給質效看闆做釋出資料度量(釋出成功率,釋出頻率,釋出時長等)。
    • 釋出出問題,可執行快速復原等功能,并提供釋出日志以及應用聚合日志的查詢,以快速定位問題。
  • 釋出平台界面:
最後一公裡,你需要一套具備品質思維的釋出平台!

釋出前品質卡點

品質是持續傳遞的内置特性。在釋出這最後一公裡,如何通過釋出平台自動化做好品質紅線的卡點,是釋出平台的一個重要特性。

在Pipeline流水線中,開發代碼送出後自動觸發單元測試,、靜态代碼掃描、安全測試、內建測試, 構成了開發和測試共同參與的一套流水線。

釋出卡點是用于保障互動品質的重要手段,為了達到持續傳遞的目标,我們把研發pipeline執行結果作為品質紅線(也可以增加人工的釋出檢查表結果),以此方式來保障整個持續傳遞的順利進行。

最後一公裡,你需要一套具備品質思維的釋出平台!

常用的自動化品質卡點政策:

  • 研發流水線狀态
    • 單元測試結果
    • 單元測試覆寫率
    • 代碼靜态檢查
    • 內建測試結果
    • 安全掃描結果
  • 釋出計劃狀态(釋出計劃管理系統)
    • 釋出時間視窗
    • 釋出評審結果等

釋出中品質監控

為保障系統的穩定性,我們每個應用在監控平台都配置有對應的撥測監控點。

如何減少釋出時的告警誤報,或者避免釋出過程中出現重大故障,這裡需要釋出平台和監控平台配合做一系列精密的控制政策。

  • 釋出場景1:
    • 描述:

      釋出新版本,在重新開機某個執行個體服務時,有一定的機率會觸發該執行個體的應用報警,對應用開發人員會造成一些不必要的幹擾。

    • 政策:

      釋出平台在将應用編譯打包好,執行正式釋出之前,調用監控平台API停止該應用下的監控任務,在釋出完後同樣調用監控平台API啟用該應用下的監控任務。

  • 釋出場景2:
    • 描述:

      釋出時應用執行個體因為各種原因(如代碼部署出錯,新版本存在明顯BUG等),出現了系統故障。

    • 政策:

      采用分批釋出政策,各個執行個體釋出完後立即觸發該執行個體的監控,如果監控發現異常,辨別該批次釋出操作失敗,并強制中止後續批次的釋出操作,以避免更多的執行個體出現問題。

  • 邏輯流程
最後一公裡,你需要一套具備品質思維的釋出平台!

這裡需要強調的是,撥測監控覆寫率在微醫會作為團隊的重要名額。因為應用隻有在配置監控點後,釋出平台才能在釋出過程中進行有效的監測和幹預。

釋出後質效度量

質效度量是研發協作平台的一個重要組成部分,主要質效名額将按照研發品質、研發效率、研發成本三方面進行細分。

最後一公裡,你需要一套具備品質思維的釋出平台!

其中在釋出過程中産生的資料,将會輸送給質效度量系統進行質效分析,重點包括:

  • 釋出頻率
  • 釋出時長
  • 釋出成功率等

所有團隊和研發成員可以結合這些釋出資料名額,發現自己存在的問題和短闆,并進行有效改進。

分批釋出

分批釋出是批次進行應用部署,每次僅對應用的一部分執行個體進行更新。分批釋出過程中如果出現故障,則終止回退,待問題修複後重新釋出。

這裡我們采用了比較簡潔的批次配置設定算法,因為公司目前使用的是雙機房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://www.cnblogs.com/cay83/p/11512792.html