文档

基于自建Spring Cloud Gateway或Zuul网关实现全链路灰度

更新时间:

Spring Cloud Gateway和Zuul是两种常用的微服务架构中的API网关,它们均能实现路由转发和过滤器处理等功能。通过配置路由规则,可以将请求路由到灰度环境中,对灰度版本进行验证和测试。借助MSE提供的全链路灰度能力,您无需修改业务代码,即可实现端到端的全链路流量控制。本文介绍如何通过配置Spring Cloud Gateway或者Zuul网关实现全链路灰度。

前提条件

背景信息

本文通过模拟真实的调用链路为您演示MSE全链路灰度功能。您无需修改任何业务代码,只需要给入口应用设置流量规则,该流量的标签会通过链路透传到下一个灰度版本中。在每个应用的调用过程中,符合金丝雀条件的流量会优先调用对应的版本,如果没有对应版本则会自动切换回基线版本(即稳定版本)。

部署spring-cloud-gateway、spring-cloud-a、spring-cloud-b、spring-cloud-c这四个业务应用,以及注册中心Nacos Server。调用链路为:spring-cloud-gateway->A->B->C。

应用之间的调用既包含了Spring Cloud服务调用,也包含了Dubbo服务调用。

image

全链路灰度提供了给流量染色,并让灰度流量优先调用灰度节点的能力,帮助您进行可控的灰度验证,保障稳定性。

全链路灰度验证通常采用以下策略:

  • 直接调用现有线上流量的一小部分进行测试,通常按照百分比控制。

  • 按照特定规则筛选线上流量进行验证,如使用指定的Header或Cookie等。

本文将分别介绍上述两种策略配置方式,以便适应微服务架构中不同的灰度发布需求。

步骤一:将应用接入MSE微服务治理

  1. 将ACK微服务应用接入MSE治理中心,您可以选择您需要的方式实现应用接入。更多信息,请参见ACK微服务应用接入MSE治理中心

    为ACK命名空间中的应用开启MSE微服务治理

    1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

    2. 在左侧导航栏,选择治理中心 > 应用治理

    3. 应用列表页面,单击ACK应用接入

    4. ACK应用接入对话框中,进行配置,配置完成后,单击确定

      image

      配置项

      说明

      集群类型

      选择ACK集群或者ACK Serverless集群

      说明

      如果您尚未授权容器服务调用微服务引擎,则需要单击请授权进行授权。

      集群名称/ID

      选择接入MSE微服务治理的集群名称/ID,可通过关键词搜索。

      ack-onepilot

      显示ack-onepilot接入状态。如果您未安装ack-onepilot,单击ack-onepilot右侧的点击安装,安装完成后状态会显示为已安装

      说明
      • 该步骤接入的组件为ack-onepilot,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理查看详情。

      • ack-onepilot安装后会自动注入Agent,可能会导致应用启动耗时增加(10s内)。

      接入类型

      选择命名空间接入

      容器集群命名空间

      选择容器集群命名空间

      治理命名空间

      选择治理命名空间。在对应命名空间下重新部署现有应用或新创建的应用,均会接入到MSE微服务治理中。关于命名空间的相关信息,请参见微服务命名空间管理

    为单个应用开启MSE微服务治理

    1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

    2. 在左侧导航栏,选择治理中心 > 应用治理

    3. 应用列表页面,单击ACK应用接入

    4. ACK应用接入对话框中,进行配置,配置完成后,单击确定

      image

      配置项

      说明

      集群类型

      选择ACK集群或者ACK Serverless集群

      说明

      如果您尚未授权容器服务调用微服务引擎,则需要单击请授权进行授权。

      集群名称/ID

      选择接入MSE微服务治理的集群名称/ID,可通过关键词搜索。

      ack-onepilot

      显示ack-onepilot接入状态。如果您未安装ack-onepilot,单击ack-onepilot右侧的点击安装,安装完成后状态会显示为已安装

      说明
      • 该步骤接入的组件为ack-onepilot,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理查看详情。

      • ack-onepilot安装后会自动注入Agent,可能会导致应用启动耗时增加(10s内)。

      接入类型

      选择单个应用接入

      接入步骤

      按照接入步骤进行操作。

      Step 1:进入集群工作负载-无状态应用页面,切换到应用的命名空间下

      Step 2:找到所接入的应用,点击「查看Yaml」

      Step 3:按以下格式编辑Labels,完成后点击「更新」

      spec:
        template:
          metadata:
            labels:
              # 填写“on”表示开启接入,需加上双引号
              msePilotAutoEnable: "on"
              # 填写接入到的治理命名空间,值不存在可自动新建
              mseNamespace: 202401
              # 填写接入MSE的实际应用名称,需加上双引号
              msePilotCreateAppName: "your-deployment-name"

