作者:王煒,CODING DevOps 後端開發工程師,擁有多年研發經驗,雲原生、DevOps、Kubernetes 資深愛好者,Servicemesher 服務網格中文社群成員。獲得 Kubernetes CKA、CKAD 認證。
前言
在 Kubernetes 上的應用實現灰度釋出,最簡單的方案是引入官方的 Nginx-ingress
來實現。
我們通過部署兩套 deployment 和 services,分別代表灰度環境和生產環境,通過負載均衡演算法,實現對兩套環境的按照灰度比例進行分流,進而實現灰度釋出。
通常的做法是當專案打包新映象後,通過修改 yaml
檔案的映象版本,執行 kubectl apply
的方式來更新服務。如果釋出流程還需要進行灰度釋出,那麼可以通過調整兩套服務的配置檔案權重來控制灰度釋出,這種方式離不開人工執行。如果專案數量多,灰度的時間跨度過長,人為誤操作的概率將大大增加,過於依賴於人工執行,這對於 DevOps
工程實踐是不能忍受的。
那麼,有沒有一種方式能夠實現無需人工干預的自動化灰度呢?例如在程式碼更新後,自動釋出到預釋出和灰度環境,並在一天的時間內自動將灰度比例從 10% 權重提高到 100%,且能夠隨時終止,灰度通過後自動釋出到生產環境?
答案是肯定的,利用 CODING DevOps
就能夠滿足此類需求。
Nginx-ingress 架構和原理
迅速回顧一下 Nginx-ingress
的架構和實現原理:
Nginx-ingress
通過前置的 Loadbalancer
型別的 Service
接收叢集流量,將流量轉發至 Nginx-ingress
Pod 內並對配置的策略進行檢查,再轉發至目標 Service
,最終將流量轉發至業務容器。
傳統的 Nginx
需要我們配置 conf
檔案策略。但 Nginx-ingress
通過實現 Nginx-ingress-Controller
將原生 conf
配置檔案和 yaml
配置檔案進行了轉化,當我們配置 yaml
檔案的策略後,Nginx-ingress-Controller
將對其進行轉化,並且動態更新策略,動態 Reload Nginx Pod
,實現自動管理。
那麼 Nginx-ingress-Controller
如何能夠動態感知叢集的策略變化呢?方法有很多種,可以通過 webhook admission 攔截器,也可以通過 ServiceAccount 與 Kubernetes Api 進行互動,動態獲取。Nginx-ingress-Controller
使用後者來實現。所以在部署 Nginx-ingress
我們會發現 Deployment
內指定了 Pod 的 ServiceAccount,以及實現了 RoleBinding ,最終達到 Pod 能夠與 Kubernetes Api 互動的目的。
實現方案預覽
為了實現以上目標,我們設計了以下持續部署流水線。
此持續部署流水線主要實現了以下幾個步驟:
1、自動部署到預釋出環境
2、是否進行 A/B 測試
3、自動灰度釋出(自動進行3次逐漸提升灰度比例)
4、釋出到生產環境
同時,本文案例還演示了從 Git 提交程式碼到自動觸發持續整合的步驟:
1、提交程式碼後觸發持續整合,自動構建映象
2、映象構建完成後,自動推送映象到製品庫
3、觸發持續部署
1、提交程式碼後觸發持續整合,自動構建映象並推送到製品庫
2、觸發持續部署,併發布到預釋出環境
3、人工確認:進行 A/B 測試(或跳過直接進入自動灰度)
進行 A/B 測試時,只有 Header 包含 location=shenzhen 可以訪問新版本,其他使用者訪問生產環境仍然為舊版本。
4、人工確認:是否自動灰度釋出(自動進行 3 輪逐漸提升灰度比例,每輪間隔 30s)
第一次灰度:新版本 30% 的灰度比例,此時訪問生產環境大約有 30% 的流量進入新版本灰度環境:
30s 後自動進行第二輪灰度:新版本 60% 的灰度比例:
60s 後自動進行第三輪灰度:新版本 90% 的灰度比例:
本案例中,我們配置了自動化灰度釋出將會以 3 次漸進式進行,每次提高 30% 的比例,每次持續 30s 後自動進入下一個灰度階段。在不同的灰度階段,會發現請求新版本出現的概率越來越高。漸進式的灰度可根據業務需要進行任意配置,例如持續 1 天時間分 10 次自動進行灰度,直至釋出到生產環境而無需人工值守。
5、灰度完成,30s 後釋出到生產環境
專案原始碼和原理分析
專案原始碼地址:https://wangweicoding.coding.net/public/nginx-ingress-gray/nginx-ingress-gray/git
├── Jenkinsfile # 持續整合指令碼
├── deployment
│ ├── canary
│ │ └── deploy.yaml # 灰度釋出部署檔案
│ ├── dev
│ │ └── deploy.yaml # 預釋出部署檔案
│ └── pro
│ └── deploy.yaml # 生產部署檔案
├── docker
│ ├── Dockerfile
│ └── html
│ └── index.html
├── nginx-ingress-init
│ ├── nginx-ingress-deployment # nginx-ingress 部署檔案
│ │ ├── ClusterRoleBinding.yaml
│ │ ├── RoleBinding.yaml
│ │ ├── clusterRole.yaml
│ │ ├── defaultBackendService.yaml
│ │ ├── defaultBackendServiceaccount.yaml
│ │ ├── deployment.yaml
│ │ ├── nginxDefaultBackendDeploy.yaml
│ │ ├── roles.yaml
│ │ ├── service.yaml
│ │ └── serviceAccount.yaml
│ └── nginx-ingress-helm # nginx-ingress Helm 包
│ └── nginx-ingress-1.36.3.tgz
└── pipeline # 持續部署流水線模板
├── gray-deploy.json # 灰度釋出流水線
├── gray-init.json # 灰度釋出初始化(首次執行)
└── nginx-ingress-init.json # nginx-ingress 初始化(首次執行)
灰度環境和生產環境主要由 deployment/canary/deploy.yaml
和 deployment/pro/deploy.yaml
來實現,主要是實現了兩套環境的:
- Deployment
- Service
- Ingress
A/B 測試和灰度由配置的 Ingress
進行控制:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx # nginx=nginx-ingress| qcloud=CLB ingress
nginx.ingress.kubernetes.io/canary: "true" # 開啟灰度
nginx.ingress.kubernetes.io/canary-by-header: "location" # A/B 測試用例 Header key
nginx.ingress.kubernetes.io/canary-by-header-value: "shenzhen" # A/B 測試用例 Header value
name: my-ingress
namespace: pro
spec:
rules:
- host: nginx-ingress.coding.pro
http:
paths:
- backend:
serviceName: nginx-canary
servicePort: 80
path: /
A/B 測試主要由註解 nginx.ingress.kubernetes.io/canary-by-header
和 nginx.ingress.kubernetes.io/canary-by-header-value
進行控制,來匹配請求 Header 的 Key 和 Value。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx # nginx=nginx-ingress| qcloud=CLB ingress
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: 30
name: my-ingress
namespace: pro
spec:
rules:
- host: nginx-ingress.coding.pro
http:
paths:
- backend:
serviceName: nginx-canary
servicePort: 80
path: /
而灰度則由註解 nginx.ingress.kubernetes.io/canary-weight
控制,值範圍可以是 0-100
,對應灰度權重比例。在 Nginx-ingress
,負載均衡演算法主要由加權輪詢
的演算法來實現分流。
整體架構圖如所示:
環境準備
1、K8S 叢集,推薦使用騰訊雲容器服務;
2、開通 CODING DevOps,提供映象構建和流水線的部署能力。
實踐步驟
1、克隆原始碼並推送至自己的 CODING Git 倉庫
```
$ git clone https://e.coding.net/wangweicoding/nginx-ingress-gray/nginx-ingress-gray.git
$ git remote set-url origin https://you coding git
$ git add .
$ git commit -a -m 'first commit'
$ git push -u origin master
```
注意,推送前請將 deployment/dev
、deployment/canary
、deployment/pro
資料夾的 deploy.yaml
image 修改為自己的製品庫映象地址。
2、建立持續整合流水線
使用“自定義構建過程”建立構建計劃,並選擇使用程式碼倉庫的 Jenkinsfile
3、新增雲賬號並建立持續部署流水線,複製專案的 pipeline Json 模板到建立的流水線內(3 個)
為了便於使用模板,建立持續部署流水線應用名為:nginx-ingress
建立繼續建立空白部署流程,複製 Json 模板到持續部署流水線中,一共建立三條流水線:
- nginx-ingress-init - 用於初始化 nginx-ingress
- gray-init - 用於首次初始化環境
- gray-deploy - 用於演示灰度釋出
注意:請將以上流水線的雲賬號選擇為自己的雲賬號,另外 gray-deploy 流水線中,請重新配置“啟動所需製品”和“觸發器”。
4、初始化 nginx-ingress(首次執行)
首次執行 nginx-ingress
流水線將自動為您部署nginx-ingress
。部署成功後,執行 kubectl get svc | grep nginx-ingress-controller
獲取 Ningx-ingress
的 EXTERNAL-IP
,此 IP 為叢集請求入口 IP 。併為本機配置 Host
,便於訪問。
5、初始化灰度釋出(首次執行)
首次執行 gray-init
流水線將自動部署一套完整的環境,否則自動化灰度流水線將會失敗。
6、自動觸發灰度釋出
現在,您可以嘗試修改專案 docker/html/index.html
檔案,推送後將自動觸發構建和持續部署,觸發後,進入“持續部署”頁面,檢視部署詳情和流程。
總結
我們主要利用了 CODING 持續部署
的等待
階段,通過對不同灰度比例的階段設定等待時間,自動化逐一執行灰度階段,最終實現無人工值守的自動化灰度釋出。
利用等待
階段,可以實現平滑的釋出流程,只有當釋出出現問題,才需要人工介入。配合持續部署通知功能,可以很方便的將當前釋出狀態推送到企業微信、釘釘等協作工具。
為了方便展示,案例中對灰度比例和等待時間進行了硬編碼,你也可以使用階段的“自定義引數”來實現對灰度比例和等待實現進行動態控制,針對當前的釋出等級動態輸入灰度比例和流程控制,使得釋出更加靈活。
生產建議
本文的 Nginx-ingress
採用 deployment
的部署方式來實現。Nginx-ingress
作為 Kubernetes
叢集的邊緣閘道器,承擔著所有入口流量,其高可用性直接決定了 Kubernetes
叢集的高可用性。
在生產環境,部署 Nginx-ingress
建議遵循以下幾點:
- 推薦使用 DaemonSet 的方式部署,避免節點故障。
- 通過標籤選擇器,將
Nginx-ingress-controller
部署在獨立的 Node 節點(如高主頻、高網路、高 IO 節點)或者低負載的節點。 - 如果採用
Deployment
的方式部署,可以為Nginx-ingress
配置 HPA 水平伸縮。