使用 Argo Rollouts 和服務網格實現自動可控的金絲雀發佈

金絲雀發佈是服務治理中的重要功能,在發佈時可以可控地將部分流量導入新版本的服務中;其餘的流量則由舊版本處理。發佈過程中,可以逐步增加新版本服務的流量。通過測試,可以決定是回滾還是升級所有實例,停用舊版本。

有了金絲雀發佈,使用真實流量對服務進行測試,通過對流量的控制可以有效的降低服務發佈的風險。

本文將介紹如何將使用 Argo Rollouts 和服務網格 osm-edge 來進行應用的自動、可控的金絲雀發佈,不涉及工作原理的分析。對工作原理有興趣的同學可以留言,可以再做一篇原理的介紹。

Argo Rollouts

Argo Rollouts[1] 包括一個 Kubernetes 控制器 [2] 和一組 CRD[3],提供如藍綠色、金絲雀、金絲雀分析、體驗等高級部署功能和 Kubernetes 的漸進交付功能。

Argo Rollouts 與 入口控制器 [4] 和服務網格集成,利用其流量管理能力,在發佈期間逐步將流量轉移到新版本。此外,Rollouts 可以查詢和解析來自各種提供商的指標,以驗證關鍵 KPI,並在更新期間推動自動推進或回滾。

Argo Rollouts 支持服務網格標準 SMI(Service Mesh Interface)[5] 的 TrafficSplit API[6],通過 TrafficSplit API 的使用來控制金絲雀發佈時的流量控制。

服務網格 osm-edge

服務網格是處理服務間網絡通信的基礎設施組件,旨在從平臺層面提供可觀性、安全以及可靠性特性,以進程外的方式提供原本由部分應用層邏輯承載的基礎能力,真正實現與業務邏輯的分離。

osm-edge[7] 是面向雲邊一體的輕量化服務網格,採用實現了服務網格標準 SMI(Service Mesh Interface)[8] 的  osm(Open Service Mesh)[9] 作爲控制平面,採用  Pipy[10]  作爲數據平面,具有高性能、低資源、簡單、易用、易擴展、廣泛兼容(支持 x86/arm64 / 龍芯 / RISC-V)的特點。

環境準備

K3s

export INSTALL_K3S_VERSION=v1.23.8+k3s2
curl -sfL https://get.k3s.io | sh -s - --disable traefik --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config

安裝服務網格

推薦使用 CLI 進行安裝。

system=$(uname -s | tr [:upper:] [:lower:])
arch=$(dpkg --print-architecture)
release=v1.1.2
curl -L https://github.com/flomesh-io/osm-edge/releases/download/${release}/osm-edge-${release}-${system}-${arch}.tar.gz | tar -vxzf -
./${system}-${arch}/osm version
cp ./${system}-${arch}/osm /usr/local/bin/

CLI 下載之後就可以通過下面的命令進行安裝了,這裏默認開啓 寬鬆流量策略模式 [11] 並安裝 Ingress[12]。

export osm_namespace=osm-system
export osm_mesh_name=osm

osm install \
    --mesh-name "$osm_mesh_name" \
    --osm-namespace "$osm_namespace" \
    --set=osm.enablePermissiveTrafficPolicy=true \
    --set=fsm.enabled=true

確認 pod 正常啓動並運行。

kubectl get pods -n osm-system
NAME                                           READY   STATUS    RESTARTS   AGE
fsm-repo-d785b55d-r92r5                        1/1     Running   0          2m20s
osm-bootstrap-646497898f-cdjq4                 1/1     Running   0          3m12s
osm-controller-7bbdcf748b-jdhsw                2/2     Running   0          3m12s
fsm-manager-7f9b665bd9-s8z6p                   1/1     Running   0          3m12s
fsm-bootstrap-57cb75d586-vvvzl                 1/1     Running   0          3m01s
osm-injector-86798c9ddb-gfqb4                  1/1     Running   0          3m12s
fsm-ingress-pipy-5bc7f4d6f6-7th6g              1/1     Running   0          3m12s
fsm-cluster-connector-local-7464b77ffd-cphxf   1/1     Running   0          4m22s

安裝 Argo Rollouts

kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

Argo Rollouts 中默認使用 SMI TrafficSplit 的 v1alpha1 版本,通過下面的命令指定其使用 v1alpha2 的版本。

kubectl patch deployment argo-rollouts -n argo-rollouts --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args", "value": ["--traffic-split-api-version=v1alpha2"]}]'

確認 pod 正常啓動並運行。

kubectl get pods -n argo-rollouts
NAME                             READY   STATUS    RESTARTS   AGE
argo-rollouts-58b9bc98f7-cj79l   1/1     Running   0          1m5s

安裝 kubectl argo 插件

使用 kubectl argo 插件可以通過命令行對發佈進行操作。

在 macOS 下,其他平臺參考 官方安裝文檔 [13]。

brew install argoproj/tap/kubectl-argo-rollouts