步骤二:部署应用,模拟线上场景

  1. 登录容器服务管理控制台,在左侧导航栏选择集群

  2. 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择工作负载 > 无状态

  3. 无状态页面选择命名空间,然后单击使用YAML创建资源

    本文示例中部署一个注册中心Nacos Server,然后部署spring-cloud-gateway、spring-cloud-a、spring-cloud-b、spring-cloud-c这四个业务应用。您也可以直接在Demo中获取对应的源码。

    • 注册中心Nacos Server YAML

      展开查看YAML文件

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nacos-server
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: nacos-server
        template:
          metadata:
            labels:
              msePilotAutoEnable: "off"
              app: nacos-server
          spec:
            containers:
              - name: nacos-server
                image: 'registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:v2.1.2'
                env:
                  - name: MODE
                    value: standalone
                  - name: JVM_XMS
                    value: 512M
                  - name: JVM_XMX
                    value: 512M
                  - name: JVM_XMN
                    value: 256M
                imagePullPolicy: Always
                livenessProbe:
                  failureThreshold: 3
                  initialDelaySeconds: 15
                  periodSeconds: 10
                  successThreshold: 1
                  tcpSocket:
                    port: 8848
                  timeoutSeconds: 3
                readinessProbe:
                  failureThreshold: 5
                  initialDelaySeconds: 15
                  periodSeconds: 15
                  successThreshold: 1
                  tcpSocket:
                    port: 8848
                  timeoutSeconds: 3
                resources:
                  requests:
                    cpu: '1'
                    memory: 2Gi
            dnsPolicy: ClusterFirst
            restartPolicy: Always
      
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: nacos-server
      spec:
        type: ClusterIP
        ports:
          - name: nacos-server-8848-8848
            port: 8848
            protocol: TCP
            targetPort: 8848
          - name: nacos-server-9848-9848
            port: 9848
            protocol: TCP
            targetPort: 9848
        selector:
          app: nacos-server
    • spring-cloud-c应用YAML

      展开查看YAML文件

      # Source: mse-simple-demo/templates/spring-cloud-c-deployment.yaml
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-c
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: spring-cloud-c
        template:
          metadata:
            labels:
              msePilotAutoEnable: "on"
              msePilotCreateAppName: "spring-cloud-c"
              app: "spring-cloud-c"
          spec:
            containers:
              - name: spring-cloud-c
                image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1"
                imagePullPolicy: Always
                env:
                  - name: nacos.host
                    value: "nacos-server"
                  - name: nacos.namespace
                    value: "public"
                resources:
                  requests:
                    cpu: 1
                    memory: 2Gi
                  limits:
                    cpu: 1
                    memory: 2Gi
                ports:
                - name: http-port
                  containerPort: 20003
            affinity:
              podAntiAffinity:
                preferredDuringSchedulingIgnoredDuringExecution:
                  - podAffinityTerm:
                      topologyKey: failure-domain.beta.kubernetes.io/zone
                    weight: 50
                  - podAffinityTerm:
                      topologyKey: kubernetes.io/hostname
                    weight: 100
    • spring-cloud-b应用YAML

      展开查看YAML文件

      # Source: mse-simple-demo/templates/spring-cloud-b-deployment.yaml
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-b
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: spring-cloud-b
        template:
          metadata:
            labels:
              msePilotAutoEnable: "on"
              msePilotCreateAppName: "spring-cloud-b"
              app: "spring-cloud-b"
          spec:
            containers:
              - name: spring-cloud-b
                image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1"
                imagePullPolicy: Always
                env:
                  - name: nacos.host
                    value: "nacos-server"
                  - name: nacos.namespace
                    value: "public"
                resources:
                  requests:
                    cpu: 1
                    memory: 2Gi
                  limits:
                    cpu: 1
                    memory: 2Gi
                ports:
                  - name: http-port
                    containerPort: 20002
            affinity:
              podAntiAffinity:
                preferredDuringSchedulingIgnoredDuringExecution:
                  - podAffinityTerm:
                      topologyKey: failure-domain.beta.kubernetes.io/zone
                    weight: 50
                  - podAffinityTerm:
                      topologyKey: kubernetes.io/hostname
                    weight: 100
    • spring-cloud-a应用YAML

      展开查看YAML文件

      # Source: mse-simple-demo/templates/spring-cloud-a-deployment.yaml
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-a
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: spring-cloud-a
        template:
          metadata:
            labels:
              msePilotAutoEnable: "on"
              msePilotCreateAppName: "spring-cloud-a"
              app: "spring-cloud-a"
          spec:
            containers:
              - name: spring-cloud-a
                image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1"
                imagePullPolicy: Always
                env:
                  - name: nacos.host
                    value: "nacos-server"
                  - name: nacos.namespace
                    value: "public"
                resources:
                  requests:
                    cpu: 1
                    memory: 2Gi
                  limits:
                    cpu: 1
                    memory: 2Gi
                ports:
                  - name: http-port
                    containerPort: 20001
            affinity:
              podAntiAffinity:
                preferredDuringSchedulingIgnoredDuringExecution:
                  - podAffinityTerm:
                      topologyKey: failure-domain.beta.kubernetes.io/zone
                    weight: 50
                  - podAffinityTerm:
                      topologyKey: kubernetes.io/hostname
                    weight: 100
    • spring-cloud-gateway应用YAML

      展开查看YAML文件

      # Source: mse-simple-demo/templates/spring-cloud-gateway-deployment.yaml
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-gateway
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: spring-cloud-gateway
        template:
          metadata:
            labels:
              msePilotAutoEnable: "on"
              msePilotCreateAppName: "spring-cloud-gateway"
              app: spring-cloud-gateway
          spec:
            containers:
              - image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-gateway:3.0.1"
                imagePullPolicy: Always
                env:
                  - name: nacos.host
                    value: "nacos-server"
                  - name: nacos.namespace
                    value: "public"
                  - name: enable.auto
                    value: "true"
                  - name: enable.rpc.invoke
                    value: "true"
                  - name: enable.sql
                    value: "false"
                  - name: enable.sentinel.demo.flow
                    value: "true"
                name: "spring-cloud-gateway"
                resources:
                  requests:
                    cpu: 1
                    memory: 2Gi
                  limits:
                    cpu: 1
                    memory: 2Gi
                ports:
                  - containerPort: 20000
            affinity:
              podAntiAffinity:
                preferredDuringSchedulingIgnoredDuringExecution:
                  - podAffinityTerm:
                      topologyKey: failure-domain.beta.kubernetes.io/zone
                    weight: 50
                  - podAffinityTerm:
                      topologyKey: kubernetes.io/hostname
                    weight: 100
      ---
      # Source: mse-simple-demo/templates/spring-cloud-gateway-service.yaml
      apiVersion: v1
      kind: Service
      metadata:
        annotations:
          service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
          service.beta.kubernetes.io/alicloud-loadbalancer-address-type: internet
        name: spring-cloud-gateway-slb
      spec:
        ports:
          - port: 80
            protocol: TCP
            targetPort: 20000
        selector:
          app: spring-cloud-gateway
        type: LoadBalancer

    执行以下命令查看部署结果:

    kubectl get svc,deploy

    预期输出:

    NAME                               TYPE           CLUSTER-IP   EXTERNAL-IP  PORT(S)             AGE
    service/kubernetes                 ClusterIP      172.16.x.x   <none>       443/TCP             23h
    service/nacos-server               ClusterIP      172.16.x.x   <none>       8848/TCP,9848/TCP   94s
    service/spring-cloud-gateway-slb   LoadBalancer   172.16.x.x   8.130.x.x    80:32641/TCP        57s
    
    NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/nacos-server           1/1     1            1           94s
    deployment.apps/spring-cloud-a         1/1     1            1           66s
    deployment.apps/spring-cloud-b         1/1     1            1           74s
    deployment.apps/spring-cloud-c         1/1     1            1           83s
    deployment.apps/spring-cloud-gateway   1/1     1            1           57s

