本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

使用開源 Gloo 進行兩階段金絲雀部署

作者: Rick Ducott | GitHub | Twitter

每天,我的同事和我都會與平臺負責人、架構師和工程師交流,他們正在使用 Gloo 作為 API 閘道器來向終端使用者公開他們的應用程式。這些應用程式可能涵蓋遺留的單體應用、微服務、託管雲服務和 Kubernetes 叢集。幸運的是,Gloo 可以輕鬆設定路由來管理、保護和觀察應用程式流量,同時支援靈活的部署架構,以滿足使用者多樣化的生產需求。

除了初始設定,平臺負責人經常要求我們幫助設計其組織內部的操作工作流程:我們如何將新應用程式上線?我們如何升級應用程式?我們如何劃分平臺、運維和開發團隊的職責?

在這篇文章中,我們將使用 Gloo 設計一個用於應用程序升級的兩階段金絲雀釋出工作流程。

  • 在第一階段,我們將透過將少量流量轉移到新版本來進行金絲雀測試。這使您可以安全地執行冒煙測試和正確性測試。
  • 在第二階段,我們將逐步將流量轉移到新版本,從而使我們能夠在負載下監控新版本,並最終淘汰舊版本。

為了保持簡單,我們將專注於使用 開源 Gloo 設計工作流程,並將閘道器和應用程式部署到 Kubernetes。最後,我們將討論一些擴充套件和高階主題,這些主題可能在後續文章中值得探討。

初始設定

首先,我們需要一個 Kubernetes 叢集。此示例不利用任何特定於雲的功能,可以在本地測試叢集(例如 minikube)上執行。本文假定對 Kubernetes 及其使用 kubectl 進行互動有基本瞭解。

我們將把最新的 開源 Gloo 安裝到 gloo-system 名稱空間,並將示例應用程式的 v1 版本部署到 echo 名稱空間。我們將透過在 Gloo 中建立路由來將此應用程式公開到叢集外部,最終得到如下所示的圖片

Setup

部署 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

我們的設定已完成,我們的叢集現在看起來像這樣

Setup

兩階段釋出策略

現在我們有了新版本 v2 的 echo 應用程式,我們希望將其釋出。我們知道當釋出完成時,我們將得到這張圖片

End State

然而,為了達到這個目標,我們可能需要進行幾輪測試,以確保應用程式的新版本符合特定的正確性和/或效能驗收標準。在這篇文章中,我們將介紹一種使用 Gloo 進行金絲雀釋出的兩階段方法,該方法可用於滿足絕大多數驗收測試。

在第一階段,我們將透過將少量流量路由到應用程式的新版本來執行冒煙測試和正確性測試。在此演示中,我們將使用標頭 stage: canary 來觸發路由到新服務,但在實踐中,可能需要根據請求的其他部分(例如經過驗證的 JWT 中的宣告)來做出此決定。

在第二階段,我們已經確定了正確性,因此我們準備將所有流量轉移到應用程式的新版本。我們將配置加權目標,並在遷移過程中轉移流量,同時監控某些業務指標,以確保服務質量保持在可接受的水平。一旦 100% 的流量轉移到新版本,舊版本就可以退役了。

在實踐中,可能只需要使用其中一個階段進行測試,在這種情況下,可以跳過另一個階段。

階段 1:v2 的初始金絲雀釋出

在此階段,我們將部署 v2,然後使用標頭 stage: canary 開始將少量特定流量路由到新版本。我們將使用此標頭執行一些基本的冒煙測試,並確保 v2 按照我們的預期工作。

Subset Routing

設定子集路由

在部署我們的 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 標頭的請求,叢集看起來像這樣

Initialize Traffic Shift

使用初始權重,我們應該看到閘道器繼續為所有流量提供 v1

➜ curl $(glooctl proxy url)/
version:v1

開始釋出

為了模擬負載測試,讓我們將一半流量轉移到 v2

Load Test

這可以透過調整權重在我們的虛擬服務上表示

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

Final Shift

我們的虛擬服務將如下所示

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

現在我們的叢集看起來像這樣

End State

閘道器的請求返回如下

➜ curl $(glooctl proxy url)/
version:v2

我們現在已經使用 Gloo 完成了應用程式更新的兩階段金絲雀釋出!

其他高階主題

在這篇文章中,我們收集了一些可以作為高階探索的良好起點的議題

  • 使用 JWT 過濾器驗證 JWT,將宣告提取到標頭,並根據宣告值路由到金絲雀版本。
  • 檢視 Gloo 建立的 Prometheus 指標Grafana 儀表板,以監控釋出執行狀況。
  • 透過將 FlaggerGloo 整合來自動化釋出。

其他一些值得進一步探討的主題

  • 透過讓團隊擁有對其上游和路由配置的所有權來支援自助式升級
  • 利用 Gloo 的委託功能和 Kubernetes RBAC 安全地分散配置管理
  • 透過應用 GitOps 原則和使用 Flux 等工具將配置推送到叢集來完全自動化持續交付過程
  • 透過使用不同的部署模式設定 Gloo 來支援混合非 Kubernetes 應用程式用例
  • 利用流量影子在將生產流量轉移到新版本之前,使用真實資料開始測試新版本

參與 Gloo 社群

Gloo 擁有龐大且不斷壯大的開源使用者社群,此外還有企業客戶群。要了解有關 Gloo 的更多資訊,請訪問

  • 檢視 倉庫,您可以在其中檢視程式碼和提交問題
  • 檢視 文件,其中包含大量指南和示例
  • 加入 Slack 頻道,開始與 Solo 工程團隊和使用者社群聊天

如果您想與我聯絡(歡迎隨時提出反饋!),您可以在 Solo slack 上找到我,或傳送電子郵件至 rick.ducott@solo.io