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

騰訊雲原生發表於2020-09-27

概述

如何在騰訊雲 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 叢集中實現簡單的藍綠髮布與灰度釋出,對於一些簡單的釋出需求場景可以考慮使用這種方案。

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

相關文章