步骤三:部署spring-cloud-c、spring-cloud-a应用的灰度版本

  1. 登录容器服务管理控制台使用如下YAML部署spring-cloud-c应用的灰度版本:

    展开查看YAML文件

    # Source: mse-simple-demo/templates/spring-cloud-c-gray-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-c-gray
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-cloud-c-gray
          version: gray
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-cloud-c"
            alicloud.service.tag: gray
            app: "spring-cloud-c-gray"
            version: gray
        spec:
          containers:
            - name: spring-cloud-c
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1"
              imagePullPolicy: Always
              env:
                - name: nacos.host
                  value: "nacos-server"
                - name: nacos.namespace
                  value: "public"
              resources:
                requests:
                  cpu: 1
                  memory: 2Gi
                limits:
                  cpu: 1
                  memory: 2Gi
              ports:
                - name: http-port
                  containerPort: 20002
          affinity:
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
                - podAffinityTerm:
                    topologyKey: failure-domain.beta.kubernetes.io/zone
                  weight: 50
                - podAffinityTerm:
                    topologyKey: kubernetes.io/hostname
                  weight: 100
  2. 使用如下YAML部署spring-cloud-a应用的灰度版本:

    展开查看YAML文件

    # Source: mse-simple-demo/templates/spring-cloud-a-gray-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-a-gray
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-cloud-a-gray
          version: gray
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-cloud-a"
            alicloud.service.tag: gray
            app: "spring-cloud-a-gray"
            version: gray
        spec:
          containers:
            - name: spring-cloud-a
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1"
              imagePullPolicy: Always
              env:
                - name: nacos.host
                  value: "nacos-server"
                - name: nacos.namespace
                  value: "public"
              resources:
                requests:
                  cpu: 1
                  memory: 2Gi
                limits:
                  cpu: 1
                  memory: 2Gi
              ports:
                - name: http-port
                  containerPort: 20001
          affinity:
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
                - podAffinityTerm:
                    topologyKey: failure-domain.beta.kubernetes.io/zone
                  weight: 50
                - podAffinityTerm:
                    topologyKey: kubernetes.io/hostname
                  weight: 100

