基于MSE Ingress的全链路灰度

通过MSE Ingress网关提供的全链路灰度能力,您可以在不需要修改任何业务代码的情况下,实现全链路流量控制。本文介绍如何通过MSE Ingress网关实现全链路灰度功能。

前提条件

使用限制

由于全链路灰度功能整合了标签路由功能,因此不推荐已经加入全链路流量控制的应用配置金丝雀发布和标签路由规则。

全链路灰度能力支持的Java版本及框架详见微服务治理支持的Java框架

背景信息

在微服务场景中,当您部署的Spring Cloud应用或Dubbo应用存在升级版本时,由于应用之间的调用是随机的,会导致无法将具有一定特征的流量路由到应用的目标版本。全链路流量控制功能将应用的相关版本隔离成一个独立的运行环境(即泳道),通过设置Ingress路由规则,将满足规则的请求流量路由到目标版本应用。

全链路灰度场景

本文以电商架构中的下单场景为例,介绍从MSE Ingress网关到微服务的全链路流控功能。假设应用的架构由MSE Ingress网关以及后端的微服务架构(Spring Cloud)组成,后端调用链路有3个:交易中心(A)、商品中心(B)和库存中心(C),可以通过客户端或者是HTML来访问后端服务,这些服务之间通过Nacos注册中心实现服务发现。

客户下单后流量从MSE Ingress网关进入,先调用交易中心(A),然后交易中心(A)再调用商品中心(B),最后商品中心(B)再调用下游的库存中心(C)。调用链路从左到右依次为:客户->MSE Ingress->A->B->C。

随着业务不断迭代,功能也不断更新。在新版本正式上线之前,需要同时对应用A和应用C进行灰度验证,确认无误后方可正式上线。新功能上线过程中,涉及到应用A和应用C同时发布新版本。通过MSE Ingress网关和MSE微服务治理提供的全链路灰度能力,您可以端到端的构建从网关到多个后端服务的全链路灰度,控制具有一定特征的灰度流量始终路由到应用对应的灰度环境,满足您同时灰度验证多个服务的诉求。此外,在应用无目标灰度版本时,自动容灾到正式环境中。

全链路灰度场景

名词解释

  • 泳道

    为相同版本应用定义的一套隔离环境。只有满足了流控路由规则的请求流量才会路由到对应泳道里的打标应用。一个应用可以属于多个泳道,一个泳道可以包含多个应用,应用和泳道是多对多的关系。

  • 泳道组

    泳道的集合。泳道组的作用主要是为了区分不同团队或不同场景。

  • MSE Ingress网关

    MSE Ingress网关是在MSE云原生网关基础上提供的Ingress流量管理方式,兼容Nginx Ingress以及超50种Nginx Ingress注解,支持多服务版本同时灰度发布。同时,灵活的服务治理能力以及全方位的安全防护保障,可以满足大规模云原生分布式应用的流量治理诉求。更多信息,请参见MSE Ingress概览

准备工作

开启MSE微服务治理

  1. 开通微服务治理专业版。

    具体操作,请参见开通MSE微服务治理

  2. 为应用开启微服务治理。

    1. 登录MSE治理中心控制台
    2. 在左侧导航栏,选择治理中心 > 运维中心 > K8s集群列表。在目标集群操作列,单击管理
    3. 集群详情页面,在目标命名空间操作列,单击开启微服务治理。然后单击确定

