本文已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
使用開放原始碼 Gloo 進行兩階段 Canary Rollout
作者: Rick Ducott | GitHub | Twitter
每天,我和我的同事都在與平台擁有者、架構師和工程師交談,他們正在使用 Gloo 作為 API 閘道,將他們的應用程式公開給終端使用者。這些應用程式可能跨越舊有單體式應用程式、微服務、受管理的雲端服務和 Kubernetes 叢集。幸運的是,Gloo 可以輕鬆設定路由來管理、保護和觀察應用程式流量,同時支援彈性的部署架構,以滿足我們使用者不同的生產需求。
除了初始設定之外,平台擁有者經常要求我們協助設計他們組織內的操作工作流程:我們如何讓新的應用程式上線?我們如何升級應用程式?我們如何在我們的平台、維運和開發團隊之間劃分責任?
在這篇文章中,我們將使用 Gloo 設計一個兩階段的 Canary 發布工作流程,用於應用程式升級
- 在第一階段,我們將透過將少量流量轉移到新版本來進行 Canary 測試。這讓您可以安全地執行冒煙測試和正確性測試。
- 在第二階段,我們將逐步將流量轉移到新版本,讓我們能夠在負載下監控新版本,並最終停用舊版本。
為了保持簡單,我們將專注於使用 開源 Gloo 設計工作流程,並將閘道和應用程式部署到 Kubernetes。最後,我們將討論一些擴展和進階主題,這些主題可能值得在後續文章中探討。
初始設定
首先,我們需要一個 Kubernetes 叢集。此範例未利用任何雲端特定功能,並且可以針對本機測試叢集 (例如 minikube) 執行。這篇文章假設您對 Kubernetes 以及如何使用 kubectl
與之互動有基本了解。
我們將最新的 開源 Gloo 安裝到 gloo-system
命名空間,並將範例應用程式的 v1
版本部署到 echo
命名空間。我們將透過在 Gloo 中建立路由來將此應用程式公開在叢集外部,最終得到如下所示的圖片
部署 Gloo
我們將使用 glooctl
命令列工具安裝 gloo,我們可以下載該工具並使用以下命令將其新增到 PATH
curl -sL https://run.solo.io/gloo/install | sh
export PATH=$HOME/.gloo/bin:$PATH
現在,您應該能夠執行 glooctl version
以查看它是否已正確安裝
➜ glooctl version
Client: {"version":"1.3.15"}
Server: version undefined, could not find any version of gloo running
現在,我們可以使用一個簡單的命令將閘道安裝到我們的叢集
glooctl install gateway
主控台應指示安裝成功完成
Creating namespace gloo-system... Done.
Starting Gloo installation...
Gloo was successfully installed!
不久之後,我們就可以看到所有 Gloo Pod 在 gloo-system
命名空間中執行
➜ kubectl get pod -n gloo-system
NAME READY STATUS RESTARTS AGE
discovery-58f8856bd7-4fftg 1/1 Running 0 13s
gateway-66f86bc8b4-n5crc 1/1 Running 0 13s
gateway-proxy-5ff99b8679-tbp65 1/1 Running 0 13s
gloo-66b8dc8868-z5c6r 1/1 Running 0 13s
部署應用程式
我們的 echo
應用程式是一個簡單的容器 (感謝我們在 HashiCorp 的朋友),它將回應應用程式版本,以幫助示範我們的 Canary 工作流程,因為我們開始測試並將流量轉移到應用程式的 v2
版本。
Kubernetes 在對此應用程式進行建模方面給了我們很大的彈性。我們將採用以下慣例
- 我們將在部署名稱中包含版本,以便我們可以並排執行應用程式的兩個版本並以不同的方式管理它們的生命週期。
- 我們將使用應用程式標籤 (
app: echo
) 和版本標籤 (version: v1
) 標記 Pod,以幫助我們的 Canary 發布。 - 我們將為應用程式部署單一 Kubernetes
Service
以設定網路。我們將使用 Gloo 組態管理發布,而不是更新此服務或使用多個服務來管理路由到不同版本。
以下是我們的 v1
echo 應用程式
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-v1
spec:
replicas: 1
selector:
matchLabels:
app: echo
version: v1
template:
metadata:
labels:
app: echo
version: v1
spec:
containers:
# Shout out to our friends at Hashi for this useful test server
- image: hashicorp/http-echo
args:
- "-text=version:v1"
- -listen=:8080
imagePullPolicy: Always
name: echo-v1
ports:
- containerPort: 8080
這是 echo
Kubernetes Service
物件
apiVersion: v1
kind: Service
metadata:
name: echo
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: echo
為了方便起見,我們已將此 yaml 發布在儲存庫中,因此我們可以使用以下命令部署它
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/1-setup/echo.yaml
我們應該看到以下輸出
namespace/echo created
deployment.apps/echo-v1 created
service/echo created
我們應該能夠看到 echo
命名空間中的所有資源都正常運作
➜ kubectl get all -n echo
NAME READY STATUS RESTARTS AGE
pod/echo-v1-66dbfffb79-287s5 1/1 Running 0 6s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/echo ClusterIP 10.55.252.216 <none> 80/TCP 6s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/echo-v1 1/1 1 1 7s
NAME DESIRED CURRENT READY AGE
replicaset.apps/echo-v1-66dbfffb79 1 1 1 7s
使用 Gloo 公開在叢集外部
我們現在可以使用 Gloo 將此服務公開在叢集外部。首先,我們將應用程式建模為 Gloo Upstream,這是 Gloo 對流量目的地的抽象
apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
name: echo
namespace: gloo-system
spec:
kube:
selector:
app: echo
serviceName: echo
serviceNamespace: echo
servicePort: 8080
subsetSpec:
selectors:
- keys:
- version
在這裡,我們根據 version
標籤設定子集。我們不必在我們的路由中使用它,但稍後我們將開始使用它來支援我們的 Canary 工作流程。
我們現在可以透過定義 虛擬服務 在 Gloo 中建立到此 Upstream 的路由
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- "*"
routes:
- matchers:
- prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
我們可以使用以下命令套用這些資源
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/1-setup/upstream.yaml
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/1-setup/vs.yaml
一旦我們套用這兩個資源,我們就可以開始透過 Gloo 將流量傳送到應用程式
➜ curl $(glooctl proxy url)/
version:v1
我們的設定已完成,我們的叢集現在看起來像這樣
兩階段發布策略
現在我們有一個新版本 v2
的 echo 應用程式,我們希望發布它。我們知道當發布完成時,我們將最終得到這張圖片
但是,為了到達那裡,我們可能需要執行幾輪測試,以確保新版本的應用程式符合某些正確性和/或效能驗收標準。在這篇文章中,我們將介紹使用 Gloo 進行 Canary 發布的兩階段方法,可用於滿足絕大多數驗收測試。
在第一階段,我們將透過將少量流量路由到應用程式的新版本來執行冒煙測試和正確性測試。在此示範中,我們將使用標頭 stage: canary
來觸發路由到新服務,儘管在實務中,可能希望根據請求的另一部分 (例如,已驗證 JWT 中的宣告) 做出此決策。
在第二階段,我們已經建立了正確性,因此我們已準備好將所有流量轉移到應用程式的新版本。我們將設定加權目的地,並在監控某些業務指標的同時轉移流量,以確保服務品質保持在可接受的水平。一旦 100% 的流量轉移到新版本,舊版本就可以停用。
在實務中,可能希望僅使用其中一個階段進行測試,在這種情況下,可以跳過另一個階段。
第 1 階段:v2 的初始 Canary 發布
在此階段,我們將部署 v2
,然後使用標頭 stage: canary
開始將少量特定流量路由到新版本。我們將使用此標頭執行一些基本冒煙測試,並確保 v2
以我們預期的方式運作
設定子集路由
在部署我們的 v2
服務之前,我們將更新我們的虛擬服務,以僅路由到具有子集標籤 version: v1
的 Pod,使用稱為 子集路由 的 Gloo 功能。
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- "*"
routes:
- matchers:
- prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v1
我們可以使用以下命令將它們套用到叢集
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/2-initial-subset-routing-to-v2/vs-1.yaml
應用程式應繼續像以前一樣運作
➜ curl $(glooctl proxy url)/
version:v1
部署 echo v2
現在我們可以安全地部署 v2
版本的 echo 應用程式
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-v2
spec:
replicas: 1
selector:
matchLabels:
app: echo
version: v2
template:
metadata:
labels:
app: echo
version: v2
spec:
containers:
- image: hashicorp/http-echo
args:
- "-text=version:v2"
- -listen=:8080
imagePullPolicy: Always
name: echo-v2
ports:
- containerPort: 8080
我們可以使用以下命令部署
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/2-initial-subset-routing-to-v2/echo-v2.yaml
由於我們的閘道已設定為專門路由到 v1
子集,因此這應該沒有任何影響。但是,如果為路由設定了 v2
子集,則它確實使 v2
可以從閘道路由。
在繼續之前,請確保 v2
正在執行
➜ kubectl get pod -n echo
NAME READY STATUS RESTARTS AGE
echo-v1-66dbfffb79-2qw86 1/1 Running 0 5m25s
echo-v2-86584fbbdb-slp44 1/1 Running 0 93s
應用程式應繼續像以前一樣運作
➜ curl $(glooctl proxy url)/
version:v1
為 Canary 測試新增到 v2 的路由
當請求上提供 stage: canary
標頭時,我們將路由到 v2
子集。如果未提供標頭,我們將繼續像以前一樣路由到 v1
子集。
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- "*"
routes:
- matchers:
- headers:
- name: stage
value: canary
prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
- matchers:
- prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v1
我們可以使用以下命令部署
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/2-initial-subset-routing-to-v2/vs-2.yaml
Canary 測試
現在我們有了這個路由,我們可以進行一些測試。首先,讓我們確保現有路由按預期運作
➜ curl $(glooctl proxy url)/
version:v1
現在我們可以開始 Canary 測試我們的新應用程式版本
➜ curl $(glooctl proxy url)/ -H "stage: canary"
version:v2
子集路由的進階使用案例
我們可能會認為這種使用使用者提供的請求標頭的方法太開放了。相反,我們可能希望將 Canary 測試限制為已知的授權使用者。
我們看到的常見實作是,Canary 路由需要有效的 JWT,其中包含特定宣告,以指示主體已授權進行 Canary 測試。Enterprise Gloo 具有開箱即用的支援,可用於驗證 JWT、根據 JWT 宣告更新請求標頭,以及根據更新的標頭重新計算路由目的地。我們將把它留到以後的文章,涵蓋 Canary 測試中更進階的使用案例。
第 2 階段:將所有流量轉移到 v2 並停用 v1
此時,我們已部署 v2
,並建立了一個用於 Canary 測試的路由。如果我們對測試結果感到滿意,我們可以繼續進行第 2 階段,並開始將負載從 v1
轉移到 v2
。我們將在 Gloo 中使用 加權目的地 來管理遷移期間的負載。
設定加權目的地
我們可以變更 Gloo 路由以路由到這兩個目的地,並使用權重來決定應該有多少流量流向 v1
子集,又有多少流量流向 v2
子集。首先,我們將其設定為使 100% 的流量繼續路由到 v1
子集,除非像之前一樣提供了 stage: canary
標頭。
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- "*"
routes:
# We'll keep our route from before if we want to continue testing with this header
- matchers:
- headers:
- name: stage
value: canary
prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
# Now we'll route the rest of the traffic to the upstream, load balanced across the two subsets.
- matchers:
- prefix: /
routeAction:
multi:
destinations:
- destination:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v1
weight: 100
- destination:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
weight: 0
我們可以使用以下命令將此虛擬服務更新套用到叢集
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/3-progressive-traffic-shift-to-v2/vs-1.yaml
現在,對於任何沒有 stage: canary
標頭的請求,叢集看起來像這樣
使用初始權重,我們應該看到閘道繼續為所有流量提供 v1
。
➜ curl $(glooctl proxy url)/
version:v1
開始發布
為了模擬負載測試,讓我們將一半的流量轉移到 v2
這可以透過調整權重在我們的虛擬服務上表達
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- "*"
routes:
- matchers:
- headers:
- name: stage
value: canary
prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
- matchers:
- prefix: /
routeAction:
multi:
destinations:
- destination:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v1
# Update the weight so 50% of the traffic hits v1
weight: 50
- destination:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
# And 50% is routed to v2
weight: 50
我們可以使用以下命令將其套用到叢集
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/3-progressive-traffic-shift-to-v2/vs-2.yaml
現在,當我們將流量傳送到閘道時,我們應該看到一半的請求傳回 version:v1
,另一半傳回 version:v2
。
➜ curl $(glooctl proxy url)/
version:v1
➜ curl $(glooctl proxy url)/
version:v2
➜ curl $(glooctl proxy url)/
version:v1
在實務中,在此過程中,您很可能會監控一些效能和業務指標,以確保流量轉移不會導致整體服務品質下降。我們甚至可以利用像 Flagger 這樣的運算子來幫助自動化此 Gloo 工作流程。Gloo Enterprise 與您的指標後端整合,並提供開箱即用且動態的、基於 Upstream 的儀表板,可用於監控發布的健康狀況。我們將把這些主題留到以後的文章,討論使用 Gloo 進行進階 Canary 測試的使用案例。
完成發布
我們將繼續調整權重,直到最終所有流量都路由到 v2
我們的虛擬服務將看起來像這樣
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- "*"
routes:
- matchers:
- headers:
- name: stage
value: canary
prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
- matchers:
- prefix: /
routeAction:
multi:
destinations:
- destination:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v1
# No traffic will be sent to v1 anymore
weight: 0
- destination:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
# Now all the traffic will be routed to v2
weight: 100
我們可以使用以下命令將其套用到叢集
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/3-progressive-traffic-shift-to-v2/vs-3.yaml
現在,當我們將流量傳送到閘道時,我們應該看到所有請求都傳回 version:v2
。
➜ curl $(glooctl proxy url)/
version:v2
➜ curl $(glooctl proxy url)/
version:v2
➜ curl $(glooctl proxy url)/
version:v2
停用 v1
此時,我們已部署新版本的應用程式,使用子集路由進行了正確性測試,透過逐步將流量轉移到新版本進行了負載和效能測試,並完成了發布。唯一剩下的任務是清理我們的 v1
資源。
首先,我們將清理我們的路由。我們將在路由上保留指定的子集,以便我們為未來的升級做好準備。
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- "*"
routes:
- matchers:
- prefix: /
routeAction:
single:
upstream:
name: echo
namespace: gloo-system
subset:
values:
version: v2
我們可以使用以下命令套用此更新
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/4-decommissioning-v1/vs.yaml
我們可以刪除不再提供任何流量的 v1
部署。
kubectl delete deploy -n echo echo-v1
現在我們的叢集看起來像這樣
對閘道的請求傳回此結果
➜ curl $(glooctl proxy url)/
version:v2
我們現在已使用 Gloo 完成應用程式更新的兩階段 Canary 發布!
其他進階主題
在這篇文章的過程中,我們收集了一些主題,這些主題可能是進階探索的良好起點
- 使用 JWT 篩選器驗證 JWT、將宣告提取到標頭上,並根據宣告值路由到 Canary 版本。
- 查看 Gloo 建立的 Prometheus 指標和 Grafana 儀表板,以監控發布的健康狀況。
- 透過將 Flagger 與 Gloo 整合來自動化發布。
其他一些值得進一步探索的主題
- 透過讓團隊擁有其 Upstream 和路由組態的所有權來支援 自助式 升級
- 利用 Gloo 的 委派 功能和 Kubernetes RBAC 來安全地分散組態管理
- 透過套用 GitOps 原則並使用 Flux 等工具將組態推送到叢集,完全自動化持續交付流程
- 透過使用不同的部署模式設定 Gloo 來支援 混合式 或 非 Kubernetes 應用程式使用案例
- 利用 流量鏡像 在將生產流量轉移到新版本之前,開始使用真實資料測試新版本
參與 Gloo 社群
除了企業客戶群之外,Gloo 還擁有龐大且不斷成長的開源使用者社群。若要深入了解 Gloo
如果您想與我聯繫 (始終歡迎提供意見反應!),您可以在 Solo slack 上找到我,或透過電子郵件 rick.ducott@solo.io 寄信給我。