Kitex Proxyless 之流量路由:配合 Istio 與 OpenTelemetry 實(shí)現(xiàn)全鏈路泳道

1. 引言
1.1 Kitex Proxyless
Kitex 是字節(jié)跳動(dòng)開源的 Golang RPC 框架,目前已經(jīng)原生支持了 xDS 標(biāo)準(zhǔn)協(xié)議, 支持以 Proxyless 的方式被 ServiceMesh 統(tǒng)一納管。
- 詳細(xì)設(shè)計(jì)見: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 簡(jiǎn)單來說就是 Kitex 服務(wù)能夠不借助 envoy sidecar 直接與 istiod 交互,基于 xDS 協(xié)議動(dòng)態(tài)獲取控制面下發(fā)的服務(wù)治理規(guī)則,并轉(zhuǎn)換為 Kitex 對(duì)應(yīng)規(guī)則來實(shí)現(xiàn)一些服務(wù)治理功能(例如本文的重點(diǎn):流量路由)。
基于 Kitex Proxyless,讓我們實(shí)現(xiàn) Kitex 能夠無需代理就可以被 ServiceMesh 統(tǒng)一管理,進(jìn)而實(shí)現(xiàn)多種部署模式下的 治理規(guī)則 Spec、治理控制面、治理下發(fā)協(xié)議、異構(gòu)數(shù)據(jù)治理能力 的統(tǒng)一。

1.2 流量路由
流量路由是指,能夠?qū)⒘髁?strong>根據(jù)其自身特定的元數(shù)據(jù)標(biāo)識(shí)路由到指定目的地。
流量路由屬于服務(wù)治理中比較核心的能力之一,也是 Kitex Proxyless 優(yōu)先支持的場(chǎng)景之一。
Kitex 基于 xDS 實(shí)現(xiàn) 流量路由 的方案大致如下:

具體流程:
- 增加一個(gè)xDS Router MW 來負(fù)責(zé) Pick Cluster(路由),并 watch 目標(biāo)服務(wù)的 LDS 及 RDS。
- 感知 LDS 變化,并提取目標(biāo)服務(wù)的 LDS 中的 Filter Chain 及其 inline RDS。
- 感知 RDS 變化,根據(jù)
VirtualHost和ServiceName來匹配(支持前綴、后綴、精確、通配),獲取目標(biāo)服務(wù)的路由配置。 - 遍歷處理匹配到的 RDS 中的路由規(guī)則,路由規(guī)則主要分為兩部分(參考:路由規(guī)范定義):
- Match(支持前綴、后綴、精確、通配等),目前版本我們支持以下兩種即可
- Path(必須項(xiàng)):從
rpcinfo提取Method進(jìn)行匹配。 - HeaderMatcher(可選項(xiàng)):從
metainfo中提取對(duì)應(yīng)元數(shù)據(jù) KeyValue,并進(jìn)行匹配。
- Route:
Cluster: 標(biāo)準(zhǔn) Cluster。
WeightedClusters(權(quán)重路由): MW 內(nèi)根據(jù)權(quán)重來選擇 cluster。
將選擇到的 Cluster 寫入
EndpointInfo.Tag,用于之后的服務(wù)發(fā)現(xiàn)。
可以看到,流量路由其實(shí)是一個(gè)根據(jù)一定規(guī)則選擇對(duì)應(yīng) SubCluster 的流程。
2. 全鏈路泳道
基于流量路由能力,我們可以延伸出很多使用場(chǎng)景,如:A/B 測(cè)試、金絲雀發(fā)布、藍(lán)綠發(fā)布等等,以及本文重點(diǎn):全鏈路泳道 。
全鏈路泳道可以理解成是對(duì)一組服務(wù)實(shí)例按照一定方式進(jìn)行拆分(例如部署環(huán)境),并基于全鏈路灰度路由能力,讓流量能夠精準(zhǔn)按照規(guī)則在指定服務(wù)實(shí)例泳道中流動(dòng)(邏輯上如同游泳場(chǎng)中的泳道)。
在 Istio 中我們一般會(huì)通過 DestinationRule 的 subset 對(duì)實(shí)例進(jìn)行分組,將一個(gè)服務(wù)拆分成不同子集(例如:按照版本、區(qū)域等屬性拆分),然后配合 VirtualService 來定義對(duì)應(yīng)的路由規(guī)則,將流量路由到對(duì)應(yīng)子集中,從而完成泳道中的單跳路由能力。
不過單單只有流量路由能力,還不足以實(shí)現(xiàn)全鏈路泳道 , 因?yàn)楫?dāng)一個(gè)請(qǐng)求跨越多個(gè)服務(wù)的時(shí)候,我們需要有一個(gè)比較好的機(jī)制能夠準(zhǔn)確識(shí)別出該流量,并基于這個(gè)特征來為每一跳流量配置路由規(guī)則。
如下圖所示:假設(shè)我們要實(shí)現(xiàn)一個(gè)用戶的請(qǐng)求能夠精確灰度到 service-b 的 v1 版本。最先想到的做法可能是所有請(qǐng)求都帶上 ??uid = 100?? 的請(qǐng)求頭,然后配置對(duì)應(yīng) VirtualService 來根據(jù) header 里的 ??uid = 100?? 匹配。

