基於容器服務的持續集成與雲端交付(四)- 多種發布方式
前言
哲學有各種各樣的流派,百家爭鳴,但是隻有一個哲學問題是嚴肅的,那就是生與死。而雲端交付過程中也隻有三個問題是嚴肅的。
- 如何重建你的係統 How to recreate your system?
- 如何安全地部署你的係統 How to safely change your system?
- 部署後的問題監控與解決 When something has gone wrong?
在前麵的文章中,我們講述了什麼是雲端交付,如何搭建從零搭建一個持續交付係統,而今天我們要談的是如何安全的部署你的係統,部署這個名詞包含了很多的含義,最簡單的解釋就是如何讓你的程序運行在最終的環境上。但是部署的方式上麵有非常多的最佳實踐。接下來我們來討論下常見的幾種發布方式,以及如何利用容器發布實現最常用的零宕機發布方式藍綠發布。
發布策略
常見的發布策略有藍綠發布、金絲雀發布(灰度發布)、ABTest,在國內的開發者中,對這幾個概念有獨立的理解。藍綠發布通常被大家成為熱部署;金絲雀發布在國內的名頭完全被他的變種發布方式蓋過了,主要是灰度發布與ABTtest,下麵我們來詳細的為大家解釋一下他們之間的異同。
藍綠發布
在發布的過程中用戶無感知服務的重啟,通常情況下是通過新舊版本並存的方式實現,也就是說在發布的流程中,新的版本和舊的版本是相互熱備的,通過切換路由權重的方式(非0即100)實現不同的應用的上線或者下線。



金絲雀發布
通過在線上運行的服務中,新加入少量的新版本的服務,然後從這少量的新版本中快速獲得反饋,根據反饋決定最後的交付形態。
灰度發布
灰度發布是通過切換線上並存版本之間的路由權重,逐步從一個版本切換為另一個版本的過程。雖然馬丁·扶老耳朵大人認為灰度發布與金絲雀發布是等同的,但是在具體的操作和目的上麵個還是有些許差別的。金絲雀發布更傾向於獲取快速的反饋,而灰度發布更傾向於從一個版本到另一個版本平穩的切換。



ABTest
ABTest和灰度發布非常像,但是從發布的目的上,可以簡單的區分灰度發布與ABTest,ABTest側重的是從A版本或者B版本之間的差異,並根據這個結果進行決策。最終選擇一個版本進行部署。因此和灰度發布相比,ABTest更傾向於去決策,和金絲雀發布相比,ABTest在權重和流量的切換上更靈活。

阿裏雲容器服務實現藍綠發布
下麵我們通過一個簡單的例子來演示藍綠發布的流程。假設我們要進行藍綠發布的應用是一個nginx的靜態頁麵,初始的應用模板如下
nginx-v1:
image: 'registry.aliyuncs.com/ringtail/nginx:1.0'
labels:
aliyun.routing.port_80: nginx
restart: always
部署後會有“Welcome to nginx”的成功頁麵提示。如果用戶想變更配置, 可以在操作麵板上選擇“變更配置”,進入信息修改階段,選擇變更的發布策略與新版本服務的配置。

nginx-v2:
image: 'registry.aliyuncs.com/ringtail/nginx:2.0'
labels:
aliyun.routing.coexist: true
aliyun.routing.port_80: nginx
restart: always
在藍綠發布中,新版本與舊版本不能共用同一個名字;如果共享同一個路由地址,那麼需要添加aliyun.routing.coexist的label,這個label的含義是,當前的服務與其他服務共享路由地址,在藍綠發布的場景中,為了保證應用的零宕機切換,新版本的服務的路由權重默認為0,需要通過路由管理頁麵進行調整,方可進行流量切換。
在進行發布的過程中,會經曆兩個狀態,一個是藍綠發布中,一個是藍綠發布待確認。“藍綠發布中”表示,新版本的服務尚未啟動完成;而“藍綠發布待確認”表示新版本的服務已經啟動完成,此時需要進行發布確認或者發布回滾方可進行下一次發布。其中,新版本的應用和舊版本的應用並存,前者用綠色標明,後者用藍色標明。如果一個服務在前後兩個版本中都存在且沒有變化,那麼會使用黃色標記,表示這個應用在藍綠發布中不會出現任何變化。

