CODING CD - Nginx Ingress 實現藍綠髮布

本文作者:楊浩佳 - CODING 後端開發工程師

 前言 

本文將介紹如何通過 CODING CD 使用 Nginx Ingress 來實現藍綠髮布

爲什麼要採用藍綠髮布?隨着業務的快速發展,對開發團隊的要求越來越高,一方面要求爲用戶提供穩定的服務,一方面要求進行快速業務迭代。因此基於系統穩定性和快速業務迭代的綜合考慮,需要採用藍綠髮布上線新版本服務的方式,實現應用服務的平穩升級。

爲什麼要使用 CODING CD?傳統的部署是修改 YAML 文件的鏡像版本,然後通過命令行 kubectl apply 的方式更新應用服務的版本,這種發佈方式過於依賴人工執行,對於 DevOps 團隊來說是不可忍受的。而通過 CODING CD 部署流程實現自動化流水線,流水線的所有階段都可以供團隊中的任何人檢查、改進和驗證,開發團隊可以提高發布的速度和降低發佈的風險和成本

 概述 

什麼是 Nginx Ingress

Nginx Ingress 是 Kubernetes Ingress 的一種實現,它通過 watch Kubernetes 集羣的 Ingress 資源,將 Ingress 規則轉換成 Nginx 的配置,然後讓 Nginx 來進行 7 層的流量轉發。

使用註解說明

我們通過給 Ingress 資源指定 Nginx Ingress 所支持的一些 annotation 可以實現藍綠髮布,需要給服務創建兩個 Ingress,一個正常的 Ingress(myapp-ingress)。
另一個是帶nginx.ingress.kubernetes.io/canary: "true" 這個固定的 annotation 的 Ingress,我們姑且稱它爲 Canary Ingress(myapp-blue-ingress)。一般代表新版本的服務,結合另外針對流量切分策略的 annotation 一起配置即可實現多種場景的藍綠髮布,以下是對本次實踐使用到的 annotation 的介紹:

更多關於 Nginx Ingress 的註解說明可以參考官方文檔。
(https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary)

什麼是藍綠髮布 

藍綠髮布,是在生產環境穩定集羣之外,額外部署一個與穩定集羣規模相同的新集羣,並通過流量控制,逐步引入流量至新集羣直至 100%,原先穩定集羣將與新集羣同時保持在線一段時間,期間發生任何異常,可立刻將所有流量切回至原穩定集羣,實現快速回滾。直到全部驗證成功後,下線老的穩定集羣,新集羣成爲新的穩定集羣。

發佈流程

藍綠髮布的流程,包括:藍綠髮布開始、藍綠初始化、藍綠驗證、藍綠取消或完成上線。

如上圖所示,老集羣集羣的版本爲 v1,該集羣通過 version=v1 標籤被 Service myapp-svc 訪問到。

 實踐 

前提條件:

Kubernetes 集羣中需要部署 Nginx Ingress 作爲 Ingress Controller,並且對外暴露了統一的流量入口。

老集羣初始化

發佈流程:

配置

這裏配置部署 myapp 應用服務所需要的 docker 鏡像製品,還有啓動參數 version,該參數的作用主要是作爲標籤爲 Service 提供篩選過濾條件,實現新老版本服務的流量控制。

發佈策略

發佈策略採用人工確認階段,可配置確認人,選擇不同發佈方式。

常規發佈

常規發佈採用預置條件檢查階段,預置條件配置 #judgment("發佈策略") == '常規發佈' 表達式,表達式中的 judgment 函數返回人工確認階段選定的選項,並通過 == 關係運算符來比較表達式中的值,如果表達式判斷成功,將執行這個分支的階段。

新集羣 (即綠集羣) 部署

apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
  strategy.spinnaker.io/max-version-history: '3'   # 表示保留 3 個歷史版本的 myapp-deploy
  strategy.spinnaker.io/versioned: 'true'   # 表示 Spinnaker 對 myapp-deploy 進行版本管理
name: myapp-deploy
spec:
replicas: 3
selector:
  matchLabels:
    app: myapp
    version: '${parameters.version}'   # 引用配置中的啓動參數 version
