通过Nginx Ingress实现灰度发布和蓝绿发布

当对服务进行版本更新升级时,需要使用到滚动升级、分批暂停发布、蓝绿发布以及灰度发布等发布方式。本文将介绍在ACK集群中如何通过Nginx Ingress Controller来实现应用服务的灰度发布。 背景...

当对服务进行版本更新升级时,需要使用到滚动升级、分批暂停发布、蓝绿发布以及灰度发布等发布方式。本文将介绍在ACK集群中如何通过Nginx Ingress Controller来实现应用服务的灰度发布。

背景信息

灰度及蓝绿发布是为新版本创建一个与老版本完全一致的生产环境,在不影响老版本的前提下,按照一定的规则把部分流量切换到新版本,当新版本试运行一段时间没有问题后,将用户的全量流量从老版本迁移至新版本。

其中蓝绿发布就是一种灰度发布方式,一部分用户继续使用老版本的服务,将一部分用户的流量切换到新版本,如果新版本运行稳定,则逐步将所有用户迁移到新版本。

容器服务ACK控制台调整了灰度发布功能的用法,分为两种。

  • canary-*注解方式:使用canary-* Annotation配置蓝绿发布与灰度发布,canary-* Annotation是社区官方实现的灰度发布方式。

  • service-*注解方式:使用service-* Annotation配置蓝绿发布与灰度发布。service-* Annotation是ACK Nginx Ingress Controller早期实现灰度发布的方式。service-matchservice-weight功能已不再维护,并即将废弃。service-* Annotation功能目前仍正常保留,不影响使用。

应用场景

基于客户端请求的流量切分场景

假设当前线上环境,您已经有一套服务Service A对外提供7层服务,此时上线了一些新的特性,需要发布上线一个新的版本Service A'。但又不想直接替换Service A服务,而是希望将请求头中包含foo=bar或者Cookie中包含foo=bar的客户端请求转发到Service A'服务中。待运行一段时间稳定后,可将所有的流量从Service A切换到Service A'服务中,再平滑地将Service A服务下线。

attachments-2023-12-IGd7ASje65824f98367dc,png

基于服务权重的流量切分场景

假设当前线上环境,您已经有一套服务Service B对外提供7层服务,此时修复了一些问题,需要发布上线一个新的版本Service B'。但又不想将所有客户端流量切换到新版本Service B'中,而是希望将20%的流量切换到新版本Service B'中。待运行一段时间稳定后,再将所有的流量从Service B切换到Service B'服务中,再平滑地将Service B服务下线。

attachments-2023-12-ZW0zqlEv65824facc43a6,png

针对以上多种不同的应用发布需求,阿里云容器服务Ingress Controller支持了多种流量切分方式:

  • 基于Request Header的流量切分,适用于灰度发布以及AB测试场景。

  • 基于Cookie的流量切分,适用于灰度发布以及AB测试场景。

  • 基于Query Param的流量切分,适用于灰度发布以及AB测试场景。

  • 基于服务权重的流量切分,适用于蓝绿发布场景。

canary-*注解方式

注解说明

Nginx Ingress Controller通过下列canary-* Annotation来支持应用服务的灰度发布机制。

attachments-2023-12-2k05R4Zg65824fca11659,png

不同灰度方式的优先级由高到低为:

canary-by-header>canary-by-cookie>canary-weight

说明

目前每个Ingress规则只支持同时指定一个Canary Ingress,大于一个的Canary Ingress将会被忽略。

步骤一:部署服务

部署Nginx服务并通过Nginx Ingress Controller对外提供7层域名访问。

  1. 创建Deployment和Service。

    1. 创建nginx.yaml

      展开查看YAML文件

    2. 执行以下命令,创建Deployment和Service。

      kubectl apply -f nginx.yaml
  2. 部署Ingress。

    1. 创建ingress.yaml

      1.19及之后版本集群
      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: gray-release
      spec:
        rules:
        - host: www.example.com
          http:
            paths:
            # 老版本服务。
            - path: /
              backend:
                service: 
                  name: old-nginx
                  port:
                    number: 80
              pathType: ImplementationSpecific
    2. 执行以下命令,部署Ingress。

      kubectl apply -f ingress.yaml
  3. 测试访问情况。

    1. 执行以下命令,获取外部IP。

      kubectl get ingress
    2. 执行以下命令,查看路由访问情况。

      curl -H "Host: www.example.com"  http://<EXTERNAL_IP>

      预期输出:

      old