選擇“路由列表”之後再點擊“設置服務權重”,調整與之對應的路由權重。如圖所示,舊版本服務的權重為100,新版本服務的權重為0;下麵我們將舊版本服務的權重調整為0,新版本服務的權重調整為100。


由於默認路由服務是進行會話保持的,您可以打開一個新的瀏覽器窗口,訪問新的版本,結果如下。

當整個發布流程驗證完畢後,需要進行發布確認,方可進行下一次發布。
點擊發布確認後,查看應用的詳情,可以看到應用的服務列表已經更新了,舊的服務已經完全下線刪除了。

複雜拓撲下的藍綠發布
場景分析
大於大多數場景而言,對客戶提供服務的軟件的形態有三種。一種是前端類服務,用戶可以直接或者間接通過網頁、接口調用使用該服務提供的能力;一種是後端類服務,用戶無法直接使用該服務提供的功能,該服務主要的使用者是其他服務,並通過其他服務最終將處理後的結果反饋給用戶;第三種是調度任務類服務,即不被用戶使用也不被其他服務調用,它的生命周期隻存在在一個任務的執行生命周期中,通常任務的執行周期完畢,服務的生命周期就停止,通常為無狀態資源密集性服務。
對於上述三種場景,以路由權重切換為主要實現方式的發布策略例如藍綠發布、A/BTest等通常情況下比較適用於前端類服務與後端類服務。下麵我們用一個簡單的例子來拆解下如何使用阿裏雲容器服務來實現這兩類服務的藍綠發布。
在上一篇發布策略的文章中,有的開發者會問我的應用的拓撲關係不是單體的,不能通過單一容器級別的路由權重切換解決。在此要明確的一件事情,藍綠發布是一種發布策略,部署的最小維度是容器,而發布的最小維度是應用。藍綠發布的原理是老的應用的版本不變,新的應用版本進行部署,如果新版本與老版本之間應用的名字以及相關的配置沒有改變,那麼會認為這個應用是新老版本中共用的,無需變更;需要進行變更的應用通過名字的不同進行區分。簡單的來講,藍綠發布是一個應用級別的更新操作,你可以對一個服務進行兩個版本之間的切換,服務是一個邏輯的概念,而不是容器這樣一個實體的概念,藍綠發布可以做複雜拓撲的應用更新操作。
DEMO
下麵我們通過一個拓撲結構複雜一點的例子來講述藍綠發布。應用的拓撲結構如下:

serviceB會調用serviceA,這兩個都是python構建的,代碼如下:
serviceA通過接口返回數據“world!”
from flask import Flask
app = Flask(__name__)
@app.route('/')
def world():
return 'world!'
if __name__ == '__main__':
app.run(port=5000,debug=True,host='0.0.0.0')
serviceB調用serviceA的接口,並將返回的數據字段前麵添加“Hello ”
from flask import Flask
import requests
import os
dep_endpoint = os.getenv('dep_endpoint');
app = Flask(__name__)
@app.route('/')
def hello():
msg = requests.get(dep_endpoint);
return 'Hello ' + msg.text;
if __name__ == '__main__':
app.run(port=5001,debug=True,host='0.0.0.0')
傳統應用之間的調用方式可以是通過配置IP地址或者域名來調用,也可以通過服務注冊中心中的地址的方式調用,但是對於一個無狀態的多實例的服務,常見的做法是使用客戶端的負載均衡器或者服務器的服務均衡器端點來實現。在容器服務中使用的方式是使用服務器端的負載均衡端點的方式,提供內部調用的路由端點,來實現後端服務的負載均衡。大致的調用方式如下。
serviceA-v1:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-a:1.0'
labels:
aliyun.scale: 3
aliyun.routing.port_5000: servicea.local
serviceB-v1:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-b:1.0'
environment:
- 'dep_endpoint=https://servicea.local'
labels:
aliyun.scale: 2
aliyun.routing.port_5001: serviceb
external_links:
- "servicea.local"
serviceB通過external_links的方式將serviceA的內部路由端點引入,在環境變量中將
dep_endpoint=https://servicea.local,使用servicea.local作為調用的地址。
訪問serviceB的對外訪問地址,可以得到:

