本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
使用開源 Gloo 進行兩階段金絲雀部署
作者: Rick Ducott | GitHub | Twitter
每天,我的同事和我都會與平臺負責人、架構師和工程師交流,他們正在使用 Gloo 作為 API 閘道器來向終端使用者公開他們的應用程式。這些應用程式可能涵蓋遺留的單體應用、微服務、託管雲服務和 Kubernetes 叢集。幸運的是,Gloo 可以輕鬆設定路由來管理、保護和觀察應用程式流量,同時支援靈活的部署架構,以滿足使用者多樣化的生產需求。
除了初始設定,平臺負責人經常要求我們幫助設計其組織內部的操作工作流程:我們如何將新應用程式上線?我們如何升級應用程式?我們如何劃分平臺、運維和開發團隊的職責?
在這篇文章中,我們將使用 Gloo 設計一個用於應用程序升級的兩階段金絲雀釋出工作流程。
- 在第一階段,我們將透過將少量流量轉移到新版本來進行金絲雀測試。這使您可以安全地執行冒煙測試和正確性測試。
- 在第二階段,我們將逐步將流量轉移到新版本,從而使我們能夠在負載下監控新版本,並最終淘汰舊版本。
為了保持簡單,我們將專注於使用 開源 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 的朋友們),它將返回應用程式版本,以幫助我們演示金絲雀工作流程,因為我們開始測試並將流量轉移到應用程式的 v2
版本。
Kubernetes 在建模此應用程式方面為我們提供了很大的靈活性。我們將採用以下約定
- 我們將在部署名稱中包含版本,以便我們可以並行執行兩個版本的應用程式並以不同的方式管理它們的生命週期。
- 我們將使用應用程式標籤(
app: echo
)和版本標籤(version: v1
)標記 Pod,以幫助我們進行金絲雀釋出。 - 我們將為應用程式部署單個 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
標籤設定了子集。我們不必在路由中使用它,但稍後我們將開始使用它來支援我們的金絲雀工作流程。
現在我們可以透過定義 虛擬服務 在 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
我們可以使用以下命令應用這些資源
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 進行金絲雀釋出的兩階段方法,該方法可用於滿足絕大多數驗收測試。
在第一階段,我們將透過將少量流量路由到應用程式的新版本來執行冒煙測試和正確性測試。在此演示中,我們將使用標頭 stage: canary
來觸發路由到新服務,但在實踐中,可能需要根據請求的其他部分(例如經過驗證的 JWT 中的宣告)來做出此決定。
在第二階段,我們已經確定了正確性,因此我們準備將所有流量轉移到應用程式的新版本。我們將配置加權目標,並在遷移過程中轉移流量,同時監控某些業務指標,以確保服務質量保持在可接受的水平。一旦 100% 的流量轉移到新版本,舊版本就可以退役了。
在實踐中,可能只需要使用其中一個階段進行測試,在這種情況下,可以跳過另一個階段。
階段 1:v2 的初始金絲雀釋出
在此階段,我們將部署 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
現在我們可以安全地部署 echo 應用程式的 v2
版本了
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
新增路由到 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
金絲雀測試
現在我們有了這個路由,我們可以進行一些測試。首先讓我們確保現有路由按預期工作
➜ curl $(glooctl proxy url)/
version:v1
現在我們可以開始對新應用程式版本進行金絲雀測試了
➜ curl $(glooctl proxy url)/ -H "stage: canary"
version:v2
子集路由的高階用例
我們可能會認為這種使用使用者提供的請求標頭的方法過於開放。相反,我們可能希望將金絲雀測試限制為已知、已授權的使用者。
我們見過的一種常見實現是金絲雀路由需要一個有效的 JWT,其中包含一個特定宣告以指示該主體已獲得金絲雀測試的授權。Enterprise Gloo 支援開箱即用地驗證 JWT,根據 JWT 宣告更新請求標頭,並根據更新後的標頭重新計算路由目標。我們將在未來的文章中討論涵蓋金絲雀測試中更高階用例的內容。
階段 2:將所有流量轉移到 v2 並淘汰 v1
此時,我們已經部署了 v2
,並建立了用於金絲雀測試的路由。如果對測試結果滿意,我們可以進入階段 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 與您的指標後端整合,並提供開箱即用、基於動態上游的儀表板,可用於監控釋出執行狀況。我們將這些主題留待未來的文章,討論 Gloo 的高階金絲雀測試用例。
完成釋出
我們將繼續調整權重,直到最終所有流量都路由到 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 完成了應用程式更新的兩階段金絲雀釋出!
其他高階主題
在這篇文章中,我們收集了一些可以作為高階探索的良好起點的議題
- 使用 JWT 過濾器驗證 JWT,將宣告提取到標頭,並根據宣告值路由到金絲雀版本。
- 檢視 Gloo 建立的 Prometheus 指標 和 Grafana 儀表板,以監控釋出執行狀況。
- 透過將 Flagger 與 Gloo 整合來自動化釋出。
其他一些值得進一步探討的主題
- 透過讓團隊擁有對其上游和路由配置的所有權來支援自助式升級
- 利用 Gloo 的委託功能和 Kubernetes RBAC 安全地分散配置管理
- 透過應用 GitOps 原則和使用 Flux 等工具將配置推送到叢集來完全自動化持續交付過程
- 透過使用不同的部署模式設定 Gloo 來支援混合或非 Kubernetes 應用程式用例
- 利用流量影子在將生產流量轉移到新版本之前,使用真實資料開始測試新版本
參與 Gloo 社群
Gloo 擁有龐大且不斷壯大的開源使用者社群,此外還有企業客戶群。要了解有關 Gloo 的更多資訊,請訪問
如果您想與我聯絡(歡迎隨時提出反饋!),您可以在 Solo slack 上找到我,或傳送電子郵件至 rick.ducott@solo.io。