部署Demo应用程序

  1. 登录容器服务管理控制台

  2. 在控制台左侧导航栏,单击集群

  3. 集群列表页面,单击目标集群名称或者目标集群右侧操作列下的详情

  4. 在集群管理页左侧导航栏,选择工作负载 > 无状态

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

  6. 对模板进行相关配置,完成配置后单击创建

    本文示例中部署一个Nacos Server应用,用于实现服务发现,部署A、B、C三个应用。其中A和C应用分别部署一个基线版本和一个灰度版本,B应用部署一个基线版本。

    部署Nacos Server应用。

    展开查看YAML文件

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nacos-server
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nacos-server
      template:
        metadata:
          labels:
            app: nacos-server
        spec:
          containers:
          - env:
            - name: MODE
              value: standalone
            image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/nacos-server:v2.1.2
            imagePullPolicy: Always
            name: nacos-server
          dnsPolicy: ClusterFirst
          restartPolicy: Always
    
    # Nacos Server Service配置
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nacos-server
    spec:
      ports:
      - port: 8848
        protocol: TCP
        targetPort: 8848
      selector:
        app: nacos-server
      type: ClusterIP
    • 部署A应用

      • 基线(base)版本的YAML如下。

        展开查看基线(base)版本的YAML文件

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: spring-cloud-a
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: spring-cloud-a
          template:
            metadata:
              labels:
                app: spring-cloud-a
                msePilotAutoEnable: 'on'
                msePilotCreateAppName: spring-cloud-a
            spec:
              containers:
              - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
                image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
                imagePullPolicy: Always
                name: spring-cloud-a
                ports:
                - containerPort: 20001
                livenessProbe:
                  tcpSocket:
                    port: 20001
                  initialDelaySeconds: 10
                  periodSeconds: 30
      • 灰度(gray)版本的YAML如下。

        展开查看灰度(gray)版本的YAML文件

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: spring-cloud-a-gray
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: spring-cloud-a-gray
          strategy:
          template:
            metadata:
              labels:
                app: spring-cloud-a-gray
                msePilotAutoEnable: 'on'
                alicloud.service.tag: gray
                msePilotCreateAppName: spring-cloud-a
            spec:
              containers:
              - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
                image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
                imagePullPolicy: Always
                name: spring-cloud-a-gray
                ports:
                - containerPort: 20001
                livenessProbe:
                  tcpSocket:
                    port: 20001
                  initialDelaySeconds: 10
                  periodSeconds: 30
    • 部署B应用

      • 基线(base)版本的YAML如下。

        展开查看基线(base)版本的YAML文件

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: spring-cloud-b
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: spring-cloud-b
          strategy:
          template:
            metadata:
              labels:
                app: spring-cloud-b
                msePilotAutoEnable: 'on'
                msePilotCreateAppName: spring-cloud-b
            spec:
              containers:
              - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
                image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1
                imagePullPolicy: Always
                name: spring-cloud-b
                ports:
                - containerPort: 8080
                livenessProbe:
                  tcpSocket:
                    port: 20002
                  initialDelaySeconds: 10
                  periodSeconds: 30
    • 部署C应用

      • 基线(base)版本的YAML如下。

        展开查看基线(base)版本的YAML文件

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: spring-cloud-c
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: spring-cloud-c
          template:
            metadata:
              labels:
                app: spring-cloud-c
                msePilotAutoEnable: 'on'
                msePilotCreateAppName: spring-cloud-c
            spec:
              containers:
              - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
                image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
                imagePullPolicy: Always
                name: spring-cloud-c
                ports:
                - containerPort: 8080
                livenessProbe:
                  tcpSocket:
                    port: 20003
                  initialDelaySeconds: 10
                  periodSeconds: 30
      • 灰度(gray)版本的YAML如下。

        展开查看灰度(gray)版本的YAML文件

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: spring-cloud-c-gray
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: spring-cloud-c-gray
          template:
            metadata:
              labels:
                app: spring-cloud-c-gray
                msePilotAutoEnable: 'on'
                alicloud.service.tag: gray
                msePilotCreateAppName: spring-cloud-c
            spec:
              containers:
              - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
                image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
                imagePullPolicy: IfNotPresent
                name: spring-cloud-c-gray
                ports:
                - containerPort: 8080
                livenessProbe:
                  tcpSocket:
                    port: 20003
                  initialDelaySeconds: 10
                  periodSeconds: 30
  7. 针对入口应用A,配置两个K8s Service。

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

    3. 服务页面,选择命名空间,然后单击使用YAML创建资源,对模板进行相关配置,完成配置后单击创建

      • spring-cloud-a-base对应A的base版本。

        展开查看YAML文件

        apiVersion: v1
        kind: Service
        metadata:
          name: spring-cloud-a-base
        spec:
          ports:
            - name: http
              port: 20001
              protocol: TCP
              targetPort: 20001
          selector:
            app: spring-cloud-a
      • spring-cloud-a-gray对应A的gray版本。

        展开查看YAML文件

        apiVersion: v1
        kind: Service
        metadata:
          name: spring-cloud-a-gray
        spec:
          ports:
            - name: http
              port: 20001
              protocol: TCP
              targetPort: 20001
          selector:
            app: spring-cloud-a-gray

步骤一:创建泳道组

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

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

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

  4. 创建泳道组面板,设置泳道组相关参数,然后单击确定

    配置项

    说明

    泳道组名称

    自定义设置泳道组的名称。

    入口类型

    选择Ingress/自建网关

    泳道组涉及应用

    选择您的入口应用或入口网关所涉及的所有相关服务。

    泳道组创建完成后,请检查入口应用和所涉及的应用是否正确。您可以在全链路灰度页面的泳道组及涉及的应用区域,查看您已创建的泳道组。如需变更泳道组信息,请单击右侧的编辑图标并修改相关信息。

