You need to enable JavaScript to run this app.
导航
通过 annotation 实现 K8S 蓝绿部署和灰度发布
最近更新时间:2024.09.11 10:37:35首次发布时间:2024.06.19 10:13:18

API 网关深度集成火山引擎容器服务 VKE,可实时动态获取 VKE 集群中部署的 K8S Service 信息,作为 K8S Service 对外提供服务的流量入口。同时,API 网关提供 Upstream 和流量权重能力,方便用户进行服务的灰度发布,实现敏捷迭代、平滑升级。
本文为您介绍如何通过 API 网关实现 K8S 蓝绿部署和灰度发布。

应用场景

为了保证服务稳定地对外提供服务,各企业都十分重视发布策略的选择。目前被业界广泛采用的服务发布策略有蓝绿部署和灰度发布,请根据实际情况选择适合的发布策略。

  • 蓝绿部署:同时运行两个版本的应用。蓝绿部署期间,旧版本保持对外提供服务,等新版本运行起来后,再将流量全部切换至新版本。一般新版本与旧版本的资源规格保持一致,相当于该服务有两套完全相同的部署环境。新版本对外提供服务后,旧版本会作为热备。如果新版本上线后出现问题,可以迅速将流量切回旧版本,极大缩短故障恢复时间。

  • 灰度发布:也称为金丝雀发布,是一种从旧版本平滑过渡到新版本的发布策略。该策略先上线一个新版本,从旧版本中切分一小部分线上流量到新版本,检验新版本在生产环境中的实际表现。新版本在环境中表现满足预期后,逐步加大流量分配比例,直至完全替代旧版本。

背景信息

本文通过一个 http-server 服务,为您演示 K8S 蓝绿部署和灰度发布。该服务已提供一个查询当前版本的接口:请求路径为/version。API 网关将该 http-server 服务抽象为一个 Upstream,并通过 Pod 标签来标识旧版本 v1 和新版本 v2。用户发送请求后,API 网关便可将请求按照设置的权重转发至新旧版本,实现应用的多版本流量管理。

前提条件

  • 容器服务 VKE

    • 已开通容器服务。

    • 已创建 VKE 集群,具体操作请参见 创建集群

  • API 网关

    • 已开通 API 网关。

    • 已创建网关实例,具体操作请参见 创建实例

按 Header 匹配灰度发布

步骤一:部署应用

部署 v1 应用,并通过 API 网关对外提供服务。

  1. 使用以下 YAML 样例,在容器服务目标集群的 default 命名空间下创建 Deployment 和 Service(http-server-v1) 。

    1. 创建 http-server-v1.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: http-server-v1
        namespace: default 
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: http-server-v1
        template:
          metadata:
            labels:
              app: http-server-v1
              version: v1
          spec:
            containers:
            - image: cr-apig-cn-beijing.cr.volces.com/apig-demo/http-server:v1
              imagePullPolicy: IfNotPresent
              name: http-server-v1
              ports:
              - containerPort: 80
      ---        
      apiVersion: v1
      kind: Service
      metadata:
        name:  http-server-v1
        namespace: default
      spec:
        ports:
        - name: http-server-v1
          port: 80
          protocol: TCP
        selector:
          app: http-server-v1
        sessionAffinity: None
        type: ClusterIP
      
    2. 执行以下命令,部署 Deployment 和 Service。

      kubectl apply -f http-server-v1.yaml
      
  2. 将当前 v1 应用所在 VKE 集群导入 API 网关,并开启路由同步。具体操作请参见 创建 Upstream 来源

  3. 使用以下 YAML 样例,部署 Ingress。

    1. 创建 ingress1.yaml

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: route1
        namespace: default
      spec:
        ingressClassName: apig
        rules:
          - host: www.example.com
            http:
              paths:
                - backend:
                    service:
                      name: http-server-v1
                      port:
                        number: 80
                  path: /version
                  pathType: Exact
      
    2. 执行以下命令,部署 Ingress。

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

    kubectl get ingress
    
  5. 执行以下测试请求命令:

    for i in seq 1 10;do curl ${您的网关服务访问域名}/version;echo;done
    

    说明

    请把命令中的 ${您的网关服务访问域名} 替换为步骤 4 中获取的自定义域名。

    响应结果:

    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    

    可以发现,所有请求均转发至 v1,符合预期。