通過下面的命令啓動 Argo Rollouts Dashboard,在瀏覽器中打開 http://localhost:3100/rollouts 就可訪問 Dashboard。

kubectl argo rollouts dashboard

接下來就是部署示例應用。

部署應用

示例應用使用常見的 bookstore 系統,包含下面幾個組件。這裏只對 bookstore 應用進行金絲雀發佈,爲了方便演示,除了 bookstore 以外的幾個應用繼續使用 Deployment 的方式部署,只有 bookstore 使用 Argo Rollouts 的 Rollout CRD[14]。

這裏捨棄了 bookthief 應用。

創建命名空間

創建命名空間 rollouts-demo ,並納入到網格管理中。

kubectl create namespace rollouts-demo
kubectl config set-context --current --namespace rollouts-demo
osm namespace add rollouts-demo

創建 Service

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: bookbuyer
  namespace: rollouts-demo
  labels:
    app: bookbuyer
spec:
  ports:
  - port: 14001
    name: bookbuyer-port
  selector:
    app: bookbuyer
---
apiVersion: v1
kind: Service
metadata:
  name: bookstore
  namespace: rollouts-demo
  labels:
    app: bookstore
spec:
  ports:
  - port: 14001
    name: bookstore-port
  selector:
    app: bookstore
---
apiVersion: v1
kind: Service
metadata:
  name: bookstore-v1
  namespace: rollouts-demo
  labels:
    app: bookstore
    version: v1
spec:
  ports:
  - port: 14001
    name: bookstore-port
  selector:
    app: bookstore
---
apiVersion: v1
kind: Service
metadata:
  name: bookstore-v2
  namespace: rollouts-demo
  labels:
    app: bookstore
    version: v2
spec:
  ports:
  - port: 14001
    name: bookstore-port
  selector:
    app: bookstore
---
apiVersion: v1
kind: Service
metadata:
  name: bookwarehouse
  namespace: rollouts-demo
  labels:
    app: bookwarehouse
spec:
  ports:
  - port: 14001
    name: bookwarehouse-port
  selector:
    app: bookwarehouse
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: rollouts-demo
spec:
  ports:
  - port: 3306
    targetPort: 3306
    name: client
    appProtocol: tcp
  selector:
    app: mysql
  clusterIP: None
EOF

部署 Deployment 應用

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bookbuyer
  namespace: rollouts-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bookbuyer
      version: v1
  template:
    metadata:
      labels:
        app: bookbuyer
        version: v1
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: bookbuyer
          image: flomesh/bookbuyer:latest
          imagePullPolicy: Always
          command: ["/bookbuyer"]
          env:
            - name: "BOOKSTORE_NAMESPACE"
              value: rollouts-demo
            - name: "BOOKSTORE_SVC"
              value: bookstore
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bookwarehouse
  namespace: rollouts-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bookwarehouse
  template:
    metadata:
      labels:
        app: bookwarehouse
        version: v1
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: bookwarehouse
          image: flomesh/bookwarehouse:latest
          imagePullPolicy: Always
          command: ["/bookwarehouse"]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: rollouts-demo
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - image: mariadb:10.7.4
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: mypassword
        - name: MYSQL_DATABASE
          value: booksdemo
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - mountPath: /mysql-data
          name: data
        readinessProbe:
          tcpSocket:
            port: 3306
          initialDelaySeconds: 15
          periodSeconds: 10
      volumes:
        - name: data
          emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 250M
EOF

部署 bookstore Rollout

我們爲 bookstore 的金絲雀發佈設置了三個階段:10%、50%、90% 流量,每個階段後都會進入暫停狀態(也可以設置暫停時間、條件等等)。

前面創建 Service 時,我們爲 bookstore 創建瞭如下三個服務,這裏正好會用到:

kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: bookstore
  namespace: rollouts-demo
spec:
  replicas: 1
  strategy:
    canary:
      canaryService: bookstore-v2
      stableService: bookstore-v1
      trafficRouting:
        smi:
          rootService: bookstore
      steps:
      - setWeight: 10
      - pause: {}
      - setWeight: 50
      - pause: {}
      - setWeight: 90
      - pause: {}
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: bookstore
  template:
    metadata:
      labels:
        app: bookstore
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: bookstore
          image: addozhang/bookstore-v1:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 14001
              name: web
          command: ["/bookstore"]
          args: ["--port", "14001"]
          env:
            - name: BOOKWAREHOUSE_NAMESPACE
              value: rollouts-demo
EOF

部署完成後,此時 bookstore-v1 的版本會運行,並不會部署新的版本,此時三個 Service 都會選擇到穩定版本的 pod。

kubectl get endpoints -n rollouts-demo -l app=bookstore
NAME           ENDPOINTS                           AGE
bookstore-v1   10.42.1.34:14001                    54s
bookstore      10.42.1.34:14001                    54s
bookstore-v2   10.42.1.34:14001                    54s