步骤二:创建泳道

  1. 全链路灰度页面顶部,选择和泳道组相同的地域,然后在页面底部单击点击创建第一个分流泳道

    如果您选择的微服务空间内已经创建过泳道,则单击创建泳道

    重要

    加入全链路流量控制的应用,不推荐使用金丝雀发布和标签路由功能。

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

    重要

    如果您的网关应用是Ingress网关,需要在容器服务管理控制台配置Ingress路由规则。

    配置项

    说明

    配置节点标签

    • 配置方式:在容器ACK控制台中,在应用YAML的spec.template.metadata.labels下增加alicloud.service.tag: ${tag}

    • 设置标签名:并为spec.template.metadata.labels增加如下两个key-value键值对。

      • msePilotCreateAppName:${AppName}

      • alicloud.service.tag:{tag}

    泳道名称

    自定义设置流控泳道的名称。

    泳道标签

    完成配置节点标签后,下拉框中会出现相应的标签列表,选择对应的标签,则会自动添加相应的应用。

    完成泳道创建后,在全链路灰度流量分配区域,可以查看或配置泳道信息。

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

    • 单击应用状态图标图标,可在泳道列表的操作列,设置该泳道应用的状态。

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

      • 关闭泳道:单击关闭,关闭创建的泳道,即该应用往后的流量会流向未打标的应用版本。

      • 修改泳道:单击编辑,修改当前泳道的配置信息。

      • 删除泳道:单击删除,删除目标泳道。

步骤三:配置基线环境的Ingress规则

如果业务域名为example.com,访问example.com的请求流量只通过基线环境(线上环境),可以通过如下代码配置基线环境的Ingress规则。

展开查看YAML文件

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: spring-cloud-a
  namespace: default
spec:
  ingressClassName: mse
  rules:
    - host: example.com
      http:
        paths:
          - backend:
              service:
                name: spring-cloud-a-base
                port:
                  number: 20001
            path: /
            pathType: Prefix

使用curl命令访问example.com路由到基线环境。

curl -H "host: example.com" http://47.98.xxx.xx/a

返回结果如下:

A[192.168.0.98][config=base] -> B[192.168.0.157] -> C[192.168.0.161]

步骤四:配置灰度环境的Ingress规则

如果基于Header策略来区分线上正式流量和灰度流量,希望带有HTTP Header为x-user-id: 100的请求流量访问example.com路由到灰度环境中,流量会优先访问链路中各个应用对应的灰度版本,若无灰度版本,则会容灾到基线版本。通过如下代码配置灰度环境的Ingress规则。

展开查看YAML文件

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/canary: 'true'
    nginx.ingress.kubernetes.io/canary-by-header: x-user-id
    nginx.ingress.kubernetes.io/canary-by-header-value: '100'
    mse.ingress.kubernetes.io/request-header-control-update: x-mse-tag gray
  name: spring-cloud-a-gray
  namespace: default
spec:
  ingressClassName: mse
  rules:
    - host: example.com
      http:
        paths:
          - backend:
              service:
                name: spring-cloud-a-gray
                port:
                  number: 20001
            path: /
            pathType: Prefix

上述代码使用注解实现路由灰度发布、Header配置以及Header控制能力。关于注解的更多用法,请参见MSE Ingress高级用法

使用curl命令访问example.com路由到灰度环境,其中带有HTTP Header为x-user-id: 100的请求流量。

curl -H "host: example.com" -H "x-user-id: 100" http://47.98.xxx.xx/a

返回结果如下。灰度流量优先访问了A和C的灰度版本,由于B没有灰度版本,所以访问了B的基线版本。

Agray[192.168.0.128][config=base] -> B[192.168.0.152] -> Cgray[192.168.0.151]

控制台查看应用的流量监控图

  • 查看单个应用的监控图

    1. 全链路灰度页面,单击目标泳道组页签。

    2. 泳道组及涉及的应用区域,单击目标应用名称,在右侧会出现对应的QPS数据

  • 查看泳道组内所有应用的监控图。

    1. 全链路灰度页面,单击目标泳道组页签。

    2. 应用 QPS 监控右侧,单击查看流量详情,可以查看该泳道所有应用的流量监控图。