当您需要在多个服务间实现全链路的灰度发布时,您可以通过配置TrafficLabel来识别流量特征,将网关入口流量分为正常流量和灰度流量,灰度流量特征会在请求调用链经过的各个服务间进行传递,从而实现全链路灰度发布。本文通过一个Demo示例来介绍如何通过TrafficLabel能力来实现微服务的全链路灰度发布。

前提条件

背景信息

灰度发布有多种实现方式,例如使用服务网格ASM结合KubeVela实现渐进式灰度发布基于ASM完成蓝绿和灰度发布。以上两种灰度,偏重于单个服务的发布;背后的技术都是利用Istio原生提供的VirtualService标签路由和权重分流来实现的。

某些场景下,仅限于两个服务间的灰度不能满足需求,例如以下场景:Cart和Order同时发布了灰度版本。多个服务间灰度发布场景

这种情况下对业务进行功能灰度验证:因为入口流量分为正常流量和灰度流量,其中User服务需要对请求流量做流量特征识别,若是灰度流量则需要请求灰度的Cart。不再是简单的按流量比例灰度分发到后端不同的版本,而且灰度流量特征会在请求调用链经过的各个服务间进行传递。

ASM全链路灰度功能基于流量打标和标签路由功能,更多信息,请参见流量打标和标签路由

Demo介绍

Demo示例编排及相关配置,您可以通过此文件下载。

Demo的架构图如下:

灰度发布流
部署编排文件demo.yaml如下:
apiVersion: v1
kind: Service
metadata:
  name: spring-boot-istio-client
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 19090
  selector:
    app: spring-boot-istio-client
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-boot-istio-client
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-boot-istio-client
      version: base
  template:
    metadata:
      annotations:
        armsPilotAutoEnable: 'on'
        armsPilotCreateAppName: spring-boot-istio-client
      labels:
        app: spring-boot-istio-client
        version: base
    spec:
      containers:
        - name: spring-boot-istio-client
          image: registry.cn-hangzhou.aliyuncs.com/aliacs-app-catalog/spring-boot-istio-client:Abase
          imagePullPolicy: Always
          tty: true
          ports:
            - name: http
              protocol: TCP
              containerPort: 19090
---
apiVersion: v1
kind: Service
metadata:
  name: spring-boot-istio-server
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 18080
      targetPort: 18080
    - name: grpc
      port: 18888
      targetPort: 18888
  selector:
    app: spring-boot-istio-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-boot-istio-server
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-boot-istio-server
      version: base
  template:
    metadata:
      annotations:
        armsPilotAutoEnable: 'on'
        armsPilotCreateAppName: spring-boot-istio-server
      labels:
        app: spring-boot-istio-server
        version: base
    spec:
      containers:
        - name: spring-boot-istio-server
          image: registry.cn-hangzhou.aliyuncs.com/aliacs-app-catalog/spring-boot-istio-server:Bbase
          imagePullPolicy: Always
          tty: true
          ports:
            - name: http
              protocol: TCP
              containerPort: 18080
            - name: grpc
              protocol: TCP
              containerPort: 18888
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-boot-istio-client-gray
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-boot-istio-client
      version: gray
  template:
    metadata:
      annotations:
        armsPilotAutoEnable: 'on'
        armsPilotCreateAppName: spring-boot-istio-client
      labels:
        app: spring-boot-istio-client
        version: gray
    spec:
      containers:
        - name: spring-boot-istio-client
          image: registry.cn-hangzhou.aliyuncs.com/aliacs-app-catalog/spring-boot-istio-client:Agray
          imagePullPolicy: Always
          tty: true
          ports:
            - name: http
              protocol: TCP
              containerPort: 19090
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-boot-istio-server-gray
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-boot-istio-server
      version: gray
  template:
    metadata:
      annotations:
        armsPilotAutoEnable: 'on'
        armsPilotCreateAppName: spring-boot-istio-server
      labels:
        app: spring-boot-istio-server
        version: gray
    spec:
      containers:
      - name: spring-boot-istio-server
        image: registry.cn-hangzhou.aliyuncs.com/aliacs-app-catalog/spring-boot-istio-server:Bgray
        imagePullPolicy: Always
        tty: true
        ports:
         - name: http
           protocol: TCP
           containerPort: 18080
         - name: grpc
           protocol: TCP
           containerPort: 18888
