Kruise Rollout 基于 Lua 腳本的可擴(kuò)展流量調(diào)度方案

前言
Cloud Native
Kruise Rollout[1]是 OpenKruise 社區(qū)開源的漸進(jìn)式交付框架。Kruise Rollout 支持配合流量和實(shí)例灰度的金絲雀發(fā)布、藍(lán)綠發(fā)布、A/B Testing 發(fā)布,以及發(fā)布過程能夠基于 Prometheus Metrics 指標(biāo)自動化分批與暫停,并提供旁路的無感對接、兼容已有的多種工作負(fù)載(Deployment、CloneSet、DaemonSet)。
目前 Kruise Rollout 新增了流量調(diào)度支持自定義資源的能力 ,從而更好的支持漸進(jìn)式發(fā)布中的流量調(diào)度。 本文將對 Kruise Rollout 所提出的方案進(jìn)行介紹。什么是漸進(jìn)式發(fā)布?
漸進(jìn)式發(fā)布(Progressive Delivery)是一種軟件部署和發(fā)布策略,旨在逐步將新版本或功能引入生產(chǎn)環(huán)境,以降低風(fēng)險(xiǎn)并確保系統(tǒng)的穩(wěn)定性。一些常見的漸進(jìn)式發(fā)布形式如下:
-
金絲雀發(fā)布: 在發(fā)布時(shí)會創(chuàng)建一個金絲雀版本的 Deployment 進(jìn)行驗(yàn)證,當(dāng)驗(yàn)證通過后,再進(jìn)行全量的工作負(fù)載升級,并刪除金絲雀版本的 Deployment。

-
A/B 測試: 按照一定的規(guī)則將用戶流量切分成 A、B 兩個不相交通路,并將導(dǎo)入不同版本的 Pod 實(shí)例進(jìn)行處理,以此來更好地觀察、對比或者灰度新版本能力。

