You need to enable JavaScript to run this app.
导航
基于 Ingress 入口网关的全链路流量灰度
最近更新时间:2024.06.24 15:47:32首次发布时间:2023.09.27 16:47:12

本文介绍如何通过微服务引擎和 Ingress 入口网关实现全链路流量灰度。

场景介绍

  • 外部带有 x_version_tag:gray 请求头的请求:作为灰度流量,请求的路径为 service1 v1 -> service2 v2 -> service3 v2 -> service4 v2 -> service5 v2 -> end。

  • 其他的请求:路由至基础泳道,请求路径为 service1 v1 -> service2 v1 -> service3 v1 -> service4 v1 -> service5 v1 -> end。

原理介绍

  1. 外部流量经过 Ingress 访问 k8s 业务集群,通过 header 头的业务请求标识,路由至灰度泳道的业务流量。示例中外部灰度请求流量增加请求头 x_version_tag:gray
  2. 在 Ingress 增加流量染色标记后在入口服务 App1 透传,并根据流量染色规则路由下游的 App2 的灰度版本。
  3. 同一 K8s 集群存在不同业务域 B。业务域之间服务访问通过集群内 Ingress 进行流量转发。当业务域 A 中 App3 访问业务域 B App4 时通过按染色规则进行灰度流量路由。
  • 入口服务未设置灰度版本

alt

  • 入口服务已设置灰度版本

alt

准备工作

  • 已创建业务集群和元集群。元集群用于部署治理中心,业务集群用于部署本文所描述的 Nacos 和业务应用。创建集群的方法参见容器服务中 创建集群
  • 已在元集群创建治理中心。治理中心的创建方法参见 创建治理中心
  • 已在业务集群中部署 Nacos 和 业务应用。对应的 YAML 文件参见 mse-lane-demo.zip
    mse-lane-demo.zip
    25.75KB

Annotations 和 Labels 注解

启动 K8s Deployment 时,需要增加部分 Labels 和 Annotations,对应的示例和说明如下。

说明

以下部分信息支持通过服务接入向导获取,操作详情参见 接入 Java 应用

spec:
  template:
    metadata:
      annotations:
        sidecar.mesh.io/prestop-timeout: "5000"
      labels:
        sidecar.mesh.io/data-plane-mode: "java_proxyless"
        sidecar.mesh.io/lane: "lane-a"
        sidecar.mesh.io/mse-namespace: "nacos-test-public-default-group"
类型参数说明示例值
annotationssidecar.mesh.io/prestop-timeout可选,开启无损下线后支持。实例延迟下线的时长,单位为毫秒,支持根据实际业务情况调整时长。sidecar.mesh.io/prestop-timeout: "5000"

labels

sidecar.mesh.io/data-plane-mode

微服务引擎数据面组件。

sidecar.mesh.io/data-plane-mode: "java_proxy"

sidecar.mesh.io/lane服务所处泳道名称。sidecar.mesh.io/lane: "lane-a"

sidecar.mesh.io/mse-namespace

服务所处 Nacos 命名空间的名称。

sidecar.mesh.io/mse-namespace: "nacos-test-public-default-group"

操作步骤