步骤二:灰度发布新版本服务

发布一个新版本的Nginx服务并配置路由规则。

  1. 部署新版本的Deployment和Service。

    1. 创建nginx1.yaml

      展开查看YAML文件

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: new-nginx
      spec:
        replicas: 1
        selector:
          matchLabels:
            run: new-nginx
        template:
          metadata:
            labels:
              run: new-nginx
          spec:
            containers:
            - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx
              imagePullPolicy: Always
              name: new-nginx
              ports:
              - containerPort: 80
                protocol: TCP
            restartPolicy: Always
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: new-nginx
      spec:
        ports:
        - port: 80
          protocol: TCP
          targetPort: 80
        selector:
          run: new-nginx
        sessionAffinity: None
        type: NodePort
    2. 执行以下命令,部署新版本的Deployment和Service。

      kubectl apply -f nginx1.yaml
  2. 设置访问新版本服务的路由规则。

    ACK支持设置以下三种路由规则,您可以根据实际情况选择路由规则。

    • 设置满足特定规则的客户端才能访问新版本服务。以下示例仅请求头中满足foo=bar的客户端请求才能路由到新版本服务。

      1. 按照以上条件,在ingress1.yaml文件中创建新的Ingress资源gray-release-canary

        1.19及之后版本集群
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: gray-release-canary
          annotations:
            # 开启Canary。
            nginx.ingress.kubernetes.io/canary: "true"
            # 请求头为foo。
            nginx.ingress.kubernetes.io/canary-by-header: "foo"
            # 请求头foo的值为bar时,请求才会被路由到新版本服务new-nginx中。
            nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
            
        spec:
          rules:
          - host: www.example.com
            http:
              paths:
              # 新版本服务。
              - path: /
                backend:
                  service: 
                    name: new-nginx
                    port:
                      number: 80
                pathType: ImplementationSpecific
      2. 执行以下命令,部署Ingress1。

        kubectl apply -f ingress1.yaml
      3. 执行以下命令,获取外部IP。

        kubectl get ingress
      4. 查看路由访问情况。

        1. 执行以下命令,访问服务。

          curl -H "Host: www.example.com"  http://<EXTERNAL_IP>

          预期输出:

          old
        2. 执行以下命令,请求头中满足foo=bar的客户端请求访问服务。

          curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>

          预期输出:

          new

        重复执行以上命令,可以看到,仅请求头中满足foo=bar的客户端请求才能路由到新版本服务。

    • 在特定规则未被满足时,再按照一定比例将请求路由到新版本服务中。以下示例要求请求头中满足foo=bar的客户端请求,若不包含该请求头,再将50%的流量路由到新版本服务中。

      1. 按照以下内容,修改步骤2中创建的Ingress。

        1.19及之后版本集群
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: gray-release-canary
          annotations:
            # 开启Canary。
            nginx.ingress.kubernetes.io/canary: "true"
            # 请求头为foo。
            nginx.ingress.kubernetes.io/canary-by-header: "foo"
            # 请求头foo的值为bar时,请求才会被路由到新版本服务new-nginx中。
            nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
            # 在未满足上述匹配规则的基础上仅允许50%的流量会被路由到新版本服务new-nginx中。
            nginx.ingress.kubernetes.io/canary-weight: "50"
        spec:
          rules:
          - host: www.example.com
            http:
              paths:
              # 新版本服务。
              - path: /
                backend:
                  service: 
                    name: new-nginx
                    port:
                      number: 80
                pathType: ImplementationSpecific
      2. 执行以下命令,部署Ingress。

        kubectl apply -f ingress.yaml
      3. 执行以下命令,获取外部IP。

        kubectl get ingress
      4. 查看路由访问情况。

        1. 执行以下命令,访问服务。

          curl -H "Host: www.example.com"  http://<EXTERNAL_IP>

          预期输出:

          old
        2. 执行以下命令,请求头中满足foo=bar的客户端请求访问服务。

          curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>

          预期输出:

          new

        重复执行以上命令。可以看到,仅请求头中满足foo=bar的客户端请求,且只有50%的流量才能路由到新版本服务。

    • 设置一定比例的请求被路由到新版本服务中,以下示例中仅50%的流量被路由到新版本服务中。

      1. 按照以下内容,修改步骤2创建的Ingress。

        1.19及之后版本集群
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: gray-release-canary
          annotations:
            # 开启Canary。
            nginx.ingress.kubernetes.io/canary: "true"
            # 仅允许50%的流量会被路由到新版本服务new-nginx中。
            # 默认总值为100。
            nginx.ingress.kubernetes.io/canary-weight: "50"
        spec:
          rules:
          - host: www.example.com
            http:
              paths:
              # 新版本服务。
              - path: /
                backend:
                  service: 
                    name: new-nginx
                    port:
                      number: 80
                pathType: ImplementationSpecific
      2. 执行以下命令,部署Ingress。

        kubectl apply -f ingress.yaml
      3. 执行以下命令,获取外部IP。

        kubectl get ingress
      4. 执行以下命令,查看路由访问情况。

        curl -H "Host: www.example.com" http://<EXTERNAL_IP>

      重复执行以上命令,可以看到仅50%的流量路由到新版本服务。