本示例服务都为SpringBoot的Java应用,且开启了ARMS应用监控。具体操作,请参见应用性能监控
对应的Deployment的template.metadata下类似如下配置:
  template:
    metadata:
      annotations:
        armsPilotAutoEnable: 'on'
        armsPilotCreateAppName: spring-boot-istio-server

步骤一:在ACK集群下部署Demo微服务

执行以下命令,部署Demo。
kubectl apply -f demo.yaml

步骤二:配置简单路由

  1. 使用以下内容,创建名为istio-config.yaml的文件。
    apiVersion: networking.istio.io/v1beta1
    kind: Gateway
    metadata:
      name: simple-springboot-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
        - hosts:
            - "*"
          port:
            name: http
            number: 80
            protocol: HTTP
    ---
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: springboot-istio-client-vs
    spec:
      gateways:
      - simple-springboot-gateway
      hosts:
        - "*"
      http:
      - match:
        - uri:
           prefix: "/hello"
        route:
          - destination:
              host: spring-boot-istio-client
    ---
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: springboot-istio-server-vs
    spec:
      hosts:
        - spring-boot-istio-server
      http:
        - route:
            - destination:
                host: spring-boot-istio-server
    ---
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: springboot-istio-client-dr
    spec:
      host: spring-boot-istio-client
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
      subsets:
        - labels:
            version: base
          name: version-base
        - labels:
            version: gray
          name: version-gray
    ---
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: springboot-istio-server-dr
    spec:
      host: spring-boot-istio-server
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
      subsets:
        - labels:
            version: base
          name: version-base
        - labels:
            version: gray
          name: version-gray
  2. 执行以下命令,配置路由。
    kubectl --kubeconfig <ASM实例的kubeconfig文件> apply -f istio-config.yaml
  3. 验证服务访问是否可以连通。
    1. 通过ASM控制台获取网关的公网IP,执行以下命令。
      export ASM_GATEWAY_IP=xxx
    2. 执行以下命令,验证服务访问是否可以连通。
      while true; do curl -H'x-asm-prefer-tag: gray'  http://${ASM_GATEWAY_IP}/hello ; echo;sleep 1;done
      预期输出:
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      gateway->A->B都是负载均衡到Base和灰度版本实例,此时通过curl命令指定的x-asm-prefer-tag没有效果,需要配置TrafficLabel以及对应的标签路由后才能生效,istio-config.yaml下默认配置是简单的路由,VirtualService下路由配置未指定subset分组。

步骤三:配置TrafficLabel

  1. 使用以下内容,创建文件traffic_label_default.yaml
    ---
    apiVersion: istio.alibabacloud.com/v1beta1
    kind: TrafficLabel
    metadata:
      name: example1
      namespace: default
    spec:
      rules:
      - labels:
          - name: userdefinelabel1
            valueFrom:
            - $getContext(x-b3-traceid)
            - $localLabel
        attachTo:
        - opentracing
        # 表示生效的协议,空为都不生效,*为都生效
        protocols: "*"
      hosts: # 表示生效的服务
      - "*"
    
    
    ---
    apiVersion: istio.alibabacloud.com/v1beta1
    kind: TrafficLabel
    metadata:
      name: ingressgateway
      namespace: istio-system
    spec:
      hosts:
      - '*'
      rules:
      - attachTo:
        - opentracing
        labels:
        - name: userdefinelabel1
          valueFrom:
          - $getContext(x-b3-traceid)
          - $localLabel
        protocols: '*'
      workloadSelector:
        labels:
          app: istio-ingressgateway
                            
  2. 执行以下命令,使用ASM实例的kubeconfig进行部署。
    kubectl --kubeconfig <ASM实例的kubeconfig文件> apply -f traffic_label_default.yaml

    该TrafficLabel定义针对default下所有服务生效,也就是demo.yaml下部署的A和B服务。

    说明 因为本示例Demo对接了ARMS采用zipkin trace类型的traceId, 因此getContext参数为x-b3-traceid

