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

使用開放原始碼 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 中建立路由來將此應用程式公開在叢集外部,最終得到如下所示的圖片

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 的朋友),它將回應應用程式版本,以幫助示範我們的 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

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

Setup

兩階段發布策略

現在我們有一個新版本 v2 的 echo 應用程式,我們希望發布它。我們知道當發布完成時,我們將最終得到這張圖片

End State

但是,為了到達那裡,我們可能需要執行幾輪測試,以確保新版本的應用程式符合某些正確性和/或效能驗收標準。在這篇文章中,我們將介紹使用 Gloo 進行 Canary 發布的兩階段方法,可用於滿足絕大多數驗收測試。

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

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

在實務中,可能希望僅使用其中一個階段進行測試,在這種情況下,可以跳過另一個階段。

第 1 階段:v2 的初始 Canary 發布

在此階段,我們將部署 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

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

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 與您的指標後端整合,並提供開箱即用且動態的、基於 Upstream 的儀表板,可用於監控發布的健康狀況。我們將把這些主題留到以後的文章,討論使用 Gloo 進行進階 Canary 測試的使用案例。

完成發布

我們將繼續調整權重,直到最終所有流量都路由到 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 完成應用程式更新的兩階段 Canary 發布!

其他進階主題

在這篇文章的過程中,我們收集了一些主題,這些主題可能是進階探索的良好起點

  • 使用 JWT 篩選器驗證 JWT、將宣告提取到標頭上,並根據宣告值路由到 Canary 版本。
  • 查看 Gloo 建立的 Prometheus 指標Grafana 儀表板,以監控發布的健康狀況。
  • 透過將 FlaggerGloo 整合來自動化發布。

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

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

參與 Gloo 社群

除了企業客戶群之外,Gloo 還擁有龐大且不斷成長的開源使用者社群。若要深入了解 Gloo

  • 查看 repo,您可以在其中查看程式碼和提交問題
  • 查看 文件,其中包含大量的指南和範例
  • 加入 slack 頻道 並開始與 Solo 工程團隊和使用者社群聊天

如果您想與我聯繫 (始終歡迎提供意見反應!),您可以在 Solo slack 上找到我,或透過電子郵件 rick.ducott@solo.io 寄信給我。