template:
  metadata:
    labels:
      app: myapp
      version: '${parameters.version}'
  spec:
    containers:
      - image: selinaxeon-docker.pkg.coding.net/coding-yhj/docker/myapp
        name: myapp
        ports:
          - containerPort: 80

${parameters.version}表達式表示引用配置中已配置的啓動參數 version。

這裏部署新集羣的 Deployment 被打上 Spinnaker 註解:

注意,此階段還需要綁定配置中已配置的 myapp-image Docker 鏡像製品,Spinnaker 將自動替換 Manifest 中匹配的 image。具體綁定替換規則參考 CODING 幫助文檔。

切換流量

apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
ports:
  - name: tcp-80-80
    port: 80
    protocol: TCP
    targetPort: 80
selector:
  app: myapp
  version: '${parameters.version}'
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
  kubernetes.io/ingress.class: nginx
name: myapp-ingress
spec:
rules:
  - host: myapp.coding.prod
    http:
      paths:
        - backend:
            serviceName: myapp-svc
            servicePort: 80
          path: /

這裏 Service myapp-svc 通過 version: '${parameters.version}' 標籤訪問到帶有相同標籤的 myapp-deploy Pod,myapp-ingress 配置路由到  Service myapp-svc 的規則,可通過 _http://myapp.coding.prod _訪問到 myapp-svc 後端服務。

老集羣下線

這裏採用擴縮容(Manifest)階段,選擇動態選擇目標並且 Target 選擇次新的,將 Deployment myapp-deploy 的 replicas 設置爲 0,完成老集羣下線操作。之所以不採用刪除 (Manifest)階段是因爲要保留 myapp-deploy 的歷史發佈版本,方便回滾操作,另外刪除 myapp-deploy 的歷史版本交給 strategy.spinnaker.io/max-version-history 註解實現。
注意,這裏執行選項如果階段失敗選項選擇終止流程中的這個分支,因爲對於老集羣初始化部署時,沒有次新的版本可供下線操作,此階段會執行失敗,導致整個流程部署失敗。
爲什麼在常規發佈多了此階段?目的是實現流水線可複用,第二次發佈新版本服務時還可以通過該部署流程執行,新集羣部署完直到服務穩定,即可同時進行切換流量老集羣下線操作。

 藍綠髮布開始 

在 “發佈策略” 的人工確認階段選擇發佈類型 “藍綠髮布”,即可開始藍綠髮布。在“藍綠髮布” 的預置條件檢查階段配置條件表達式:#judgment("發佈策略") == '藍綠髮布'

 藍綠初始化 

新集羣 (即藍集羣) 部署

apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
  strategy.spinnaker.io/max-version-history: '3'
  strategy.spinnaker.io/versioned: 'true'
name: myapp-deploy
spec:
replicas: 3
selector:
  matchLabels:
    app: myapp
    version: '${parameters.version}'
template:
  metadata:
    labels:
      app: myapp
      version: '${parameters.version}'
  spec:
    containers:
      - image: selinaxeon-docker.pkg.coding.net/coding-yhj/docker/myapp
        name: myapp
        ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp-blue-svc
spec:
ports:
  - name: tcp-80-80
    port: 80
    protocol: TCP
    targetPort: 80
selector:
  app: myapp
  version: '${parameters.version}'
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/canary: 'true'   # 開啓 canary 功能
  nginx.ingress.kubernetes.io/canary-by-header: blueGreenVersion   # 基於請求頭路由
  nginx.ingress.kubernetes.io/canary-by-header-value: blue   # 請求頭滿足 blueGreenVersion=blue 的請求會被路由到 myapp-blue-svc
name: myapp-blue-ingress
spec:
rules:
  - host: myapp.coding.prod
    http:
      paths:
        - backend:
            serviceName: myapp-blue-svc
            servicePort: 80
          path: /