步骤四:创建灰度环境泳道组

  1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择治理中心 > 全链路灰度

  3. 单击创建泳道组及泳道。如果您选择的微服务空间内已经创建过泳道组,则单击+创建泳道组

  4. 创建泳道组面板,单击+ 创建泳道组,在创建泳道组页面,设置如下相关配置,然后单击确定

    配置项

    说明

    泳道组名称

    自定义泳道组的名称。

    入口类型

    选择Java服务网关

    入口应用

    选择spring-cloud-gateway

    泳道组涉及应用

    选择spring-cloud-aspring-cloud-bspring-cloud-c

    创建泳道组

    泳道组创建完成后,在全链路灰度页面的泳道组区域,可以查看您创建的泳道组。如需变更泳道组信息,单击编辑图标,可在页面自行修改相关信息。

步骤五:创建灰度环境泳道

说明
  • 以Java网关作为全链路灰度入口时,MSE支持两种泳道模式。

    • 按请求内容路由:设置内容匹配条件,符合条件的请求则进入该泳道。

    • 按比例路由:随机选取一定比例的请求进入该泳道。同一客户端发起的请求可能会进入不同泳道,导致体验前后不同。

  • 网关路由规则Path/泳道路由模式在一个泳道组中需要保持一致。您只有在创建泳道组中第一条泳道时可以调整网关路由规则Path和泳道路由模式。

  1. 全链路灰度页面底部,单击点击创建第一个分流泳道。如果您选择的微服务空间内已经创建过泳道,则单击创建泳道

  2. 创建泳道面板,设置流控泳道相关配置,然后单击确定

    创建按请求内容路由的泳道

    配置项

    说明

    添加应用

    选择或输入应用所属泳道标签,下拉框中会出现相应的标签列表,选择对应的标签,则会自动添加相应的应用。

    泳道名称

    自定义设置流控泳道的名称。本示例将泳道名称设置为gray

    路由规则

    设置相应的路由规则条件。

    • 输入Path。若为空则代表匹配所有路径。

    • 泳道路由模式:选择按请求内容路由

    • 条件模式:路由条件之间的关系。

    • 条件列表:单击下方的+ 添加新的规则条件,新增规则条件。

      本示例设置灰度条件为:请求Parameter名为name,值为xiaoming。配置如下:

      • 参数类型:Parameter

      • 参数:name

      • 条件:==

      • :xiaoming

    创建按比例路由的泳道

    重要

    请确保MSE Java Agent的版本为3.2.3及以上,否则会影响百分比灰度能力。

    配置项

    说明

    添加应用

    选择或输入应用所属泳道标签,下拉框中会出现相应的标签列表,选择对应的标签,则会自动添加相应的应用。

    泳道名称

    自定义设置流控泳道的名称。本示例将泳道名称设置为gray

    路由规则

    设置相应的路由规则条件。

    • 输入Path。若为空则代表匹配所有路径

    • 选择已在网关创建的基线路由:选择创建的基线路由。

    • 泳道路由模式:选择按比例路由

    • 流量比例:30%。

