本文主要用于介紹在API后端服務版本迭代過程中,新版本服務正式發布前通過API網關進行灰度發布,A/B Test的通用方法實踐。該方法的核心是通過配置后端路由插件來確保可以控制服務升級對用戶造成的影響。
一、概述
灰度發布是指在API的新、舊版本間平滑過渡的一種發布方式。API網關客戶在應用迭代過程中會不斷有新版本API發布,在新版本正式發布前,可以使用灰度流量控制先進行小規模驗證,將升級帶來的影響限定在指定的用戶范圍內可以最大程度上保障線上業務的穩定運行,通過收集使用體驗的數據,對應用新版本的功能、性能、穩定性等指標進行評判,然后再全量升級。
常見的灰度發布和A/B Test場景中,需要根據最終用戶的信息進行區分,借助API網關的路由插件功能,能夠從API HTTP Request請求中識別出和最終用戶相關的參數,如userId,并進行邏輯條件判斷,從而將請求分流到不同的后端服務中。
二、典型場景
API網關后端路由插件支持的灰度分流條件
使用API網關的APP進行灰度:獲得授權的APP事實上代表身份不同的API調用者,這種情況下當有兩個以上授權APP的時候,可以根據調用者APP將請求轉發到不同后端。假設,某個API授權給了多個應用,在API后端服務有升級的情況下,為了控制升級對線上可能存在的未知影響,我們可以參照本文3.1 給出的后端路由插件配置,將其中兩個應用的請求路由到新版后端服務。
基于JWT的灰度: 當API綁定JWT插件時,API網關可以從JWT中解析沒有加密過的用戶身份相關的claim,比如用戶userId, 支持按照從 Token 中解析得到的參數路由到不同后端,從而實現灰度發布。比如用戶可以參考本文 3.2 節給出的插件配置,通過限制userId字段的尾數,實現20%的用戶請求到新版本服務。
基于HTTP Request 內的用戶參數進行灰度,當Query | Form | Header 中存在和用戶身份直接相關的參數時,我們就可以直接利用該請求參數轉發請求到不同后端。比如針對一部分使用老版本客戶端的用戶返回特定的提示信息,提示用戶升級客戶端,可以參考3.3節的說明進行配置。
針對特定訪問IP段進行灰度:可以利用不同的請求源IP地址段進行后端路由,從而實現灰度發布。比如有的時候我們為了更高效的測試,需要在內測環境和生產環境使用完全相同的前端請求代碼,可以參考3.4節的說明實現不同環境訪問不同后端。
三、配置過程
本節是針對上述四種場景給出對應的后端路由插件配置,需要應用灰度發布的API綁定相應插件后,當請求符合條件時,請求會被轉發到插件配置的后端, 否則將被正常訪問API后端服務。當有多個灰度場景時,可以參考文檔后端路由在一個后端路由插件中配置多條路由。在插件條件表達式中使用的參數要按照文檔參數與條件表達式的使用的描述在插件中定義。
3.1 根據調用者APP進行灰度發布。如果用戶需要指定授權給某些應用的API調用請求進入灰度環境,可以利用系統參數 CaAppId 來配置路由條件,配置如下
---
routes:
- name: appGrey
condition: "$CaAppId = 4534463 or $CaAppId = 86734572"
backend:
type: HTTP
address: "http://example-test.alicloudapi.com:8080"
path: "/web/xxx"
method: GET
timeout: 7000
3.2 根據JWT中的自定義claim進行灰度發布。假設JWT中有名為userId的自定義claim,并且userId的尾數均勻分布。則按照如下的3個步驟進行配置,可以對20%的用戶進行灰度。
用戶需要先在API上綁定配置如下的JWT插件
---
parameter: X-Token # 從指定的參數中獲取JWT, 對應API的參數
parameterLocation: header # API為映射模式時可選, API為透傳模式下必填, 用于指定JWT的讀取位置, 僅支持`query`,`header`
claimParameters: # claims參數轉換, 網關會將jwt claims映射為后端參數
- claimName: userId # claim名稱,支持公共和私有
parameterName: userId # 映射后參數名稱
location: query # 映射后的參數位置, 支持`query,header,path,formData`
#
# `Json Web Key`的`Public Key`
jwk:
kty: RSA
e: AQAB
kid: showJwt
alg: RS256
n: gNHI8tm3lnsdCi09SrBPs9-Oau7Z1SFhIEOT2h5AJ49FSJA0XEyU4OadtV70BLIEy94dzcUK8f0e477AVoUO0RZdcXjztFtpJnA1Ktrzn9zAmKcXb2IuKXrBKkQStcKqoSbBlR84mDElp_gxfNqpmoLy0q08rkmjh1utd8E_S4QMDDaFtQ68ggJcDY-oX5FSiVidKNrKagEzQKpk5SgJFE8wpJOkW-YKouqLsL5lFyqnkgn7J3MvDqEBKqgiCY-zXYaxnkLNfkrAt7jTe4b4a2PiKD0-bHIZwzd2NVhuLGwx4pB1tFL51E-KeewZhTsoUbQ3v_ZerZ2_630WOH7IWQ
把灰度條件要用到的字段 userId 作為自定義 claim 加到JWT的payload部分,參考文檔基于JWT的token認證, 發起API請求的時候在請求header部分增加一個 X-Token 的頭,值為使用私鑰JWK對JWT進行簽名生成的ID Token。
再綁定一個如下配置的后端路由插件,可以把userId尾數為“0”和 “1”的用戶請求轉發到指定的后端地址,從而實現對20%的用戶進行灰度發布
---
parameters:
userId: "Token:userId"
routes:
- name: userIdGrey
condition: "$userId like '%0' or $userId like '%1'"
backend:
type: HTTP
address: "http://example-test.alicloudapi.com:8080"
path: "/web/xxx"
method: GET
timeout: 7000
3.3 根據客戶端版本進行灰度發布。例如,需要灰度的API請求示例如下:
http://example-cn-hangzhou.alicloudapi.com/testJwtShow?clientVersion=1.9
需要對clientVersion小于2.0.5的版本進行灰度,配置如下:
---
parameters:
clientVersion: "Query:clientVersion"
routes:
- name: oldVersionClient
condition: "$clientVersion < '2.0.5'"
backend:
type: "MOCK"
statusCode: 400
3.4 根據請求源IP進行灰度發布。當一些服務內測時,生產環境和內測環境的有訪問不同后端的需求,可以根據請求源IP分發到不同版本后端。如下配置,可以利用API網關系統參數CaClientIp 將源IP地址段為 106.11.XX.XX/24 的請求轉發到指定的后端。
routes:
- name: InternalTesting
condition: "$CaClientIp in_cidr '106.11.XX.XX/24'"
backend:
type: HTTP
address: "http://example-test.alicloudapi.com:8080"
path: "/web/xxx"
method: GET
timeout: 7000