概述#

上一章用 Service 完成了藍綠部署,這次輪到本系列另一位主角 Ingress 上場,示範如何用它實作金絲雀部署(Canary Deployment)。在 Kubernetes 裡,Ingress 透過 hostname 與 pathname 處理七層流量,並由 Ingress Controller 將請求分配到一個或多個 Service。Kubernetes 官方維護的 Controller 為 Nginx Ingress,可以透過註解(Annotations)為不同部署情境提供細緻的流量控制。

Nginx Ingress 的金絲雀功能#

Nginx Ingress 提供三種基於 Header、Cookie、權重的流量切分策略,只要在註解中加上對應設定即可:

  • nginx.ingress.kubernetes.io/canary:值為 true 即視為 Canary Ingress,會與既有 Ingress 互相搭配進行流量切分。
  • nginx.ingress.kubernetes.io/canary-by-header-value:當請求 Header 與設定值匹配時,將流量導向 Canary Ingress。
  • nginx.ingress.kubernetes.io/canary-by-header-pattern:行為與上一項類似但支援正則表達式。注意:若同時設定了 canary-by-header-value,本項會被忽略。
  • nginx.ingress.kubernetes.io/canary-by-cookie:請求 Cookie 與設定值匹配時導向 Canary Ingress;設成 always 則導所有流量。
  • nginx.ingress.kubernetes.io/canary-weight:以 0 至 100 的整數宣告要把多少百分比的流量導向 Canary Ingress。

優先級由高到低:canary-by-header → canary-by-cookie → canary-weight

金絲雀部署的特色#

金絲雀部署不像藍綠部署那樣是非黑即白的切換,而是介於兩者之間,能平滑過渡到下一個版本。先把新版本推送給少量使用者,確認穩定後再擴大比例,藉此把新功能上線的風險壓到最低。這種「灰度發布(Canary Release)」的精神,對缺少完整測試或對新版本沒把握的場景特別實用。

落實步驟#

  1. 啟動 v1 版本,搭配 Ingress 對外提供服務。
  2. 部署 v2 版本,等待完全就緒。此時新舊兩版本並存於叢集中。
  3. 加入 Canary Ingress,並設定欲分流到 v2 的權重。
  4. 觀察一段時間後若條件成立,把主 Ingress 指向 v2,並刪除 Canary Ingress。
  5. 關閉舊的 v1 版本資源。

實戰練習#

1. 啟動 v1 與 Ingress#

# app-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v1
  template:
    metadata:
      labels:
        app: my-app
        version: v1
    spec:
      containers:
        - name: foo
          image: mikehsu0618/foo
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: foo-service
spec:
  selector:
    app: my-app
    version: v1
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: foo-service
      port:
        number: 8080
kubectl apply -f app-v1.yaml,ingress.yaml

連續打十次驗證對外只有 v1:

for i in {1..10}; do curl localhost; echo; done
# 預期會看到 10 筆 {"data":"Hello foo"}

2. 部署 v2 但暫不接流量#

# app-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar-deployment
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v2
  template:
    metadata:
      labels:
        app: my-app
        version: v2
    spec:
      containers:
        - name: bar
          image: mikehsu0618/bar
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: bar-service
spec:
  selector:
    app: my-app
    version: v2
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
kubectl apply -f app-v2.yaml

再打一次驗證仍然只有 v1 接受外部請求:

for i in {1..10}; do curl localhost; echo; done
# 預期仍是 10 筆 {"data":"Hello foo"}

3. 加入 Canary Ingress 進行權重分流#

# canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
  name: canary-ingress
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: bar-service
      port:
        number: 8080

canary-weight: "10" 代表把 10% 流量分到 bar-service,其餘 90% 留在 foo-service

kubectl apply -f canary-ingress.yaml

再次發出十筆請求,可預期約 10% 會被導向 v2:

for i in {1..10}; do curl localhost; echo; done
# 大致上會看到一筆 {"data":"Hello bar"},其餘為 {"data":"Hello foo"}

4. 完成切換#

確認 v2 表現穩定後,把主 Ingress 後端改為 bar-service,刪除 Canary Ingress 與舊的 foo-deploymentfoo-service,整個金絲雀部署就告一段落。

小結#

進階部署策略不外乎組合 Deployment、Service、Pod、Ingress 這幾位老朋友,再以 Label、Selector、Annotation 控制流量。Nginx Ingress Controller 是個 LoadBalancer 層的小宇宙,深入研究會更有收穫;但更重要的是把基礎概念內化,遇到任何花式部署需求都能自己拼出對應的解法。

原文出處#

  • GitHub:https://github.com/MikeHsu0618/2022-ithelp/tree/main/Day15
  • iThome:https://ithelp.ithome.com.tw/articles/10290852