使用 Argo Rollouts 實現(xiàn)應用漸進式發(fā)布
Argo Rollouts 是一個 Kubernetes Operator 實現(xiàn),它為 Kubernetes 提供更加高級的部署能力,如藍綠、金絲雀、金絲雀分析、實驗和漸進式交付功能,為云原生應用和服務實現(xiàn)自動化、基于 GitOps 的逐步交付。
支持如下特性:
- 藍綠更新策略
- 金絲雀更新策略
- 更加細粒度、加權流量拆分
- 自動回滾
- 手動判斷
- 可定制的指標查詢和業(yè)務 KPI 分析
- Ingress 控制器集成:NGINX,ALB
- 服務網(wǎng)格集成:Istio,Linkerd,SMI
- Metrics 指標集成:Prometheus、Wavefront、Kayenta、Web、Kubernetes Jobs、Datadog、New Relic、Graphite、InfluxDB
實現(xiàn)原理
與 Deployment 對象類似,Argo Rollouts 控制器將管理 ReplicaSets 的創(chuàng)建、縮放和刪除,這些 ReplicaSet 由 Rollout 資源中的 spec.template 定義,使用與 Deployment 對象相同的 pod 模板。
當 spec.template 變更時,這會向 Argo Rollouts 控制器發(fā)出信號,表示將引入新的 ReplicaSet,控制器將使用 spec.strategy 字段內(nèi)的策略來確定從舊 ReplicaSet 到新 ReplicaSet 的 rollout 將如何進行,一旦這個新的 ReplicaSet 被放大(可以選擇通過一個 Analysis),控制器會將其標記為穩(wěn)定。
如果在 spec.template 從穩(wěn)定的 ReplicaSet 過渡到新的 ReplicaSet 的過程中發(fā)生了另一次變更(即在發(fā)布過程中更改了應用程序版本),那么之前的新 ReplicaSet 將縮小,并且控制器將嘗試發(fā)布反映更新 spec.template 字段的 ReplicasSet。
相關概念
在繼續(xù)之前我們先來了解一些基本的概念。
Rollout(滾動)
Rollout 是一個 Kubernetes 的 CRD 資源,相當于 Kubernetes Deployment 對象,在需要更高級的部署或漸進式交付功能的情況下,它旨在取代 Deployment 對象,Rollout 提供了 Kubernetes Deployment 所不能提供的功能。
- 藍綠部署
- 金絲雀部署
- 與 Ingress 控制器和服務網(wǎng)格整合,實現(xiàn)高級流量路由
- 與用于藍綠和金絲雀分析的指標提供者集成
- 根據(jù)成功或失敗的指標,自動發(fā)布或回滾
漸進式交付
漸進式交付是以受控和漸進的方式發(fā)布產(chǎn)品更新的過程,從而降低發(fā)布的風險,通常將自動化和指標分析結合起來以驅動更新的自動升級或回滾。
漸進式交付
漸進式交付通常被描述為持續(xù)交付的演變,將 CI/CD 中的速度優(yōu)勢擴展到部署過程。通過將新版本限制在一部分用戶,觀察和分析正確的行為,然后逐漸增加更多的流量,同時不斷驗證其正確性。
部署策略
為了明確 Argo Rollouts 的行為方式,以下是 Argo Rollouts 提供的各種部署策略實施的描述。
- RollingUpdate(滾動更新) :慢慢地用新版本替換舊版本,隨著新版本的出現(xiàn),舊版本會慢慢縮減,以保持應用程序的總數(shù)量。這是 Deployment 對象的默認策略。
- Recreate(重新創(chuàng)建) :Recreate 會在啟動新版本之前刪除舊版本的應用程序,這可確保應用程序的兩個版本永遠不會同時運行,但在部署期間會出現(xiàn)停機時間。
- Blue-Green(藍綠) :藍綠發(fā)布指同時部署了新舊兩個版本的應用程序,在此期間,只有舊版本的應用程序會收到生產(chǎn)流量,這允許開發(fā)人員在將實時流量切換到新版本之前針對新版本進行測試。
Blue-Green
- Canary(金絲雀) :金絲雀發(fā)布指將一部分用戶暴露在新版本的應用程序中,而將其余流量提供給舊版本,一旦新版本被驗證是正確的,新版本可以逐漸取代舊版本。Ingress 控制器和服務網(wǎng)格,如 NGINX Ingress 和 Istio,可以使金絲雀的流量拆分模式比原生的更復雜(例如,實現(xiàn)非常細粒度的流量分割,或基于 HTTP 頭的分割)。
Canary
上圖顯示了一個有兩個階段的金絲雀(10%和 33%的流量進入新版本),通過使用 Argo Rollouts,我們可以根據(jù)實際的使用情況定義確切的階段數(shù)和流量百分比。
架構
下面展示了由 Argo Rollouts 管理的 Deployment 的所有組件。
argo rollouts 架構
Rollout Controller
這是主控制器,用于監(jiān)視集群的事件并在 Rollout 類型的資源發(fā)生更改時做出反應。控制器將讀取 rollout 的所有詳細信息,并使集群處于 rollout 定義中描述的相同狀態(tài)。
請注意,Argo Rollouts 不會篡改或響應正常 Deployment 資源上發(fā)生的任何變更,這意味著你可以在一個使用其他方法部署應用的集群中安裝 Argo Rollouts。
Rollout 資源
Rollout 資源是 Argo Rollouts 引入和管理的一種自定義 Kubernetes 資源,它與原生的 Kubernetes Deployment 資源基本兼容,但有額外的字段來控制更加高級的部署方法,如金絲雀和藍/綠部署。
Argo Rollouts 控制器將只對 Rollout 資源中的變化做出反應,不會對正常的 Deployment 資源做任何事情,所以如果你想用 Argo Rollouts 管理你的 Deployment,你需要將你的 Deployment 遷移到 Rollouts。
AnalysisTemplate 與 AnalysisRun
Analysis 是一種自定義 Kubernetes 資源,它將 Rollout 連接到指標提供程序,并為某些指標定義特定閾值,這些閾值將決定 Rollout 是否成功。對于每個 Analysis,你可以定義一個或多個指標查詢及其預期結果,如果指標查詢正常,則 Rollout 將繼續(xù)發(fā)布;如果指標顯示失敗,則自動回滾;如果指標無法提供成功/失敗的結果,則暫停發(fā)布。
為了執(zhí)行分析,Argo Rollouts 提供了兩個自定義的 Kubernetes 資源:AnalysisTemplate 和 AnalysisRun。
AnalysisTemplate 包含有關要查詢哪些指標的說明。附加到 Rollout 的實際結果是 AnalysisRun 自定義資源,可以在特定 的 Rollout 上定義 AnalysisTemplate,也可以在集群上定義全局的 AnalysisTemplate,以供多個 Rollout 共享作為 ClusterAnalysisTemplate,而 AnalysisRun 資源的范圍僅限于特定的 rollout。
請注意,在 Rollout 中使用分析和指標是完全可選的,你可以通過 API 或 CLI 手動暫停和繼續(xù)發(fā)布,也可以使用其他外部方法(例如冒煙測試)。你不需要僅使用 Argo Rollouts 的指標解決方案,你還可以在 Rollout 中混合自動(即基于分析)和手動步驟。
除了指標之外,你還可以通過運行 Kubernetes Job 或運行 webhook 來決定發(fā)布的成功與否。
Metric Providers
Argo Rollouts 包括多個流行指標提供程序的本機集成,您可以在分析資源中使用這些提供程序來自動升級或回滾部署。有關特定設置選項,請參閱每個提供商的文檔。
Argo Rollouts 包括幾個流行的指標提供者的集成,你可以在分析資源中使用,來自動升級或回滾發(fā)布。
安裝
直接使用下面的命令安裝 Argo Rollouts:
$ kubectl create namespace argo-rollouts
$ kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/download/v1.6.0/install.yaml
這里會創(chuàng)建一個名為 argo-rollouts 的命名空間,Argo Rollouts 控制器運行在上面。
$ kubectl get pods -n argo-rollouts
NAME READY STATUS RESTARTS AGE
argo-rollouts-b9bbbc884-5h5jz 1/1 Running 0 7m21s
$ kubectl get crd |grep argo
analysisruns.argoproj.io 2023-09-21T06:20:34Z
analysistemplates.argoproj.io 2023-09-21T06:20:34Z
clusteranalysistemplates.argoproj.io 2023-09-21T06:20:34Z
experiments.argoproj.io 2023-09-21T06:20:35Z
rollouts.argoproj.io 2023-09-21T06:20:35Z
此外,我們還可以安裝一個 kubectl 插件,對于命令行管理和可視化發(fā)布非常方便。使用 curl 安裝 Argo Rollouts kubectl 插件:
# https://ghproxy.com/https://github.com//argoproj/argo-rollouts/releases/download/v1.6.0/kubectl-argo-rollouts-linux-amd64
$ curl -LO https://github.com/argoproj/argo-rollouts/releases/download/v1.6.0/kubectl-argo-rollouts-linux-amd64
然后賦予 kubectl-argo-rollouts 二進制文件可執(zhí)行權限:
$ chmod +x ./kubectl-argo-rollouts-linux-amd64
將該二進制文件移動到你的 PATH 路徑下面去:
$ sudo mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
執(zhí)行下面的命令來驗證插件是否安裝成功:
$ kubectl argo rollouts version
kubectl-argo-rollouts: v1.6.0+7eae71e
BuildDate: 2023-09-06T18:36:42Z
GitCommit: 7eae71ed89f1a3769864435bddebe3ca05384df3
GitTreeState: clean
GoVersion: go1.20.7
Compiler: gc
Platform: linux/amd64
金絲雀發(fā)布
接下來我們通過幾個簡單的示例來說明 Rollout 的部署、升級、發(fā)布和中斷等操作,以此來展示 Rollouts 的各種功能。
1、部署 Rollout
首先我們部署一個 Rollout 資源和一個針對該資源的 Kubernetes Service 對象,這里我們示例中的 Rollout 采用了金絲雀的更新策略,將 20% 的流量發(fā)送到金絲雀上,然后手動發(fā)布,最后在升級的剩余時間內(nèi)逐漸自動增大流量,可以通過如下所示的 Rollout 來描述這個策略:
# basic-rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
spec:
replicas: 5 # 定義5個副本
strategy: # 定義升級策略
canary: # 金絲雀發(fā)布
steps: # 發(fā)布的節(jié)奏
- setWeight: 20
- pause: {} # 會一直暫停
- setWeight: 40
- pause: { duration: 10 } # 暫停10s
- setWeight: 60
- pause: { duration: 10 }
- setWeight: 80
- pause: { duration: 10 }
revisionHistoryLimit: 2 # 下面部分其實是和 Deployment 兼容的
selector:
matchLabels:
app: rollouts-demo
template:
metadata:
labels:
app: rollouts-demo
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:blue
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
memory: 32Mi
cpu: 5m
還包括一個如下所示的 Service 資源對象:
# basic-service.yaml
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
直接創(chuàng)建上面的兩個資源對象:
$ kubectl apply -f basic-rollout.yaml
$ kubectl apply -f basic-service.yaml
任何 Rollout 的初始創(chuàng)建都會立即將副本擴展到 100%(跳過任何金絲雀升級步驟、分析等...),因為還沒有發(fā)生升級。
$ kubectl get pods -l app=rollouts-demo
NAME READY STATUS RESTARTS AGE
rollouts-demo-687d76d795-7xztq 1/1 Running 0 4m48s
rollouts-demo-687d76d795-bnnwx 1/1 Running 0 4m48s
rollouts-demo-687d76d795-dtlns 1/1 Running 0 4m48s
rollouts-demo-687d76d795-q6867 1/1 Running 0 4m48s
rollouts-demo-687d76d795-zlzdv 1/1 Running 0 4m48s
Argo Rollouts 的 kubectl 插件允許我們可視化 Rollout 以及相關資源對象,并展示實時狀態(tài)變化,要在部署過程中觀察 Rollout,可以通過運行插件的 get rollout --watch 命令,比如:
$ kubectl argo rollouts get rollout rollouts-demo --watch
watch rollouts
2、更新 Rollout
上面已經(jīng)部署完成,接下來就需要執(zhí)行更新了,和 Deployment 類似,對 Pod 模板字段的任何變更都會導致新的版本(即 ReplicaSet)被部署,更新 Rollout 通常是修改容器鏡像的版本,然后執(zhí)行 kubectl apply ,為了方便,rollouts 插件還單獨提供了一個 set image 的命令,比如這里我們運行以下所示命令,用 yellow 版本的容器更新上面的 Rollout:
$ kubectl argo rollouts set image rollouts-demo \
rollouts-demo=argoproj/rollouts-demo:yellow
rollout "rollouts-demo" image updated
在 rollout 更新期間,控制器將通過 Rollout 更新策略中定義的步驟進行。這個示例的 rollout 為金絲雀設置了 20% 的流量權重,并一直暫停 rollout,直到用戶取消或促進發(fā)布。在更新鏡像后,再次觀察 rollout,直到它達到暫停狀態(tài)。
$ kubectl argo rollouts get rollout rollouts-demo --watch
watch rollouts
當 demo rollout 到達第二步時,我們可以從插件中看到,Rollout 處于暫停狀態(tài),現(xiàn)在有 5 個副本中的 1 個運行新版本的 pod,其余 4 個仍然運行舊版本,這相當于 setWeight: 20 步驟所定義的 20%的金絲雀權重。
3、Promote Rollout
經(jīng)過上面的更新后,Rollout 現(xiàn)在處于暫停狀態(tài),當一個 Rollout 到達一個沒有持續(xù)時間的暫停步驟時,它將一直保持在暫停狀態(tài),直到它被恢復/繼續(xù)。要手動將 Rollout 切換到下一個步驟,請運行插件的 promotion
命令。
$ kubectl argo rollouts promote rollouts-demo
rollout 'rollouts-demo' promoted
切換后 Rollout 將繼續(xù)執(zhí)行剩余的步驟。在我們的例子中,剩余的步驟是完全自動化的,所以 Rollout 最終會完成步驟,直到它已經(jīng)完全過渡到新版本。再次觀察 Rollout,直到它完成所有步驟。
$ kubectl argo rollouts get rollout rollouts-demo --watch
watch rollouts
promote 命令還支持用 --full 標志跳過所有剩余步驟和分析。
可以看到 stable 版本已經(jīng)切換到 revision:2 這個 ReplicaSet 了。在更新過程中,無論何時,無論是通過失敗的金絲雀分析自動中止,還是由用戶手動中止,Rollout 都會退回到 stable 版本。
4、中斷 Rollout
接下來我們來了解如何在更新過程中手動中止 Rollout,首先,使用 set image 命令部署一個新的 red 版本的容器,并等待 rollout 再次達到暫停的步驟。
$ kubectl argo rollouts set image rollouts-demo \
rollouts-demo=argoproj/rollouts-demo:red
rollout "rollouts-demo" image updated
這一次我們將中止更新,而不是將滾動切換到下一步,這樣它就回到了 stable 版本,該插件同樣提供了一個 abort 命令,可以在更新過程中的任何時候手動中止 Rollout。
$ kubectl argo rollouts abort rollouts-demo
當中止?jié)L動時,它將擴大 ReplicaSet 的 stable 版本(在本例中是 yellow 版本),并縮小任何其他版本。盡管 ReplicaSet 的穩(wěn)定版本可能正在運行,并且是健康的,但整個 Rollout 仍然被認為是退化的,因為期望的版本(red 版本)不是實際運行的版本。
watch rollouts
為了使 Rollout 再次被認為是健康的而不是有問題的版本,有必要將所需的狀態(tài)改回以前的穩(wěn)定版本。在我們的例子中,我們可以簡單地使用之前的 yellow 鏡像重新運行 set image 命令即可。
$ kubectl argo rollouts set image rollouts-demo \
rollouts-demo=argoproj/rollouts-demo:yellow
運行這個命令后,可以看到 Rollout 立即變成了 health 狀態(tài),而且沒有任何關于創(chuàng)建新 ReplicaSets 的動態(tài)。
watch rollouts
當 Rollout 還沒有達到預期狀態(tài)(例如它被中止了,或者正在更新中),而穩(wěn)定版本的資源清單被重新應用,Rollout 檢測到這是一個回滾,而不是一個更新,并將通過跳過分析和步驟快速部署穩(wěn)定的 ReplicaSet。
Argo Rollouts 與 NGINX Ingress 集成
上面例子中的 Rollout 沒有使用 Ingress 控制器或服務網(wǎng)格來控制流量。它使用 Kubernetes Service 來實現(xiàn)近似的金絲雀權重,基于新舊副本數(shù)量的比例來實現(xiàn)。所以,這個 Rollout 有限制,為了實現(xiàn)更細粒度的金絲雀,這就需要一個 Ingress 控制器或服務網(wǎng)格了。
接下來我們介紹下 Argo Rollouts 如何與 NGINX Ingress Controller 集成以進行流量控制。
當然首先要保證已經(jīng)在集群中安裝了 ingress-nginx 控制器。當 NGINX Ingress 用作流量路由器時,Rollout 金絲雀策略必須定義以下強制字段:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
spec:
strategy:
canary:
# 引用一個 Service,控制器將更新該服務以指向金絲雀 ReplicaSet
canaryService: rollouts-demo-canary
# 引用一個 Service,控制器將更新該服務以指向穩(wěn)定的 ReplicaSet
stableService: rollouts-demo-stable
trafficRouting:
nginx:
# 指向穩(wěn)定 Service 的規(guī)則所引用的 Ingress
# 該 Ingress 將被克隆并賦予一個新的名稱,以實現(xiàn)NGINX流量分割。
stableIngress: rollouts-demo-stable
其中 canary.trafficRouting.nginx.stableIngress 中引用的 Ingress 需要有一個 host 規(guī)則,該規(guī)則具有針對 canary.stableService 下引用的服務的后端。
接下來按照上面的描述來定義我們示例的資源清單文件:
# rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
spec:
replicas: 1
strategy:
canary:
canaryService: rollouts-demo-canary
stableService: rollouts-demo-stable
trafficRouting:
nginx:
stableIngress: rollouts-demo-stable
steps:
- setWeight: 5
- pause: {}
revisionHistoryLimit: 2
selector:
matchLabels:
app: rollouts-demo
template:
metadata:
labels:
app: rollouts-demo
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:blue
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
memory: 32Mi
cpu: 5m
上面的資源清單中,我們定義了一個 rollouts-demo 的 Rollout 資源,它的 canaryService 和 stableService 分別引用了兩個 Service 資源, stableIngress 引用了一個 Ingress 資源,steps 定義了金絲雀發(fā)布的步驟,這里我們定義了兩個步驟,第一個步驟將權重設置為 5%,第二個步驟是暫停,這樣就可以在第一個步驟中將 5% 的流量發(fā)送到金絲雀上,然后手動發(fā)布,最后在升級的剩余時間內(nèi)逐漸自動增大流量。對應的 Service 資源對象如下所示:
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo-canary
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
# 該 selector 將使用金絲雀 ReplicaSet 的 pod-template-hash 進行更新,比如 rollouts-pod-template-hash: 7bf84f9696
---
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo-stable
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
# 該 selector 將使用穩(wěn)定版的 ReplicaSet 的 pod-template-hash 進行更新,比如 rollouts-pod-template-hash: 789746c88d
然后最后還需要定義一個 Ingress 對象:
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rollouts-demo-stable
spec:
ingressClassName: nginx
rules:
- host: rollouts-demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
# 引用服務名稱,也在 Rollout spec.strategy.canary.stableService 字段中指定
name: rollouts-demo-stable
port:
number: 80
接下來我們先刪除前面示例的資源對象:
$ kubectl delete -f basic-rollout.yaml
$ kubectl delete -f basic-service.yaml
然后再創(chuàng)建上面的資源對象:
$ kubectl apply -f rollout.yaml
$ kubectl apply -f service.yaml
$ kubectl apply -f ingress.yaml
應用清單后,我們可以在集群中看到以下資源對象:
$ kubectl get ro
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
rollouts-demo 1 1 1 1 69s
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
rollouts-demo-canary ClusterIP 10.111.85.229 <none> 80/TCP 72s
rollouts-demo-stable ClusterIP 10.110.107.123 <none> 80/TCP 72s
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
rollouts-demo-rollouts-demo-stable-canary nginx rollouts-demo.local 80 41s
rollouts-demo-stable nginx rollouts-demo.local 80 41s
我們可以注意到新增了一個名為 rollouts-demo-rollouts-demo-stable-canary 的 Ingress 對象。這個對象是 canary ingress,它是 nginx.stableIngress 下引用的用戶管理 Ingress 的克隆。 nginx ingress 控制器使用它來實現(xiàn)金絲雀流量分割。生成的入口的名稱是使用 <ROLLOUT-NAME>-<INGRESS-NAME>-canary 制定的。
同樣現(xiàn)在也可以查看 Rollout 對象的具體狀態(tài):
rollouts
接下來我們通過更改鏡像版本來更新發(fā)布,然后等待它達到暫停狀態(tài)。
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
kubectl argo rollouts get rollout rollouts-demo
rollouts pause
此時,Rollout 的金絲雀版和穩(wěn)定版都在運行,其中 5% 的流量路由到金絲雀版。需要注意的一件事是,盡管只運行兩個 pod,但此次發(fā)布仍能夠實現(xiàn) 5% 的金絲雀權重。這是能夠實現(xiàn)的,因為流量分割發(fā)生在 Ingress 控制器處。
可以通過訪問該 Demo 來大致了解:
rollouts demo
當檢查 rollout 控制器生成的 Ingress 副本時,我們發(fā)現(xiàn)它比原始 Ingress 有以下變化:
- 注解中添加了兩個額外的 NGINX 特定金絲雀注解。
- Ingress 規(guī)則將有一條將后端指向金絲雀服務的規(guī)則。
對應的資源清單如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "5"
name: rollouts-demo-rollouts-demo-stable-canary
namespace: default
spec:
ingressClassName: nginx
rules:
- host: rollouts-demo.local
http:
paths:
- backend:
service:
name: rollouts-demo-canary
port:
number: 80
path: /
pathType: Prefix
隨著 Rollout 逐步進行,canary-weight 注解將調整以匹配步驟的當前 setWeight。NGINX Ingress 控制器檢查原始 Ingress、金絲雀 Ingress 和金絲雀權重注解,以確定在兩個入口之間分配的流量百分比。NGINX Ingress 如何實現(xiàn)金絲雀流量分割在 NGINX Ingress 章節(jié)中我們已經(jīng)詳細講解過了。
最后我們可以執(zhí)行 promote 命令來將 Rollout 推進到下一個步驟,這樣就完成了金絲雀發(fā)布。
$ kubectl argo rollouts promote rollouts-demo
rollout 'rollouts-demo' promoted
當然此時應用會全部切換到 yellow 版本的容器:
rollouts demo
Dashboard
Argo Rollouts Kubectl 插件可以提供一個本地 Dashboard,來可視化你的 Rollouts。
要啟動這個 Dashboard,需要在包含 Rollouts 資源對象的命名空間中運行 kubectl argo rollouts dashboard 命令,然后訪問localhost:3100 即可。
argo rollouts ui
點擊 Rollout 可以進行詳細頁面,在詳細頁面可以看到 Rollout 的配置信息,還可以直接在 UI 界面上執(zhí)行一些常用的操作,比如重啟、重啟、中斷等。
rollouts detail
Analysis 和漸進式交互
Argo Rollouts 提供了幾種執(zhí)行分析的方法來推動漸進式交付,首先需要了解幾個 CRD 資源:
- Rollout:Rollout 是 Deployment 資源的直接替代品,它提供額外的 blueGreen 和 canary 更新策略,這些策略可以在更新期間創(chuàng)建 AnalysisRuns 和 Experiments,可以推進更新,或中止更新。
- Experiments:Experiments CRD 允許用戶臨時運行一個或多個 ReplicaSet。除了運行臨時 ReplicaSet 之外,Experiments CRD 還可以與 ReplicaSet 一起啟動 AnalysisRuns。通常,這些 AnalysisRun 用于確認新的 ReplicaSet 是否按預期運行。
- AnalysisTemplate:AnalysisTemplate 是一個模板,它定義了如何執(zhí)行金絲雀分析,例如它應該執(zhí)行的指標、頻率以及被視為成功或失敗的值,AnalysisTemplate 可以用輸入值進行參數(shù)化。
- ClusterAnalysisTemplate:ClusterAnalysisTemplate 和 AnalysisTemplate 類似,但它是全局范圍內(nèi)的,它可以被整個集群的任何 Rollout 使用。
- AnalysisRun:AnalysisRun 是 AnalysisTemplate 的實例化。AnalysisRun 就像 Job 一樣,它們最終會完成,完成的運行被認為是成功的、失敗的或不確定的,運行的結果分別影響 Rollout 的更新是否繼續(xù)、中止或暫停。
分析
后臺分析
金絲雀正在執(zhí)行其部署步驟時,分析可以在后臺運行。
以下示例是每 10 分鐘逐漸將 Canary 權重增加 20%,直到達到 100%。在后臺,基于名為 success-rate 的 AnalysisTemplate 啟動 AnalysisRun,success-rate 模板查詢 Prometheus 服務器,以 5 分鐘間隔/樣本測量 HTTP 成功率,它沒有結束時間,一直持續(xù)到停止或失敗。如果測量到的指標小于 95%,并且有三個這樣的測量值,則分析被視為失敗。失敗的分析會導致 Rollout 中止,將 Canary 權重設置回零,并且 Rollout 將被視為降級。否則,如果 Rollout 完成其所有 Canary 步驟,則認為 rollout 是成功的,并且控制器將停止運行分析。
如下所示的 Rollout 資源對象:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: guestbook
spec:
# ...
strategy:
canary:
analysis:
templates:
- templateName: success-rate
startingStep: 2 # 延遲開始分析,到第3步開始
args:
- name: service-name
value: guestbook-svc.default.svc.cluster.local
steps:
- setWeight: 20
- pause: { duration: 10m }
- setWeight: 40
- pause: { duration: 10m }
- setWeight: 60
- pause: { duration: 10m }
- setWeight: 80
- pause: { duration: 10m }
上面我們引用了一個 success-rate 的模板:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 5m
# NOTE: prometheus queries return results in the form of a vector.
# So it is common to access the index 0 of the returned array to obtain the value
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
內(nèi)聯(lián)分析
分析也可以作為內(nèi)嵌分析步驟來執(zhí)行,當分析以內(nèi)聯(lián)方式進行時,在到達該步驟時啟動 AnalysisRun,并在運行完成之前阻止其推進。分析運行的成功或失敗決定了部署是繼續(xù)進行下一步,還是完全中止部署。
如下所示的示例中我們將 Canary 權重設置為 20%,暫停 5 分鐘,然后運行分析。如果分析成功,則繼續(xù)推出,否則中止。
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: guestbook
spec:
# ...
strategy:
canary:
steps:
- setWeight: 20
- pause: { duration: 5m }
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: guestbook-svc.default.svc.cluster.local
上面的對象中我們將 analysis 作為一個步驟內(nèi)聯(lián)到了 Rollout 步驟中,當 20%流量暫停 5 分鐘后,開始執(zhí)行 success-rate 這個分析模板。
這里 AnalysisTemplate 與上面的后臺分析例子相同,但由于沒有指定間隔時間,分析將執(zhí)行一次測量就完成了。
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
- name: prometheus-port
value: 9090
metrics:
- name: success-rate
successCondition: result[0] >= 0.95
provider:
prometheus:
address: "http://prometheus.example.com:{{args.prometheus-port}}"
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
此外我們可以通過指定 count 和 interval 字段,可以在一個較長的時間段內(nèi)進行多次測量。
metrics:
- name: success-rate
successCondition: result[0] >= 0.95
interval: 60s
count: 5
provider:
prometheus:
address: http://prometheus.example.com:9090
query: ...
多個模板的分析
Rollout 在構建 AnalysisRun 時可以引用多個 AnalysisTemplate。這樣我們就可以從多個 AnalysisTemplate 中來組成分析,如果引用了多個模板,那么控制器將把這些模板合并在一起,控制器會結合所有模板的指標和 args 字段。如下所示:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: guestbook
spec:
# ...
strategy:
canary:
analysis:
templates:
- templateName: success-rate
- templateName: error-rate
args:
- name: service-name
value: guestbook-svc.default.svc.cluster.local
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 5m
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: error-rate
spec:
args:
- name: service-name
metrics:
- name: error-rate
interval: 5m
successCondition: result[0] <= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code=~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
當執(zhí)行的分析的時候,控制器會將上面的 success-rate 和 error-rate 兩個模板合并到一個 AnalysisRun 對象中去。
需要注意的是如果出現(xiàn)以下情況,控制器在合并模板時將出錯:
- 模板中的多個指標具有相同的名稱
- 兩個同名的參數(shù)都有值
分析模板參數(shù)
AnalysisTemplates 可以聲明一組參數(shù),這些參數(shù)可以由 Rollouts 傳遞。然后,這些參數(shù)可以像在 metrics 配置中一樣使用,并在 AnalysisRun 創(chuàng)建時被實例化,參數(shù)占位符被定義為 {{ args.<name> }},如下所示:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: args-example
spec:
args:
# required
- name: service-name
- name: stable-hash
- name: latest-hash
# optional
- name: api-url
value: http://example/measure
# from secret
- name: api-token
valueFrom:
secretKeyRef:
name: token-secret
key: apiToken
metrics:
- name: webmetric
successCondition: result == 'true'
provider:
web:
# placeholders are resolved when an AnalysisRun is created
url: "{{ args.api-url }}?service={{ args.service-name }}"
headers:
- key: Authorization
value: "Bearer {{ args.api-token }}"
jsonPath: "{$.results.ok}"
在創(chuàng)建 AnalysisRun 時,Rollout 中定義的參數(shù)與 AnalysisTemplate 的參數(shù)會合并,如下所示:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: guestbook
spec:
---
strategy:
canary:
analysis:
templates:
- templateName: args-example
args:
# required value
- name: service-name
value: guestbook-svc.default.svc.cluster.local
# override default value
- name: api-url
value: http://other-api
# pod template hash from the stable ReplicaSet
- name: stable-hash
valueFrom:
podTemplateHashValue: Stable
# pod template hash from the latest ReplicaSet
- name: latest-hash
valueFrom:
podTemplateHashValue: Latest
此外分析參數(shù)也支持 valueFrom,用于讀取 meta 數(shù)據(jù)并將其作為參數(shù)傳遞給 AnalysisTemplate,如下例子是引用元數(shù)據(jù)中的 env 和 region 標簽,并將它們傳遞給 AnalysisTemplate。
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: guestbook
labels:
appType: demo-app
buildType: nginx-app
...
env: dev
region: us-west-2
spec:
...
strategy:
canary:
analysis:
templates:
- templateName: args-example
args:
...
- name: env
valueFrom:
fieldRef:
fieldPath: metadata.labels['env']
# region where this app is deployed
- name: region
valueFrom:
fieldRef:
fieldPath: metadata.labels['region']
藍綠預發(fā)布分析
使用 BlueGreen 策略的 Rollout 可以在使用預發(fā)布將流量切換到新版本之前啟動一個 AnalysisRun。分析運行的成功或失敗決定 Rollout 是否切換流量,或完全中止 Rollout,如下所示:
kind: Rollout
metadata:
name: guestbook
spec:
---
strategy:
blueGreen:
activeService: active-svc
previewService: preview-svc
prePromotionAnalysis:
templates:
- templateName: smoke-tests
args:
- name: service-name
value: preview-svc.default.svc.cluster.local
上面我們的示例中一旦新的 ReplicaSet 完全可用,Rollout 會創(chuàng)建一個預發(fā)布的 AnalysisRun,Rollout 不會將流量切換到新版本,而是會等到分析運行成功完成。
注意:如果指定了 autoPromotionSeconds 字段,并且 Rollout 已經(jīng)等待了 auto promotion seconds 的時間,Rollout 會標記 AnalysisRun 成功,并自動將流量切換到新版本。如果 AnalysisRun 在此之前完成,Rollout 將不會創(chuàng)建另一個 AnalysisRun,并等待 autoPromotionSeconds 的剩余時間。
藍綠發(fā)布后分析
使用 BlueGreen 策略的 Rollout 還可以在流量切換到新版本后使用發(fā)布后分析。如果發(fā)布后分析失敗或出錯,Rollout 則進入中止狀態(tài),并將流量切換回之前的穩(wěn)定 ReplicaSet,當后分析成功時,Rollout 被認為是完全發(fā)布狀態(tài),新的 ReplicaSet 將被標記為穩(wěn)定,然后舊的 ReplicaSet 將根據(jù) scaleDownDelaySeconds(默認為 30 秒)進行縮減。
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: guestbook
spec:
---
strategy:
blueGreen:
activeService: active-svc
previewService: preview-svc
scaleDownDelaySeconds: 600 # 10 minutes
postPromotionAnalysis:
templates:
- templateName: smoke-tests
args:
- name: service-name
value: preview-svc.default.svc.cluster.local
失敗條件
failureCondition 可以用來配置分析運行失敗,下面的例子是每隔 5 分鐘持續(xù)輪詢 Prometheus 服務器來獲得錯誤總數(shù),如果遇到 10 個或更多的錯誤,則認為分析運行失敗。
metrics:
- name: total-errors
interval: 5m
failureCondition: result[0] >= 10
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code~"5.*"}[5m]
))
無結果的運行
分析運行 j 結果也可以被認為是不確定的,這表明運行既不成功,也不失敗。無結果的運行會導致發(fā)布在當前步驟上暫停。這時需要人工干預,以恢復運行,或中止運行。當一個指標沒有定義成功或失敗的條件時,分析運行可能成為無結果的一個例子。
metrics:
- name: my-query
provider:
prometheus:
address: http://prometheus.example.com:9090
query: ...
此外當同時指定了成功和失敗的條件,但測量值沒有滿足任何一個條件時,也可能發(fā)生不確定的分析運行。
metrics:
- name: success-rate
successCondition: result[0] >= 0.90
failureCondition: result[0] < 0.50
provider:
prometheus:
address: http://prometheus.example.com:9090
query: ...
不確定的分析運行的一個場景是使 Argo Rollouts 能夠自動執(zhí)行分析運行,并收集測量結果,但仍然允許我們來判斷決定測量值是否可以接受,并決定繼續(xù)或中止。
延遲分析運行
如果分析運行不需要立即開始(即給指標提供者時間來收集金絲雀版本的指標),分析運行可以延遲特定的指標分析。每個指標可以被配置為有不同的延遲,除了特定指標的延遲之外,具有后臺分析的發(fā)布可以延遲創(chuàng)建分析運行,直到達到某個步驟為止
如下所示延遲一個指定的分析指標:
metrics:
- name: success-rate
# Do not start this analysis until 5 minutes after the analysis run starts
initialDelay: 5m
successCondition: result[0] >= 0.90
provider:
prometheus:
address: http://prometheus.example.com:9090
query: ...
延遲開始后臺分析運行,直到步驟 3(設定重量 40%)。
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: guestbook
spec:
strategy:
canary:
analysis:
templates:
- templateName: success-rate
startingStep: 2
steps:
- setWeight: 20
- pause: { duration: 10m }
- setWeight: 40
- pause: { duration: 10m }
Job Metrics
此外 Kubernetes Job 還可用于運行分析,使用 Job 時,如果 Job 完成且退出代碼為零,則指標被視為成功,否則指標失敗。
metrics:
- name: test
provider:
job:
metadata:
annotations:
foo: bar # annotations defined here will be copied to the Job object
labels:
foo: bar # labels defined here will be copied to the Job object
spec:
backoffLimit: 1
template:
spec:
containers:
- name: test
image: my-image:latest
command:
[my-test-script, my-service.default.svc.cluster.local]
restartPolicy: Never
Web Metrics
同樣還可以針對某些外部服務執(zhí)行 HTTP 請求來獲取測量結果。下面示例向某個 URL 發(fā)出 HTTP GET 請求。 Webhook 響應必須返回 JSON 內(nèi)容。jsonPath 表達式的結果將分配給可在 successCondition 和 failureCondition 表達式中引用的 result 變量。如果省略,將使用整個 body 作為結果變量。
metrics:
- name: webmetric
successCondition: result == true
provider:
web:
url: "http://my-server.com/api/v1/measurement?service={{ args.service-name }}"
timeoutSeconds: 20 # defaults to 10 seconds
headers:
- key: Authorization
value: "Bearer {{ args.api-token }}"
jsonPath: "{$.data.ok}"
比如下面的示例表示如果 data.ok 字段為真且 data.successPercent 大于 0.90,測量將是成功的。
{ "data": { "ok": true, "successPercent": 0.95 } }
metrics:
- name: webmetric
successCondition: "result.ok && result.successPercent >= 0.90"
provider:
web:
url: "http://my-server.com/api/v1/measurement?service={{ args.service-name }}"
headers:
- key: Authorization
value: "Bearer {{ args.api-token }}"
jsonPath: "{$.data}"
當然關于 Argo Rollouts 的使用還有很多細節(jié),可以參考官方文檔:https://argoproj.github.io/argo-rollouts/以了解更多。