结合Kruise Rollouts实现基于MSE的全链路灰度

作为Kubernetes应用部署工具,Kruise Rollouts提供金丝雀发布、蓝绿发布等多种灰度发布策略,并通过与MSE微服务治理的结合,实现对新版本应用在服务调用链路上的平滑灰度升级,确保新版本的稳定性。

全链路灰度介绍

在微服务架构场景下,传统的灰度发布模式往往不能满足交付的复杂需求,全链路灰度发布的场景也就应运而生,此时每个微服务都会有灰度环境或分组来接受灰度流量。开发者希望进入上游灰度环境的流量也能进入下游灰度环境中,确保一个请求始终在灰度环境中传递,从而形成流量泳道。在泳道内的流量链路中,即使这个调用链路上有一些微服务应用不存在灰度环境,那么这些微服务应用在请求下游的时候依然能够重新回到灰度环境中。此时可以根据服务的实际情况,控制多个服务同时进行发布变更,从而保证整个系统的稳定性,效果如下图所示:

image.png

Kruise Rollouts介绍

Kruise Rollouts是OpenKruise社区开源的渐进式交付框架。Kruise Rollouts支持配合流量和实例灰度的灰度发布、蓝绿发布和A/B Testing发布。基于Prometheus Metrics指标,Kruise Rollouts还可以实现发布过程的自动化分批与暂停,并提供旁路的无感对接,兼容已有的多种工作负载(Deployment、CloneSet、StatefulSet),更多信息,请参见Kruise Rollouts

Kruise Rollouts是一种旁路式的工作机制。您只需配置一份Rollouts资源并将其下发到K8s集群中,后续的业务发布和升级均无需额外操作,并且可以与Helm和PaaS平台低成本无缝对接。使用Kruise Rollouts实现灰度发布架构图如下:image.png

前提条件

步骤一:准备工作

  1. 安装Kruise Rollouts组件。

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

    2. 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择运维管理 > 组件管理

    3. 组件管理页面,单击应用管理页签。

    4. ack-kruise卡片右下方,单击安装

    5. 在弹出的对话框中,单击确定

  2. 安装MSE Ingress组件。创建MseIngressConfig和IngressClass,具体操作,请参见通过MSE Ingress访问容器服务

  3. 为应用开启微服务治理。具体操作,请参见ACK微服务应用接入MSE治理中心