步骤四:验证TrafficLabel路由

  1. 验证A->B是否符合预期,其中包括流到A的灰度流量打到灰度版本,以及Base流量打到Base版本。

    配置B服务的TrafficLabel路由b-vs-tf.yaml, 在A服务侧生效,对应如下模型:

    验证路由示意图
    1. 使用以下内容,创建文件b-vs-tf.yaml
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: springboot-istio-server-vs
      spec:
        hosts:
          - spring-boot-istio-server
        http:
          - route:
              - destination:
                  host: spring-boot-istio-server
                  subset: $userdefinelabel1
    2. 执行以下命令,在A服务侧生效。
      kubectl -f <ASM实例的kubeconfig文件> apply -f b-vs-tf.yaml
    3. 执行以下命令,验证流到A的灰度流量打到灰度版本。
      while true; do curl -H'x-asm-prefer-tag: version-gray'  http://${ASM_GATEWAY_IP}/hello ; echo;sleep 1;done

      预期输出:

      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
    4. 执行以下命令,验证流到A的Base流量打到Base版本。
      while true; do curl -H'x-asm-prefer-tag: version-base'  http://${ASM_GATEWAY_IP}/hello ; echo;sleep 1;done

      预期输出:

      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Gray --> gRPC B-Base.
      --> HTTP A-Gray --> gRPC B-Base.
      --> HTTP A-Gray --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Gray --> gRPC B-Base.
      --> HTTP A-Gray --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Gray --> gRPC B-Base.
      说明 入口流量访问A服务并未流转到指定版本,需要再配置A服务的TrafficLabel路由。
  2. 验证ASM网关->A是否符合预期,其中包括入口请求灰度流量打到A的灰度版本,Base流量打到A的Base版本,并传递到B服务。
    配置A服务的TrafficLabel路由a-vs-tf.yaml,在ASM网关侧生效。
    说明 ASM网关也支持TrafficLabel路由。
    1. 使用以下示例,创建文件a-vs-tf.yaml
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: springboot-istio-client-vs
      spec:
        gateways:
        - simple-springboot-gateway
        hosts:
          - "*"
        http:
        - match:
          - uri:
             prefix: "/hello"
          route:
            - destination:
                host: spring-boot-istio-client
                subset: $userdefinelabel1
    2. 执行以下命令,在ASM网关侧生效。
      kubectl -f <ASM实例的kubeconfig文件> apply -f a-vs-tf.yaml
    3. 执行以下命令,验证入口请求灰度流量打到A的灰度版本。
       while true; do curl -H'x-asm-prefer-tag: version-gray'  http://${ASM_GATEWAY_IP}/hello ; echo;sleep 1;done
      预期输出:
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
    4. 执行以下命令,验证Base流量打到A的Base版本,并传递到B服务。
      while true; do curl -H'x-asm-prefer-tag: version-base'  http://${ASM_GATEWAY_IP}/hello ; echo;sleep 1;done
      预期输出:
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
      --> HTTP A-Base --> gRPC B-Base.
  3. 验证TrafficLabel路由对应的权重分流是否符合预期。
    1. 使用以下示例,创建名为a-vs-tf-10-90.yaml文件。
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: springboot-istio-client-vs
      spec:
        gateways:
        - simple-springboot-gateway
        hosts:
          - "*"
        http:
        - match:
          - uri:
             prefix: "/hello"
          route:
            - destination:
                host: spring-boot-istio-client
                subset: $userdefinelabel1
              weight: 10
            - destination:
                host: spring-boot-istio-client
                subset: version-base
              weight: 90
      说明 无论Gray或Base什么流量,只有10%打到对应的分组,其余打到version-base。
    2. 执行以下命令,在网关侧生效。
      kubectl --kubeconfig <ASM实例的kubeconfig文件> apply -f a-vs-tf-10-90.yaml
    3. 执行以下命令,验证灰度流量。
       while true; do curl -H'x-asm-prefer-tag: version-gray'  http://${ASM_GATEWAY_IP}/hello ; echo;sleep 1;done
      预期输出:
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.
      --> HTTP A-Gray --> gRPC B-Gray.
      --> HTTP A-Base --> gRPC B-Gray.