完成泳道创建后,在全链路灰度流量分配区域,可以查看泳道详情,还可以进行如下操作。

  • 操作列,选择开启,创建的泳道将会生效,即流量会按照泳道方式进行流转,满足规则的流量会优先流向标记有当前泳道对应标签的应用版本,如果没有对应标签的应用版本,则流向未打标的应用版本。

  • 操作列,选择关闭,关闭创建的泳道,即该应用往后的流量会流向未打标的应用版本。

  • 单击图标图标,可以查看该泳道的流量比例。

  • 单击应用状态图标图标,可以设置该泳道上应用的状态。

步骤六:测试基线及灰度版本流量

测试按请求内容路由的泳道

  1. 使用curl命令测试基线流量:

    curl 8.130.x.x/A/a
    A[192.168.x.x][config=base] -> B[192.168.x.x] -> C[192.168.x.x]
    说明

    命令中的8.130.x.x是Spring Cloug Gateway暴露出的公网IP地址。

  2. 使用curl命令测试灰度流量:

    curl 8.130.x.x/A/a?name=xiaoming
    Agray[192.168.x.x][config=base] -> B[192.168.x.x] -> Cgray[192.168.x.x]
    说明
    • 当参数带上name=xiaoming时,会命中灰度标签,并向后透传。

    • 比如灰度请求到A应用和C应用时,会请求到Agray和Cgray节点。

    • 请求到B应用时,由于不存在Bgray节点,仍然会请求B的基线节点。

测试按比例路由的泳道

可以通过如下Python脚本测试按比例路由的分流情况(需要安装requests包)。注意将"x.x.x.x"替换为Java网关的入口SLB地址。

# pip3 install requests
# python3 traffic.py
import requests


TOTAL_REQUEST = 100
ENTRY_URL = 'http://x.x.x.x/A/a'

def parse_tag(text:str):
    '''
    A[10.0.23.64][config=base] -> B[10.0.23.65] -> C[10.0.23.61]
    Agray[10.0.23.64][config=base] -> B[10.0.23.65] -> Cgray[10.0.23.61]
    Ablue[10.0.23.64][config=base] -> B[10.0.23.65] -> Cblue[10.0.23.61]
    '''
    print(text)
    app_parts = text.split(' -> ')
    # tag_app: C[10.0.23.61] / Cgray[10.0.23.61]
    tag_app = app_parts[-1]
    
    splits = tag_app.split('[')
    
    # tag_part: C / Cgray
    tag_part = splits[0]
    tag = tag_part[1:]
    return tag if len(tag) > 0 else 'base'

def get_tag(url:str):
    resp = requests.get(url)
    resp.encoding = resp.apparent_encoding
    return parse_tag(resp.text)

def cal_tag_count(url:str, total_request:int):
    count_map = {}
    for i in range(total_request):
        tag = get_tag(url)
        if tag not in count_map:
            count_map[tag] = 1
        else:
            count_map[tag] += 1

    print()
    print('Total Request:', total_request)
    print('Traffic Distribution:', count_map)


if __name__ == '__main__':
    cal_tag_count(ENTRY_URL, TOTAL_REQUEST)

从结果可以看到,约有30%比例的流量去往了灰度环境。

image.png

步骤七:可观测

若应用出现异常,您可以通过MSE提供的可观测能力查看异常数据,帮助您快速定位问题。

微服务治理可观测

在MSE微服务治理的全链路灰度页面,单击目标应用,在QPS监控图(总)区域,可查看对应泳道基线版本和灰度版本的流量情况。

image.png

  • 总数:该应用总的QPS。

  • 异常QPS:该应用出错的请求数。

  • gray:该应用的灰度版本的QPS。

相关文档

  • 本页导读 (1)
文档反馈