作者|曾宇星(宇曾)
稽核&校對:曾宇星(宇曾)
編輯&排版:雯燕
背景
微服務軟體架構下,業務新功能上線前搭建完整的一套測試系統進行驗證是相當費人費時的事,随着所拆分出微服務數量的不斷增大其難度也愈大。這一整套測試系統所需付出的機器成本往往也不低,為了保證應用新版本上線前的功能正确性驗證效率,這套系統還必須一直單獨維護好。當業務變得龐大且複雜時,往往還得準備多套,這是整個行業共同面臨且難解的成本和效率挑戰。如果能在同一套生産系統中完成新版本上線前的功能驗證的話,所節約的人力和财力是相當可觀的。
除了開發階段的功能驗證,生産環境中引入灰階釋出才能更好地控制新版本軟體上線的風險和爆炸半徑。灰階釋出是将具有一定特征或者比例的生産流量配置設定到需要被驗證的服務版本中,以觀察新版本上線後的運作狀态是否符合預期。
阿裡雲 ASM Pro(相關連結請見文末)基于 Service Mesh 所建構的全鍊路灰階方案,能很好幫助解決以上兩個場景的問題。
ASM Pro 産品功能架構圖:

核心能力使用的就是上圖擴充的流量打标和按标路由以及流量 Fallback 的能力,下面詳細介紹說明。
場景說明
全鍊路灰階釋出的常見場景如下:
以 Bookinfo 為例,入口流量會帶上期望的 tag 分組,sidecar 通過擷取請求上下文(Header 或 Context) 中的期望 tag,将流量路由分發到對應 tag 分組,若對應 tag 分組不存在,預設會 fallback 路由到 base 分組,具體 fallback 政策可配置。接下來較長的描述具體的實作細節。
入口流量的 tag 标簽,一般是在網關層面基于類似 tag 插件的方式,将請求流量進行打标。 比如将 userid 處于一定範圍的打上代表灰階的 tag,考慮到實際環境網關的選擇和實作的多樣性,網關這塊實作不在本文讨論的範圍内。
下面我們着重讨論基于 ASM Pro 如何做到全鍊路流量打标和實作全鍊路灰階。
實作原理
Inbound 是指請求發到 App 的入口流量,Outbond 是指 App 向外發起請求的出口流量。
上圖是一個業務應用在開啟 mesh 後典型流量路徑:業務 App 接收到一個外部請求 p1,接着調用背後所依賴的另一個服務的接口。此時,請求的流量路徑是 p1->p2->p3->p4,其中 p2 是 Sidecar 對 p1 的轉發,p4 是 Sidecar 對 p3 的轉發。為了實作全鍊路灰階,p3 和 p4 都需要擷取到 p1 進來的流量标簽,才能将請求路由到标簽所對應的後端服務執行個體,且 p3 和 p4 也要帶上同樣的标簽。關鍵在于,如何讓标簽的傳遞對于應用完全無感,進而實作全鍊路的标簽透傳,這是全鍊路灰階的關鍵技術。ASM Pro 的實作是基于分布式鍊路追蹤技術(比如,OpenTracing、OpenTelemetry 等)中的 traceId 來實作這一功能。
在分布式鍊路追蹤技術中,traceId 被用于唯一地辨別一個完整的調用鍊,鍊路上的每一個應用所發出的扇出(fanout)調用,都會通過分布式鍊路追蹤的 SDK 将源頭的 traceId 給帶上。ASM Pro 全鍊路灰階解決方案的實作正是建立在這一分布式應用架構所廣泛采納的實踐之上的。
上圖中,Sidecar 本來所看到的 inbound 和 outbound 流量是完全獨立的,無法感覺兩者的對應關系,也不清楚一個 inbound 請求是否導緻了多個 outbound 請求的發生。換句話說,圖中 p1 和 p3 兩個請求之間是否有對應關系 Sidecar 并不知情。
在 ASM Pro 全鍊路灰階解決方案中,通過 traceId 将 p1 和 p3 兩個請求做關聯,具體說來依賴了 Sidecar 中的 x-request-id 這個 trace header。Sidecar 内部維護了一張映射表,其中記錄了 traceId 和标簽的對應關系。當 Sidecar 收到 p1 請求時,将請求中的 traceId 和标簽存儲到這張表中。當收到 p3 請求時,從映射表中查詢獲得 traceId 所對應的标簽并将這一标簽加入到 p4 請求中,進而實作全鍊路的打标和按标路由。下圖大緻示例了這一實作原理。
換句話說,ASM Pro 的全鍊路灰階功能需要應用使用分布式鍊路追蹤技術。如果想運用這一技術的應用沒有使用分布式鍊路追蹤技術的話不可避免地涉及到一定的改造工作。對于 Java 應用來說,仍可以考慮采用 Java Agent 以 AOP 的方式讓業務無需改造地實作 traceId 在 inbound 和 outbound 之間透傳。
實作流量打标
ASM Pro 中引入了全新的 TrafficLabel CRD 用于定義 Sidecar 所需透傳的流量标簽從哪裡擷取。下面所例舉的 YAML 檔案中,定義了流量标簽來源和需要将标簽存儲 OpenTracing 中(具體是 x-trace 頭)。其中流量标的名為 trafficLabel,取值依次從 $getContext(x-request-id) 到最後從本地環境的$(localLabel)中擷取。
apiVersion: istio.alibabacloud.com/v1beta1
kind: TrafficLabel
metadata:
name: default
spec:
rules:
- labels:
- name: trafficLabel
valueFrom:
- $getContext(x-request-id) //若使用aliyun arms,對應為x-b3-traceid
- $(localLabel)
attachTo:
- opentracing
# 表示生效的協定,空為都不生效,*為都生效
protocols: "*"
CR 定義包含兩塊,即标簽的擷取和存儲。
- 擷取邏輯:先根據協定上下文或者頭(Header 部分)中的定義的字段擷取流量标簽,如果沒有,會根據 traceId 通過 Sidecar 本地記錄的 map 擷取, 該 map 表中儲存了 traceId 對應流量辨別的映射。若 map 表中找到對應映射,會将該流量打上對應的流量标,若擷取不到,會将流量标取值為本地部署對應環境的 localLabel。localLabel 對應本地部署的關聯 label,label 名為 ASM_TRAFFIC_TAG。
本地部署對應環境的标簽名為"ASM_TRAFFIC_TAG",實際部署可以結合 CI/CD 系統來關聯。
- 存儲邏輯:attachTo 指定存儲在協定上下文的對應字段,比如 HTTP 對應 Header 字段,Dubbo 對應 rpc context 部分,具體存儲到哪一個字段中可配置。
有了TrafficLabel 的定義,我們知道如何将流量打标和傳遞标簽,但光有這個還不足以做到全鍊路灰階,我們還需要一個可以基于 trafficLabel 流量辨別來做路由的功能,也就是“按标路由”,以及路由 fallback 等邏輯,以便當路由的目的地不存在時,可以實作降級的功能。
按流量标簽路由
這一功能的實作擴充了 Istio 的 VirtualService 和 DestinationRule。
在 DestinationRule 中定義 Subset
自定義分組 subset 對應的是 trafficLabel 的 value
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: myapp
spec:
host: myapp/*
subsets:
- name: myproject # 項目環境
labels:
env: abc
- name: isolation # 隔離環境
labels:
env: xxx # 機器分組
- name: testing-trunk # 主幹環境
labels:
env: yyy
- name: testing # 日常環境
labels:
env: zzz
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: myapp
spec:
hosts:
- myapp/*
ports:
- number: 12200
name: http
protocol: HTTP
endpoints:
- address: 0.0.0.0
labels:
env: abc
- address: 1.1.1.1
labels:
env: xxx
- address: 2.2.2.2
labels:
env: zzz
- address: 3.3.3.3
labels:
env: yyy
Subset 支援兩種指定形式:
- labels 用于比對應用中帶特定标記的節點(endpoint);
- 通過 ServiceEntry 用于指定屬于特定 subset 的 IP 位址,注意這種方式與labels指定邏輯不同,它們可以不是從注冊中心(K8s 或者其他)拿到的位址,直接通過配置的方式指定。适用于 Mock 環境,這個環境下的節點并沒有向服務注冊中心注冊。
在 VirtualService 中基于 subset
1)全局預設配置
- route 部分可以按順序指定多個 destination,多個 destination 之間按照 weight 值的比例來配置設定流量。
- 每個 destination 下可以指定 fallback 政策,case 辨別在什麼情況下執行 fallback,取值:noinstances(無服務資源)、noavailabled(有服務資源但是服務不可用),target 指定 fallback 的目标環境。如果不指定 fallback,則強制在該 destination 的環境下執行。
- 按标路由邏輯,我們通過改造 VirtualService,讓 subset 支援占位符 $trafficLabel, 該占位符 $trafficLabel 表示從請求流量标中擷取目标環境, 對應 TrafficLabel CR 中的定義。
全局預設模式對應泳道,也就是單個環境内封閉,同時指定了環境級别的 fallback 政策。自定義分組 subset 對應的是 trafficLabel 的 value
配置樣例如下:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: default-route
spec:
hosts: # 對所有應用生效
- */*
http:
- name: default-route
route:
- destination:
subset: $trafficLabel
weight: 100
fallback:
case: noinstances
target: testing-trunk
- destination:
host: */*
subset: testing-trunk # 主幹環境
weight: 0
fallback:
case: noavailabled
target: testing
- destination:
subset: testing # 日常環境
weight: 0
fallback:
case: noavailabled
target: mock
- destination:
host: */*
subset: mock # Mock中心
weight: 0
2)個人開發環境定制
- 先打到日常環境,當日常環境沒有服務資源時,再打到主幹環境。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: projectx-route
spec:
hosts: # 隻對myapp生效
- myapp/*
http:
- name: dev-x-route
match:
trafficLabel:
- exact: dev-x # dev環境: x
route:
- destination:
host: myapp/*
subset: testing # 日常環境
weight: 100
fallback:
case: noinstances
target: testing-trunk
- destination:
host: myapp/*
subset: testing-trunk # 主幹環境
weight: 0
3) 支援權重配置
将打了主幹環境标并且本機環境是 dev-x 的流量,80% 打到主幹環境,20% 打到日常環境。當主幹環境沒有可用的服務資源時,流量打到日常。
sourceLabels 為本地 workload 對應的 label
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: dev-x-route
spec:
hosts: # 對哪些應用生效(不支援多應用配置)
- myapp/*
http:
- name: dev-x-route
match:
trafficLabel:
- exact: testing-trunk # 主幹環境标
sourceLabels:
- exact: dev-x # 流量來自某個項目環境
route:
- destination:
host: myapp/*
subset: testing-trunk # 80%流量打向主幹環境
weight: 80
fallback:
case: noavailabled
target: testing
- destination:
host: myapp/*
subset: testing # 20%流量打向日常環境
weight: 20
按(環境)标路由
該方案依賴業務部署應用時帶上相關辨別(例子中對應 label 為 ASM_TRAFFIC_TAG: xxx),常見為環境辨別,辨別可以了解是服務部署的相關元資訊,這個依賴上遊部署系統 CI/CD 系統的串聯,大概示意圖如下:
- K8s 場景,通過業務部署時自動帶上對應環境/分組 label 辨別即可,也就是采用K8s 本身作為中繼資料管理中心。
- 非 K8s 場景,可以通過微服務已內建的服務注冊中心或者中繼資料配置管理服務(metadata server)來內建實作。
注:ASM Pro 自研開發了ServiceDiretory 元件(可以參看 ASM Pro 産品功能架構圖),實作了多注冊中心對接以及部署元資訊的動态擷取;
應用場景延伸
下面是典型的一個基于流量打标和按标路由實作的多套開發環境治理功能;每個開發者對應的 Dev X 環境隻需部署有版本更新的服務即可;如果需要和其他開發者聯調,可以通過配置 fallback 将服務請求 fallback 流轉到對應開發環境即可。如下圖的 Dev Y 環境的B -> Dev X 環境的 C。
同理,将 Dev X 環境等同于線上灰階版本環境也是可以的,對應可以解決線上環境的全鍊路灰階釋出問題。
總結
本文介紹的基于“流量打标”和“按标路由” 能力是一個通用方案,基于此可以較好地解決測試環境治理、線上全鍊路灰階釋出等相關問題,基于服務網格技術做到與開發語言無關。同時,該方案适應于不同的7層協定,目前已支援 HTTP/gRpc 和 Dubbo 協定。
對應全鍊路灰階,其他廠商也有一些方案,對比其他方案 ASM Pro 的解決方案的優點是:
- 支援多語言、多協定。
- 統一配置模闆 TrafficLabel, 配置簡單且靈活,支援多級别的配置(全局、namespace 、pod 級别)。
- 支援路由 fallback 實作降級。
基于“流量打标” 和 “按标路由”能力還可以用于其他相關場景:
- 大促前的性能壓測。線上上壓測的場景中,為了讓壓測資料和正式的線上資料實作隔離,常用的方法是對于消息隊列,緩存,資料庫使用影子的方式。這就需要流量打标的技術,通過 tag 區分請求是測試流量還是生産流量。當然,這需要 Sidecar 對中間件比如 Redis、RocketMQ 等進行支援。
- 單元化路由。常見的單元化路由場景,可能是需要根據請求流量中的某些元資訊比如 uid,然後通過配置得出對應所屬的單元。在這個場景中,我們可以通過擴充 TrafficLabel 定義擷取“單元标”的函數來給流量打上“單元标”,然後基于“單元标”将流量路由到對應的服務單元。
相關連結:
1)阿裡雲 ASM Pro:
https://servicemesh.console.aliyun.com/