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

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

采用 Ingress 灰度发布方式用户可以:

  1. 控制新版本流量分配权重,以小部分线上流量对服务进行验证
  2. 通过 cookie 或者 header 使得部分受控用户在线上对发布进行验证
  3. 当发布验证失败后,可以快速回滚到旧版本

使用前提

  • 使用由阿里云容器服务 Kubernetes 版本提供的 Kubernetes 集群

实现原理

如下图所示,当采用 ACK Nginx Ingress 灰度发布时,假定当前运行版本为 primary,将要发布版本为 canary。

发布过程:

  1. 发布前检查:预检查当前 Ingress 是否有且只关联了唯一的 Service 实例,且 Service 实例下有且只有唯一版本的 Deployment。
  2. 生成 Canary 版本:克隆 primary 版本的 Service 以及 Deployment 生成 canary 版本的 Service 和 Deployment,同时修改 canary 版本 Deployment 的镜像到新版本。
  3. 修改 Ingress 流量规则:根据发布配置调整 Ingress 配置,开始执行灰度。
  4. 人工验证:通过 cookie 或者 header 对灰度版本进行验证,根据结果选择完成发布或者回滚。
  5. 完成灰度:修改 Ingress 配置以及流量规则,下线 Primary 版本的 Service 以及 Deployment 实例。
  6. 回滚发布:修改 Ingress 配置以及流量规则,下线 Canary 版本的 Service 以及 Deployment。

快速开始

1. 初始化应用部署

通过以下配置快速初始化应用部署:

apiVersion: apps/v1
kind: Deployment
metadata:  
    name: old-nginx  
    labels:    run: old-nginx
spec:  
    replicas: 2  
    selector:    
        matchLabels:     
            run: old-nginx  
    template:    
        metadata:      
            labels:        
                run: old-nginx    
        spec:      
            containers:      
            - image: nginx         
                imagePullPolicy: Always       
                name: 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
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:    
    name: gray-release
spec:  
    rules:  
    - host: www.example.com    
        http:      
             paths:      
             # 老版本服务      
            - path: /        
                backend:          
                   serviceName: old-nginx          
                    servicePort: 80

保存以上内容到,gray-release.yaml,使用 kubectl 或者控制台完成应用初始化:

kubectl apply -f gray-release.yaml

2. 配置流水线

如下所示,配置如下信息:

配置说明:

配置项 说明
命名空间 当前服务所在在 Kubernetes 集群命名空间, 示例中为 default
Ingress名称 发布的目标 Ingress 实例名称,示例中为 gray-release
服务端口 Ingress 后端 Service 实例对外暴露的端口,示例中为 80
应用端口 镜像对外暴露的端口,示例中为 80
容器名称 应用发布时需要更新镜像的容器名,示例中为 nginx
镜像 通过前序任务镜像构建产生的镜像,或者是特定的镜像名称
灰度方式 选择使用 header 或者 cookie 方式进行灰度验证,并设置匹配的 Key/Value
灰度初始化流量权重 默认灰度版本上线后的流量权重
启动等待时间 灰度版本发布后等待该时间后再修改 Ingress 配置
下线等待时间 Ingress 配置调整后等待该时长后再移除应用实例

3. 运行流水线

保存并运行流水线,当新版本发布后,流水线将任务将处于灰度发布中,等待人工验证以确定后续操作。

灰度中查看Kubernetes资源状态如下所示:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:  
    annotations:    
        kubectl.kubernetes.io/last-applied-configuration: |      {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"nginx.ingress.kubernetes.io/service-match":"gray-release-v20201231112858: cookie(\"foo\", /^bar$/)","nginx.ingress.kubernetes.io/service-weight":"gray-release-v20201231112858: 0, old-nginx: 100"},"name":"gray-release","namespace":"default"},"spec":{"rules":[{"host":"www.example.com","http":{"paths":[{"backend":{"serviceName":"old-nginx","servicePort":80},"path":"/","pathType":"ImplementationSpecific"},{"backend":{"serviceName":"gray-release-v20201231112858","servicePort":80},"path":"/","pathType":"ImplementationSpecific"}]}}]}}    
        nginx.ingress.kubernetes.io/service-match: 'gray-release-v20201231112858: cookie("foo",      /^bar$/)'    
        nginx.ingress.kubernetes.io/service-weight: 'gray-release-v20201231112858: 0,      old-nginx: 100'  
    name: gray-release  
    namespace: default
spec:  
    rules:  
    - host: www.example.com    
       http:      
           paths:      
           - backend:          
                serviceName: old-nginx          
                servicePort: 80        
            path: /        
            pathType: ImplementationSpecific      
         - backend:          
                serviceName: gray-release-v20201231112858          
                servicePort: 80        
            path: /        
            pathType: ImplementationSpecific

在灰度中云效自动创建了灰度版本的服务,命名规则未:<ingressname>-<version>

查看灰度版本的 Service 以及 Deployment 详情:

apiVersion: v1
kind: Service
metadata:  
    labels:    
        run: old-nginx    
        version: v20201231112858  
    name: gray-release-v20201231112858  
    namespace: default
spec:  
    clusterIP: 10.0.6.88  
    ports:  
    - port: 80    
       protocol: TCP    
       targetPort: 80  
    selector:    
        run: old-nginx    
        version: v20201231112858  
     type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:  
     generation: 1  
     labels:    
        run: old-nginx    
        version: v20201231112858  
     name: gray-release-v20201231112858  
     namespace: default
spec:  
    progressDeadlineSeconds: 600  
    replicas: 2  
    revisionHistoryLimit: 10  
    selector:    
        matchLabels:      
            run: old-nginx      
            version: v20201231112858  
    strategy:    
        rollingUpdate:      
            maxSurge: 25%      
            maxUnavailable: 25%    
        type: RollingUpdate  
    template:    
        metadata:      
            creationTimestamp: null      
            labels:       
                 run: old-nginx
                version: v20201231112858    
    spec:      
        containers:      
        -  image: nginx:latest       
            imagePullPolicy: Always       
            name: nginx        
            ports:        
            - containerPort: 80          
              protocol: TCP

用户可以通过在浏览器设置 cookie 的后来访问新版本的应用:

document.cookie="foo=bar"

4. 确认发布完成

在人工验证成功后,在卡片上点击“完成”按钮,后流水线将会自动完成ingress的配置调整,以及老版本的应用下线操作:

此时查看线上应用 Ingress 信息,如下所示:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:  
   annotations:    
      kubectl.kubernetes.io/last-applied-configuration: |      {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"gray-release","namespace":"default"},"spec":{"rules":[{"host":"www.example.com","http":{"paths":[{"backend":{"serviceName":"gray-release-v20201231112858","servicePort":80},"path":"/","pathType":"ImplementationSpecific"}]}}]}}  
   name: gray-release  
   namespace: default
spec:  
  rules:  
  - host: www.example.com  
     http:   
       paths:   
       - backend:  
              serviceName: gray-release-v20201231112858
               servicePort: 80        
          path: /        
          pathType: ImplementationSpecific
---

常见发布失败的问题:

  1. 当 Ingress 关联了多个 Service 实例时发布失败。
  2. 当 Service 关联了多个 Deployment 实例时发布失败,Service 的 LabelSelector 需要确保与 Deployment 的 labels 保持匹配。
  3. 线上版本镜像和当前发布镜像未变化时,发布失败。
  4. 而发布配置中的容器名称无法匹配到容器定义时发布失败。