背景
微服務軟體架構下,業務新功能上線前搭建完整的一套測試系統進行驗證是相當費人費時的事,随着所拆分出微服務數量的不斷增大其難度也俞大。這一整套測試系統所需付出的機器成本往往也不低,為了保證應用新版本上線前的功能正确性驗證效率這套系統還必須一直單獨維護好。當業務變得龐大且複雜時,往往還得準備多套,這是整個行業共同面臨且難解的成本和效率挑戰。如果能在同一套生産系統中完成新版本上線前的功能驗證的話,所節約的人力和财力是相當可觀的。
除了開發階段的功能驗證,生産環境中引入灰階釋出才能更好地控制新版本軟體上線的風險和爆炸半徑。灰階釋出是将具有一定特征或者比例的生産流量配置設定到需要被驗證的服務版本中,以觀察新版本上線後的運作狀态是否符合預期。
阿裡雲 ASM Pro(
https://servicemesh.console.aliyun.com/)基于 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, 取值依次從$getenv(http.traceid)到最後從本地環境的$(localLabel)中擷取。
apiVersion: istio.alibabacloud.com/v1beta1
kind: TrafficLabel
metadata:
name: default
spec:
rules:
- labels:
- name: trafficLabel
valueFrom:
- $getContext(http.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政策。
配置樣例如下:
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:
host:
- 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定義擷取“單元标”的函數來給流量打上“單元标”,然後基于“單元标”将流量路由到對應的服務單元。