步骤二:部署Demo应用

  1. 部署业务应用(Deployment、Service和Ingress)。

    1. 使用如下内容,创建mse-demo.yaml文件。

      展开查看mse-demo.yaml文件

      # a service
      apiVersion: v1
      kind: Service
      metadata:
        name: spring-cloud-a
      spec:
        ports:
          - name: http
            port: 20001
            protocol: TCP
            targetPort: 20001
        selector:
          app: spring-cloud-a
      # a application
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-a
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: spring-cloud-a
        template:
          metadata:
            annotations:
              msePilotCreateAppName: spring-cloud-a
            labels:
              app: 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-demo-hz/spring-cloud-a:mse-2.0.0
                imagePullPolicy: Always
                name: spring-cloud-a
                ports:
                  - containerPort: 20001
      # b application
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-b
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: spring-cloud-b
        template:
          metadata:
            annotations:
              msePilotCreateAppName: spring-cloud-b
            labels:
              app: 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-demo-hz/spring-cloud-b:mse-2.0.0
                imagePullPolicy: Always
                name: spring-cloud-b
      # c application
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-c
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: spring-cloud-c
        template:
          metadata:
            annotations:
              msePilotCreateAppName: spring-cloud-c
            labels:
              app: 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-demo-hz/spring-cloud-c:mse-2.0.0
                imagePullPolicy: Always
                name: spring-cloud-c
      ---
      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: nacos/nacos-server:v2.2.0
              imagePullPolicy: Always
              name: nacos-server
            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
      ---
      apiVersion: v1
      kind: Service
      metadata:
        labels:
          app: demo-mysql
        name: demo-mysql
      spec:
        ports:
          - port: 3306
            targetPort: 3306
        selector:
          app: demo-mysql
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: demo-mysql
      spec:
        selector:
          matchLabels:
            app: demo-mysql
        replicas: 1
        strategy:
          type: Recreate
        template:
          metadata:
            labels:
              app: demo-mysql
          spec:
            containers:
              - args:
                  - --character-set-server=utf8mb4
                  - --collation-server=utf8mb4_unicode_ci
                env:
                  - name: MYSQL_ROOT_PASSWORD
                    value: root
                image: registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/demo-mysql:3.0.1
                name: demo-mysql
                ports:
                  - containerPort: 3306
    2. 执行如下命令,部署业务应用。

      kubectl apply -f mse-demo.yaml
    3. 使用如下内容,创建mse-Ingress.yaml文件。

      展开查看mse-Ingress.yaml文件

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        annotations:
          mse.ingress.kubernetes.io/service-subset: ""
        name: spring-cloud-a
      spec:
        ingressClassName: mse
        rules:
          - http:
              paths:
                - backend:
                    service:
                      name: spring-cloud-a
                      port:
                        number: 20001
                  path: /
                  pathType: Prefix
    4. 执行如下命令,创建Ingress规则。

      kubectl apply -f mse-ingress.yaml
    5. 执行如下命令,获取外部IP。

      kubectl get ingress

      预期输出如下所示:

      NAME         CLASS    HOSTS   ADDRESS        PORTS   AGE
      spring-cloud-a   <none>   *       EXTERNAL_IP     80     12m
    6. 执行如下命令,查看路由访问情况。替换以下<EXTERNAL_IP>为您上一步获取的外部IP。

      curl http://<EXTERNAL_IP>/A/a

      预期输出如下所示:

      A[192.168.42.115][config=base] -> B[192.168.42.118] -> C[192.168.42.101]% 