允许引流一小部分流量到服务新版本,待验证通过后,逐步调大流量,直至切流完毕,期间可伴随着新版本的扩容,旧版本的缩容操作,达到资源利用率最大化。

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

  1. 使用以下 YAML 样例,在容器服务目标集群的 default 命名空间下创建 Deployment 和 Service(http-server-v2)。

    灰度发布策略中,新版本的副本数无需与原始保持一致,仅需保持资源始终满足灰度流量,故将副本数调为 1。

    注意

    v2 应用支持同集群部署和跨集群部署。

    • 同集群部署时,需部署在同一集群同一命名空间下。

    • 跨集群部署时,需部署在跨集群的同名命名空间下。

    1. 创建 http-server-v2.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: http-server-v2
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: http-server-v2
        template:
          metadata:
            labels:
              app: http-server-v2
              version: v2
          spec:
            containers:
            - image: cr-apig-cn-beijing.cr.volces.com/apig-demo/http-server:v2
              imagePullPolicy: IfNotPresent
              name: http-server-v2
              ports:
              - containerPort: 80
      ---        
      apiVersion: v1
      kind: Service
      metadata:
        name: http-server-v2
        namespace: default
      spec:
        ports:
        - name: http-server-v2
          port: 80
          protocol: TCP
        selector:
          app: http-server-v2
        sessionAffinity: None
        type: ClusterIP
      
    2. 执行以下命令,部署 Deployment 和 Service。

      kubectl apply -f http-server-v2.yaml
      
  2. (可选)如果 v2 应用跨集群部署,需将 v2 应用所在 VKE 集群导入同一 API 网关实例,并开启路由同步。导入成功后,之前创建的 Upstream 将同时监听这两个集群的 default 命名空间下的 http-server 服务。具体操作可参见 创建 Upstream 来源

  3. 设置访问新版本服务的路由规则。

    1. 创建 ingress2.yaml

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        annotations:
          nginx.ingress.kubernetes.io/canary: 'true'
          # headers中包含foo=bar时,才会发送到http-server-v2上,不带此header时,发送到原始路由中配置的服务http-server-v1上
          nginx.ingress.kubernetes.io/canary-by-header: 'foo'
          nginx.ingress.kubernetes.io/canary-by-header-value: 'bar'
        name: route2
        namespace: default
      spec:
        ingressClassName: apig
        rules:
          - host: www.example.com
            http:
              paths:
                - backend:
                    service:
                      name: http-server-v2
                      port:
                        number: 80
                  path: /version
                  pathType: Exact
      
    2. 执行以下命令,部署 Ingress。

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

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

    1. 执行以下测试请求命令:

      for i in seq 1 10;do curl ${您的网关服务访问域名}/version;echo;done
      

      说明

      请把命令中的 ${您的网关服务访问域名} 替换为步骤 4 中获取的自定义域名。

      响应结果:

      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      {"code": 200, "message": "version: v1"}
      
    2. 执行以下测试请求命令:

      for i in seq 1 10;do curl -H "foo:bar" ${您的网关服务访问域名}/version;echo;done
      

      响应结果:

      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      {"code": 200, "message": "version: v2"}
      
    3. 可以发现,两条路由都通过 www.example.com/version 访问,如果带 Header(foo=bar),那就分流到 http-server-v2 上;否则分流到 http-server-v1 上,符合期望比例。

    注意

    在真实业务场景中,新版本验证完毕后,便可继续调大访问新版本的流量权重。期间注意对新版本扩容,按需对旧版本缩容。

步骤三:删除老版本服务

新版本服务已经稳定运行一段时间后,需要将业务全部转移到新版本服务,然后下线老版本服务。为了达到该目标,需要将旧版本的 Service 指向新版本服务的 Deployment,并且删除旧版本的 Deployment 和新版本的 Service。

  1. 修改老版本服务文件 http-server-v1.yaml,将路由目标指向新版本服务。

    apiVersion: v1
    kind: Service
    metadata:
      name:  http-server-v1
      namespace: default
    spec:
      ports:
      - name: http-server
        port: 80
        protocol: TCP
      selector:
        app: http-server-v2
      sessionAffinity: None
      type: ClusterIP
    
  2. 执行以下命令,部署旧版本服务。

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

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

    for i in seq 1 10;do curl ${您的网关服务访问域名}/version;echo;done
    

    说明

    请把命令中的 ${您的网关服务访问域名} 替换为步骤 3 中获取的自定义域名。

    响应结果:

    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    

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

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

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

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

      kubectl delete svc http-server-v2
      

按权重进行灰度发布

步骤一:部署应用