查看 TrafficSplit 的設定,訪問 bookstore 的所有流量都會進入 bookstore-v1 的 endpoint 中:

kubectl get trafficsplit bookstore -n rollouts-demo -o yaml
apiVersion: split.smi-spec.io/v1alpha2
kind: TrafficSplit
metadata:
  name: bookstore
  namespace: rollouts-demo
spec:
  backends:
  - service: bookstore-v2
    weight: 0
  - service: bookstore-v1
    weight: 100
  service: bookstore

查看 Argo Rollouts Dashboard,可以看到剛剛創建的 Rollout

點開可以看到詳細信息,包括當前的 revision,已經我們設置的發佈步驟。

創建 bookbuyer Ingress

爲了方便查看運行效果,爲 bookbuyer 創建 Ingress。然後就可以通過 http://ingress_host:80 來訪問 bookbuyer 應用了,也可以通過 http://ingress_host:80/reset 來重置應用頁面的計數器,方便灰度發佈的效果確認。

kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: bookbuyer
  namespace: rollouts-demo
spec:
  ingressClassName: pipy
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: bookbuyer
            port:
              number: 14001
---
kind: IngressBackend
apiVersion: policy.openservicemesh.io/v1alpha1
metadata:
  name: bookbuyer
  namespace: rollouts-demo
spec:
  backends:
  - name: bookbuyer
    port:
      number: 14001
      protocol: http
  sources:
  - kind: Service
    namespace: osm-system
    name: fsm-ingress-pipy-controller
EOF

發佈測試

筆者的 ingress IP 是 192.168.1.12,因此在瀏覽器中打開 http://192.168.1.12。此時,所有的流量都到了 bookstore-v1

執行應用升級

通過下面的命令,部署 bookstore v2 版本開始金絲雀發佈。除了命令行,也可以在 Dashboard 的發佈詳情頁面上修改容器的鏡像。

kubectl argo rollouts set image bookstore bookstore=addozhang/bookstore-v2:latest

然後通過下面的命令可以查看穩定版和金絲雀版本的實例狀態。

kubectl argo rollouts get rollout bookstore

重置 bookstore 應用頁面的計數器後,可以發現 v1 和 v2 流量接近 9:1。

Dashboard 的發佈詳情頁上,會顯示 v2 版本的 revision,右側 steps 列表中可以看到當前處於第一個 pause 階段。

推進到 50% 流量

驗證完成後,就可以將發佈推進到下一階段。可以使用下面的命令,也可在 Dashboard 詳情頁上點擊 PROMOTE 按鈕。

kubectl argo rollouts promote bookstore
rollout 'bookstore' promoted

還是先清空應用頁面計數器,然後查看效果。

推進到 100% 流量

接下來,可以繼續推薦到下一階段 90%。也可以通過下面的命令直接進入到最後的階段 100%,在 Dashboard 詳情頁上點擊 PROMOTE-FULL 也可達到同樣的效果。

kubectl argo rollouts promote bookstore --full
rollout 'bookstore' fully promoted

其他操作

在發佈的任何一個階段,可以通過命令或者頁面上的操作回到上一個階段,或者退出發佈。

#回到上一階段
kubectl argo rollouts undo bookstore
#退出發佈
kubectl argo rollouts abort bookstore

總結

服務網格以進程外無侵入的方式爲服務提供了豐富的治理功能,從平臺層面提升系統的可觀測性、安全以及可靠性。金絲雀發佈是服務網格的主要應用場景之一,大大減低了應用發佈帶來的風險,將不穩定性限制在可控的範圍內。

同時 Argo Rollouts 也提供了豐富的設置,來控制發佈的流程,滿足不同的使用需求。

參考資料

[1] 

Argo Rollouts: https://argoproj.github.io/argo-rollouts/

[2] 

Kubernetes 控制器: https://kubernetes.io/docs/concepts/architecture/controller/

[3] 

CRD: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/

[4] 

入口控制器: https://kubernetes.io/docs/concepts/services-networking/ingress/

[5] 

SMI(Service Mesh Interface): https://smi-spec.io

[6] 

TrafficSplit API: https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-split/v1alpha2/traffic-split.md

[7] 

osm-edge: https://osm-edge-docs.flomesh.io

[8] 

SMI(Service Mesh Interface): https://smi-spec.io

[9] 

osm(Open Service Mesh): https://openservicemesh.io

[10] 

Pipy: https://github.com/flomesh-io/pipy

[11] 

寬鬆流量策略模式: https://osm-edge-docs.flomesh.io/zh/docs/guides/traffic_management/permissive_mode/

[12] 

Ingress: https://osm-edge-docs.flomesh.io/zh/docs/demos/ingress_fsm/

[13] 

官方安裝文檔: https://argoproj.github.io/argo-rollouts/installation/#kubectl-plugin-installation

[14] 

Rollout CRD: https://argoproj.github.io/argo-rollouts/features/specification/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ARcTA88CcgACPvUljhQtDQ