步骤三:删除老版本服务

系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。为了达到该目标,需要将旧版本的Service指向新版本服务的Deployment,并且删除旧版本的Deployment和新版本的Service。

  1. 修改旧版本Service文件nginx.yaml,使其指向新版本服务。

    展开查看YAML文件

    apiVersion: v1
    kind: Service
    metadata:
      name: old-nginx
    spec:
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
      selector:
        # 指向新版本服务。
        run: new-nginx
      sessionAffinity: None
      type: NodePort
  2. 执行以下命令,部署旧版本服务。

    kubectl apply -f nginx.yaml
  3. 执行以下命令,获取外部IP。

    kubectl get ingress
  4. 执行以下命令,查看路由访问情况。

    curl -H "Host: www.example.com" http://<EXTERNAL_IP>

    预期输出:

    new

    重复执行以上命令,可以看到请求全部被路由到了新版本的服务。

  5. 执行以下命令,删除Canary Ingress资源gray-release-canary

    kubectl delete ingress gray-release-canary
  6. 删除旧版本的Deployment和新版本的Service。

    1. 执行以下命令,删除旧版本的Deployment。

      kubectl delete deploy old-nginx
    2. 执行以下命令,删除新版本的Service。

      kubectl delete svc new-nginx

service-*注解方式

注解说明

Nginx Ingress Controller通过下列Annotation来支持应用服务的灰度发布机制。

  • nginx.ingress.kubernetes.io/service-match

    该注解用来配置新版本服务的路由匹配规则。

    nginx.ingress.kubernetes.io/service-match: | 
        <service-name>: <match-rule>
    # 参数说明:
    # service-name:服务名称,满足match-rule的请求会被路由到该服务中。
    # match-rule:路由匹配规则
    #
    # 路由匹配规则:
    # 1. 支持的匹配类型
    # - header:基于请求头,支持正则匹配和完整匹配。
    # - cookie:基于cookie,支持正则匹配和完整匹配。
    # - query:基于请求参数,支持正则匹配和完整匹配。
    #
    # 2. 匹配方式
    # - 正则匹配格式:/{regular expression}/,使用反斜杠(//)表明采用正则方式匹配。
    # - 完整匹配格式:"{exact expression}",使用引号("")表明采用完整方式匹配。

    路由匹配规则配置示例:

    # 请求头中满足foo正则匹配^bar$的请求被转发到新版本服务new-nginx中。
    new-nginx: header("foo", /^bar$/)
    # 请求头中满足foo完整匹配bar的请求被转发到新版本服务new-nginx中。
    new-nginx: header("foo", "bar")
    # cookie中满足foo正则匹配^sticky-.+$的请求被转发到新版本服务new-nginx中。
    new-nginx: cookie("foo", /^sticky-.+$/)
    # query param中满足foo完整匹配bar的请求被转发到新版本服务new-nginx中。
    new-nginx: query("foo", "bar")
  • nginx.ingress.kubernetes.io/service-weight

    该注解用来配置新旧版本服务的流量权重。

    nginx.ingress.kubernetes.io/service-weight: | 
        <new-svc-name>:<new-svc-weight>, <old-svc-name>:<old-svc-weight>
    参数说明:
    new-svc-name:新版本服务名称
    new-svc-weight:新版本服务权重
    old-svc-name:旧版本服务名称
    old-svc-weight:旧版本服务权重

    服务权重配置示例:

    nginx.ingress.kubernetes.io/service-weight: | 
        new-nginx: 20, old-nginx: 60

步骤一:部署服务