部署 v1 应用,并通过 API 网关对外提供服务。

  1. 使用以下 YAML 样例,在容器服务目标集群的 default 命名空间下创建 Deployment 和 Service(http-server-v1) 。

    1. 创建 http-server-v1.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: http-server-v1
        namespace: default 
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: http-server-v1
        template:
          metadata:
            labels:
              app: http-server-v1
              version: v1
          spec:
            containers:
            - image: cr-apig-cn-beijing.cr.volces.com/apig-demo/http-server:v1
              imagePullPolicy: IfNotPresent
              name: http-server-v1
              ports:
              - containerPort: 80
      ---        
      apiVersion: v1
      kind: Service
      metadata:
        name: http-server-v1
        namespace: default
      spec:
        ports:
        - name: http-server
          port: 80
          protocol: TCP
        selector:
          app: http-server-v1
        sessionAffinity: None
        type: ClusterIP
      
    2. 执行以下命令,部署 Deployment 和 Service。

      kubectl apply -f http-server-v1.yaml
      
  2. 将当前 v1 应用所在 VKE 集群导入 API 网关,并开启路由同步。具体操作请参见 创建 Upstream 来源

  3. 使用以下 YAML 样例,部署 Ingress。

    1. 创建 ingress1.yaml

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        generation: 1
        name: route1
        namespace: default
      spec:
        ingressClassName: apig
        rules:
          - host: www.example.com
            http:
              paths:
                - backend:
                    service:
                      name: http-server-v1
                      port:
                        number: 80
                  path: /version
                  pathType: Exact
      
    2. 执行以下命令,部署 Ingress。

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

    kubectl get ingress
    
  5. 执行以下测试请求命令,查看路由访问情况。

    for i in seq 1 10;do curl ${您的网关服务访问域名}/version;echo;done
    

    说明

    请把命令中的 ${您的网关服务访问域名} 替换为步骤 4 中获取的自定义域名。

    响应结果:

    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    

    可以发现,所有请求均转发至 v1,符合预期。

允许引流一小部分流量到服务新版本,待验证通过后,逐步调大流量,直至切流完毕,期间可伴随着新版本的扩容,旧版本的缩容操作,达到资源利用率最大化。

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

  1. 使用以下 YAML 样例,在容器服务目标集群的 default 命名空间下创建 Deployment 和 Service(http-server-v2)。

    灰度发布策略中,新版本的副本数无需与原始保持一致,仅需保持资源始终满足灰度流量,故将副本数调为 1。

    注意

    v2 应用支持同集群部署和跨集群部署。

    • 同集群部署时,需部署在同一集群同一命名空间下。

    • 跨集群部署时,需部署在跨集群的同名命名空间下。

    1. 创建 http-server-v2.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: http-server-v2
        namespace: default 
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: http-server-v2
        template:
          metadata:
            labels:
              app: http-server-v2
              version: v2
          spec:
            containers:
            - image: cr-apig-cn-beijing.cr.volces.com/apig-demo/http-server:v2
              imagePullPolicy: IfNotPresent
              name: http-server-v2
              ports:
              - containerPort: 80
      ---        
      apiVersion: v1
      kind: Service
      metadata:
        name: http-server-v2
        namespace: default
      spec:
        ports:
        - name: http-server
          port: 80
          protocol: TCP
        selector:
          app: http-server-v2
        sessionAffinity: None
        type: ClusterIP
      
    2. 执行以下命令,部署 Deployment 和 Service。

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

    1. 按照以下内容,创建 ingress2.yaml

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        annotations:
          nginx.ingress.kubernetes.io/canary: 'true'
          # 30%的流量发送http-server-v2上,剩下的70%发送到原始路由中配置的服务的http-server-v1上
          nginx.ingress.kubernetes.io/canary-weight: '30'
        generation: 1
        name: route2
        namespace: default
      spec:
        ingressClassName: apig
        rules:
          - host: www.example.com
            http:
              paths:
                - backend:
                    service:
                      name: http-server-v2
                      port:
                        number: 80
                  path: /version
                  pathType: Exact
      
    2. 执行以下命令,部署 Ingress。

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

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

    for i in seq 1 10;do curl ${您的网关服务访问域名}/version;echo;done
    

    说明

    请把命令中的 ${您的网关服务访问域名} 替换为步骤 4 中获取的自定义域名。

    响应结果:

    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v1"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v1"}
    

    可以发现,10 个请求中有 3 个请求转发至新版本 v2,流量比符合期望比例。

    注意

    在真实业务场景中,新版本验证完毕后,便可继续调大访问新版本的流量权重。期间注意对新版本扩容,按需对旧版本缩容。

步骤三:删除老版本服务

新版本服务已经稳定运行一段时间后,需要将业务全部转移到新版本服务,然后下线老版本服务。

  1. 修改 ingress1.yaml,将路由目标指向新版本服务。

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      generation: 1
      name: route1
      namespace: default
    spec:
      ingressClassName: apig
      rules:
        - host: www.example.com
          http:
            paths:
              - backend:
                  service:
                    name: http-server-v2
                    port:
                      number: 80
                path: /version
                pathType: Exact
    
  2. 执行以下命令,部署 ingress。

    kubectl apply -f ingress1.yaml
    
  3. 执行以下命令,查看路由访问情况。

    for i in seq 1 10;do curl ${您的网关服务访问域名}/version;echo;done
    

    说明

    请把命令中的 ${您的网关服务访问域名} 替换为步骤 4 中获取的自定义域名。

    响应结果:

    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    {"code": 200, "message": "version: v2"}
    

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

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

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

      kubectl delete deploy http-server-v1
      
    2. 执行以下命令,删除旧版本的 Service。

      kubectl delete svc http-server-v1