使用 Nginx Ingress 實現金絲雀釋出/灰度釋出

旧巷g發表於2024-11-18

使用 Nginx Ingress 實現金絲雀釋出/灰度釋出

說明:

使用 Nginx Ingress 實現金絲雀釋出的叢集,需部署 Nginx Ingress 作為 Ingress Controller,並且對外暴露統一的流量入口。詳情請參見 在 TKE 上部署 Nginx Ingress

使用場景

使用 Nginx Ingress 實現金絲雀釋出適用場景主要取決於業務流量切分的策略。目前 Nginx Ingress 支援基於 Header、Cookie 和服務權重三種流量切分的策略,基於這三種策略可實現以下兩種釋出場景:

場景1: 灰度新版本到部分使用者

假設線上已執行了一套對外提供7層服務的 Service A,此時需上線開發的新版本 Service A',但不期望直接替換原有的 Service A,僅灰度部分使用者,待執行一段時間足夠穩定後再逐漸全量上線新版本,平滑下線舊版本。針對此場景可使用 Nginx Ingress 基於 Header 或 Cookie 進行流量切分的策略來發布,業務使用 Header 或 Cookie 來標識不同型別的使用者,並透過配置 Ingress 來實現讓帶有指定 Header 或 Cookie 的請求被轉發到新版本,其它請求仍然轉發到舊版本,從而將新版本灰度給部分使用者。示意圖如下:

img

場景2: 切分一定比例的流量到新版本

假設線上已執行了一套對外提供7層服務的 Service B,此時修復了 Service B 的部分問題,需灰度上線新版本 Service B'。但不期望直接替換原有的 Service B,需先切換10%的流量至新版本,待執行一段時間足夠穩定後再逐漸加大新版本流量比例直至完全替換舊版本,最終平滑下線舊版本。示意圖如下:

img

註解說明

透過給 Ingress 資源指定 Nginx Ingress 所支援的 annotation 可實現金絲雀釋出。需給服務建立兩個 Ingress,其中一個為常規 Ingress,另一個為帶 nginx.ingress.kubernetes.io/canary: "true" 固定 annotation 的 Ingress,稱為 Canary Ingress。Canary Ingress 一般代表新版本的服務,結合另外針對流量切分策略的 annotation 一起配置即可實現多種場景的金絲雀釋出。以下為相關 annotation 的詳細介紹:

**nginx.ingress.kubernetes.io/canary-by-header** 表示如果請求頭中包含指定的 header 名稱,並且值為 always,就將該請求轉發給該 Ingress 定義的對應後端服務。如果值為 never 則不轉發,可以用於回滾到舊版。如果為其他值則忽略該 annotation。

**nginx.ingress.kubernetes.io/canary-by-header-value** 該 annotation 可以作為 canary-by-header 的補充,可指定請求頭為自定義值,包含但不限於 alwaysnever。當請求頭的值命中指定的自定義值時,請求將會轉發給該 Ingress 定義的對應後端服務,如果是其它值則忽略該 annotation。

**nginx.ingress.kubernetes.io/canary-by-header-pattern**canary-by-header-value 類似,區別為該 annotation 用正規表示式匹配請求頭的值,而不是隻固定某一個值。如果該 annotation 與 canary-by-header-value 同時存在,該 annotation 將被忽略。

**nginx.ingress.kubernetes.io/canary-by-cookie**canary-by-header 類似,該 annotation 用於 cookie,僅支援 alwaysnever

**nginx.ingress.kubernetes.io/canary-weight** 表示 Canary Ingress 所分配流量的比例的百分比,取值範圍 [0-100]。例如,設定為10,則表示分配10%的流量給 Canary Ingress 對應的後端服務。

說明:

以上規則會按優先順序進行評估,優先順序為: canary-by-header -> canary-by-cookie -> canary-weight

當 Ingress 被標記為 Canary Ingress 時,除了 nginx.ingress.kubernetes.io/load-balancenginx.ingress.kubernetes.io/upstream-hash-by 外,所有其他非 Canary 註釋都將被忽略。

使用示例

注意: 下面以 TKE 叢集為例,為您演示如何使用 Nginx Ingress 進行金絲雀釋出。在操作過程中,請注意以下事項:

\1. 相同服務的 Canary Ingress 僅能夠定義一個,導致後端服務最多支援兩個版本。

\2. 在 Ingress 中必須配置域名,否則將無法生效。

\3. 即便流量完全切到了 Canary Ingress 上,舊版服務仍需存在,否則會出現報錯。

使用 YAML 建立資源

本文提供以下兩種方式使用 YAML 部署工作負載及建立 Service:

方式1:在單擊 TKE 或 Serverless 叢集詳情頁右上角的 YAML 建立資源,並將本文示例的 YAML 檔案內容輸入編輯介面。

方式2:將示例 YAML 儲存為檔案,再使用 kubectl 指定 YAML 檔案進行建立。例如 kubectl apply -f xx.yaml

部署兩個版本的服務