部署Nginx服务并通过Nginx Ingress Controller对外提供7层域名访问。

  1. 创建Deployment和Service。

    1. 创建nginx.yaml

      展开查看YAML文件

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: old-nginx
      spec:
        replicas: 2
        selector:
          matchLabels:
            run: old-nginx
        template:
          metadata:
            labels:
              run: old-nginx
          spec:
            containers:
            - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx
              imagePullPolicy: Always
              name: old-nginx
              ports:
              - containerPort: 80
                protocol: TCP
            restartPolicy: Always
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: old-nginx
      spec:
        ports:
        - port: 80
          protocol: TCP
          targetPort: 80
        selector:
          run: old-nginx
        sessionAffinity: None
        type: NodePort
    2. 执行以下命令,创建Deployment和Service。

      kubectl apply -f nginx.yaml
  2. 部署Ingress。

    1. 创建ingress.yaml

      1.19版本之前集群
      1.19及之后版本集群
      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: gray-release
      spec:
        rules:
        - host: www.example.com
          http:
            paths:
            # 老版本服务。
            - path: /
              backend:
                service: 
                  name: old-nginx
                  port:
                    number: 80
              pathType: ImplementationSpecific
    2. 执行以下命令,部署Ingress。

      kubectl apply -f ingress.yaml
  3. 测试访问情况。

    1. 执行以下命令,获取外部IP。

      kubectl get ingress
    2. 执行以下命令,查看路由访问情况。

      curl -H "Host: www.example.com"  http://<EXTERNAL_IP>

      预期输出:

      old

步骤二:灰度发布新版本服务

