结合泳道与哈希打标实现基于用户身份的灰度测试

服务网格 ASM(Service Mesh)支持将应用的多个版本或者特征隔离成一个独立的运行环境(即泳道),然后通过设置泳道规则,将满足规则的请求流量路由到目标版本或特征的应用上。在生产环境中,开发者可能会希望使用泳道对稳定版本和灰度版本进行隔离,并根据用户身份路由至不同的泳道中。具体来说,您可能希望指定一部分特定身份的用户路由至灰度版本进行测试,其余用户则通过权重的方式随机匹配一定数量的请求路由至灰度版本。本文将介绍如何结合泳道和哈希打标来实现按用户身份的灰度测试。

前提条件

操作步骤

本示例场景将创建三个应用,调用链如下图。

  • mocka,包含v1版本。

  • mockb,包含v1版本。

  • mockc,包含v1版本和v2版本。

image

其中应用通过x-user-id请求头来标识用户身份,同时应用之间也会透传此请求头。本场景将演示以下内容:

  • x-user-id: jason时,请求流向新版本应用。

  • 对于其余用户,根据x-user-id哈希,并按照哈希结果将指定比例的用户路由到新版本。

步骤一:部署示例应用

  1. 使用以下内容创建sample.yaml。

    展开查看YAML内容

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mocka-v1
      labels:
        app: mocka
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mocka
          version: v1
          ASM_TRAFFIC_TAG: v1
      template:
        metadata:
          labels:
            app: mocka
            version: v1
            ASM_TRAFFIC_TAG: v1
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v1
            - name: app
              value: mocka
            - name: upstream_url
              value: "http://mockb:8000/"
            ports:
            - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockb-v1
      labels:
        app: mockb
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockb
          version: v1
          ASM_TRAFFIC_TAG: v1
      template:
        metadata:
          labels:
            app: mockb
            version: v1
            ASM_TRAFFIC_TAG: v1
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v1
            - name: app
              value: mockb
            - name: upstream_url
              value: "http://mockc:8000/"
            ports:
            - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockc-v1
      namespace: default
      labels:
        app: mockc
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockc
          version: v1
          ASM_TRAFFIC_TAG: v1
      template:
        metadata:
          labels:
            app: mockc
            version: v1
            ASM_TRAFFIC_TAG: v1
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v1
            - name: app
              value: mockc
            ports:
            - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockc-v2
      namespace: default
      labels:
        app: mockc
        version: v2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockc
          version: v2
          ASM_TRAFFIC_TAG: v2
      template:
        metadata:
          labels:
            app: mockc
            version: v2
            ASM_TRAFFIC_TAG: v2
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v2
            - name: app
              value: mockc
            ports:
            - containerPort: 8000
  2. 使用数据面集群的kubeconfig,执行以下命令,部署示例应用。

    kubectl apply -f sample.yaml

步骤二:创建网关规则

使用以下内容创建ingressgateway且命名空间为istio-system的网关规则。具体操作,请参见管理网关规则

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: ingressgateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - '*'

