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