API网关灰度发布最佳实践

本文主要用于介绍在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