藍綠初始化流程:首先推送路由規則,控制全部流量在老集羣,保證新集羣部署啓動期間不會接收任何流量。
我們通過實例的 version: '${parameters.version}' 標籤來識別是藍集羣還是綠集羣,因此啓動部署流程前須填寫不同的啓動參數,確保 Service myapp-blue-svc 通過不同的 version 標籤值篩選到藍集羣的實例 Pod;另外,通過發佈 Ingress 規則 myapp-blue-ingress,請求頭滿足 blueGreenVersion=blue 匹配的請求會被路由到藍集羣的 Service myapp-blue-svc。推送完規則後,就可以部署新集羣了。
注意,此階段也需要綁定配置中已配置的 myapp-image Docker 鏡像製品。
用戶需要在正確匹配請求頭纔可以訪問新版本服務:
curl -H 'blueGreenVersion:blue' http://myapp.coding.prod

 藍綠驗證 

藍綠驗證流程:初始化完成後,可以推送路由規則,將部分請求流量或全部流量路由至新集羣進行驗證。驗證過程中如果沒有問題,可以不斷將流量遷移至新集羣,直至所有流量都在新集羣。如果藍綠驗證成功則進入藍綠完成上線流程,驗證失敗則進入藍綠取消流程。

藍綠驗證採用人工確認階段,配置確認人,可選擇控制部分請求流量或全部流量路由至新集羣進行驗證。這裏還配置了自定義參數 blue_ratio,該參數是控制部分流量到新集羣的請求百分比。

控制部分流量到新集羣

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/canary: 'true'
  nginx.ingress.kubernetes.io/canary-weight: '${#stage("藍綠驗證")["context"]["customParams"]["blue_ratio"]}'
name: myapp-blue-ingress
spec:
rules:
  - host: myapp.coding.prod
    http:
      paths:
        - backend:
            serviceName: myapp-blue-svc
            servicePort: 80
          path: /

#stage("藍綠驗證")["context"]["customParams"]["blue_ratio"] 表達式表示引用人工確認階段中已配置的自定義參數 blue_ratio,這將控制隨機 blue_ratio% 請求進入新版本集羣。

在此階段的執行選項還需配置條件表達式 #judgment("藍綠驗證") == '控制部分流量到新集羣',控制部署流程分支執行方向。

藍綠驗證結果確認

如果在控制部分流量到新集羣驗證過程中發現問題,可以通過此人工確認階段實現將全部流量切回老集羣。

在此階段的執行選項也需要配置條件表達式 #judgment("藍綠驗證") == '控制部分流量到新集羣',因爲上一階段因不滿足條件被跳過執行,此階段爲上一階段的下游階段,還會繼續執行,因此也需要配置條件表達式。

控制全部流量到新集羣

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/canary: 'true'
  nginx.ingress.kubernetes.io/canary-weight: '100'
name: myapp-blue-ingress
spec:
rules:
  - host: myapp.coding.prod
    http:
      paths:
        - backend:
            serviceName: myapp-blue-svc
            servicePort: 80
          path: /

說明:通過設置 nginx.ingress.kubernetes.io/canary-weight: '100' 控制全部流量到新集羣。

