手把手教你在 TKE 集群中實現簡單的藍綠髮布和灰度發布

概述

如何在騰訊雲 Kubernetes 集群實現藍綠髮布和灰度發布?通常要向集群額外部署其它開源工具來實現,比如 Nginx Ingress,Traefik 等,或者讓業務上 Service Mesh(服務網格),利用服務網格的能力來實現。這些方案多多少少都是需要一點點門檻的,如果藍綠髮布或灰度發布的需求不複雜,同時不希望讓集群引入更多的組件或複雜的用法,可以考慮使用本文的簡單方案,利用 Kubernetes 原生的特性以及騰訊雲 TKE/EKS 集群自帶的 LB 插件實現簡單的藍綠髮布和灰度發布。

: 本文適用產品範圍: TKE 集群、EKS 集群 (彈性集群)

原理介紹

我們通常使用 Deployment、StatefulSet 等 Kubernetes 自帶的工作負載來部署業務,每個工作負載都管理一組 Pod,以 Deployment 為例:

img

通常還會為每個工作負載創建對應的 Service,Service 通過 selector 來匹配後端 Pod,其它服務或者外部通過訪問 Service 即可訪問到後端 Pod 提供的服務。要對外暴露可以直接將 Service 類型設置為 LoadBalancer,LB 插件會自動為其創建 CLB (騰訊雲負載均衡器) 作為流量入口。

如何實現藍綠髮布?以 Deployment 為例,集群中部署兩個不同版本的 Deployment,它們的 Pod 擁有共同的 label,但有一個 label 的值不同,用於區分不同的版本,Service 使用 selector 選中了其中一個版本的 Deployment 的 Pod,通過修改 Service 的 selector 中決定 服務版本的 label 的值來改變 Service 後端對應的 Deployment,實現讓服務從一個版本直接切換到另一個版本,即藍綠髮布:

img

如何實現灰度發布?雖然我們通常會為每個工作負載都創建一個 Service,但 Kubernetes 並沒有限制 Service 一定要與工作負載一一對應,因為 Service 是通過 selector 來匹配後端 Pod 的,只要不同工作負載的 Pod 都能被相同 selector 選中,就可以實現一個 Service 對應多個版本的工作負載的效果,調整不同版本工作負載的副本數就相當於調整不同版本服務的權重,實現灰度發布:

img

使用 YAML 創建資源

本文的示例將使用 yaml 的方式部署工作負載和創建 Service,有兩種操作方式。

方式一:在 TKE 或 EKS 控制台右上角點擊 YAML 創建資源,然後將本文示例的 yaml 粘貼進去:

img

方式二:將示例的 yaml 保存成文件,然後使用 kubectl 指定 yaml 文件來創建,如: kubectl apply -f xx.yaml

部署多版本工作負載

要實現藍綠髮布或灰度發布,首先我們需要在集群中部署多個版本的工作負載,這裡以簡單的 nginx 為例,部署第一個版本:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
      version: v1
  template:
    metadata:
      labels:
        app: nginx
        version: v1
    spec:
      containers:
      - name: nginx
        image: "openresty/openresty:centos"
        ports:
        - name: http
          protocol: TCP
          containerPort: 80
        volumeMounts:
        - mountPath: /usr/local/openresty/nginx/conf/nginx.conf
          name: config
          subPath: nginx.conf
      volumes:
      - name: config
        configMap:
          name: nginx-v1
---
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app: nginx
    version: v1
  name: nginx-v1
data:
  nginx.conf: |-
    worker_processes  1;
    events {
        accept_mutex on;
        multi_accept on;
        use epoll;
        worker_connections  1024;
    }
    http {
        ignore_invalid_headers off;
        server {
            listen 80;
            location / {
                access_by_lua '
                    local header_str = ngx.say("nginx-v1")
                ';
            }
        }
    }

再部署第二個版本:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
      version: v2
  template:
    metadata:
      labels:
        app: nginx
        version: v2
    spec:
      containers:
      - name: nginx
        image: "openresty/openresty:centos"
        ports:
        - name: http
          protocol: TCP
          containerPort: 80
        volumeMounts:
        - mountPath: /usr/local/openresty/nginx/conf/nginx.conf
          name: config
          subPath: nginx.conf
      volumes:
      - name: config
        configMap:
          name: nginx-v2