但這樣的做法有幾個(gè)明顯的缺點(diǎn):
- 不夠通用: 以具體某個(gè)業(yè)務(wù)屬性標(biāo)識(shí)(如:uid)作為流量路由匹配規(guī)則,我們需要將這個(gè)業(yè)務(wù)屬性手動(dòng)在全鏈路里透?jìng)?,這本身對(duì)業(yè)務(wù)侵入性較大,需要業(yè)務(wù)配合改造。并且當(dāng)我們要使用其他業(yè)務(wù)屬性的時(shí)候,又需要全鏈路業(yè)務(wù)都改造一遍,可想而知,是非常不通用的做法。
- 路由規(guī)則容易頻繁變動(dòng),容易造成規(guī)則臃腫: 以具體某個(gè)業(yè)務(wù)屬性標(biāo)識(shí)(如:uid)作為流量路由匹配規(guī)則,假設(shè)我們要換一個(gè)業(yè)務(wù)屬性,或者給其他用戶設(shè)置路由規(guī)則的時(shí)候,得去改造原有的路由規(guī)則,或者針對(duì)不同業(yè)務(wù)屬性重復(fù)定義多套路由規(guī)則,很容易就會(huì)造成路由臃腫,以至于難以維護(hù)。
因此,要實(shí)現(xiàn)全鏈路的流量路由統(tǒng)一,我們還需要額外借助一個(gè)更通用的 流量染色 與 染色標(biāo)識(shí)全鏈路透?jìng)?/strong> 能力。
2.1 流量染色
流量染色是指對(duì)請(qǐng)求流量打上特殊標(biāo)識(shí),并在整個(gè)請(qǐng)求鏈路中攜帶這個(gè)標(biāo)識(shí),而所謂的全鏈路泳道,就是整個(gè)鏈路基于統(tǒng)一的灰度流量染色標(biāo)識(shí)來設(shè)置流量路由規(guī)則,使得流量能夠精準(zhǔn)控制在不同泳道中。
通常我們會(huì)在網(wǎng)關(guān)層進(jìn)行流量染色,通常會(huì)根據(jù)原始請(qǐng)求中的元數(shù)據(jù),來進(jìn)行一定規(guī)則(條件、比例)轉(zhuǎn)換成對(duì)應(yīng)的染色標(biāo)識(shí)。
- 按條件染色:當(dāng)請(qǐng)求元數(shù)據(jù)滿足一定條件之后,就給當(dāng)前請(qǐng)求打上染色標(biāo)識(shí),如:請(qǐng)求頭中
uid=100、cookie 匹配等等。 - 按比例染色:按照一定比例,給請(qǐng)求打上染色標(biāo)識(shí)。
有了一套統(tǒng)一的流量染色機(jī)制之后,我們配置路由規(guī)則的時(shí)候,就不需要關(guān)心具體的業(yè)務(wù)屬性標(biāo)識(shí)了,只需要根據(jù) 染色標(biāo)識(shí) 來配置即可。
將具體的業(yè)務(wù)屬性抽象成條件染色規(guī)則,使其更通用,即使業(yè)務(wù)屬性發(fā)生了變化,路由規(guī)則也無需再頻繁變動(dòng)了。
2.2 染色標(biāo)識(shí)全鏈路透?jìng)?/h3>染色標(biāo)識(shí)通常會(huì)依靠 Tracing Baggage 來透?jìng)?,Baggage 是用于在整個(gè)鏈路中傳遞業(yè)務(wù)自定義 KV 屬性,例如傳遞流量染色標(biāo)識(shí)、傳遞 AccountID 等業(yè)務(wù)標(biāo)識(shí)等等。
染色標(biāo)識(shí)通常會(huì)依靠 Tracing Baggage 來透?jìng)?,Baggage 是用于在整個(gè)鏈路中傳遞業(yè)務(wù)自定義 KV 屬性,例如傳遞流量染色標(biāo)識(shí)、傳遞 AccountID 等業(yè)務(wù)標(biāo)識(shí)等等。