注意,在此階段的執行選項需要配置條件表達式 ${ (#stage("藍綠驗證")["status"].toString() == "SUCCEEDED" && #judgment( '藍綠驗證') == '控制全部流量到新集羣') || (#stage("藍綠驗證結果確認")["status"].toString() == "SUCCEEDED" && #judgment('藍綠驗證結果確認') == '驗證成功,控制全部流量到新集羣') }。表達式中的 stage 函數使用 #stage("藍綠驗證")["status"].toString() 返回 "藍綠驗證" 人工確認階段的執行狀態。Spinnaker 表達式的語法基於 Spring Expression Language (SpEL),可以使用 && 和 || 等關係運算符。

這裏需要這麼複雜的條件表達式,是因爲既可以從 “藍綠驗證” 人工確認階段直接選擇控制全部流量到新集羣選項進入此階段,也可以從 “藍綠驗證結果確認 “人工確認階段選擇驗證成功,控制全部流量到新集羣選項進入此階段,因此需要強大的條件表達式來控制流程分支的走向。由此可見,Spinnaker 部署流程的強大,可支持複雜的流程分支控制,滿足各種發佈場景的不同需求。而且流水線只需一次配置,多次執行。

藍綠最終效果確認

如果在控制全部流量到新集羣驗證過程中發現問題,可以通過此人工確認階段進入 “藍綠取消” 流程,實現將全部流量切回老集羣。
在此階段的執行選項也需要配置跟 “控制全部流量到新集羣” 階段一樣的條件表達式,來控制部署流程分支執行方向。

 藍綠完成上線 

新集羣驗證成功

新集羣驗證成功採用預置條件檢查階段,預置條件配置比較簡單:#judgment("藍綠最終效果確認") == '新集羣驗證成功'

藍綠完成上線

apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
ports:
  - name: tcp-80-80
    port: 80
    protocol: TCP
    targetPort: 80
selector:
  app: myapp
  version: '${parameters.version}'
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
  kubernetes.io/ingress.class: nginx
name: myapp-blue-ingress
spec:
rules:
  - host: myapp.coding.prod
    http:
      paths:
        - backend:
            serviceName: myapp-svc
            servicePort: 80
          path: /

如果藍綠驗證沒有發現問題,那麼就可以完成藍綠髮布上線了。只有當全部流量在新集羣時才能操作完成上線。完成上線後,如果再有需要回滾,那隻能走普通的回滾流程了。
這裏 Service myapp-svc 通過 version: '${parameters.version}' 標籤訪問到帶有相同標籤的新集羣 Pod,實現流量切換,myapp-blue-ingress 也配置路由到  Service myapp-svc 的規則。

老集羣下線

“老集羣下線”跟常規發佈的 “老集羣下線” 相似,都採用擴縮容(Manifest)階段,只不過執行選項採用默認配置。

 藍綠取消 

新集羣驗證失敗

新集羣驗證失敗採用預置條件檢查階段,預置條件比較複雜:${ (#stage("藍綠驗證結果確認")["status"].toString() == "SUCCEEDED" && #judgment( '藍綠驗證結果確認') == '驗證失敗,藍綠取消') || (#stage("藍綠最終效果確認")["status"].toString() == "SUCCEEDED" && #judgment('藍綠最終效果確認') == '新集羣驗證失敗') }。因爲既可以從 “藍綠驗證結果確認” 人工確認階段直接選擇驗證失敗,藍綠取消選項進入此階段,也可以從 “藍綠最終效果確認人工確認階段選擇新集羣驗證失敗選項進入此階段。

藍綠取消

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/canary: 'true'
  nginx.ingress.kubernetes.io/canary-by-header: blueGreenVersion
  nginx.ingress.kubernetes.io/canary-by-header-value: blue
name: myapp-blue-ingress
spec:
rules:
  - host: myapp.coding.prod
    http:
      paths:
        - backend:
            serviceName: myapp-blue-svc
            servicePort: 80
          path: /

如果在藍綠驗證過程中發現了問題,藍綠取消可秒級將全部流量切回老集羣,具體步驟:推送路由規則控制所有流量到老集羣。

這裏修改 myapp-blue-ingress 的路由規則,只有請求頭匹配 blueGreenVersion=blue 纔可以訪問到新集羣,不匹配則訪問老集羣,實現了全部流量切換回老集羣。

新集羣下線

這裏採用刪除(Manifest)階段,選擇動態選擇目標並且 Target 選擇最新的,將新集羣的 Deployment myapp-deploy 刪除,完成老集羣下線操作。

當然,也可先通過修改路由規則的方式,先將流量切回老集羣,然後保留現場,方便進行問題排查。

 效果 

終於配置完成藍綠髮布的部署流程,接下來我們來看一下藍綠髮布效果。

初始化綠集羣

藍綠髮布成功

藍綠髮布失敗

 結語 

在上面的示例中,我們通過在 CODING CD 中配置一條流水線,實現了基於 Nginx Ingress 的藍綠髮布。部署流程只需配置一次,便可永久使用,大大減少了手工部署帶來的失誤,提升了應用的發佈效率。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xsq8ScuscbAW3pVgAcPL7Q