應用不停機發布的思考與初識
應用發布,簡單來說就是將已開發完成的系統功能部署到生產環境,并可正常對用戶提供服務。
傳統的應用發布步驟一般采用“三步曲”:
第一步:停止應用
第二步:更新應用
第三步:啟動應用
那你肯定會問,從停止應用一直到啟動應用期間,系統功能是不是無法正常使用?
沒錯。在應用發布過程中,可能會出現頁面白屏、訪問超時等各種異常,而且一般會持續很久,所以發布時間基本上都集中在凌晨,講究點的可能就配上一句友好提示“系統正在維護,請稍后訪問!”。
這種情況大部分都出現在傳統行業,原因可能是覺得沒有必要,因為:
- 傳統行業的業務一般都集中的日間,也就是說凌晨基本沒有業務,或者非重要業務。
- 就算凌晨無法使用這些功能,覺得也沒關系,第二天再來就是了,客戶又不會跑了。
但真的是這樣嗎?如今越來越多的傳統行業,都在向互聯網服務模式轉型,其服務主要特點:“全天”不間斷為客戶提供“優質”的“線上”服務,拆分一下關鍵詞:
- “全天”代表著任何時間
- “優質”代表著客戶體驗
- “線上”代表著線上自助
所以說,一個優質的客戶服務勢必會對服務可用性提出更高的要求,而傳統的停機發布不但會對客戶造成使用影響,還會變向對企業造成非直接經濟損失。
例如:客戶體驗下降導致的客戶流失
僅此而已?非也!作為曾經也同樣是一名運維工程師的我來說,凌晨發布家常便飯,還時不時來一次長達8小時的“長途之旅”,身體就直接被掏空,加上第二天還在“補血”中,還會被各種騷擾電話轟炸,這簡直就是一場噩夢。
由此可見,應用不停機發布的重要性,通過它可以幫助我們:一是在應用發布過程中線上服務持續可用,提升服務可用性,二是可在白天或是任何時間發布,提升運維人員的機動性。
那么我們需要怎么做,才能實現應用不停機發布呢,那接下來就讓我們進入正題。
一
應用不停機發布,從字面上很好理解,就是應用發布過程中服務不中斷。
但仔細回想一下,原先應用發布過程中,反正服務會中斷,那就在應用發布完成后,通過網絡屏蔽外部流量的方式,先進行核心或常用功能的內部驗證,在確保沒有問題后,再解除網絡屏蔽,并對外提供服務。
那現在呢?應用發布后直接對外?不需要進行內部或小流量驗證?想必每個人的答案都有所不同。對此,可以對應用不停機發布能力,簡單定義三個成熟度。
成熟度一級:應用發布過程服務不中斷。
成熟度二級:應用發布過程服務不中斷,單應用功能可先進行內部/小流量驗證。
成熟度三級:應用發布過程服務不中斷,多應用(聯動)功能可先進行內部/小流量驗證。
注:不同的成熟度對技術能力的要求會有所不同,建議通過逐步演進的方式來提升成熟度,并在演進過程中對不同的技術能力進行驗證,從而不斷完善。
有人會說藍綠發布,或有人會說滾動發布,灰度發布就可以實現以上能力。但其實,這些答案并不準確,或者說并不全面,它們雖有交集,但無法涵蓋。
既然提到發布模式,那我們先來簡單比較一下幾種發布模式的優缺點。
注:大部分的技術文章里都會提到,可通過以上任何一種發布模式,做到用戶無感知的應用發布,但在實際情況下,并不完全足夠,還需要通過一些輔助手段組合在一起才能實現。
結合以上發布模式的優缺點,如果你的組織在基礎架構技術能力上已經非常成熟,那么灰度發布一定是最佳之選,但如果還處于初級階段,那可能并不是,而藍綠發布的資源投入較高,也不是一種非常好的選擇。
剩下的只有滾動發布,但滾動發布的發布策略會比較復雜,特別在容器環境下,同一應用多個服務實例的滾動策略還需要單獨來實現。
所以,前期可以選擇一種折中的方式,即:單獨搭建一套預發驗證環境,應用架構需對齊生產環境,但容量方面可以最小化,一般一個應用至少2個服務實例。
這樣,我們就可以在對生產環境做應用發布前,事先對生產預驗證環境進行應用發布,發布后僅對內部用戶可見,驗證通過后,再對生產環境采用全量應用發布。
二
應用不停機發布是一項綜合性能力,當明確好一種發布模式后,就需要逐步識別會涉及到哪些技術組件,以及明確技術組件在整個解決方案中所擔任的職責邊界,從而使它們能夠相互協同工作。
如下列舉了一些主要的技術組件:
- 應用管理平臺
主要負責控制整個應用發布流程,以及集成并調度不同的技術組件,協同完成應用不停機發布。
- 負載均衡(4層)
主要負責服務請求的流量接入,可根據所識別的流量特征,負載分發到不同的負載均衡(7層)。
- 負載均衡(7層)
主要負責服務請求的流量路由,可根據所識別的流量特征,路由分發到同一應用的不同實例。
- 容器/虛擬機平臺
主要負責容器/虛擬機資源管理,包括容器/虛擬機的創建、更新、銷毀等。
- 域名解析系統
主要負責域名地址管理,包括域名的注冊、更新、解析等,以及提供用戶訪問應用或應用間訪問等尋址能力。
- 注冊中心
主要負責服務資源管理,包括服務的注冊、更新、注銷等,以及提供服務請求方發現服務提供方的能力。
在明確技術組件后,還需要對技術組件進行合理的架構規劃,以適配不同的網絡架構要求。
本次主要將對負載均衡進行特別說明,一方面它是負責處理流量的核心技術組件,另一方面網絡架構的不同,對它的部署架構影響可能也是最大的。
在傳統的網絡架構環境中,出于對網絡安全或其他考慮因素,通常會劃分出多個不同的網絡區域,網絡區域間的訪問需要通過開設防火墻訪問策略才可以進行互通。
但這種方式必然會對應用服務間直接進行點對點訪問的方式造成影響,主要原因是虛擬機或容器環境中,應用的IP地址可能會發生變化,導致無法提前明確防火墻訪問策略。
所以,一般都會考慮使用負載均衡(代理模式)來解決這個問題。
建議前期優先選擇方案三,雖然鏈路較長會小幅影響性能,但此部署方案相對較為成熟,一方面可以避免流量負載不均的問題,另一方面對于應用的改造成本也會相對較低。
注:除網絡區域隔離會遇到這種情況外,在某些網絡架構中,不同的容器集群間也同樣無法訪問,所以也同樣適用。
另外,除必要情況下,也應盡量減少跨網絡區域或跨容器集群間的訪問,例如:優先容器集群內路由訪問,跨網絡區域頻繁交互的應用服務建議遷移至同一網絡區域等。
負載均衡通??梢苑譃?層模式和7層模式,其中4層更關注流量負載,而7層更關注流量路由。
一般建議將負載均衡(4層)和負載均衡(7層)進行分層部署,以充分發揮它們的強項。
建議前期優先選擇方案二,雖然無法實現多容器集群間的全局流量調度,但對于當前可觀測性和排障能力還不夠健全的組織,通過物理隔離降低運維難度也是一種不錯的選擇。
綜上架構決策,最終的全局流量鏈路大致如下,默認情況下,數據中心間流量隔離,即:單數據中心流量收斂,但可根據實際情況進行選擇性放行。
三
應用不停機發布的基礎是服務路由,在這里,我們可以把應用服務分為兩種角色,服務請求方或服務提供方,而服務請求方通過不同的尋址方式,最終訪問到服務提供方的形式,可以稱之為服務路由。
服務路由可以分為南北向和東西向。
- 南北向:服務請求方—>負載均衡(4層)—>負載均衡(7層)—>服務提供方
- 東西向:服務請求方—>服務提供方
不難發現,其主要區別就是,南北向服務請求方需借助負載均衡(代理模式)訪問服務提供方,而東西向服務請求方直接訪問服務提供方。
注:域名解析結果會緩存在本地,可緩解域名解析服務壓力,但緩存時間應根據不同的服務水平進行評估,否則將會延長解析地址切換及故障轉移時長。有條件的話建議采用httpdns來解決本地緩存問題。
但不管是南北向還是東西向,服務提供方在被訪問前,得讓服務請求方感知或可見,常見的方式主要有兩種,一種是不依賴注冊中心的,而另一種則是依賴注冊中心的。
如果當前應用架構上暫未使用微服務框架,即:不依賴注冊中心,服務提供方可以手動或自動將服務在負載均衡上進行服務注冊,服務請求方直接調用負載均衡。
如果當前應用架構上已經使用微服務框架,即:依賴注冊中心,服務提供方可以在注冊中心上進行服務注冊,服務請求方在注冊中心進行服務發現,或者由負載均衡在注冊中心進行服務發現,服務請求方直接調用負載均衡。
注:在多注冊中心的場景下,可通過統一注冊中心完成異構注冊中心的服務發現。
另外,我們還會對同一應用的不同服務實例進行分組,分組策略可根據不同的條件來決定,例如:不同環境、不同版本等。
假設根據不同環境(預生產環境和生產環境)這個條件,可以把部署在預發驗證環境的應用服務分為預發組,把部署在生產環境的應用服務分為線上組。
然后通過配置路由策略,下發到負載均衡或應用服務,就可以實現簡單的服務路由了。
例如:不同分組間默認情況下不允許服務路由,但特殊場景下允許預發組服務路由至線上組。
注:若服務請求方是前端頁面或客戶端,也可以對前端流量也進行分組,例如:根據網絡環境,將接入公司WI-FI網絡環境的流量識別成預發組。
四
應用不停機發布的核心是應用如何優雅停止,光有服務路由可能還不夠,它雖然已經解決了大部分問題,但離成功還差最后一步。
前面我們提到過應用發布要做到用戶無感知,那如果應用發布過程中出現瞬斷或短時間中斷,而用戶又正好在使用,那算不算用戶就感知到了呢?
不過,如果你覺得線上服務中斷5分鐘也可以忍受,那我只能呵呵了。但我相信大部分具備互聯網服務模式的頭部公司,別說發布一次服務停止5分鐘,可能就連5秒鐘也無法忍受。
那我們先來分析一下為什么會出現瞬斷或短時間中斷:
服務請求處理還沒完成,應用就被強行停止(例如:運維必備大招之kill -9 進程),導致處理中的請求無法正常返回。
服務提供方雖然已停止,但未通知服務請求方,服務請求方仍然繼續訪問已停止的服務提供方,導致出現異常。
針對以上兩種情況,還需要分南北向和東西向進行討論。
在南北向,服務請求方與服務提供方間是通過負載均衡進行服務路由,所以,在應用發布時,需要在負載均衡上進行一些特殊處理。
1)在負載均衡上,逐一對應用服務實例進行流量屏蔽,該屏蔽只會影響新的請求,等待服務請求處理完成后,再執行如下操作:
- 容器:銷毀服務實例,創建新版本服務實例
- 虛擬機:更新服務實例,更新后解除屏蔽
注:商業化的負載均衡一般都支持優雅下線/上線。
2)因服務請求方的請求都會先經過負載均衡,而負載均衡的成員節點只要確保至少有1個服務實例存在即可,該場景不需要通知,因此不適用。
在東西向,服務請求方與服務提供方間是直接進行服務路由的,所以,在應用發布時,需要分別在服務請求方和服務提供方進行一些特殊處理,而這些處理方式受限于應用框架,這里先介紹一種常用框架,即:Springboot+Eureka應用框架
- 在服務提供方上實現Shutdown hook,即:等待服務請求處理完成后,再進行應用停止。
- 在服務提供方停止前,需通知所有服務請求方,并在完成通知后再進行應用停止。
其中第2點看上去似乎很簡單,但實際上會比較復雜,對此我做了一些降級,用于權衡收益價值和改造成本。
方式一:最多中斷5秒
step1:依賴spring-boot-starter-actuator組件,暴露/shutdown端點
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
step2:調整Eureka配置參數,該調整會分別增加Eureka客戶端和服務端性能壓力
step3:容器銷毀服務實例或虛擬機更新服務實例前,請求如下地址進行應用服務實例停止
curl -X http://應用服務地址/actuator/shutdown
方式二:不中斷(不通知)
step1:依賴spring-boot-starter-actuator組件,暴露/shutdown和/service-registry端點
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown,service-registry
step2:調整Eureka配置參數,該調整會分別增加Eureka客戶端和服務端性能壓力
step3:容器銷毀服務實例或虛擬機更新服務實例前,請求如下地址進行應用服務實例下線
curl -X http://應用服務地址/actuator/service-registry?status=DOWN
注:此時,服務提供方仍然可以提供服務,但再次獲取服務實例的服務請求方不會再獲取該服務實例。
step4:服務請求方最多等待5秒后會更新服務列表,所以,在完成以上操作后,可以休眠5秒鐘,再請求如下地址停止應用服務實例
curl -X http://應用服務地址/actuator/shutdown
注:eureka.client.registry-fetch-interval-seconds+ribbon.ServerListRefreshInterval=5秒。
方式三:不中斷(通知)
step1:依賴spring-boot-starter-actuator組件,暴露/shutdown和/service-registry端點
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown,service-registry
step2:調整Eureka配置參數,該調整會增加Eureka服務端性能壓力
step3:容器銷毀服務實例或虛擬機更新服務實例前,請求如下地址進行應用服務實例下線
curl -X http://應用服務地址/actuator/service-registry?status=DOWN
注:此時,服務提供方仍然可以提供服務,但再次獲取服務實例的服務請求方不會再獲取該服務實例。
step4:在注冊中心上記錄當前正訂閱該服務實例的服務請求方列表,并根據列表通知它們立即重新獲取最新的服務實例,通知完成后再請求如下地址停止應用服務實例
curl -X http://應用服務地址/actuator/shutdown
以上三種方式,可以結合價值收益和改造成本進行權衡,接受瞬斷的可以選擇方式一,而對技術有極致追求的可以選擇方式三,如果兩個都不是,那就選擇方式二吧。
注:每個應用服務的Eureka配置參數并不一定能夠完全統一,這樣可能就會造成大量配置管理成本的增加,但如果可以統一,那方式二還是不錯的選擇。
五
在具備以上條件能力后,應用發布不停機的基本框架已成型,但這樣應用發布就能實現不停機了?
那有這么簡單,我們在開發上還需要遵循一些規范,但符合這些規范的話,可能會增加我們的一些開發成本。
因此,并不是說每次應用發布都強行需要實現不停機發布,而是應該進行合理的取舍。不過,一個好的系統設計必然能做到既能遵循開發規范,也不會增加太多開發成本。
如下列舉了一些常用的開發規范,實際情況可按需調整,其目的是為了不管是接口更新還是數據庫更新等,都應盡量做到向上兼容。
1)接口
- 允許新增字段,必填字段需要設置缺省值;
- 允許原有字段擴展長度或新增字典值;
- 不允許修改原有字段的語義及格式;
- 不允許刪除原有字段;
- 無法兼容時,應新增接口;
- 接口下線前,需確保無調用方。
2)數據庫
- 允許新增字段,必填字段需要設置缺省值;
- 允許原有字段擴展長度或新增字典值;
- 不允許修改原有字段的語義及格式;
- 不允許刪除原有字段;
- 無法兼容時,應新增表;
- 新老表并存期間,數據統計需聚合處理;
- 老表下線前,需進行數據遷移。
3)消息
- 優先考慮新老格式兼容;
- 無法兼容時,生產者和消費者可共同約定新的主題;
- 若生產者無法約定新的主題,消費者可增加消息分發層進行主題重命名。
4)緩存
優先考慮新老格式兼容;
寫在最后
感謝你可以很耐心的讀到這里,整篇文章主要圍繞應用不停機發布進行了思考,從為什么要做,能帶來什么價值,一直到應該怎么做,要關心哪些方面,進行了簡要說明,主要目的是為了能讓大家,對應用不停機發布有一個大概的框架認識。
實現應用不停機發布的手段,也并非僅有文章中說的那些方式,涉及到的技術組件可能也不止這些,但其解決思路基本都差不多,具體的技術實現方式,可以結合自身的架構環境再進行適配和調整。