作者:至簡
軟體是以持續迭代的方式去不斷演進的,某種程度上,我們并不擔心軟體不完善,但擔心軟體的迭代速度太慢而影響了完善的速度,在分布式軟體領域,如何快速、安全地驗證新的軟體版本一直是大家所關心并探索的,服務網格(Service Mesh)的出現將這個領域的探索推向了新的高度,“泳道”這一概念在分布式軟體領域并非新詞,只不過,這次我們是以服務網格為基礎技術去構建,充分發揮云原生技術天然具備靈活治理流量的優勢,
本文分享了阿里云內部所沉淀的全鏈路流量打標與路由的能力,做出服務網格技術新體驗的同時,很好地兌現了服務網格的新價值,
概念與場景
圖 1 以 Istio 官方所提供的 Bookinfo 示例程式為例示例說明了使用場景中的關鍵概念,其中紫色的圓邊方框代表了 Envoy,圖中所有泳道的性質是一樣的,不同的命名只是為了區分細分場景或用戶,
? 基線(baseline):指業務所有服務都部署到了這一環境中,基線可以來自真實的生產環境,也可以專為開發作業而構建的與生產環境完全獨立的環境,
? 流量泳道(traffic lane):代表了一個與基線環境相隔離的軟環境,通過給機器(即 Kubernetes 中的 Pod)打標簽的方法,將機器加入到該泳道中,顯然,加入泳道的機器在網路層面與基線中的機器是互通的,
? 流量回退(traffic fallback):泳道中所部署的服務數量并非要求與基線環境完全一致,當泳道中并不存在呼叫鏈中所依賴的其他服務時,流量需要回退至基線環境,進一步在必要的時候返流泳道,比如,圖 1 中 dev1 泳道中并不存在 productpage 服務所依賴的 reviews 服務,因此需要讓流量回退到基線中的 reviews 服務(圖中深藍色線所示),緊接著基線中的 reviews 服務需要將流量打回 dev1 泳道中的 ratings 服務,
? 流量標識透傳(traffic label passthrough):所有服務邊上的 Sidecar 都需要有能力將調入請求中所攜帶的流量標簽自動放到由這一請求所分叉出的每一個調出請求中去,以便實作全鏈路流量標識透傳和按流量標識路由,否則泳道與基線間的流量無法來回穿梭,
? 入口服務(entrance service):指流量進入泳道時所觸達的第一個服務,圖 1 中代表服務的圖形在左邊框邊上打上三角形標識的就代表它是一個入口服務,

圖1
泳道技術可以運用于如下場景:
? 單個服務的日常開發或多個服務間的日常開發聯調,開發者建立泳道,將增加了新功能的服務部署到泳道中,基于流量的特征通過定義規則將測驗流量引入泳道中進行驗證,由于泳道只需部署被測驗服務的新版本,省去了搭建全鏈路測驗環境的麻煩,在這一場景中,需要注意測驗流量所存在的資料落盤問題,處理好開發與聯調程序中所留下的臟資料,
? 全鏈路灰度,對于涉及重大功能上線中的多個服務,可以通過泳道以全鏈路灰度的方式做更為全面的功能驗證,全鏈路功能驗收通過后,再將服務的新版本發布至基線,
? 關鍵業務重保,類似零售場景的業務(比如 POS 機收銀),我們不希望因為軟體的故障而引發巨大的輿情,這時可以通過泳道對業務流量進行隔離實作重保,
技術實作
流量打標方案與實作
在運用泳道技術時,根據流量打標的位置不同而存在三種不同的方案,值得指出,雖然方案有所不同,但就服務網格而言的技術實作是完全一致的,將方案羅列出來是為了幫助讀者更好地理解,
圖 2 示例說明了方案一,在這個方案中,流量進入服務網格的 Ingress 網關之前還有一級網關,我們姑且稱之為 API 網關(比如,Nginx),通常 API 網關可以根據流量的特征,在轉發收到的請求前先加上額外的頭,從而完成對流量的打標動作,圖中對于特定的流量將增加名為 x-asm-traffic-lane: dev1 的 HTTP 頭,代表需要將流量打到 dev1 泳道中,在這個方案,服務網格中的 Envoy 無需有任何流量打標動作,

圖 2
圖 3 示例說明了方案二,本方案中,客戶端的流量直接打到服務網格的 Ingress 網關上,由 Ingress 網關根據流量的特征通過 Istio 原生的 VirtualService 匹配規則識別出后,在轉發請求前加上名為 x-asm-traffic-lane 的 HTTP 頭,隨后將流量路由到相應的泳道,

圖 3
圖 4 示例說明了方案三,本質上,這一方案與方案二是完全一樣的,同樣通過 Istio 原生的 VirtualService 匹配規則識別出相應的流量后加上名為 x-asm-traffic-lane 的 HTTP 頭,唯一的不同在于,方案二中的 Envoy 的角色是 Ingress,而方案三中 Envoy 的角色是 Sidecar,