為什么需要對網(wǎng)關(guān)資源提供支持?
Kruise Rollout 目前已經(jīng)對 Gateway API 提供了支持,那么為什么還需要對不同供應(yīng)商的網(wǎng)關(guān)資源提供支持呢?在解釋這個問題之前,我們先來簡單介紹一下 Gateway API。 當(dāng)前社區(qū)中不同的供應(yīng)商都有自己的網(wǎng)關(guān)資源,并提出了自己的標(biāo)準(zhǔn),而 Kubernetes 為了提供一個統(tǒng)一的網(wǎng)關(guān)資源標(biāo)準(zhǔn),構(gòu)建標(biāo)準(zhǔn)化的,獨(dú)立于供應(yīng)商的 API,提出了 Gateway API。目前,盡管 Gateway API 還處于開發(fā)階段,但已經(jīng)有很多項(xiàng)目表示支持或計(jì)劃支持 Gateway API。包括:
- Istio 是最流行的服務(wù)網(wǎng)格項(xiàng)目之一,Istio 1.9 版本計(jì)劃引入實(shí)驗(yàn)性的 Gateway API 支持。用戶可以通過 Gateway 和 HTTPRoute 資源來配置 Istio 的 Envoy 代理。
- Apache APISIX 是一個動態(tài)、實(shí)時(shí)、高性能的 API 網(wǎng)關(guān),APISIX 目前支持Gateway API 規(guī)范的 v1beta1 版本,用于其 Apache APISIX Ingress Controller。
- Kong 是一個為混合云和多云環(huán)境構(gòu)建的開源 API 網(wǎng)關(guān),Kong 在 Kong Kubernetes Ingress Controller (KIC) 以及 Kong Gateway Operator 中支持 Gateway API。
然而由于目前 Gateway API 并不能覆蓋供應(yīng)商所提出網(wǎng)關(guān)資源的所有功能,并且仍然有大量用戶使用供應(yīng)商提供的網(wǎng)關(guān)資源,雖然用戶可以通過開發(fā) Gateway API 對網(wǎng)關(guān)資源進(jìn)行適配,但這樣的工作量較大,所以僅僅為 Gateway API 提供支持是遠(yuǎn)遠(yuǎn)不夠的,盡管隨著 Gateway API 特性的不斷豐富,在未來,使用 Gateway API 將成為一種更加推薦的方式。因此,雖然 Kruise Rollout 目前已經(jīng)提供了對 Gateway API 的支持,如何對現(xiàn)有供應(yīng)商多種多樣的網(wǎng)關(guān)資源提供支持仍然是一個重要的問題。
如何兼容社區(qū)多樣的網(wǎng)關(guān)方案? 當(dāng)前社區(qū)中已經(jīng)存在許多廣泛使用的供應(yīng)商提供的網(wǎng)關(guān)資源,比如:Istio、Kong、Apisix 等,然而正如前文所述,這些資源的配置并沒有形成統(tǒng)一的標(biāo)準(zhǔn),因此無法設(shè)計(jì)出一套通用的代碼對資源進(jìn)行處理,這種情況給開發(fā)人員帶來了一些不便和挑戰(zhàn)。
argo-rollouts 與 flagger 兼容方案 為了能夠兼容更多的社區(qū)網(wǎng)關(guān)資源,一些方案被提出,例如 flagger、argo-rollouts 為每一種網(wǎng)關(guān)資源都提供了代碼實(shí)現(xiàn)。這些方案的實(shí)現(xiàn)相對簡單,但也存在一些問題:
-
面對大量的社區(qū)網(wǎng)關(guān)資源時(shí),需要消耗大量精力進(jìn)行實(shí)現(xiàn)
-
每次實(shí)現(xiàn)都需要重新進(jìn)行發(fā)布,自定義能力較差
-
在某些環(huán)境下用戶可能使用定制的網(wǎng)關(guān)資源,在這種情況下難以適配
-
每一種資源都有不同的配置規(guī)則,配置較為復(fù)雜
-
每添加一個新的網(wǎng)關(guān)資源都需要為其實(shí)現(xiàn)新的接口,維護(hù)難度較大
argo-rollouts 不同資源配置 因此,需要一種支持用戶定制,可以靈活插拔的實(shí)現(xiàn)方案,以適配社區(qū)以及用戶定制的多種多樣的網(wǎng)關(guān)資源,來滿足社區(qū)不同的用戶的需求,增強(qiáng) Kruise Rollout 的兼容性和擴(kuò)展性。 為此,我們提出了一種基于 Lua 腳本的網(wǎng)關(guān)資源可擴(kuò)展流量調(diào)度方案。
Kruise Rollout:基于 Lua 腳本的可擴(kuò)展流量調(diào)度
Cloud Native
Kruise Rollout 使用基于 Lua 腳本的網(wǎng)關(guān)資源定制方案,本方案通過調(diào)用 Lua 腳本根據(jù)發(fā)布策略和網(wǎng)關(guān)資源原始狀態(tài)來獲取并更新資源的期待工作狀態(tài)(狀態(tài)包含 spec、labels 以及 annotations),可以使用戶能夠輕松地適配和集成不同類型的網(wǎng)關(guān)資源,而無需修改現(xiàn)有的代碼和配置。
本方案對于網(wǎng)關(guān)資源的處理可以表示為上圖,整個過程可以描述為:
- 用戶定義了 Rollout 流量灰度規(guī)則、需要修改的資源等信息,開始金絲雀發(fā)布
- 根據(jù) Rollout 配置獲取指定資源
- 根據(jù)資源調(diào)用對應(yīng)的 Lua 腳本
- 將資源當(dāng)前狀態(tài)轉(zhuǎn)為字符串存入資源 annotation 中,并與發(fā)布策略一同輸入 Lua 腳本
- 利用 Lua 腳本根據(jù)當(dāng)前狀態(tài)和發(fā)布策略處理得到新狀態(tài)并更新資源
- 發(fā)布結(jié)束后,從 annotation 中獲取資源的原始狀態(tài)對資源進(jìn)行恢復(fù)
通過使用 Kruise Rollout,用戶可以:
- 定制處理網(wǎng)關(guān)資源的 Lua 腳本,可以自由的實(shí)現(xiàn)對資源的處理邏輯,為更多資源提供支持
- 利用一套通用的 Rollout 配置模版對不同資源進(jìn)行配置,降低配置的復(fù)雜性,方便用戶配置
同時(shí),Kruise Rollout 采用的方案僅需要添加 5 個新接口即可實(shí)現(xiàn)對多種多樣網(wǎng)關(guān)資源的支持。相比之下,其他方案例如 argo-rollouts 則為不同供應(yīng)商的網(wǎng)關(guān)資源提供了不同的接口,對于 Istio 和 Apisix 來說,argo-rollouts 分別提供了 14 個和 4 個新的接口,而且,該方案隨著對更多網(wǎng)關(guān)資源的支持,接口數(shù)量還會持續(xù)增長。相比之下,Kruise Rollout 并不需要為新的網(wǎng)關(guān)資源提供新的接口,這使得 Kruise Rollout 成為一種更簡潔、更易于維護(hù)的選擇,而不會增加過多的接口負(fù)擔(dān)。同時(shí),編寫 Lua 腳本相對于開發(fā) Gateway API 對網(wǎng)關(guān)資源進(jìn)行適配,可以大大減小開發(fā)人員的工作量。
以下展示了一個利用 Lua 腳本對 Istio DestinationRule 進(jìn)行處理的的示例。
-
首先定義 rollout 配置文件:
apiVersion: rollouts.kruise.io/v1alpha1kind: Rollout...spec:...trafficRoutings:- service: mockacreateCanaryService: false # 使用原有service,不創(chuàng)建新的canary servicenetworkRefs: # 需要控制的網(wǎng)關(guān)資源- apiVersion: networking.istio.io/v1alpha3kind: DestinationRulename: ds-demopatchPodTemplateMetadata:labels:version: canary # 為新版本pod打上label
2. 對 Istio DestinationRule 進(jìn)行處理的 Lua 腳本為:
local spec = obj.data.spec -- 獲取資源的spec,obj.data為資源的狀態(tài)信息local canary = {} -- 初始化一條指向新版本的canary路由規(guī)則canary.labels = {} -- 初始化canary路由規(guī)則的labelscanary.name = "canary" -- 定義canary路由規(guī)則名稱-- 循環(huán)處理rollout配置的新版本pod labelfor k, v in pairs(obj.patchPodMetadata.labels) docanary.labels[k] = v -- 向canary規(guī)則中加入pod labelendtable.insert(spec.subsets, canary) -- 向資源的spec.subsets中插入canary規(guī)則return obj.data -- 返回資源狀態(tài)
3. 處理完的 DestinationRule 為:
apiVersion: networking.istio.io/v1beta1kind: DestinationRulespec:...subsets:labels: # -+version: canary # |- Lua腳本處理后新插入的規(guī)則name: canary # -+labels:version: basename: version-base
Kruise Rollout 進(jìn)行 Istio 資源流量調(diào)度實(shí)踐
Cloud Native
接下來介紹一個利用我們所提出方案對 Istio 進(jìn)行支持的具體案例。
1. 首先部署如下圖所示的服務(wù)。該服務(wù)由以下幾部分構(gòu)成:
-
- 由 Ingress Gateway 作為外部流量網(wǎng)關(guān)
- 通過 VirtualService 和 DestinationRule 將流量調(diào)度至 nginx pod 中
-
利用 ConfigMap 作為主頁 nginx pod 的主頁
nginx 服務(wù)的 deployment 如下所示:
apiVersion: apps/v1kind: Deploymentmetadata:name: nginx-deploymentspec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxversion: basespec:containers:- name: nginximage: nginxports:- containerPort: 80volumeMounts:- name: html-volumemountPath: /usr/share/nginx/htmlvolumes:- name: html-volumeconfigMap:name: nginx-configmap-base # 掛載ConfigMap作為index
2. 創(chuàng)建 rollout 資源,配置發(fā)布規(guī)則,該 rollout 分為兩批發(fā)布:
-
- 第一批將 20% 的流量轉(zhuǎn)發(fā)至新發(fā)布的 pod 中
-
第二批將帶有 header version=canary 的流量轉(zhuǎn)發(fā)至新版本 pod 中
apiVersion: rollouts.kruise.io/v1alpha1kind: Rolloutmetadata:name: rollouts-demoannotations:rollouts.kruise.io/rolling-style: canaryspec:disabled: falseobjectRef:workloadRef:apiVersion: apps/v1kind: Deploymentname: nginx-deploymentstrategy:canary:steps:- weight: 20 # 第一批轉(zhuǎn)發(fā)20%的流量進(jìn)入新版本pod- replicas: 1 # 第二批將包含version=canary header的流量轉(zhuǎn)發(fā)入新版本podmatches:- headers:- type: Exactname: versionvalue: canarytrafficRoutings:- service: nginx-service # 舊版本pod使用的servicecreateCanaryService: false # 不創(chuàng)建新的canary service,新舊pod共用一個servicenetworkRefs: # 需要修改的網(wǎng)關(guān)資源- apiVersion: networking.istio.io/v1alpha3kind: VirtualServicename: nginx-vs- apiVersion: networking.istio.io/v1beta1kind: DestinationRulename: nginx-drpatchPodTemplateMetadata: # 為新版本pod打上version=canary的labellabels:version: canary
3. 修改 nginx 服務(wù) deployment 中掛載的 ConfigMap 開始金絲雀發(fā)布。
apiVersion: apps/v1kind: Deploymentmetadata:name: nginx-deploymentspec:...volumes:- name: html-volumeconfigMap:name: nginx-configmap-canary # 掛載新的ConfigMap作為index
4. 開始發(fā)布第一批,Kruise Rollout 自動調(diào)用定義的 Lua 腳本對 VirtualService 和 DestinationRule 資源進(jìn)行修改,進(jìn)行流量調(diào)度,將 20% 的流量轉(zhuǎn)發(fā)至新版本 pod 中,此時(shí)整個服務(wù)的流量表示為下圖所示:
5. 執(zhí)行命令 kubectl-kruise rollout approve rollout/rollouts-demo,開始發(fā)布第二批,Kruise Rollout 自動調(diào)用定義的 Lua 腳本對 VirtualService 和 DestinationRule 資源進(jìn)行修改,進(jìn)行流量調(diào)度,將包含 version=canary header 的流量轉(zhuǎn)發(fā)至新版本 pod 中,此時(shí)整個服務(wù)的流量表示為下圖所示:
6. 執(zhí)行命令 kubectl-kruise rollout approve rollout/rollouts-demo,發(fā)布結(jié)束,VirtualService 和 DestinationRule 資源恢復(fù)至發(fā)布前狀態(tài),所有流量路由至新版本 pod。
如何利用 Lua 腳本快速配置網(wǎng)關(guān)資源的流量調(diào)度
Cloud Native
在調(diào)用 Lua 腳本獲取資源狀態(tài)新狀態(tài)時(shí),Kruise Rollout 支持兩種 Lua 腳本調(diào)用方式,分別為:
- 自定義的 Lua 腳本:用戶自定義的,以 ConfigMap 的形式定義并在 Rollout 中調(diào)用
- 已發(fā)布的 Lua 腳本:社區(qū)通用的、已經(jīng)穩(wěn)定的 Lua 腳本,隨 Kruise Rollout 打包發(fā)布
Kruise Rollout 默認(rèn)首先查找本地是否存在已發(fā)布的 Lua 腳本,這些腳本通常需要設(shè)計(jì)測試案例進(jìn)行單元測試驗(yàn)證其可用性,具有更好的穩(wěn)定性。測試案例的格式如下所示,Kruise Rollout 利用 Lua 腳本根據(jù) rollout 中定義的發(fā)布策略對資源原始狀態(tài)進(jìn)行處理,得到發(fā)布過程中每一步的資源新狀態(tài),并與測試案例中 expected 中定義的期待狀態(tài)進(jìn)行對比,以驗(yàn)證 Lua 腳本是否按照預(yù)期工作。
rollout:# rollout配置original:# 資源的原始狀態(tài)expected:# 發(fā)布過程中資源的期待狀態(tài)
在資源的 Lua 腳本未發(fā)布的情況下,用戶還可以快速的通過在 ConfigMap 中配置 Lua 腳本的方式由 Kruise Rollout 調(diào)用從而對資源進(jìn)行處理。
apiVersion: v1kind: ConfigMapmetadata:name: kruise-rollout-configurationnamespace: kruise-rolloutdata:# 鍵以lua.traffic.routing.Kind.CRDGroup的形式命名"lua.traffic.routing.DestinationRule.networking.istio.io": |--- 定義Lua腳本local spec = obj.data.speclocal canary = {}canary.labels = {}canary.name = "canary"for k, v in pairs(obj.patchPodMetadata.labels) docanary.labels[k] = vendtable.insert(spec.subsets, canary)return obj.data
詳細(xì)的 Lua 腳本配置說明參見 Kruise Rollout 官網(wǎng)[2]。
未來規(guī)劃
Cloud Native
- 更多網(wǎng)關(guān)協(xié)議支持:Kruise Rollout 目前是以 Lua 腳本插件化的方式支持多類型的網(wǎng)關(guān)協(xié)議,我們后續(xù)會重點(diǎn)加大這方面的投入,但面對百花齊放的協(xié)議類型,單靠社區(qū) Maintainer 的單薄力量還遠(yuǎn)遠(yuǎn)不夠,希望更多的社區(qū)小伙伴加入我們,一起來不斷完善這方面的內(nèi)容。
-
全鏈路灰度支持: 全鏈路灰度是具有更加細(xì)粒度和全面的灰度發(fā)布模式,它涵蓋了應(yīng)用程序的所有服務(wù),而不止對單一的服務(wù)進(jìn)行灰度,可以更好的對新服務(wù)進(jìn)行模擬和測試。目前可以通過社區(qū)的網(wǎng)關(guān)資源如 Istio 進(jìn)行配置來實(shí)現(xiàn),但人工配置往往需要消耗較大的精力。我們將對這一部分進(jìn)行探索,從而實(shí)現(xiàn)對全鏈路灰度的支持。