发布一个新版本的Nginx服务并配置路由规则。

  1. 部署新版本的Deployment和Service。

    1. 创建nginx1.yaml

        1. 展开查看YAML文件

          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: new-nginx
          spec:
            replicas: 1
            selector:
              matchLabels:
                run: new-nginx
            template:
              metadata:
                labels:
                  run: new-nginx
              spec:
                containers:
                - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx
                  imagePullPolicy: Always
                  name: new-nginx
                  ports:
                  - containerPort: 80
                    protocol: TCP
                restartPolicy: Always
          ---
          apiVersion: v1
          kind: Service
          metadata:
            name: new-nginx
          spec:
            ports:
            - port: 80
              protocol: TCP
              targetPort: 80
            selector:
              run: new-nginx
            sessionAffinity: None
            type: NodePort
        2. 部署新版本的Deployment和Service。

          kubectl apply -f nginx1.yaml
      1. 设置访问新版本服务的路由规则。

        ACK支持设置以下三种路由规则,您可以根据实际情况选择路由规则。


        • 设置满足特定规则的客户端才能访问新版本服务。以下示例仅请求头中满足foo=bar的客户端请求才能路由到新版本服务。

          1. 按照以下内容,修改步骤2创建的Ingress。

          1.19及之后版本集群
          apiVersion: networking.k8s.io/v1
          kind: Ingress
          metadata:
            name: gray-release
            annotations:
              # 请求头中满足正则匹配foo=bar的请求才会被路由到新版本服务new-nginx中。
              nginx.ingress.kubernetes.io/service-match: | 
                new-nginx: header("foo", /^bar$/)
          spec:
            rules:
            - host: www.example.com
              http:
                paths:
                # 老版本服务。
                - path: /
                  backend:
                    service: 
                      name: old-nginx
                      port:
                        number: 80
                  pathType: ImplementationSpecific
                # 新版本服务。
                - path: /
                  backend:
                    service: 
                      name: new-nginx
                      port:
                        number: 80
                  pathType: ImplementationSpecific
          1. 执行以下命令,部署Ingress。

            kubectl apply -f ingress.yaml
          2. 执行以下命令,获取外部IP。

            kubectl get ingress
          3. 查看路由访问情况。

            1. 执行以下命令,访问服务。

              curl -H "Host: www.example.com"  http://<EXTERNAL_IP>

              预期输出:

              old
            2. 执行以下命令,请求头中满足foo=bar的客户端请求访问服务。

              curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>

              预期输出:

              new

          重复执行以上命令,可以看到,仅请求头中满足foo=bar的客户端请求才能路由到新版本服务。


          • 在满足特定规则的基础上设置一定比例的请求被路由到新版本服务中。以下示例要求请求头中满足foo=bar的客户端请求,且仅允许其中50%的流量被路由到新版本服务中。

            1. 按照以下内容,修改步骤2创建的Ingress。

              1.19及之后版本集群
              apiVersion: networking.k8s.io/v1
              kind: Ingress
              metadata:
                name: gray-release
                annotations:
                  # 请求头中满足正则匹配foo=bar的请求才会被路由到新版本服务new-nginx中。
                  nginx.ingress.kubernetes.io/service-match: |
                      new-nginx: header("foo", /^bar$/)
                  # 在满足上述匹配规则的基础上仅允许50%的流量会被路由到新版本服务new-nginx中。
                  nginx.ingress.kubernetes.io/service-weight: |
                      new-nginx: 50, old-nginx: 50
              spec:
                rules:
                - host: www.example.com
                  http:
                    paths:
                    # 老版本服务。
                    - path: /
                      backend:
                        service: 
                          name: old-nginx
                          port:
                            number: 80
                      pathType: ImplementationSpecific
                    # 新版本服务。
                    - path: /
                      backend:
                        service: 
                          name: new-nginx
                          port:
                            number: 80
                      pathType: ImplementationSpecific
            2. 执行以下命令,部署Ingress。

              kubectl apply -f ingress.yaml
            3. 执行以下命令,获取外部IP。

              kubectl get ingress
            4. 查看路由访问情况。

              1. 执行以下命令,访问服务。

                curl -H "Host: www.example.com"  http://<EXTERNAL_IP>

                预期输出:

                old
              2. 执行以下命令,请求头中满足foo=bar的客户端请求访问服务。

                curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>

                预期输出:

                new

              重复执行以上命令。可以看到,仅请求头中满足foo=bar的客户端请求,且只有50%的流量才能路由到新版本服务。

                • 设置一定比例的请求被路由到新版本服务中,以下示例中仅50%的流量被路由到新版本服务中。

                  1. 按照以下内容,修改步骤2创建的Ingress。

                    1.19版本之前集群
                    1.19及之后版本集群
                    apiVersion: networking.k8s.io/v1
                    kind: Ingress
                    metadata:
                      name: gray-release
                      annotations:
                          # 允许50%的流量被路由到新版本服务new-nginx中。
                          nginx.ingress.kubernetes.io/service-weight: |
                              new-nginx: 50, old-nginx: 50
                    spec:
                      rules:
                      - host: www.example.com
                        http:
                          paths:
                          # 老版本服务。
                          - path: /
                            backend:
                              service: 
                                name: old-nginx
                                port:
                                  number: 80
                            pathType: ImplementationSpecific
                          # 新版本服务。
                          - path: /
                            backend:
                              service: 
                                name: new-nginx
                                port:
                                  number: 80
                            pathType: ImplementationSpecific
                  2. 执行以下命令,部署Ingress。

                    kubectl apply -f ingress.yaml
                  3. 执行以下命令,获取外部IP。

                    kubectl get ingress
                  4. 执行以下命令,查看路由访问情况。

                    curl -H "Host: www.example.com" http://<EXTERNAL_IP>

                    重复执行以上命令,可以看到仅50%的流量路由到新版本服务。

              步骤三:删除老版本服务

              系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。

              1. 按照以下内容,修改步骤2创建的Ingress。

                1.19及之后版本集群
                apiVersion: networking.k8s.io/v1
                kind: Ingress
                metadata:
                  name: gray-release
                spec:
                  rules:
                  - host: www.example.com
                    http:
                      paths:
                      # 新版本服务。
                      - path: /
                        backend:
                          service: 
                            name: new-nginx
                            port:
                              number: 80
                        pathType: ImplementationSpecific
              2. 执行以下命令,部署Ingress。

                kubectl apply -f ingress.yaml
              3. 执行以下命令,获取外部IP。

                kubectl get ingress
              4. 执行以下命令,查看路由访问情况。

                curl -H "Host: www.example.com" http://<EXTERNAL_IP>

                预期输出:

                new

                重复执行以上命令,可以看到请求全部被路由到了新版本的服务。

              5. 删除旧版本的Deployment和Service。

                1. 执行以下命令,删除旧版本的Deployment。

                  kubectl delete deploy <Deployment名称>
                2. 执行以下命令,删除旧版本的Service。

                  kubectl delete svc <Service名称>
  • 发表于 2023-12-20 10:22
  • 阅读 ( 29 )

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
shitian
shitian

662 篇文章

作家榜 »

  1. shitian 662 文章
  2. 石天 437 文章
  3. 每天惠23 33 文章
  4. 小A 29 文章