泳道模式,流量特征不传递

某些场景下,您可能希望开启类似泳道模式,也就是不传递上下文流量特征(即流量颜色标记,红色表示Gray流量,蓝色表示Base流量),出口流量标记采用本地Label,对应以下模型:泳道模式示意

此场景中,您只需修改TrafficLabel定义,去除$getContext(x-b3-traceid)关闭流量标签的传递,将颜色标记从$localLabel获取即可。

cat traffic_label_default_swimlane.yaml示例如下:
apiVersion: istio.alibabacloud.com/v1beta1
kind: TrafficLabel
metadata:
  name: example1
  namespace: default
spec:
  rules:
  - labels:
      - name: userdefinelabel1
        valueFrom:
        - $localLabel
    attachTo:
    - opentracing
    # 表示生效的协议,空为都不生效,*为都生效。
    protocols: "*"
  hosts: # 表示生效的服务。
  - "*"

ASM网关侧的流量灰度

如上文中的a-vs-tf.yaml,配置入口流量<gatewayIP>/hello接口的灰度,对入口请求有一定的要求:要求流量标识通过x-asm-prefer-tag指定流量Tag,如上测试是curl -H 'x-asm-prefer-tag: xxx'手动指定的。

实际业务场景下,客户端App或者真实用户通过浏览器访问未必会设置该Header,这种情形下,您可以通过ASM网关的自定义Header功能以及Lua插件的能力,实现将业务场景的灰度映射到x-asm-prefer-tag Header, 进行标准化处理。

例如您可以通过EnvoyFilter设置“使用iPhone13的用户”为灰度流量,示例如下。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  labels:
    provider: "asm"
    asm-system: "true"
  name: gateway-lua-filter-add-x-asm-prefer-tag-header
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      proxy:
        proxyVersion: "^1.*"
      context: GATEWAY
      listener:
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch:
      operation: INSERT_BEFORE
      value:
       name: envoy.lua
       typed_config:
         "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
         inlineCode: |
              function envoy_on_request(request_handle)
                local user_agent = request_handle:headers():get("user-agent")
                request_handle:logInfo("user_agent:"..user_agent)
                if string.match(user_agent,"^.*iPhone13.*") then
                    request_handle:headers():add("x-asm-prefer-tag","version-gray")
                else
                   request_handle:headers():add("x-asm-prefer-tag","version-base")
                end
              end
              function envoy_on_response(response_handle)
              end

FAQ

为什么全链路灰度功能没有生效?

全链路灰度功能生效的前提是应用的Trace能力生效,本文的SpringCloud服务采用ARMS无侵入方式接入Trace,若测试结果不符合预期,请确认是否正确开启了应用性能监控,您可以通过以下方式检查是否开启应用性能监控:

登录链路追踪Tracing Analysis控制台,在控制台左侧导航栏单击全局拓扑。在全局拓扑页面可以看到调用链ingressgateway -> springcloud-istio-client -> springcloud-istio-server,说明开启应用性能监控成功。全局拓扑

若您是在部署demo服务后开启的应用性能监控,您需要在开启应用性能监控后,重新部署demo服务。关于开启应用监控的具体操作,请参见应用性能监控