我們最開始基本結構的應用就已經部署完畢了,下麵開始進行不同服務的藍綠發布。
前端服務的藍綠發布
首先我們先看如果做前端服務的藍綠發布,也就是說要對serviceB進行藍綠發布的流程。大致的結構圖如下。

在這個應用中前端服務的藍綠發布,也就是對serviceB進行藍綠發布,下麵我們修改serviceB的代碼
from flask import Flask
import requests
import os
dep_endpoint = os.getenv('dep_endpoint');
app = Flask(__name__)
@app.route('/')
def hello():
msg = requests.get(dep_endpoint);
return 'Welcome to the ' + msg.text; //ץද"Hello " => "Welcome to the "
if __name__ == '__main__':
app.run(port=5001,debug=True,host='0.0.0.0')
修改編排模板,進行藍綠發布
serviceA-v1:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-a:1.0'
labels:
aliyun.scale: 3
aliyun.routing.port_5000: servicea.local
serviceB-v2:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-b:2.0'
environment:
- 'dep_endpoint=https://servicea.local'
labels:
aliyun.scale: 2
aliyun.routing.port_5001: serviceb
external_links:
- "servicea.local"
進行藍綠發布更新後,可以看到更新後的服務列表,其中黃色的serviceA-v1表示當前的應用在藍綠發布的過程中不會產生變化,serviceB-v1為老版本,serviceB-v2為新版本

通過路由列表選擇相應的權重管理,進行權重的調整,將serviceA-v1的權重調整為0,將serviceA-v2的權重調整為100,此時訪問serviceB的網址可以發現。

驗證完畢,點擊確認發布完成,完成藍綠發布。
後端服務的藍綠發布
我們再看下如何做後端服務的藍綠發布,也就是說對serviceA進行藍綠發布。大致的結構圖如下。

修改編排模板
serviceA-v2:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-a:2.0'
labels:
aliyun.scale: 3
aliyun.routing.coexist: true
aliyun.routing.port_5000: servicea.local
serviceB-v2:
image: 'registry-internal.cn-hangzhou.aliyuncs.com/ringtail/demo-service-b:2.0'
environment:
- 'dep_endpoint=https://servicea.local'
labels:
aliyun.scale: 2
aliyun.routing.coexist: true
aliyun.routing.port_5001: serviceb
external_links:
- "servicea.local"
部署後的服務列表:

此時可以發現serviceB-v2在本次發布中不會進行變更,調整服務的權重

此時再訪問serviceB的地址,可以得到如下的結果

總結
藍綠發布是一種用於升級與更新的發布策略,對於增量升級有比較好的支持,但是對於涉及數據表結構變更等等不可逆轉的升級,並不完全合適用藍綠發布來實現,需要結合一些業務的邏輯以及數據遷移與回滾的策略才可以完全滿足需求,希望給位開發者可以在自己的業務場景中,更靈活的使用和實現藍綠發布。
附錄
代碼倉庫地址:https://github.com/ringtail/BlueGreenDevelopment
個人簡介
莫源,阿裏雲高級研發工程師。在加入阿裏巴巴之前,先後在北京天方地圓科技有限公司、微軟亞洲研究院任職。現主要負責阿裏雲容器服務產品的底層服務發現係統、集群管理係統的研發,從事容器的持續交付、持續集成的方案的設計與實現。在雲計算、分布式係統、圖像識別與虛擬現實方向有多年的開發經驗。
最後更新:2017-07-04 15:32:17