簡化后端服務(wù)的 A/B/n 測試
A/B/n 測試,或拆分測試,是一種測試過程,用戶流量通過該過程隨機分布在應(yīng)用程序(或應(yīng)用程序組件)的兩個或多個版本之間。評估業(yè)務(wù)指標(biāo)以確定獲勝版本——產(chǎn)生更大利潤或業(yè)務(wù)價值的版本。例如,購物應(yīng)用程序可能使用收入和用戶參與度作為業(yè)務(wù)指標(biāo)。
我們專注于部署在 Kubernetes 中的后端服務(wù)的 A/B/n 測試。例如,在下圖中,前端可能是一個基于Node.js的在線商店。它依靠后端推薦服務(wù)向用戶提供產(chǎn)品建議。我們有興趣對推薦服務(wù)的多個版本進(jìn)行 A/B/n 測試。在圖中,我們有兩個版本,v1(當(dāng)前或默認(rèn)版本)和v2 (候選版本)。
計算業(yè)務(wù)指標(biāo)
A/B/n 測試依賴于對業(yè)務(wù)指標(biāo)的評估。這些指標(biāo)衡量應(yīng)用程序特定版本的好處或價值。例如,在在線商店應(yīng)用程序中,相關(guān)指標(biāo)可能是銷售收入或用戶參與度。業(yè)務(wù)指標(biāo)是特定于應(yīng)用程序的;它們不能由基礎(chǔ)設(shè)施計算,而必須由應(yīng)用程序本身計算。當(dāng)銷售收入等指標(biāo)由應(yīng)用程序組件(如上面的前端在線商店)計算時,間接包括了后端推薦引擎的貢獻(xiàn)。但是,前端組件無法將度量值與特定版本的后端相關(guān)聯(lián),因為它通常不知道使用了哪個版本的后端服務(wù)。
Iter8 SDK
為了正確地將指標(biāo)歸因于后端版本,前端有必要知道每個用戶會話正在使用哪個后端版本。為了協(xié)助前端服務(wù),可以使用 Iter8 SDK。Iter8是一個開源的Kubernetes發(fā)布優(yōu)化器,可以幫助您在幾秒鐘內(nèi)開始測試 Kubernetes 應(yīng)用程序。使用 Iter8,您可以執(zhí)行各種實驗,例如 SLO 驗證、金絲雀測試、混沌注入測試,以及現(xiàn)在的 A/B/n 測試。Iter8 SDK 提供了兩個接口:
- Lookup(component, user_session),它標(biāo)識一個組件的版本,調(diào)用者應(yīng)該使用該版本向給定的用戶會話發(fā)送請求。
- WriteMetric(metric_value, component, user_session),它將度量與組件的推薦版本相關(guān)聯(lián)。
下面的序列圖顯示了我們的購物應(yīng)用程序?qū)@些接口的使用:
為響應(yīng)用戶請求,前端組件調(diào)用Lookup()以確定要使用哪個版本的后端組件。Lookup()返回固定數(shù)量的用戶定義曲目標(biāo)簽之一。Lookup()保證為相同的用戶會話推薦相同的曲目標(biāo)簽,確保后續(xù)對后端的調(diào)用將發(fā)送到相同的版本。然后,前端服務(wù)將其請求發(fā)送到推薦的后端,使用軌道作為路由的關(guān)鍵。當(dāng)前端稍后為用戶會話計算業(yè)務(wù)指標(biāo)時,它可以安全地與推薦的后端版本相關(guān)聯(lián)。WriteMetric()可用于執(zhí)行此操作,從而無需前端跟蹤到后端版本的映射。
由于Lookup()返回一組固定的軌道標(biāo)簽中的一個,前端服務(wù)必須配置為將流量路由到一組固定的服務(wù)——每個軌道一個。要跟蹤的版本的映射隨時間變化,并且作為候選版本部署的一部分使用已部署的 Kubernetes 對象上的標(biāo)簽完成。
顯然,SDK 的引入對應(yīng)用程序前端組件的開發(fā)人員提出了要求。此外,該方法依賴于前端應(yīng)用程序可以根據(jù)一組軌道標(biāo)識符路由后端請求的假設(shè)。這意味著對應(yīng)用程序配置的要求。我們通過示例演示使用 Iter8 SDK 是多么容易。
Iter8 SDK 使用 gRPC 實現(xiàn)。該協(xié)議緩沖區(qū)文檔中描述了這些接口??梢詮闹袨楦鞣N語言生成特定于語言的代碼。接口本身由 A/B/n 服務(wù)實現(xiàn)。
A/B/n 測試的應(yīng)用程序開發(fā)
我們將考慮一個簡單的兩層應(yīng)用程序。用 Node.js 編寫的前端代表一個在線商店。 用Go編寫的后端表示由前端調(diào)用的推薦引擎,用于向用戶展示替代產(chǎn)品??梢栽诖颂幷业酱藨?yīng)用程序的完整源代碼。
前端組件支持兩個接口:
- /getRecommendation要求產(chǎn)品推薦。
- /buy完成購買。
作為購買(請求)的副作用,/buy計算用戶會話的業(yè)務(wù)指標(biāo)。接口的實現(xiàn)/getRecommendation依賴于后端推薦服務(wù)。我們想在此后端推薦服務(wù)上運行 A/B/n 測試。
Iter8 SDK 使用 gRPC 實現(xiàn)。這些接口在協(xié)議緩沖區(qū)文檔中進(jìn)行了描述??梢詮闹袨楦鞣N語言生成特定于語言的代碼。我們生成的代碼在示例應(yīng)用程序中,可以直接復(fù)制以用于您自己的應(yīng)用程序。
要使用 Iter8 SDK,需要 gRPC 和生成的庫:
并實例化一個客戶端:
此客戶端用于兩種用例:調(diào)用后端服務(wù)之前和寫入指標(biāo)值時。
調(diào)用后端服務(wù)
在調(diào)用后端推薦服務(wù)之前,需要Lookup()先調(diào)用該方法。返回的軌道標(biāo)識符應(yīng)該用作索引來選擇發(fā)送請求的路線。在我們的示例前端中,這可以按如下方式實現(xiàn):
在此實現(xiàn)中,用戶會話是從請求標(biāo)頭中提取的X-User。請注意,如果與 Iter8 SDK 交互出現(xiàn)任何問題,將選擇默認(rèn)路由??梢栽诖颂幷业酵暾氖纠a。
編寫指標(biāo)
當(dāng)/buy調(diào)用接口時,表示銷售完成,計算業(yè)務(wù)指標(biāo)。在我們的示例應(yīng)用程序中,一個隨機值被分配給指標(biāo)sample_metric:
而已!不需要進(jìn)一步的更改,無論運行了多少 A/B/n 測試——或者沒有。
可以在此處 (Node.js)找到完整的示例前端代碼。Python和Go中提供了替代實現(xiàn)。與節(jié)點示例一樣,生成的代碼可以直接復(fù)制到您自己的應(yīng)用程序中。
為 A/B/n 測試部署應(yīng)用程序
在部署將要進(jìn)行 A/B/n 測試的應(yīng)用程序組件時,唯一的要求是添加 Iter8 A/B/n 服務(wù)用于標(biāo)識組件版本的標(biāo)簽。在我們的例子中,我們計劃只測試后端推薦組件。
Iter8 SDK要求每個版本部署的資源實例中至少有一個包含以下標(biāo)簽:
- app.kubernetes.io/name:應(yīng)用程序(組件)名稱。
- app.kubernetes.io/version: 版本名稱。
- iter8-tools/track:要用于此版本的曲目標(biāo)簽。
- iter8-tools/abn: 指示版本是否準(zhǔn)備好接收流量的標(biāo)志。
作為說明,可以使用以下命令手動部署示例應(yīng)用程序。首先,部署前端在線商店組件:
接下來,部署示例后端推薦組件的當(dāng)前或默認(rèn)版本。我們將所需的標(biāo)簽添加到部署對象中,因為我們希望在此組件上運行 A/B/n 測試。
最后,部署 Iter8 服務(wù)(如果尚未部署):
運行 A/B/n 測試
我們現(xiàn)在準(zhǔn)備運行 A/B 測試,比較后端推薦組件的當(dāng)前部署的默認(rèn)版本和新的候選版本。運行測試有兩個步驟。第一步是部署組件的一個或多個候選版本。使用我們的示例應(yīng)用程序,我們展示了部署后端推薦引擎的候選版本所需的步驟。待候選版本完全部署后,Iter8 Service會開始將分配給它的track label發(fā)送給前端服務(wù)。作為響應(yīng),前端商店將開始向新版本的推薦引擎發(fā)送請求。第二步是啟動 Iter8 實驗來評估收集到的指標(biāo)。我們展示了一個多循環(huán)實驗——一個定期執(zhí)行直到被刪除的實驗。在每次執(zhí)行時,
部署候選版本
可以以任何方式部署候選版本——手動(如我們所示)或使用 CI 工作流。如上所述,必須將所需標(biāo)簽添加到至少一個 Kubernetes 資源對象。
在我們的示例應(yīng)用程序中部署后端推薦服務(wù)的候選版本:
在部署候選版本時,必須注意確保候選版本在前端向其發(fā)送任何請求之前已完全初始化。這可以通過僅在候選版本完全初始化后設(shè)置 iter8-tools/abn 標(biāo)簽來確保。一旦初始化,Iter8 A/B/n 服務(wù)將開始提供響應(yīng)Lookup()請求的版本。
這些步驟如下圖所示。最初,僅部署默認(rèn)版本 v1 并接收來自前端的所有流量。
當(dāng)部署候選版本 v2 時,前端繼續(xù)將所有請求發(fā)送到默認(rèn)版本。
一旦候選版本準(zhǔn)備好接收流量,例如,當(dāng) pod 為 時Ready,設(shè)置標(biāo)簽iter8.tools/abn。這會觸發(fā) Iter8 服務(wù)開始向前端推薦它,而前端又開始向兩個版本發(fā)送請求。在我們的示例應(yīng)用程序中:kubectl label deployment backend-candidate iter8.tools/abn=true
實際上,測試取決于應(yīng)用到前端服務(wù)的用戶負(fù)載。在本教程中,我們使用將請求發(fā)送到存儲端點的腳本來應(yīng)用負(fù)載,/getRecommendation并/buy.使用將本地請求轉(zhuǎn)發(fā)到集群:kubectl port-forward svc/frontend 8090:8090
并為不同的用戶生成負(fù)載;例如,對于用戶foo和foobar:
啟動 Iter8 實驗
啟動 Iter8 實驗以定期讀取通過 編寫的業(yè)務(wù)指標(biāo)WriteMetric()??梢允褂妙A(yù)定義的 abnmetrics 任務(wù):
此命令啟動 Iter8 實驗,該實驗運行預(yù)定義的 abnmetrics 任務(wù)以讀取在默認(rèn)命名空間中運行的后端應(yīng)用程序組件的記錄指標(biāo)。使用 cronjob runner 表示實驗將根據(jù) cronjobSchedule 定期運行(在本例中為每分鐘一次)。實驗結(jié)果將隨時間更新。
檢查實驗結(jié)果并決定是否推廣候選版本。第一份報告將在實驗任務(wù)第一次運行后(大約一分鐘)可用。示例報告如下:
提升贏家
在推廣候選版本時,必須注意確保沒有用戶流量意外發(fā)送到正在升級或刪除的版本。任何可用于執(zhí)行促銷的方法都應(yīng)包括以下步驟。還顯示了示例應(yīng)用程序的手動步驟。
最初,默認(rèn)版本和候選版本都從前端接收請求。
首先,iter8.tools/abn從與默認(rèn)版本關(guān)聯(lián)的資源中取消設(shè)置標(biāo)簽。這會在轉(zhuǎn)換期間禁用到默認(rèn)版本的流量——Iter8 SDK 接口Lookup()將從其推薦后端列表中刪除默認(rèn)軌道:
接下來,使用新版本重新部署與默認(rèn)軌道關(guān)聯(lián)的對象。
更新的對象準(zhǔn)備就緒后,添加iter8.tools/abn指示它已準(zhǔn)備好接收流量的標(biāo)簽。
此時,默認(rèn)和候選軌道標(biāo)簽都與相同的后端版本相關(guān)聯(lián)。現(xiàn)在可以刪除候選版本。為此,取消設(shè)置iter8.tools/abn標(biāo)簽以終止到候選資源的流量:
最后,刪除候選資源。
最后的想法
我們探討了進(jìn)行 A/B/n 測試的一些挑戰(zhàn),尤其是應(yīng)用程序的后端服務(wù)。關(guān)鍵挑戰(zhàn)涉及前端組件計算業(yè)務(wù)指標(biāo)無法正確地將它們與有助于其計算的后端版本相關(guān)聯(lián)。Iter8 SDK 使前端能夠正確地建立這種關(guān)聯(lián)。它通過提供一個查找接口來實現(xiàn)這一點,該接口允許前端服務(wù)識別后端服務(wù)的版本以在處理用戶請求時使用。這樣,它可以可靠地將業(yè)務(wù)指標(biāo)分配給后端版本。我們展示了使用 Iter8 SDK 修改前端服務(wù)和運行 A/B/n 測試是多么容易。
只需要幾行額外的代碼就可以對前端進(jìn)行一次性更改。啟用候選版本進(jìn)行測試只需要添加一些標(biāo)簽。
試用本教程后,使用您自己的應(yīng)用程序進(jìn)行試用。