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.kubernetes.io/canary-by-header
: 表示如果請求頭中包含這裏指定的 header 名稱,並且值爲always
的話,就將該請求轉發給該 Ingress 定義的對應後端服務;如果值爲never
就不轉發,可以用於回滾到舊版;如果是其它值則忽略該 annotation。 -
nginx.ingress.kubernetes.io/canary-by-header-value
: 這個可以作爲canary-by-header
的補充,允許指定請求頭的值可以自定義成其它值,不再只能是always
或never
;當請求頭的值命中這裏的自定義值時,請求將會轉發給該 Ingress 定義的對應後端服務,如果是其它值則將會忽略該 annotation。 -
nginx.ingress.kubernetes.io/canary-weight
: 表示 Canary Ingress 所分配流量的比例的百分比,取值範圍 [0-100],比如設置爲 10,意思是分配 10% 的流量給 Canary Ingress 對應的後端服務。
更多關於 Nginx Ingress 的註解說明可以參考官方文檔。
(https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary)
什麼是藍綠髮布
藍綠髮布,是在生產環境穩定集羣之外,額外部署一個與穩定集羣規模相同的新集羣,並通過流量控制,逐步引入流量至新集羣直至 100%,原先穩定集羣將與新集羣同時保持在線一段時間,期間發生任何異常,可立刻將所有流量切回至原穩定集羣,實現快速回滾。直到全部驗證成功後,下線老的穩定集羣,新集羣成爲新的穩定集羣。
發佈流程
藍綠髮布的流程,包括:藍綠髮布開始、藍綠初始化、藍綠驗證、藍綠取消或完成上線。
如上圖所示,老集羣集羣的版本爲 v1,該集羣通過 version=v1 標籤被 Service myapp-svc 訪問到。
-
額外部署了一個新集羣,版本爲 v2,該集羣通過 version=v2 標籤被 Service myapp-blue-svc 訪問到。
-
通過流量控制,控制部分流量或全部流量到新集羣,進行藍綠驗證,同時原穩定集羣繼續保持在線。
-
如果驗證沒有發現任何故障,則應用 myapp 的藍綠髮布完成,v2 版本集羣成爲新的穩定集羣。
-
如果在驗證中發現了故障,則通過流量控制,將全部流量切回原來穩定的老集羣,實現快速回滾。
實踐
前提條件:
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 註解:
-
strategy.spinnaker.io/versioned
,當該註解被設置爲 ‘true’ 時,Spinnaker 將對 Deployment 資源進行版本管理。簡單來說,就是發佈後的 Deployment 名稱將帶有版本號,如 myapp-deploy-v000。 -
strategy.spinnaker.io/max-version-history
,該註解表示 Spinnaker 將配置要保留的資源版本數。當 Spinnaker 發佈的 k8s 資源的歷史版本數超過 max-version-history 時,Spinnaker 會刪除舊版本的資源。這裏設置爲 '3',表示要保留 3 個歷史版本的 Deployment myapp-deploy。
注意,此階段還需要綁定配置中已配置的 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