要實(shí)現(xiàn)流量染色標(biāo)識(shí)在全鏈路透?jìng)?,我們通常?huì)借助 Tracing Baggage 機(jī)制,在全鏈路中傳遞對(duì)應(yīng)染色標(biāo)識(shí),大部分 Tracing 框架都支持 Baggage 概念機(jī)能力,如:OpenTelemetry、Skywalking、Jaeger 等等。
有了一套通用的全鏈路透?jìng)鳈C(jī)制,業(yè)務(wù)方就只需要接入一遍 tracing 即可,無需每次業(yè)務(wù)屬性標(biāo)識(shí)發(fā)生變化就配合改造一次。
下面會(huì)借助一個(gè)具體的工程案例介紹,來介紹并演示如何基于 Kitex Proxyless 和 OpenTelemetry Baggage 實(shí)現(xiàn)全鏈路泳道功能。
3. 案例介紹:Bookinfo
該案例是使用 Hertz、Kitex 重寫經(jīng)典的 Istio Bookinfo 項(xiàng)目:
- 使用 istiod 來作為xDS server,作為 CRD 配置和下發(fā)的入口;
- 使用 wire 來實(shí)現(xiàn)依賴注入;
- 使用 opentelemetry 來實(shí)現(xiàn)全鏈路追蹤;
- 使用 Kitex-xds 和 opentelemetry baggage 來實(shí)現(xiàn)proxyless 模式下的全鏈路泳道;
- 使用 arco-design 和 react 實(shí)現(xiàn)一個(gè)Bookinfo UI。
3.1 架構(gòu)
整體架構(gòu)與 Bookinfo 保持一致,分為四個(gè)單獨(dú)的微服務(wù):
- ?
?productpage??. 這個(gè)微服務(wù)會(huì)調(diào)用details和reviews兩個(gè)微服務(wù); - ?
?details??. 這個(gè)微服務(wù)中包含了書籍的信息; - ?
?reviews??. 這個(gè)微服務(wù)中包含了書籍相關(guān)的評(píng)論。它還會(huì)調(diào)用ratings微服務(wù); - ?
?ratings??. 這個(gè)微服務(wù)中包含了由書籍評(píng)價(jià)組成的評(píng)級(jí)信息。
??reviews?? 微服務(wù)有 3 個(gè)版本:
- v1 版本會(huì)調(diào)用
ratings服務(wù),并使用 1 顆 ?? 顯示評(píng)分; - v2 版本會(huì)調(diào)用
ratings服務(wù),并使用 5 顆 ???????????? 顯示評(píng)分; - v3 版本不會(huì)調(diào)用
ratings服務(wù)。

3.2 泳道示意圖
整體區(qū)分成 2 個(gè)泳道:
- 基準(zhǔn)泳道 : 未被染色的流量會(huì)被路由到基準(zhǔn)泳道中。
- 分支泳道 : 被染色的流量會(huì)被路由到 reviews-v2 ->ratings-v2 的分支泳道中。