步骤三:创建泳道组和泳道

  1. 创建泳道组。

    1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理

    2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择流量管理中心 > 流量泳道

    3. 流量泳道页面,单击创建泳道组,在创建泳道组面板,配置相关信息,然后单击确定

      配置项

      说明

      泳道组名称

      本示例配置为canary

      入口网关

      选择ingressgateway

      泳道模式

      选择宽松模式

      调用链路上下文透传方式

      选择透传trace id

      trace id请求头

      本示例配置为x-user-id。

      引流请求头

      用于网关根据请求头内容向不同泳道引流及泳道上下文保持,可任意指定。本示例配置为x-asm-prefer-tag

      泳道服务

      选择目标Kubernetes集群和default命名空间,在下方列表中选中mockamockbmockc服务,单击移动图标,添加目标服务到已选择区域。

  2. 创建s1和s2泳道,并分别绑定v1和v2版本。

    1. 流量泳道页面的流量规则定义区域,单击创建泳道

    2. 创建泳道对话框,配置相关信息,然后单击确定

      配置项

      说明

      泳道名称

      分别配置为s1和s2

      配置服务标签

      标签名称:选择ASM_TRAFFIC_TAG

      标签值:两条泳道分别选择v1v2

      添加服务

      s1泳道:选择mocka(default)mockb(default)mockc(default)

      s2泳道:选择mockc(default)

      创建s1泳道的示例图如下:

      image

      两条泳道创建完成后,示例效果如下:

      image

      说明

      默认情况下,您在泳道组中创建的第一条泳道将被设定为基线泳道。您可以修改基线泳道,当流量发往其他泳道中不存在的服务时,通过回退机制将请求转发至基线泳道。具体操作,请参见在宽松模式中修改基线泳道

  3. 创建泳道对应的引流规则。

    1. 使用以下内容,为泳道创建网关引流规则,该引流规则可以分为三部分:

      1. 包含x-user-id: jason的请求流向s2泳道,并为请求加入x-asm-prefer-tag: s2请求头用于标识该请求流向s2泳道。

      2. 包含x-asm-prefer-tag: s2的请求流向s2泳道。

      3. 包含x-asm-prefer-tag: s1的请求流向s1泳道。

      展开查看YAML内容

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: swimlane-ingress-vs
        namespace: istio-system
      spec:
        gateways:
        - istio-system/ingressgateway
        hosts:
        - '*'
        http:
        # 路由规则 1:包含 x-user-id: jason 的请求流向 s2 泳道
        # 并为请求加入 x-asm-prefer-tag: s2 请求头用于标识该请求流向 s2 泳道
        - match:
          - headers:
              x-user-id:
                exact: jason
            uri:
              exact: /
          name: r2
          route:
          - destination:
              host: mocka.default.svc.cluster.local
              subset: s2
            fallback:
              target:
                host: mocka.default.svc.cluster.local
                subset: s1
            headers:
              request:
                set:
                  x-asm-prefer-tag: s2
        # 路由规则 2:包含 x-asm-prefer-tag: s2 的请求流向 s2 泳道
        - match:
          - headers:
              x-asm-prefer-tag:
                exact: s2
            uri:
              exact: /
          name: r2
          route:
          - destination:
              host: mocka.default.svc.cluster.local
              subset: s2
            # 当 s2 泳道中不包含 mocka 应用时,将请求转发至 s1 泳道中的 mocka
            fallback:
              target:
                host: mocka.default.svc.cluster.local
                subset: s1
        # 路由规则 3:包含 x-asm-prefer-tag: s1 的请求流向 s1 泳道
        - match:
          - headers:
              x-asm-prefer-tag:
                exact: s1
            uri:
              exact: /
          name: r1
          route:
          - destination:
              host: mocka.default.svc.cluster.local
              subset: s1

步骤四:部署哈希打标插件

  1. 使用以下内容,创建wasm.yaml。

    apiVersion: extensions.istio.io/v1alpha1
    kind: WasmPlugin
    metadata:
      name: hash-tagging
      namespace: istio-system
    spec:
      imagePullPolicy: IfNotPresent 
      selector:
        matchLabels:
          istio: ingressgateway
      url: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-wasm-hash-tagging:v1.22.6.2-g72656ba-aliyun 
      phase: AUTHN
      pluginConfig:
        rules:
          - header: x-user-id
            modulo: 100
            tagHeader: x-asm-prefer-tag
            policies:
              # 20% 的用户的请求流量会路由至泳道 s2
              - range: 20
                tagValue: s2
              # 80% 的用户的请求流量会路由至泳道 s1
              - range: 100
                tagValue: s1
  2. 使用ASM实例的kubeconfig,执行以下命令,部署打标插件。

    kubectl apply -f wasm.yaml

步骤五:验证

  1. 执行以下命令,配置入口网关地址的临时环境变量。

    export GATEWAY_ADDRESS=`kubectl get svc -n istio-system | grep istio-ingressgateway | awk '{print $4}'`
  2. 执行以下命令,以Jason身份访问应用。

    curl ${GATEWAY_ADDRESS} -H 'x-user-id: jason'

    预期输出:

    -> mocka(version: v1, ip: 10.0.0.15)-> mockb(version: v1, ip: 10.0.0.130)-> mockc(version: v2, ip: 10.0.0.133)%     

    可以看到,请求直接路由到mockc应用的v2版本。

  3. 执行以下命令,指定随机用户路由到新版本。

     for i in 'bob' 'stacy' 'jessie' 'vance' 'jack'; do curl ${GATEWAY_ADDRESS} -H "x-user-id: $i";echo "   user $i requested"; done

    预期输出:

    -> mocka(version: v1, ip: 10.0.0.15)-> mockb(version: v1, ip: 10.0.0.130)-> mockc(version: v1, ip: 10.0.0.131)   user bob requested
    -> mocka(version: v1, ip: 10.0.0.15)-> mockb(version: v1, ip: 10.0.0.130)-> mockc(version: v1, ip: 10.0.0.131)   user stacy requested
    -> mocka(version: v1, ip: 10.0.0.15)-> mockb(version: v1, ip: 10.0.0.130)-> mockc(version: v2, ip: 10.0.0.133)   user jessie requested
    -> mocka(version: v1, ip: 10.0.0.15)-> mockb(version: v1, ip: 10.0.0.130)-> mockc(version: v1, ip: 10.0.0.131)   user vance requested
    -> mocka(version: v1, ip: 10.0.0.15)-> mockb(version: v1, ip: 10.0.0.130)-> mockc(version: v2, ip: 10.0.0.133)   user jack requested

    可以看到,用户Jessie和Jack的请求直接路由到了mockc应用的v2版本,其他用户路由到了v1版本。