作者:王煒,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 水平伸縮。