天天看點

Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

作者:位元組跳動技術團隊

1. 引言

1.1 Kitex Proxyless

Kitex 是位元組跳動開源的 Golang RPC 架構,目前已經原生支援了 xDS 标準協定, 支援以 Proxyless 的方式被 ServiceMesh 統一納管。

詳細設計見:

Proposal: Kitex support xDS Protocol · Issue #461 · cloudwego/kitex (https://github.com/cloudwego/kitex/issues/461)

具體使用方式見:

官方文檔(https://www.cloudwego.io/zh/docs/kitex/tutorials/advanced-feature/xds/)

Kitex Proxyless 簡單來說就是 Kitex 服務能夠不借助 envoy sidecar 直接與 istiod 互動,基于 xDS 協定動态擷取控制面下發的服務治理規則,并轉換為 Kitex 對應規則來實作一些服務治理功能(例如本文的重點:流量路由)。

基于 Kitex Proxyless,讓我們實作 Kitex 能夠無需代理就可以被 ServiceMesh 統一管理,進而實作多種部署模式下的 治理規則 Spec、治理控制面、治理下發協定、異構資料治理能力 的統一。

Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

1.2 流量路由

流量路由是指,能夠将流量根據其自身特定的中繼資料辨別路由到指定目的地。

流量路由屬于服務治理中比較核心的能力之一,也是 Kitex Proxyless 優先支援的場景之一。

Kitex 基于 xDS 實作 流量路由 的方案大緻如下:

Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

具體流程:

  1. 增加一個 xDS Router MW 來負責 Pick Cluster(路由),并 watch 目标服務的 LDS 及 RDS。
  2. 感覺 LDS 變化,并提取目标服務的 LDS 中的 Filter Chain 及其 inline RDS。
  3. 感覺 RDS 變化,根據 VirtualHost 和 ServiceName 來比對(支援字首、字尾、精确、通配),擷取目标服務的路由配置。
  4. 周遊處理比對到的 RDS 中的路由規則,路由規則主要分為兩部分(參考:路由規範定義):

(1)Match(支援字首、字尾、精确、通配等),目前版本我們支援以下兩種即可

  • Path(必須項):從 rpcinfo 提取 Method進行比對。
  • HeaderMatcher(可選項):從 metainfo 中提取對應中繼資料 KeyValue,并進行比對。

(2)Route:

  • Cluster: 标準 Cluster。
  • WeightedClusters(權重路由): MW 内根據權重來選擇 cluster。
  • 将選擇到的 Cluster 寫入 EndpointInfo.Tag,用于之後的服務發現。

可以看到,流量路由其實是一個根據一定規則選擇對應 SubCluster 的流程。

2. 全鍊路泳道

基于流量路由能力,我們可以延伸出很多使用場景,如:A/B 測試、金絲雀釋出、藍綠釋出等等,以及本文重點:全鍊路泳道 。

全鍊路泳道可以了解成是對一組服務執行個體按照一定方式進行拆分(例如部署環境),并基于全鍊路灰階路由能力,讓流量能夠精準按照規則在指定服務執行個體泳道中流動(邏輯上如同遊泳場中的泳道)。

在 Istio 中我們一般會通過 DestinationRule 的 subset 對執行個體進行分組,将一個服務拆分成不同子集(例如:按照版本、區域等屬性拆分),然後配合 VirtualService 來定義對應的路由規則,将流量路由到對應子集中,進而完成泳道中的單跳路由能力。

不過單單隻有流量路由能力,還不足以實作全鍊路泳道 , 因為當一個請求跨越多個服務的時候,我們需要有一個比較好的機制能夠準确識别出該流量,并基于這個特征來為每一跳流量配置路由規則。

如下圖所示:假設我們要實作一個使用者的請求能夠精确灰階到 service-b 的 v1 版本。最先想到的做法可能是所有請求都帶上 uid = 100 的請求頭,然後配置對應 VirtualService 來根據 header 裡的 uid = 100 比對。

Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

但這樣的做法有幾個明顯的缺點:

  1. 不夠通用: 以具體某個業務屬性辨別(如:uid)作為流量路由比對規則,我們需要将這個業務屬性手動在全鍊路裡透傳,這本身對業務侵入性較大,需要業務配合改造。并且當我們要使用其他業務屬性的時候,又需要全鍊路業務都改造一遍,可想而知,是非常不通用的做法。
  2. 路由規則容易頻繁變動,容易造成規則臃腫: 以具體某個業務屬性辨別(如:uid)作為流量路由比對規則,假設我們要換一個業務屬性,或者給其他使用者設定路由規則的時候,得去改造原有的路由規則,或者針對不同業務屬性重複定義多套路由規則,很容易就會造成路由臃腫,以至于難以維護。

是以,要實作全鍊路的流量路由統一,我們還需要額外借助一個更通用的 流量染色 與 染色辨別全鍊路透傳 能力。

2.1 流量染色

流量染色是指對請求流量打上特殊辨別,并在整個請求鍊路中攜帶這個辨別,而所謂的全鍊路泳道,就是整個鍊路基于統一的灰階流量染色辨別來設定流量路由規則,使得流量能夠精準控制在不同泳道中。

通常我們會在網關層進行流量染色,通常會根據原始請求中的中繼資料,來進行一定規則(條件、比例)轉換成對應的染色辨別。

  • 按條件染色:當請求中繼資料滿足一定條件之後,就給目前請求打上染色辨別,如:請求頭中 uid=100、cookie 比對等等。
  • 按比例染色:按照一定比例,給請求打上染色辨別。

有了一套統一的流量染色機制之後,我們配置路由規則的時候,就不需要關心具體的業務屬性辨別了,隻需要根據 染色辨別 來配置即可。

将具體的業務屬性抽象成條件染色規則,使其更通用,即使業務屬性發生了變化,路由規則也無需再頻繁變動了。

2.2 染色辨別全鍊路透傳

染色辨別通常會依靠 Tracing Baggage 來透傳,Baggage 是用于在整個鍊路中傳遞業務自定義 KV 屬性,例如傳遞流量染色辨別、傳遞 AccountID 等業務辨別等等。
Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

要實作流量染色辨別在全鍊路透傳,我們通常會借助 Tracing Baggage 機制,在全鍊路中傳遞對應染色辨別,大部分 Tracing 架構都支援 Baggage 概念機能力,如:OpenTelemetry、Skywalking、Jaeger 等等。

有了一套通用的全鍊路透傳機制,業務方就隻需要接入一遍 tracing 即可,無需每次業務屬性辨別發生變化就配合改造一次。

下面會借助一個具體的工程案例介紹,來介紹并示範如何基于 Kitex Proxyless 和 OpenTelemetry Baggage 實作全鍊路泳道功能。

3. 案例介紹:Bookinfo

該案例是使用 Hertz、Kitex 重寫經典的 Istio Bookinfo 項目:

  • 使用 istiod 來作為 xDS server,作為 CRD 配置和下發的入口;
  • 使用 wire 來實作依賴注入;
  • 使用 opentelemetry 來實作全鍊路追蹤;
  • 使用 Kitex-xds 和 opentelemetry baggage 來實作 proxyless 模式下的全鍊路泳道;
  • 使用 arco-design 和 react 實作一個 Bookinfo UI。

3.1 架構

整體架構與 Bookinfo 保持一緻,分為四個單獨的微服務:

  • productpage. 這個微服務會調用 details 和 reviews 兩個微服務;
  • details. 這個微服務中包含了書籍的資訊;
  • reviews. 這個微服務中包含了書籍相關的評論。它還會調用 ratings 微服務;
  • ratings. 這個微服務中包含了由書籍評價組成的評級資訊。

reviews 微服務有 3 個版本:

  • v1 版本會調用 ratings 服務,并使用 1 顆 ⭐️ 顯示評分;
  • v2 版本會調用 ratings 服務,并使用 5 顆 ⭐️⭐️⭐️⭐️⭐️⭐️ 顯示評分;
  • v3 版本不會調用 ratings 服務。
Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

3.2 泳道示意圖

整體區分成 2 個泳道:

  • 基準泳道 : 未被染色的流量會被路由到基準泳道中。
  • 分支泳道 : 被染色的流量會被路由到 reviews-v2 ->ratings-v2 的分支泳道中。
Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

3.3 流量染色

網關統一負責對流量進行染色,例如請求 header 中 uid=100 的流量都統一進行染色,為請求攜帶上 env=dev 的 baggage 。

Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

染色方式可以根據不同的網關實作具體選擇,例如當我們選擇 istio ingress 作為網關的時候,我們可以借助 EnvoyFilter + Lua 的方式來編寫網關染色規則。

3.4 為服務執行個體打标

  1. 為對應 workload 打上對應 version 辨別。
以 reviews 為例,隻需要給對應 pod 打上 version: v1 的 label 即可。
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/instance: reviews
    app.kubernetes.io/name: reviews
  name: reviews-v1
spec:
  selector:
    matchLabels:
      app.kubernetes.io/instance: reviews
      app.kubernetes.io/name: reviews
      version: v1
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "false"
      labels:
        app.kubernetes.io/instance: reviews
        app.kubernetes.io/name: reviews
        version: v1
        ...
           

2. 基于 DestinationRule 為服務設定一系列的 subsets:

Productpage: v1

Reviews: v1、v2、v3

Ratings: v1、v2

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: productpage
spec:
  host: productpage
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: ratings
spec:
  host: ratings
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
           

3.5 流量路由規則

網關已經将請求頭中攜帶了 uid=100 的流量進行了染色,自動帶上了 env=dev 的 baggage,是以我們隻需要根據 header 進行路由比對即可,下面是具體的路由規則配置示例:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
    - match:
        - headers:
            baggage:
              exact: "env=dev"
      route:
        - destination:
            host: reviews
            subset: v2
          weight: 100
    - route:
        - destination:
            host: reviews
            subset: v1
          weight: 80
        - destination:
            host: reviews
            subset: v3
          weight: 20

---

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
    - ratings
  http:
    - match:
        - headers:
            baggage:
              exact: "env=dev"
      route:
        - destination:
            host: ratings
            subset: v2
          weight: 100
    - route:
        - destination:
            host: ratings
            subset: v1
          weight: 100
           

3.6 檢視效果

3.6.1 基準泳道

入口流量請求頭中不帶 uid=100 的請求,會自動路由到基準泳道服務,reviews v1 和 v3 服務間輪詢,展示的效果是評分為 0 或 1 随機。

Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

3.6.2 分支泳道

  1. 我們這邊通過浏覽器 mod-header 插件,來模拟入口流量請求頭中攜帶了 uid=100 的場景。
Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

2. 再點選重新整理按鈕,可以發現請求打到了分支泳道,流量泳道功能成功生效。

Kitex Proxyless 流量路由 配合 Istio 與 OpenTelemetry 實作全鍊路泳道

4. 總結與展望

至此我們已經基于 Kitex Proxyless 與 OpenTelemetry 實作了一個完整的全鍊路泳道,并且無需借助 Envoy sidecar,就能基于 Isito 标準治理規則 Spec,來為 Kitex 設定對應的路由規則了。

當然,除了滿足 流量路由 能力之外,Kitex Proxyless 也在持續疊代優化,滿足更多資料面治理能力需求。Proxyless 作為一種 ServiceMesh 資料面探索和實踐,除了能夠豐富網格資料面部署形态之外,也希望可以不斷打磨 Kitex,增強其在開源生态相容方面的能力,打造一個開放包容的微服務生态體系。

5. 相關項目連結

下面是該案例涉及的項目清單:

  • biz-demo:https://github.com/cloudwego/biz-demo
  • kitex:https://github.com/cloudwego/kitex
  • hertz:https://github.com/cloudwego/hertz
  • kitex-xds:https://github.com/kitex-contrib/xds
  • kitex-opentelemetry:https://github.com/kitex-contrib/obs-opentelemetry
  • hertz-opentelemetry:https://github.com/hertz-contrib/obs-opentelemetry

該完整案例已送出在 biz-demo (https://github.com/cloudwego/biz-demo) 倉庫中,感興趣的同學可以前往查閱。

biz-demo 會包含一些基于 CloudWeGo 技術棧且具備一定業務場景的完整 Demo,初衷是能夠為企業使用者在生産中使用提供有價值的參考,非常歡迎更多同學能夠參與到 CloudWeGo 相關場景與案例的貢獻中來,一起來做一些有意思的嘗試。