\1. 在叢集中部署第一個版本的 Deployment,本文以 nginx-v1 為例。YAML 示例如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v1
  namespace: default
  labels:
    app: nginx-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-v1
  template:
    metadata:
      labels:
        app: nginx-v1
    spec:
      containers:
        - name: nginx-v1
          image: nginx:1.15.2
          command:
            - sh
            - '-c'
            - >-
              echo nginx-v1 > /usr/share/nginx/html/index.html && exec nginx -g
              'daemon off;'
          ports:
            - name: nginxprot
              containerPort: 80
              protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-v1
  namespace: default
  labels:
    app: nginx-v1
spec:
  ports:
    - name: nginxprot
      protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx-v1
  type: ClusterIP

\2. 再部署第二個版本的 Deployment,本文以 nginx-v2 為例。YAML 示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v2
  namespace: default
  labels:
    app: nginx-v2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-v2
  template:
    metadata:
      labels:
        app: nginx-v2
    spec:
      containers:
        - name: nginx-v2
          image: nginx:1.15.2
          command:
            - sh
            - '-c'
            - >-
              echo nginx-v2 > /usr/share/nginx/html/index.html && exec nginx -g
              'daemon off;'
          ports:
            - name: nginxprot
              containerPort: 80
              protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-v2
  namespace: default
  labels:
    app: nginx-v2
spec:
  ports:
    - name: nginxprot
      protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx-v2

您可登入 容器服務控制檯,在叢集的工作負載詳情頁檢視部署情況。如下圖所示:

img

\3.建立 Ingress,對外暴露服務,指向 v1 版本的服務。YAML 示例如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  namespace: default
spec:
  ingressClassName: nginx
  rules:
    - host: canary.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-v1
                port:
                  number: 80

\4.執行以下命令,進行訪問驗證。

curl -H "Host: canary.example.com" http://EXTERNAL-IP # EXTERNAL-IP 替換為 Nginx Ingress 自身對外暴露的 IP

返回結果如下:

nginx-v1

基於 Header 的流量切分

建立 Canary Ingress,指定 v2 版本的後端服務,並增加 annotation。實現僅將帶有名為 Region 且值為 cd 或 sz 的請求頭的請求轉發給當前 Canary Ingress,模擬灰度新版本給成都和深圳地域的使用者。YAML 示例如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-canary
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/canary: 'true'
    nginx.ingress.kubernetes.io/canary-by-header: Region
    nginx.ingress.kubernetes.io/canary-by-header-pattern: cd|sz
spec:
  ingressClassName: nginx
  rules:
    - host: canary.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-v2
                port:
                  number: 80
​

執行以下命令,進行訪問測試。

curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP # EXTERNAL-IP 替換為 Nginx Ingress 自身對外暴露的 IP
nginx-v2
curl -H "Host: canary.example.com" -H "Region: bj" http://EXTERNAL-IP
nginx-v1
curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP
nginx-v2
curl -H "Host: canary.example.com" http://EXTERNAL-IP
nginx-v1

可檢視當僅有 header Region 為 cd 或 sz 的請求才由 v2 版本服務響應。

基於 Cookie 的流量切分

使用 Cookie 則無法自定義 value,以模擬灰度成都地域使用者為例,僅將帶有名為 user_from_cd 的 Cookie 的請求轉發給當前 Canary Ingress。YAML 示例如下:

說明:

若您已配置以上步驟建立 Canary Ingress,則請刪除後再參考本步驟建立。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-canary
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/canary: 'true'
    nginx.ingress.kubernetes.io/canary-by-cookie: "user_from_cd"
spec:
  ingressClassName: nginx
  rules:
    - host: canary.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-v2
                port:
                  number: 80
​

執行以下命令,進行訪問測試。

curl -s -H "Host: canary.example.com" --cookie "user_from_cd=always" http://EXTERNAL-IP # EXTERNAL-IP 替換為 Nginx Ingress 自身對外暴露的 IP
nginx-v2
curl -s -H "Host: canary.example.com" --cookie "user_from_bj=always" http://EXTERNAL-IP
nginx-v1
curl -s -H "Host: canary.example.com" http://EXTERNAL-IP
nginx-v1

可檢視當僅有 cookie user_from_cdalways 的請求才由 v2 版本的服務響應。

基於服務權重的流量切分

使用基於服務權重的 Canary Ingress 時,直接定義需要匯入的流量比例即可。以匯入10%流量到 v2 版本為例,YAML 示例如下:

說明:

若您已配置以上步驟建立 Canary Ingress,則請刪除後再參考本步驟建立。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-canary
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/canary: 'true'
    nginx.ingress.kubernetes.io/canary-weight: '10'
spec:
  ingressClassName: nginx
  rules:
    - host: canary.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-v2
                port:
                  number: 80
​

執行以下命令,進行訪問測試。

for i in {1..10}; do curl -H "Host: canary.example.com" http://EXTERNAL-IP; done;
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v2
nginx-v1
nginx-v1
nginx-v1

可檢視,有十分之一的機率由 v2 版本的服務響應,符合10%服務權重的設定。

參考資料

希望大家多給博主提提建議

Nginx Ingress 金絲雀註解官方文件

在 TKE 上部署 Nginx Ingress

使用 Nginx Ingress 實現金絲雀釋出-實踐教程-騰訊雲

相關文章