---
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app: nginx
    version: v2
  name: nginx-v2
data:
  nginx.conf: |-
    worker_processes  1;
    events {
        accept_mutex on;
        multi_accept on;
        use epoll;
        worker_connections  1024;
    }
    http {
        ignore_invalid_headers off;
        server {
            listen 80;
            location / {
                access_by_lua '
                    local header_str = ngx.say("nginx-v2")
                ';
            }
        }
    }

可以在控制台看到部署的情況:

img

實現藍綠髮布

為我們部署的 Deployment 創建 LoadBalancer 類型的 Service 對外暴露服務,指定使用 v1 版本的服務:

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    protocol: TCP
    name: http
  selector:
    app: nginx
    version: v1

測試訪問:

$ for i in {1..10}; do curl EXTERNAL-IP; done; # 替換 EXTERNAL-IP 為 Service 的 CLB IP 地址
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1

全是 v1 版本的響應,現在我們切到 v2 版本,修改 Service 的 selector,讓它選中 v2 版本的服務,如果在控制台改,先找到對應 Service,點擊 編輯YAML:

img

修改 selector 部分:

  selector:
    app: nginx
    version: v2

或者也可以直接用 kubectl 修改:

kubectl patch service nginx -p '{"spec":{"selector":{"version":"v2"}}}'

再次測試訪問:

$ for i in {1..10}; do curl EXTERNAL-IP; done; # 替換 EXTERNAL-IP 為 Service 的 CLB IP 地址
nginx-v2
nginx-v2
nginx-v2
nginx-v2
nginx-v2
nginx-v2
nginx-v2
nginx-v2
nginx-v2
nginx-v2

全是 v2 版本的響應,成功實現了藍綠髮布。

實現灰度發布

相比藍綠髮布,我們為不給 Service 指定使用 v1 版本的服務,從 selector 中刪除 version 標籤,讓 Service 同時選中兩個版本的 Deployment 的 Pod:

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    protocol: TCP
    name: http
  selector:
    app: nginx

測試訪問:

$ for i in {1..10}; do curl EXTERNAL-IP; done; # 替換 EXTERNAL-IP 為 Service 的 CLB IP 地址
nginx-v1
nginx-v1
nginx-v2
nginx-v2
nginx-v2
nginx-v1
nginx-v1
nginx-v1
nginx-v2
nginx-v2

可以看到,一半是 v1 版本的響應,另一半是 v2 版本的響應。現在我們來調節 v1 和 v2 版本的 Deployment 的副本,將 v1 版本調至 1 個副本,v2 版本調至 4 個副本。

可以通過控制台操作:

img

也可以通過 kubectl 操作:

kubectl scale deployment/nginx-v1 --replicas=1
kubectl scale deployment/nginx-v2 --replicas=4

然後再次進行訪問測試:

$ for i in {1..10}; do curl EXTERNAL-IP; done; # 替換 EXTERNAL-IP 為 Service 的 CLB IP 地址nginx-v2nginx-v1nginx-v2nginx-v2nginx-v2nginx-v2nginx-v1nginx-v2nginx-v2nginx-v2$ for i in {1..10}; do curl EXTERNAL-IP; done; # 替換 EXTERNAL-IP 為 Service 的 CLB IP 地址
nginx-v2
nginx-v1
nginx-v2
nginx-v2
nginx-v2
nginx-v2
nginx-v1
nginx-v2
nginx-v2
nginx-v2

可以看到,10 次訪問中只有 2 次返回了 v1 版本,v1 與 v2 的響應比例與其副本數比例一致,為 1:4,通過控制不同版本服務的副本數就實現了灰度發布。

總結

本文我們介紹了如何在有限的條件下在 Kubernetes 集群中實現簡單的藍綠髮布與灰度發布,對於一些簡單的發布需求場景可以考慮使用這種方案。

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多乾貨!!