场景一:入口服务未设置灰度版本

  1. 在已创建的业务集群创建 Nacos 应用,并支持 K8s 服务访问,示例代码如下。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      annotations:
      name: nacostest
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nacostest
      template:
        metadata:
          labels:
            app: nacostest
        spec:
          containers:
          - env:
            - name: MODE
              value: standalone
            image: mse-cn-beijing.cr.volces.com/mse/nacos-server:v2.1.0 
            imagePullPolicy: IfNotPresent
            name: nacos
            ports:
            - containerPort: 8848
              protocol: TCP
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nacostest
    spec:
      ports:
      - name: http
        port: 8848
        protocol: TCP
        targetPort: 8848
      - name: grpc
        port: 9848
        protocol: TCP
        targetPort: 9848
      selector:
        app: nacostest
      type: NodePort
    
  2. 创建 App1 ~ App5,5 个 Java 应用, App2 ~ App5 存在灰度版本 v2。

    说明

    • App1 和 App3 的基线版本说明如下,其他应用的代码示例参见准备工作中的 mse-lane-demo.zip
    • 启动 K8s Deployment 时,需要增加部分 Annotations 和 Labels,对应的示例和说明参见 Annotations 和 Labels 注解
    • App1 的基线版本。

      说明

      • 通过 CurServiceName、CurServiceVersion 标识当前服务版本。
      • 通过 NextServiceName 标识下游访问服务。
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: service1
        namespace: istio-app
      spec:
        progressDeadlineSeconds: 600
        replicas: 1
        revisionHistoryLimit: 10
        selector:
          matchLabels:
            app: service1
        strategy:
          rollingUpdate:
            maxSurge: 25%
            maxUnavailable: 25%
          type: RollingUpdate
        template:
          metadata:
            annotations:
              sidecar.mesh.io/prestop-timeout: "5000"
            labels:
              app: service1
              service.istio.io/canonical-name: "service1"
              sidecar.mesh.io/data-plane-mode: "java_proxyless"
              sidecar.mesh.io/mse-namespace: "nacos-test-public-default-group"
          spec:
            containers:
            - args:
              - -c
              - /jdk/bin/java -jar lenforry.jar
              command:
              - /bin/sh
              env:
              - name: CurServiceName
                value: service1
              - name: CurServiceVersion
                value: v1
              - name: NextServiceName
                value: service2
              - name: VERSION
                valueFrom:
                  fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.name
              - name: POD_NAME
                valueFrom:
                  fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.name
              image: monica-cn-beijing.cr.volces.com/monica/topology:v1
              imagePullPolicy: IfNotPresent
              name: c0
              ports:
              - containerPort: 80
                name: http
                protocol: TCP
              resources:
                limits:
                  cpu: 510m
                  memory: 1Gi
                requests:
                  cpu: 500m
                  memory: 1Gi
      
    • App3 的基线版本。
      App3 请求 App4 时,由于归属不同业务域无法直连访问。可通过集群内的 Ingress 进行代理转发。将 App3 的 NextServiceName 设置为 Ingress 暴漏的 VIP 地址,通过 Ingress 路由规则匹配 App4 对应服务版本。

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: service3
        namespace: istio-app
      spec:
        progressDeadlineSeconds: 600
        replicas: 1
        revisionHistoryLimit: 10
        selector:
          matchLabels:
            app: service3
        strategy:
          rollingUpdate:
            maxSurge: 25%
            maxUnavailable: 25%
          type: RollingUpdate
        template:
          metadata:
            annotations:
              sidecar.mesh.io/prestop-timeout: "5000"
            labels:
              app: service1
              service.istio.io/canonical-name: "service3"
              sidecar.mesh.io/data-plane-mode: "java_proxyless"
              sidecar.mesh.io/mse-namespace: "nacos-test-public-default-group"
          spec:
            containers:
            - args:
              - -c
              - /jdk/bin/java -jar lenforry.jar
              command:
              - /bin/sh
              env:
              - name: CurServiceName
                value: service3
              - name: CurServiceVersion
                value: v1
              - name: NextServiceName
                value: 180.***.***.56
              - name: VERSION
                valueFrom:
                  fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.name
              - name: POD_NAME
                valueFrom:
                  fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.name
              image: monica-cn-beijing.cr.volces.com/monica/topology:v1
              imagePullPolicy: IfNotPresent
              name: c0
              ports:
              - containerPort: 80
                name: http
                protocol: TCP
              resources:
                limits:
                  cpu: 510m
                  memory: 1Gi
                requests:
                  cpu: 500m
                  memory: 1Gi
      
  3. 对 App4 创建 2 个 K8s svc 指向 App4 的基线和灰度版本。Ingress 根据 App3 请求流量中的染色标识 baggage 路由至 App4 对应泳道版本。

    • service4-base-svc:用于映射 Ingress 规则路由至 App4 的基线版本。
    • service4-gray-svc:用于映射 Ingress 规则路由至 App4 的灰度版本。
    • svc4-ingress-lane-demo:用于获取 App3 带有灰度染色标识进行流量透传并路由到灰度版本。
    • svc4-ingress:用于将 App3 流量中未带有流量染色标识路由到基线版本。
  4. 创建 App1 的 k8s service service1-base-svc,关联 App1 Deployment,以支持外部流量通过 Ingress + K8s 的服务发现方式访问 App1 服务。

  5. 在 Ingress 设置染色规则。svc1-ingress 示例代码如下。

    说明

    • 对于请求头包含 x_version_tag:gray 流量进行染色
    • 在 Header 中增加 MSE 泳道标识 baggage:meshEnv=g81bix7ftty9wzlvv1hq****-demo-lane
    • 将匹配 /getnext 且 host 为 service1 的请求路由至 service1-base-svc 服务。
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        nginx.ingress.kubernetes.io/configuration-snippet: |
          # 如果有header x_version_tag=gray,则是灰度流量,
          # 添加x-mse-tag=gray,透传流量标签
          set $mseTag "prod";
          if ($http_x_version_tag = "gray") {
              set $mseTag "meshEnv=g81bix7ftty9wzlvv1hq****-demo-lane";
          }
          proxy_set_header baggage $mseTag;
        vke.volcengine.com/description: ""
      creationTimestamp: "2023-08-22T08:32:32Z"
      generation: 5
      name: svc1-ingress
      namespace: istio-app
      resourceVersion: "1817755"
      uid: b6df5a51-347d-4abb-89ba-1265f376****
    spec:
      ingressClassName: nginx
      rules:
      - host: service1
        http:
          paths:
          - backend:
              service:
                name: service1-base-svc
                port:
                  number: 80
            path: /getnext
            pathType: Prefix
    status:
      loadBalancer:
        ingress:
        - ip: 180.***.***.56
    