圖 4
流量一旦完成打標后,由服務網格中的每一個 Envoy 基于流量標和控制面下發的配置做全鏈路的標透傳和按標路由,
流量標識透傳
圖 5 示例說明了服務網格中一個服務與邊上的 Envoy (Sidecar)間的流量細節,

圖 5
從 Envoy 的視角來看,包含對流入和流出兩種流量的轉發,I1 是流入的流量,收到后將轉發給本地的 Svc A;O1 是流出的流量(因為處理 l1 的需要而去呼叫別的服務所引發),收到后將轉發給外部的被呼叫的服務,流入與流出只與請求(request)相關,與請求對應的回應(response)沒有關系,顯然,一個流入的請求可能導致有多個流出的請求發生(即“分叉”),這完全取決于 Svc A 的具體業務邏輯,
泳道技術所需解決的核心點在于,當流入的流量打上了相應的標簽后,如何讓由其所分叉出的每一個流出的流量都帶上同樣的標簽,我們采用的方案是結合鏈路追蹤技術(比如,OpenTelemetry)去解決,鏈路追蹤技術是通過 traceId 去唯一標識一條呼叫鏈樹,為根請求分配并帶上全網唯一的 traceId 后,之后由其所分叉出的所有新呼叫都得帶上值完全一樣的 HTTP 頭,換句話說服務開發者需要在編程的程序中確保這一頭被傳播到后續的服務呼叫中(比如,呼叫 OpenTelemetry 的 SDK 完成頭傳播),換句話說,運用泳道技術的前提是需要每個服務都采用了鏈路追蹤技術,作為微服務構架的最佳實踐之一這一先決條件很容易滿足,回到圖 5,Svc A 收到 I2 請求并對之處理時需要發起 O1 呼叫,此時需要確保 I2 中的 traceId 頭傳播到 O1 請求中,這是 Svc A 的開發者需要特別注意的一個細節,
一旦服務網格中所有服務的請求都帶上了 traceId,那么通過 Envoy 實作全鏈路的流量標透傳就是非常簡單的事了,大體分這幾大步驟:
? Envoy 內部構建一張映射表,記錄 traceId 與流量標的映射,比如,圖 5 所示的流量標是放在 x-asm-traffic-lane 這一 HTTP 頭中的,x-asm-traffic-lane: dev1 代表的流量標是 dev1,x-asm-traffic-lane: canary 代表的流量標是 canary,
? 當請求 I1 進到 Envoy 時,Envoy 基于請求中所帶的 traceId 和流量標,在映射表中增加一條映射記錄,
? Envoy 對于收到的每一個 O1 請求,基于請求中的 traceId 從映射表中找到對應的流量標并將之增加到 O2 請求中再轉發出去,
通過服務網格基于 traceId 打標的技術方案的好處在于,將流量打標的動作和流量標傳遞做到完全與服務解耦,將這一能力下沉到本來擅長流量治理的服務網格中,讓流量調度的靈動性得以進一步解鎖,
流量標識與 traceld 的定義
我們在 Istio 已有 CR 的基礎上,新增了 TrafficLabel 這個全新的 CRD,選擇新增而非直接對 VirtualService 做擴展的原因是,VirtualService 的設計之初是應用維度的,當一個業務復雜到全鏈路有很多應用需要放到泳道中時,就得對每個應用的 VirtualService 進行變更,背后所導致的時效性和可操作性會是一個問題,擴展 VirtualService 去實作的另一種思路是,讓 VirtualService 具備配置全域規則的能力,而這就要用到規則的合并機制,這一點從實操層面也有問題,Istio 社區對于多個 VirtualService 進行合并的需求有不少討論,目前只在網關上支持合并,對于 Sidecar 則不提供這一能力,原因在于擔心合并的順序不同而導致故障,
圖 6 示例說明了如何使用 TrafficLabel 這一 CR 在 istio-system 根命名空間定義全域有效的流量打標方法,其中定義了名為 x-asm-traffic-lane 的標簽,作為 HTTP 請求的頭用于存放流量標識(比如,dev1、dev2、canary 等),以及 traceId 基于 x-request-id 獲得,用戶可以根據自己所選型的鏈路追蹤系統的具體實作加以設定,圖中設定為從 x-request-id 頭中獲得,是因為 Envoy 實作了通過 x-request-id 做全網鏈路唯一性標識的功能,采用 x-request-id 作為映射表鍵意味著,我們可以直接用 Istio 開源社區所提供的 Bookinfo 示例程式去演示泳道的效果,因為 Bookinfo 中的所有服務都做了 x-request-id 頭從調入請求到調出請求的傳播,
apiVersion: istio.alibabacloud.com/v1beta1
kind: TrafficLabel
metadata:
name: global-traffic-label
spec:
rules:
- labels:
- name: x-asm-traffic-lane
protocols: "http"
traceIdHeader: x-request-id
hosts:
- "*"
圖6
按流量標路由
為了支持按流量標路由,需要對 Istio 的 VirtualService 做擴展,讓 destination 欄位支持用 $x-asm-traffic-lane 這樣的變數指定流量的目的地,如下圖 7 所示,換句話說,包含 x-asm-traffic-lane: dev2 頭的流量會打到 dev2 這個泳道中,背后其實是使用 DestinationRule 定義的名為 dev2 的 subset,如圖 8 所示,請注意,圖 7 中 VirtualService 中的 $x-asm-traffic-lane 這一名稱要與圖 6 中 TrafficLabel 內所定義的名保持一致,
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: dev2
route:
- destination:
host: reviews
subset: dev2
fallback:
case: noinstances|notavailable
target:
host: reviews
subset: baseline
headers:
request:
set:
x-asm-traffic-lane: dev2
- route:
- destination:
host: reviews
subset: $x-asm-traffic-lane
fallback:
case: noinstances|notavailable
target:
host: reviews
subset: baseline
- route:
- destination:
host: reviews
subset: baseline
圖7
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- labels:
version: v2
name: baseline
- labels:
version: v3
name: dev2
圖8
從圖 8 中 DestinationRule 的定義不難看出,除了 baseline 外只定義了 dev2 這個泳道,圖 7 則是對應情形下的 VirtualService 定義,兩者對應的使用場景正是圖 1 中的基線和 dev2 泳道,
產品實作
在云原生的大技術背景之下,易用性被放到了聚光燈之下,我們深刻理解背后意味著什么,為此,在設計產品的互動時,努力清空自己所知、站在用戶所面臨的場景下去思考和優化,在功能性和易用性之間努力做好平衡,
在用戶使用泳道前,我們認為他已構建了一個包含所有服務在內的基線環境,在 K8s 中,基線環境通常被部署于特定的命名空間中以便更好地運維和管理其中的服務,用戶創建泳道時,只需提供泳道名稱,本節接下來的內容以創建名為 dev2 的泳道展開,