步骤三:通过Kruise Rollouts实现自动化的全链路灰度发布

  1. 定义Kruise Rollouts的灰度发布规则。

    说明

    以下Rollout资源将定义灰度发布规则,发布分为三批:

    1. A/B Testing发布,具有header[User-Agent]=xiaoming的流量将导入到新版本,其它则为老版本。

    2. 按照流量比例进行灰度,此批次将灰度50%的实例及流量。

    3. 将灰度完成所有的实例。

    1. 使用如下内容,创建rollout.yaml文件。

      展开查看rollout.yaml文件

      # 用于圈定全链路的应用
      # a rollout configuration
      apiVersion: rollouts.kruise.io/v1alpha1
      kind: Rollout
      metadata:
        # 其它微服务应用类似配置,例如:rollout-b, rollout-c
        name: rollout-a
        annotations:
          rollouts.kruise.io/trafficrouting: mse-traffic
      spec:
        objectRef:
          workloadRef:
            apiVersion: apps/v1
            kind: Deployment
            name: spring-cloud-a
        strategy:
          canary:
            steps:
            - pause: {}
              replicas: 1
            patchPodTemplateMetadata:
              labels:
                alicloud.service.tag: gray
                opensergo.io/canary-gray: gray
      ---
      # b rollout configuration
      apiVersion: rollouts.kruise.io/v1alpha1
      kind: Rollout
      metadata:
        name: rollout-b
        annotations:
          rollouts.kruise.io/trafficrouting: mse-traffic
      spec:
        objectRef:
          workloadRef:
            apiVersion: apps/v1
            kind: Deployment
            name: spring-cloud-b
        strategy:
          canary:
            steps:
              - pause: {}
                replicas: 1
            patchPodTemplateMetadata:
              labels:
                alicloud.service.tag: gray
                opensergo.io/canary-gray: gray
      ---
      # c rollout configuration
      apiVersion: rollouts.kruise.io/v1alpha1
      kind: Rollout
      metadata:
        name: rollout-c
        annotations:
          rollouts.kruise.io/trafficrouting: mse-traffic
      spec:
        objectRef:
          workloadRef:
            apiVersion: apps/v1
            kind: Deployment
            name: spring-cloud-c
        strategy:
          canary:
            steps:
              - pause: {}
                replicas: 1
            patchPodTemplateMetadata:
              labels:
                alicloud.service.tag: gray
                opensergo.io/canary-gray: gray
      ---
      # 全链路泳道配置
      apiVersion: rollouts.kruise.io/v1alpha1
      kind: TrafficRouting
      metadata:
        name: mse-traffic
      spec:
        objectRef:
        - service: spring-cloud-a
          ingress:
            classType: mse
            name: spring-cloud-a
        strategy:
          matches:
          # A/B Testing发布,基于header的流量灰度方式。
          - headers:
            - type: Exact
              name: User-Agent
              value: xiaoming
              # 此批次灰度的流量比例50%。
          #weight: 30 , 灰度30%流量
          requestHeaderModifier:
            set:
            - name: x-mse-tag
              value: gray

    2. 执行如下命令,将该Rollout资源下发到K8s集群。

      kubectl apply -f rollout.yaml
    3. 执行如下命令,查看Rollout资源的状态。

      kubectl get rollout

      预期输出STATUS=Healthy,表明Rollout资源正常工作。

  2. 升级应用版本。

    Kruise Rollouts是一个常态化的配置,将其下发到集群后,后续业务版本发布只需调整Deployment配置,无需再对Kruise Rollouts进行额外操作。例如,业务将spring-cloud-a服务跟spring-cloud-c镜像版本升级到mse-2.0.1,然后通过执行kubectl apply -f mse-demo.yaml命令将Deployment部署到集群。将Deployment配置下发到K8s集群时,除kubectl方式外,也可以使用Helm或Vela等方式。

    • 修改mse-demo.yaml文件,将spring-cloud-a服务跟spring-cloud-c镜像版本升级到mse-2.0.1。

      展开查看YAML文件

      # a application
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-a
        ...
            containers:
              - env:
                  - name: JAVA_HOME
                    value: /usr/lib/jvm/java-1.8-openjdk/jre
                image: registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/spring-cloud-a:mse-2.0.0
                imagePullPolicy: Always
                name: spring-cloud-a
                ports:
                  - containerPort: 20001
      ...
      # c application
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-c
      spec:
        ...
                image: registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/spring-cloud-c:mse-2.0.1
    • 执行如下命令,查看Rollout资源的状态。

      kubectl get rollouts rollouts-a -n default
      kubectl get rollouts rollouts-c -n default

      预期输出:

      NAME            STATUS        CANARY_STEP   CANARY_STATE   MESSAGE                                                                         AGE
      rollouts-a   Progressing   1             StepPaused     Rollout is in step(1/1), and you need manually confirm to enter the next step   41m
      rollouts-c   Progressing   1             StepPaused     Rollout is in step(1/1), and you need manually confirm to enter the next step   41m

      通过预期输出的STATUSCANARY,可以观察Rollout的过程以及步骤:

      • 若预期输出STATUS=Progressing:表明已经在灰度发布过程中。

      • 若预期输出CANARY_STATE=StepPaused:表明当前批次已经完成,是否需要继续,可通过人工确认。

  3. 确认灰度发布的新版本发布正常后,继续后续发布。

    前面的步骤仅发布部分实例以及部分的金丝雀流量灰度,通过一些业务日志或监控确认新版本发布正常后,可通过rollout.rollouts.kruise.io/<rollouts-demo> approved命令继续后续发布,其中<rollouts-demo>表示Rollout资源的名称。

  4. (可选)若新版本服务异常,可进行业务回滚。

    如果在Rollout过程中,发现新版本服务异常,可以通过Deployment配置恢复到之前版本。然后通过kubectl apply -f mse-demo.yaml命令进行部署,无需对Rollout资源做任何改动。

在微服务治理架构中,全链路灰度功能提供流量分流,极大地方便了测试和发布时的快速验证,结合Kruise Rollouts能够大幅度帮助DevOps提升线上稳定性。