發佈是持續交付的最後一千米。
傳統上,軟件的最終發佈是個充滿壓力的過程,須要大量的手工配置、操做和團隊配合。爲了發佈的可靠性,開發人員須要準備詳盡的部署文檔,而後再把相關信息同步給運維人員執行部署,由運維人員執行一系列個性化的發佈腳本,部署完後還須要測試人員作詳盡的手工驗證。
每一個步驟裏都有不少須要人爲判斷和信息溝通的事情,稍有不慎就很會產生人爲錯誤形成系統故障,發佈時間和結果都不可預測,發佈以後忙活到凌晨,絞盡腦汁想着怎麼讓剛剛部署的應用程序可以正常工做,最後經常不得不回滾,相似這樣的場景都很常見。
要解決這樣的痛點,除了在軟件研發時採用小步迭代的方式,下降交付複雜度外,一套易用、快速、穩定、容錯力強,必要時有能力快速回滾的發佈系統必不可少,本文將重點建設微醫在發佈平臺建設過程當中的一些優秀實踐。java
質量是持續交付的內置特性。在發佈這最後一千米,如何經過發佈平臺自動化作好質量紅線的卡點,是發佈平臺的一個重要特性。
在Pipeline流水線中,開發代碼提交後自動觸發單元測試,、靜態代碼掃描、安全測試、集成測試, 構成了開發和測試共同參與的一套流水線。
發佈卡點是用於保障交互質量的重要手段,爲了達到持續交付的目標,咱們把研發pipeline執行結果做爲質量紅線(也能夠增長人工的發佈檢查表結果),以此方式來保障整個持續交付的順利進行。算法
經常使用的自動化質量卡點策略:shell
爲保障系統的穩定性,咱們每一個應用在監控平臺都配置有對應的撥測監控點。
如何減小發布時的告警誤報,或者避免發佈過程當中出現重大故障,這裏須要發佈平臺和監控平臺配合作一系列精密的控制策略。編程
發佈場景2:json
邏輯流程安全
質效度量是研發協做平臺的一個重要組成部分,主要質效指標將按照研發質量、研發效率、研發成本三方面進行細分。服務器
其中在發佈過程當中產生的數據,將會輸送給質效度量系統進行質效分析,重點包括架構
全部團隊和研發成員能夠結合這些發佈數據指標,發現本身存在的問題和短板,並進行有效改進。app
分批發布是批次進行應用部署,每次僅對應用的一部分實例進行升級。分批發布過程當中若是出現故障,則終止回退,待問題修復後從新發布。
這裏咱們採用了比較簡潔的批次分配算法,由於公司目前使用的是雙機房IDC,當應用進行分批發布時,首批發佈會在兩個機房中隨機各選擇一個實例執行,其餘的實例則放到了第二批發布。負載均衡
選擇發佈暫停,則可在首批發布完後暫停發佈,等人工確認首批發布的實例沒有問題後,再執行後續其餘實例的發佈,如此可有效保障發佈的穩定性。
發佈過程當中,可在發佈平臺中實時查看運行日誌,若發現問題,可隨時執行暫停、取消或者回滾等操做。
每一個實例進行部署時,須要保證沒有請求會派發到該實例,不然用戶就會看到502的錯誤。因此須要有一個「下線」的操做,把當前機器從負載均衡中摘除,而後在部署完成以後,再把本身掛回到負載均衡中,這個過程稱爲「上線」。
爲了實現該目的,可基於OpenResty自研Nginx網關對實例上下線進行實時調度,基於 OpenResty 的 Nginx 網關的實現過程比較複雜,這裏再也不詳盡展開。
在發佈過程當中,若是出現了一些意料以外的狀況,發佈平臺也提供了一些經常使用的功能,知足開發人員定位和處理問題的須要,同時也儘可能避免開發人員直接登陸服務器操做。
主要對接了公司統一的日誌平臺系統,可實時查看應用日誌,而且聚合了多實例的日誌信息,減小几個實例不停切換尋找問題的痛苦。
某個實例故障時,可快速重啓或停用實例。
每一個發佈的版本發佈平臺都會有備份,當發佈新版本發現問題時,可快速回滾到歷史版本
在整套發佈平臺中,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'