泳道創建好后,需要將服務發布到泳道之中,由于所發布的服務已存于基線環境中并創建了 K8s Service 資源,所以泳道中發布服務其實是創建對應服務下的一個 Deployment,直觀的理解就是創建已有服務的另一個軟體版本,不難想到,這一發布動作包含確認基線版本,實體數和容器鏡像地址,

當服務發布到泳道中后,需要通過泳道的服務串列確保所有服務都正常啟動,此時泳道中并沒有流量進入,需要通過配置引流規則才能將基線中的流量引入泳道中,

引流規則可以基于 HTTP 頭、URI、Cookie 的特征去配置,方便我們精確地選擇被測流量進入泳道,下圖的規則是指將 HTTP 頭 end-user 的值為 dev2 的流量引導致 dev2 泳道中,配置規則的同時,需要正確指定入口服務,

引流規則應用后,即可在網頁上以 dev2 用戶名登錄,從而看到 dev2 泳道中服務所呈現的效果,下面兩圖分別示例了全基線和 dev2 泳道所看到的頁面效果,由于 dev2 泳道中并沒有部署 productpage 和 details 兩個服務,所以這兩個服務會回退為使用基線中的,而最終呈現的效果就是兩圖中 The Comedy of Errors 和 Book Details 兩塊的內容是完全一致的,


當服務發布到泳道中上線后,可以在泳道的服務串列中方便地查看各服務與基線版本的流量比對情況,幫助開發者更好地了解泳道中服務的運行情況,

此外,通過服務拓撲圖也可以清晰地看到,dev2 泳道中服務的呼叫情況(圖中的 lane-dev2),

總結與展望
我們所探索的基于服務網格的泳道技術,讓開發者能秒級創建隔離環境用于開發測驗或業務重保,通過精確的引流規則將“爆炸半徑”控制到最小,很好地兌現了云原生服務網格技術的新體驗和新價值,
接下來,我們將進一步以場景化的方式打通泳道和版本灰度的功能,讓用戶能基于直覺使用好這些功能,功能層面,我們將進一步完善泳道中支持的協議,比如 RocketMQ、Dubbo 3.0 等,通過豐富泳道技術的應用場景去最大程度地發揮其價值,
最后,我們將持續以 Service Mesh as Infra 的理念去構建微服務架構的現代化服務治理平臺,和業界伙伴一同加速這一新技術的發展和推廣,
作者介紹:李云(花名:至簡),阿里云服務網格混合云產品技術負責人,2018 年開始在阿里集團帶領團隊從事服務網格技術的發展與建設作業,在 QCon 做過多次云原生與服務網格的技術分享,
發布云原生技術最新資訊、匯集云原生技術最全內容,定期舉辦云原生活動、直播,阿里產品及用戶最佳實踐發布,與你并肩探索云原生技術點滴,分享你需要的云原生內容,
關注【阿里巴巴云原生】公眾號,獲取更多云原生實時資訊!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/455593.html
標籤:其他
