漫談全鏈路灰度發(fā)布
1概述
在當(dāng)今快節(jié)奏的軟件開發(fā)環(huán)境中,隨著用戶需求的不斷變化和競爭日益激烈,軟件更新和發(fā)布的頻率已成為常態(tài)。然而,與此同時,保證用戶體驗的穩(wěn)定性和可靠性也是至關(guān)重要的。傳統(tǒng)的大規(guī)模軟件發(fā)布往往會面臨著線上故障風(fēng)險,可能導(dǎo)致用戶體驗下降,甚至影響業(yè)務(wù)正常運(yùn)行。
為了解決這一矛盾,在軟件開發(fā)領(lǐng)域催生出了灰度發(fā)布的概念?;叶劝l(fā)布是一種漸進(jìn)式的軟件發(fā)布方式,它允許將新功能或更新逐步推送給一部分用戶,而不是一次性全部發(fā)布。這樣的方式能夠有效降低線上故障的風(fēng)險,保障用戶體驗,同時也為開發(fā)團(tuán)隊提供了更多時間和機(jī)會在全面發(fā)布前進(jìn)行驗證和修復(fù)。
然而,隨著軟件架構(gòu)的演進(jìn),尤其是微服務(wù)架構(gòu)的普及,軟件系統(tǒng)往往由多個微服務(wù)組成,不同服務(wù)的版本升級需要協(xié)調(diào)和同步。在這種背景下,單一服務(wù)的灰度發(fā)布已經(jīng)不能完全適應(yīng)需求,全鏈路灰度發(fā)布應(yīng)運(yùn)而生。
全鏈路灰度發(fā)布考慮到整個軟件系統(tǒng)的多個微服務(wù),允許多個微服務(wù)同時進(jìn)行版本控制和升級,以確保整個系統(tǒng)的平穩(wěn)過渡和穩(wěn)定性,是一種更為全面和細(xì)致的灰度發(fā)布方式。通過全鏈路灰度發(fā)布,開發(fā)團(tuán)隊能夠更加精確地控制不同服務(wù)版本的發(fā)布比例,降低系統(tǒng)風(fēng)險,保障線上穩(wěn)定性,最大程度地滿足用戶需求。
圖片
以圖 1 為例,軟件系統(tǒng)包含網(wǎng)關(guān)和 4 個微服務(wù),通過全鏈路灰度發(fā)布,可以使ServiceB 和 ServiceD 進(jìn)行灰度發(fā)布,通過灰線所示的流量進(jìn)行灰度功能驗證,同時不影響藍(lán)線所示的正常訪問流量。
2全鏈路灰度發(fā)布核心問題
我們通過圖1 可以清晰地看到,在實施全鏈路灰度發(fā)布時,需要部署相關(guān)服務(wù)的灰度版本,并確保在整個請求的調(diào)用鏈上,網(wǎng)關(guān)和微服務(wù)組件能夠準(zhǔn)確識別正式流量和特定版本灰度流量,并根據(jù)流量類型動態(tài)地將請求路由到正確版本的上游微服務(wù)上。所以,我們需要解決以下幾個問題:
- 微服務(wù)實例具有版本信息,針對不同流量對應(yīng)版本實例提供服務(wù);
- 請求流量具有流量特征,可以區(qū)分出是請求不同版本微服務(wù)的流量;
- 調(diào)用鏈上各組件可以根據(jù)流量特征將請求動態(tài)路由到正確版本的微服務(wù)上。當(dāng)前的微服務(wù)架構(gòu)主要分為兩類模式:一類是建立在傳統(tǒng)微服務(wù)框架(例如Spring Cloud等)之上的微服務(wù)體系,另一類是云原生時代采用Kubernetes和服務(wù)網(wǎng)格(如Istio)構(gòu)建的微服務(wù)架構(gòu),為了方便起見,我們姑且簡稱為傳統(tǒng)模式和云原生模式。下面我們將介紹在兩類模式下,如何解決以上問題,進(jìn)行全鏈路灰度發(fā)布。
微服務(wù)標(biāo)識
我們通過給微服務(wù)實例添加標(biāo)識的方式,使微服務(wù)實例具有版本信息,針對不同流量對應(yīng)版本實例提供服務(wù)。
傳統(tǒng)模式
在傳統(tǒng)模式下,一般需要產(chǎn)品根據(jù)使用的微服務(wù)框架決定添加標(biāo)識的方式。我們以基于 Spring Cloud 框架 + Nacos注冊中心 的微服務(wù)為例,一般通過在微服務(wù)元數(shù)據(jù)配置(spring.cloud.nacos.discovery.metadata)中添加標(biāo)識,配置示例如下:
spring:
cloud:
nacos:
discovery:
metadata:
version: ${APP-VERSION:v1}
微服務(wù)在 Nacos 注冊中心中服務(wù)信息如下:
圖片
云原生模式
在云原生模式下,為微服務(wù)實例添加標(biāo)識更加方便,不需要修改微服務(wù)代碼配置,一般在微服務(wù) Deployment 的 Pod 模版中添加Labal標(biāo)識即可,實例如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
selector:
matchLabels:
app: demo
version: v1
template:
metadata:
labels:
app: demo
version: v1
流量標(biāo)識
和微服務(wù)標(biāo)識相同,我們也是通過為流量添加不同標(biāo)識的方式區(qū)分流量,流量標(biāo)識在業(yè)界還有一個更專業(yè)更形象的叫法叫流量染色。
流量染色分兩步:第一步在流量源頭染色,第二步在調(diào)用鏈內(nèi)染色。
第一步比較簡單,通過前端或者網(wǎng)關(guān)根據(jù)流量特點(比如瀏覽器類型,用戶標(biāo)識,地域標(biāo)識等)為流量添加標(biāo)識(比如在HTTP Header中添加version標(biāo)簽)。
第二步是全鏈路灰度中最核心也是最復(fù)雜的部分,需要流量標(biāo)識在調(diào)用鏈中透傳下去,保證調(diào)用鏈上的每個微服務(wù)組件都能根據(jù)標(biāo)識識別流量并動態(tài)路由。
傳統(tǒng)的微服務(wù)框架注重基礎(chǔ)功能的實現(xiàn),例如服務(wù)注冊與發(fā)現(xiàn)、負(fù)載均衡等,而未將流量標(biāo)識透傳作為核心特性之一,導(dǎo)致在實際應(yīng)用中難以實現(xiàn)流量標(biāo)識的無縫傳遞。
云原生時代的 Kubernetes + Istio 同樣無法幫助微服務(wù)實現(xiàn)流量標(biāo)識透傳。不少人存在誤解,認(rèn)為 Istio 能對微服務(wù)實例的出入流量進(jìn)行攔截,應(yīng)該原生支持流量標(biāo)識透傳,但實際上 Istio 本身沒有流量標(biāo)識透傳的能力。
圖片
Istio Sidecar 對微服務(wù)入口和出口流量攔截如上圖所示,Sidecar 雖然能將入口流量1 攔截后轉(zhuǎn)給微服務(wù)容器(入口流量2),也能將微服務(wù)容器出口流量 3 攔截并轉(zhuǎn)發(fā)到 Pod 外(出口流量4),但 Sidecar 不知道出口流量 3 和入口流量 2 的對應(yīng)關(guān)系,在實際情況中,Sidecar 會攔截很多的出口流量,也會攔截很多的入口流量,但 Sidecar 并不知道某一個出口流量對應(yīng)哪個入口流量。只有微服務(wù)應(yīng)用知道對應(yīng)關(guān)系,因為微服務(wù)應(yīng)用親自做了流量處理(微服務(wù)應(yīng)用收到入口流量請求2后,進(jìn)行業(yè)務(wù)邏輯處理,然后再發(fā)出出口流量3,請求下一級微服務(wù)),所以 Istio 雖然能對微服務(wù)出入流量攔截,但不知道出入流量的對應(yīng)關(guān)系,無法將入口流量的標(biāo)識自動添加到出口流量上,無法做流量標(biāo)識透傳。
流量標(biāo)識透傳方式
那么如何進(jìn)行流量標(biāo)識透傳呢,通常有以下3種方式:
微服務(wù)修改源碼方式
微服務(wù)側(cè)進(jìn)行業(yè)務(wù)代碼改造,從入口流量請求中獲取流量標(biāo)識,并在出口流量中添加流量標(biāo)識,代碼示例如下:
// 從請求中獲取流量標(biāo)識 version
String versionValue = request.getHeader("version");
// 構(gòu)造新請求需要的 Header,獲取到的流量標(biāo)識添加到新請求的 Header 中
HttpHeaders headers = new HttpHeaders();
headers.set("version", versionValue);
// 發(fā)起出口流量請求
使用基礎(chǔ)SDK方式
將從入口流量請求中獲取流量標(biāo)識,并在出口流量中添加流量標(biāo)識這種共性邏輯封裝到基礎(chǔ) SDK 中,其原理通常涉及 SDK 對請求和響應(yīng)的攔截處理。這種方法的核心在于SDK能夠攔截到微服務(wù)內(nèi)部的請求,從請求中獲取流量標(biāo)識,并在微服務(wù)發(fā)起外部請求時,將這個標(biāo)識加入請求中,實現(xiàn)流量標(biāo)識的透傳?;A(chǔ) SDK 的工作機(jī)制一般包括以下幾個關(guān)鍵步驟:
- 攔截請求和響應(yīng): SDK會通過某種方式(例如AOP、攔截器等)攔截微服務(wù)的請求和響應(yīng),這使得SDK能夠在請求進(jìn)入微服務(wù)之前或響應(yīng)返回之后對其進(jìn)行處理。
- 獲取流量標(biāo)識: 在請求被微服務(wù)處理之前,SDK會從請求中獲取流量標(biāo)識。這可能包括從HTTP頭部、Cookie、請求參數(shù)等位置獲取特定的標(biāo)識符。
- 向外發(fā)送請求: 當(dāng)微服務(wù)需要向外部服務(wù)發(fā)起請求時,SDK會在請求中添加之前獲取到的流量標(biāo)識。這意味著,SDK會將其添加到新的請求頭、請求體或其他適當(dāng)?shù)奈恢茫源_保這個標(biāo)識被透傳到外部服務(wù)。
通過以上步驟,基礎(chǔ) SDK 能夠在微服務(wù)內(nèi)部對流量進(jìn)行攔截、獲取流量標(biāo)識,并在微服務(wù)發(fā)起外部請求時,將這個標(biāo)識透傳到外部服務(wù)中去。
在一些大型企業(yè)內(nèi)部,基礎(chǔ)設(shè)施團(tuán)隊會提供基礎(chǔ) SDK 供產(chǎn)品團(tuán)隊使用,也有一些相關(guān)的開源方案可以參考,例如阿里開源的KtEnv,提供了一個Java語言的SDK示例,采用Spring框架的切面機(jī)制來自動化"環(huán)境標(biāo)簽"的傳遞,其中環(huán)境標(biāo)簽即為一種流量標(biāo)識。
使用基礎(chǔ)Agent方式
使用 Agent 技術(shù)實現(xiàn)流量標(biāo)識透傳是一種相對隱式且高度可配置的方式。Agent 是一種可以介入到 JVM 運(yùn)行時的程序,它可以對 Java 應(yīng)用程序進(jìn)行動態(tài)的字節(jié)碼操作和增強(qiáng)。
在實現(xiàn)流量標(biāo)識透傳時,Agent 可以通過動態(tài)字節(jié)碼增強(qiáng)技術(shù),通過字節(jié)碼操作工具(如ASM、ByteBuddy等)對特定類或方法進(jìn)行字節(jié)碼增強(qiáng),動態(tài)地修改微服務(wù)應(yīng)用的字節(jié)碼,使得在請求處理鏈路中自動獲取到流量標(biāo)識,并在請求發(fā)起時將這些標(biāo)識添加到外部請求中。這些標(biāo)識可能包括從HTTP頭部、上下文信息、或者其他標(biāo)識性的數(shù)據(jù)。
有一些基礎(chǔ)Agent開源方案可以選擇參考,如:Homer,這是專門為javaweb應(yīng)用提供了無感知的header透傳的開源方案,華為的Sermant,Sermant 是利用JavaAgent技術(shù)為Java應(yīng)用程序提供服務(wù)網(wǎng)格功能的開源方案,提供了流量透傳插件tag-transmission,可以幫助微服務(wù)實現(xiàn)流量透傳功能。
三種方式總結(jié)
這三種實現(xiàn)流量標(biāo)識透傳的方式各自具有獨特的優(yōu)勢和適用場景。總結(jié)如下:
- 業(yè)務(wù)代碼修改: 這種方式簡單直接,但會增加業(yè)務(wù)代碼的復(fù)雜性和維護(hù)成本,尤其在大型微服務(wù)體系中,需要在多個服務(wù)間添加相似的邏輯,不夠靈活和智能,實際應(yīng)用較少。
- 基礎(chǔ) SDK: 使用基礎(chǔ) SDK 相對于業(yè)務(wù)代碼修改更為智能化和自動化,減少了對業(yè)務(wù)代碼的侵入,同時也能夠提供一定程度的可配置性和擴(kuò)展性,但對業(yè)務(wù)代碼仍然有侵入,并且和語言相關(guān),在大型多語言微服務(wù)體系中需要提供多語言SDK,另外 SDK 版本升級困難。
- Agent 方式: 基于 Agent 技術(shù),可以在不修改業(yè)務(wù)代碼的情況下,實現(xiàn)流量標(biāo)識的攔截和傳遞。這種方式尤其適用于不想或不能直接修改業(yè)務(wù)代碼的場景,其靈活性和智能化程度較高,但也存在和語言綁定,版本升級相對困難的問題。
總體而言,三種方式各有利弊,在實際場景中,需要根據(jù)具體需求和現(xiàn)有架構(gòu),選擇適合的方式。
流量路由
流量路由和微服務(wù)標(biāo)識類似,由于傳統(tǒng)模式和云原生模式都支持,較流量標(biāo)識簡單很多。
在傳統(tǒng)微服務(wù)框架(如 Spring Cloud)中,實現(xiàn)動態(tài)路由通常通過 API 網(wǎng)關(guān)(如Spring Cloud Gateway)或負(fù)載均衡器(如Netflix Ribbon)等組件,根據(jù)特定的策略或規(guī)則,對流量進(jìn)行分發(fā)和路由。例如,可以基于請求頭中的流量標(biāo)識信息,利用負(fù)載均衡策略,將請求分發(fā)到不同版本的微服務(wù)實例上,實現(xiàn)動態(tài)路由。
而在云原生架構(gòu)下(例如 Kubernetes + Istio),動態(tài)路由更加簡單。通過 Istio 中的流量管理功能,定義Gateway、VirtualService、DestinationRule等規(guī)則和配置來實現(xiàn)流量的精細(xì)化控制和路由。
3全鏈路灰度發(fā)布實踐
我們在云原生模式下,對概述部分圖1 所示的微服務(wù)進(jìn)行全鏈路灰度發(fā)布實踐。微服務(wù)版本情況及調(diào)用鏈路和圖 1 一致,微服務(wù)實例列表如下:
圖片
網(wǎng)關(guān)采用 Istio Ingress Gateway,流量標(biāo)識透傳采用基礎(chǔ) Agent 方式,可以在微服務(wù)調(diào)用鏈路中透傳 key 為 et-mark 的 HTTP Request Header ,流量路由通過 Istio 流量管理功能實現(xiàn),關(guān)鍵Gateway、VirtualService、DestinationRule 規(guī)則部分如下:
---
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: ingress-gateway
namespace: e2e-canary-release
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- 'www.e2e-canary-release.com'
port:
name: http
number: 80
protocol: HTTP
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-a
namespace: e2e-canary-release
spec:
gateways:
- e2e-canary-release/ingress-gateway
hosts:
- 'www.e2e-canary-release.com'
http:
- route:
- destination:
host: service-a
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-b
namespace: e2e-canary-release
spec:
hosts:
- service-b
http:
- match:
- headers:
et-mark:
exact: v2
route:
- destination:
host: service-b
subset: v2
- route:
- destination:
host: service-b
subset: v1
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: service-b
namespace: e2e-canary-release
spec:
host: service-b
subsets:
- labels:
version: v1
name: v1
- labels:
version: v2
name: v2
實際效果如下,可以看到默認(rèn)情況下請求流量流經(jīng)的微服務(wù)版本均為正式版本v1,當(dāng)請求header中包含流量標(biāo)識時,即流量為灰度流量時,會按照圖1 中的灰色路徑流轉(zhuǎn),實現(xiàn)了全鏈路灰度發(fā)布。
圖片
圖片
4總結(jié)
本文首先介紹了全鏈路灰度發(fā)布的概念、作用以及實現(xiàn)全鏈路灰度發(fā)布時需要解決的關(guān)鍵問題,針對每個問題分別從傳統(tǒng)模式和云原生模式介紹了對應(yīng)的解決方案,其中對流量標(biāo)識透傳做了詳細(xì)的介紹,然后在云原生模式下,對微服務(wù) Demo 進(jìn)行了全鏈路灰度發(fā)布實踐,展示了實踐效果。由于能力和時間有限,一些內(nèi)容僅進(jìn)行了粗淺介紹,希望后續(xù)可以繼續(xù)深入研究分享,文中存在錯誤的地方,也望大家指正。
5參考文章及相關(guān)鏈接
- 深入剖析全鏈路灰度技術(shù)內(nèi)幕
https://developer.aliyun.com/article/834510#slide-1 - 基于 Istio 的全鏈路灰度方案探索和實踐https://xie.infoq.cn/article/f6a1db8756e8bfa831947ee05
- 聊聊 Spring Cloud 全鏈路灰度發(fā)布方案~ https://z.itpub.net/article/detail/5D9F94265D666C4607B92CBC32667692
- Spring Cloud Alibaba-全鏈路灰度設(shè)計https://www.nowcoder.com/discuss/517248839594541056
- 標(biāo)記透傳:微服務(wù)系統(tǒng)如何做標(biāo)記透傳方案選型?https://leeshengis.com/archives/444794
- 流量治理的基石——基于字節(jié)碼增強(qiáng)的全鏈路流量標(biāo)簽透傳https://juejin.cn/post/7282957826510667816
- KtEnv
https://alibaba.github.io/virtual-environment/#/zh-cn/doc/use-sdk?id=%E4%BD%BF%E7%94%A8sdk - Homer
https://github.com/kaikeba/homer - Sermant
https://sermant.io/zh/
作者:張海文,中國移動云能力中心高級軟件研發(fā)工程師,移動云服務(wù)網(wǎng)格負(fù)責(zé)人,QCon、KubeCon等大會分享者,專注于云原生、微服務(wù)、算力網(wǎng)絡(luò)等。