结果验证

登录业务集群中的任意 Pod 执行以下命令即可查看流量的请求路径。

  • 流量匹配
for i in $(seq 1 100); do curl -s -o /dev/null curl -X GET 180.***.***.56/getnext -H "Host: service1" -H "x_version_tag:100"  -w"\n";done
  • 流量不匹配
for i in $(seq 1 100); do curl -s -o /dev/null curl -X GET 180.***.***.56/getnext -H "Host: service1" -H "x_version_tag:101"  -w"\n";done 

说明

180.***.***.56 为 Ingress 的 VIP 地址。

经验证可知:

  • 当请求 service1 的入口流量包含 x_version_tag:gray 时路由至灰度版本
  • 当不包含指定请求头时,路由至基线版本。

alt

场景二:入口服务已设置灰度版本

已设置灰度版本相对未设置灰度版本需要新增以下资源。

  • 新增 App1 灰度版本 service1-v2
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: service1-v2
      namespace: istio-app
    spec:
      progressDeadlineSeconds: 600
      replicas: 1
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          app: service1-v2
      strategy:
        rollingUpdate:
          maxSurge: 25%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          annotations:
            sidecar.mesh.io/prestop-timeout: "5000"
          labels:
            app: service1
            service.istio.io/canonical-name: "service1"
            sidecar.mesh.io/data-plane-mode: "java_proxyless"
            sidecar.mesh.io/mse-namespace: "nacos-test-public-default-group"
            sidecar.mesh.io/lane: "demo-lane"
        spec:
          containers:
          - args:
            - -c
            - /jdk/bin/java -jar lenforry.jar
            command:
            - /bin/sh
            env:
            - name: CurServiceName
              value: service1
            - name: CurServiceVersion
              value: v2
            - name: NextServiceName
              value: service2
            - name: VERSION
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            image: monica-cn-beijing.cr.volces.com/monica/topology:v1
            imagePullPolicy: IfNotPresent
            name: c0
            ports:
            - containerPort: 80
              name: http
              protocol: TCP
            resources:
              limits:
                cpu: 510m
                memory: 1Gi
              requests:
                cpu: 500m
                memory: 1Gi
    
  • 新增 App1 K8s svc service1-gray-svc
    apiVersion: v1
    kind: Service
    metadata:
      name: service1-gray-svc
      namespace: istio-app
    spec:
      ports:
      - name: http
        port: 80
        protocol: TCP
        targetPort: 80
      selector:
          app: service1-v2
      sessionAffinity: None
      type: ClusterIP
    
  • 在 Ingress 新增染色规则 svc1-ingress-lane-demo
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        nginx.ingress.kubernetes.io/canary: "true"
        nginx.ingress.kubernetes.io/canary-by-header: x_version_tag
        nginx.ingress.kubernetes.io/canary-by-header-value: "gray"
        nginx.ingress.kubernetes.io/canary-weight: "0"
        vke.volcengine.com/description: ""
      creationTimestamp: "2023-08-22T12:17:33Z"
      generation: 3
      name: svc1-ingress-lane-demo
      namespace: istio-app
      resourceVersion: "1820324"
      uid: 9e514e6c-f39e-420c-846a-728ed5d7****
    spec:
      ingressClassName: nginx
      rules:
      - host: service1
        http:
          paths:
          - backend:
              service:
                name: service1-gray-svc
                port:
                  number: 80
            path: /getnext
            pathType: Prefix
    status:
      loadBalancer:
        ingress:
        - ip: 180.***.***.56
    

结果验证

登录业务集群中的任意 Pod 执行以下命令即可查看流量的请求路径。

  • 流量匹配
for i in $(seq 1 100); do curl -s -o /dev/null curl -X GET 180.***.***.56/getnext -H "Host: service1" -H "x_version_tag:100"  -w"\n";done
  • 流量不匹配
for i in $(seq 1 100); do curl -s -o /dev/null curl -X GET 180.***.***.56/getnext -H "Host: service1" -H "x_version_tag:101"  -w"\n";done

说明

180.***.***.56 为 Ingress 的 VIP 地址。

经验证可知:

  • 当请求 service1 的入口流量包含 x_version_tag:gray 时路由至灰度版本
  • 当不包含指定请求头时,路由至基线版本。

alt