3.3 流量染色
網(wǎng)關(guān)統(tǒng)一負(fù)責(zé)對(duì)流量進(jìn)行染色,例如請(qǐng)求 header 中 uid=100 的流量都統(tǒng)一進(jìn)行染色,為請(qǐng)求攜帶上 ??env=dev?? 的 baggage 。

染色方式可以根據(jù)不同的網(wǎng)關(guān)實(shí)現(xiàn)具體選擇,例如當(dāng)我們選擇 istio ingress 作為網(wǎng)關(guān)的時(shí)候,我們可以借助 ??EnvoyFilter?? + ??Lua?? 的方式來編寫網(wǎng)關(guān)染色規(guī)則。
3.4 為服務(wù)實(shí)例打標(biāo)
- 為對(duì)應(yīng)workload 打上對(duì)應(yīng) version 標(biāo)識(shí)。
以 reviews 為例,只需要給對(duì)應(yīng) pod 打上 version: v1 的 label 即可。
- 基于DestinationRule 為服務(wù)設(shè)置一系列的 subsets:
- Productpage: v1
- Reviews: v1、v2、v3
- Ratings: v1、v2
3.5 流量路由規(guī)則
網(wǎng)關(guān)已經(jīng)將請(qǐng)求頭中攜帶了 uid=100 的流量進(jìn)行了染色,自動(dòng)帶上了 ??env=dev?? 的 baggage,因此我們只需要根據(jù) header 進(jìn)行路由匹配即可,下面是具體的路由規(guī)則配置示例:
3.6 查看效果
3.6.1 基準(zhǔn)泳道
入口流量請(qǐng)求頭中不帶 uid=100 的請(qǐng)求,會(huì)自動(dòng)路由到基準(zhǔn)泳道服務(wù),reviews v1 和 v3 服務(wù)間輪詢,展示的效果是評(píng)分為 0 或 1 隨機(jī)。

3.6.2 分支泳道
- 我們這邊通過瀏覽器 mod-header 插件,來模擬入口流量請(qǐng)求頭中攜帶了
uid=100的場(chǎng)景。

2. 再點(diǎn)擊刷新按鈕,可以發(fā)現(xiàn)請(qǐng)求打到了分支泳道,流量泳道功能成功生效。

4. 總結(jié)與展望
至此我們已經(jīng)基于 Kitex Proxyless 與 OpenTelemetry 實(shí)現(xiàn)了一個(gè)完整的全鏈路泳道,并且無需借助 Envoy sidecar,就能基于 Isito 標(biāo)準(zhǔn)治理規(guī)則 Spec,來為 Kitex 設(shè)置對(duì)應(yīng)的路由規(guī)則了。
當(dāng)然,除了滿足 流量路由 能力之外,Kitex Proxyless 也在持續(xù)迭代優(yōu)化,滿足更多數(shù)據(jù)面治理能力需求。Proxyless 作為一種 ServiceMesh 數(shù)據(jù)面探索和實(shí)踐,除了能夠豐富網(wǎng)格數(shù)據(jù)面部署形態(tài)之外,也希望可以不斷打磨 Kitex,增強(qiáng)其在開源生態(tài)兼容方面的能力,打造一個(gè)開放包容的微服務(wù)生態(tài)體系。
5. 相關(guān)項(xiàng)目鏈接
下面是該案例涉及的項(xiàng)目清單:
- 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) 倉(cāng)庫(kù)中,感興趣的同學(xué)可以前往查閱。
biz-demo 會(huì)包含一些基于 CloudWeGo 技術(shù)棧且具備一定業(yè)務(wù)場(chǎng)景的完整 Demo,初衷是能夠?yàn)槠髽I(yè)用戶在生產(chǎn)中使用提供有價(jià)值的參考,非常歡迎更多同學(xué)能夠參與到 CloudWeGo 相關(guān)場(chǎng)景與案例的貢獻(xiàn)中來,一起來做一些有意思的嘗試。




































