基於 Istio 的全鏈路灰度方案探索和實踐

阿里巴巴雲原生發表於2021-11-07

作者|曾宇星(宇曾)
稽核&校對:曾宇星(宇曾)
編輯&排版:雯燕

背景

微服務軟體架構下,業務新功能上線前搭建完整的一套測試系統進行驗證是相當費人費時的事,隨著所拆分出微服務數量的不斷增大其難度也愈大。這一整套測試系統所需付出的機器成本往往也不低,為了保證應用新版本上線前的功能正確性驗證效率,這套系統還必須一直單獨維護好。當業務變得龐大且複雜時,往往還得準備多套,這是整個行業共同面臨且難解的成本和效率挑戰。如果能在同一套生產系統中完成新版本上線前的功能驗證的話,所節約的人力和財力是相當可觀的。

除了開發階段的功能驗證,生產環境中引入灰度釋出才能更好地控制新版本軟體上線的風險和爆炸半徑。灰度釋出是將具有一定特徵或者比例的生產流量分配到需要被驗證的服務版本中,以觀察新版本上線後的執行狀態是否符合預期。

阿里雲 ASM Pro(相關連結請見文末)基於 Service Mesh 所構建的全鏈路灰度方案,能很好幫助解決以上兩個場景的問題。

ASM Pro 產品功能架構圖:

1.png

核心能力使用的就是上圖擴充套件的流量打標和按標路由以及流量 Fallback 的能力,下面詳細介紹說明。

場景說明

全鏈路灰度釋出的常見場景如下:

2.png

以 Bookinfo 為例,入口流量會帶上期望的 tag 分組,sidecar 通過獲取請求上下文(Header 或 Context) 中的期望 tag,將流量路由分發到對應 tag 分組,若對應 tag 分組不存在,預設會 fallback 路由到 base 分組,具體 fallback 策略可配置。接下來詳細描述具體的實現細節。

入口流量的 tag 標籤,一般是在閘道器層面基於類似 tag 外掛的方式,將請求流量進行打標。 比如將 userid 處於一定範圍的打上代表灰度的 tag,考慮到實際環境閘道器的選擇和實現的多樣性,閘道器這塊實現不在本文討論的範圍內。

下面我們著重討論基於 ASM Pro 如何做到全鏈路流量打標和實現全鏈路灰度。

實現原理

3.png

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 請求中,從而實現全鏈路的打標和按標路由。下圖大致示例了這一實現原理。

4.png

換句話說,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 系統來關聯。

5.png

  • 儲存邏輯: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)來整合實現。

6.png

注:ASM Pro 自研開發了ServiceDiretory 元件(可以參看 ASM Pro 產品功能架構圖),實現了多註冊中心對接以及部署元資訊的動態獲取;

應用場景延伸

下面是典型的一個基於流量打標和按標路由實現的多套開發環境治理功能;每個開發者對應的 Dev X 環境只需部署有版本更新的服務即可;如果需要和其他開發者聯調,可以通過配置 fallback 將服務請求 fallback 流轉到對應開發環境即可。如下圖的 Dev Y 環境的B -> Dev X 環境的 C。

同理,將 Dev X 環境等同於線上灰度版本環境也是可以的,對應可以解決線上環境的全鏈路灰度釋出問題。

7.png

總結

本文介紹的基於“流量打標”和“按標路由” 能力是一個通用方案,基於此可以較好地解決測試環境治理、線上全鏈路灰度釋出等相關問題,基於服務網格技術做到與開發語言無關。同時,該方案適應於不同的7層協議,當前已支援 HTTP/gRpc 和 Dubbo 協議。

對應全鏈路灰度,其他廠商也有一些方案,對比其他方案 ASM Pro 的解決方案的優點是:

  • 支援多語言、多協議。
  • 統一配置模板 TrafficLabel, 配置簡單且靈活,支援多級別的配置(全域性、namespace 、pod 級別)。
  • 支援路由 fallback 實現降級。

基於“流量打標” 和 “按標路由”能力還可以用於其他相關場景:

  • 大促前的效能壓測。線上上壓測的場景中,為了讓壓測資料和正式的線上資料實現隔離,常用的方法是對於訊息佇列,快取,資料庫使用影子的方式。這就需要流量打標的技術,通過 tag 區分請求是測試流量還是生產流量。當然,這需要 Sidecar 對中介軟體比如 Redis、RocketMQ 等進行支援。
  • 單元化路由。常見的單元化路由場景,可能是需要根據請求流量中的某些元資訊比如 uid,然後通過配置得出對應所屬的單元。在這個場景中,我們可以通過擴充套件 TrafficLabel 定義獲取“單元標”的函式來給流量打上“單元標”,然後基於“單元標”將流量路由到對應的服務單元。

相關連結:

1)阿里雲 ASM Pro:
https://servicemesh.console.aliyun.com/

相關文章