使用 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]。
-
bookbuyer
是一個 HTTP 客戶端,它發送請求給bookstore
。這個流量是 允許 的。 -
bookthief
是一個 HTTP 客戶端,很像bookbuyer
,也會發送 HTTP 請求給bookstore
。這個流量應該被 阻止。 -
bookstore
是一個服務器,負責對 HTTP 請求給與響應。同時,該服務器也是一個客戶端,發送請求給bookwarehouse
服務。這個流量是被 允許 的。 -
bookwarehouse
是一個服務器,應該只對bookstore
做出響應。bookbuyer
和bookthief
都應該被其阻止。 -
mysql
是一個 MySQL 數據庫,只有bookwarehouse
可以訪問。
這裏捨棄了 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
創建瞭如下三個服務,這裏正好會用到:
-
根服務
bookstore
-
穩定版服務
bookstore-v1
-
金絲雀版本
bookstore-v2
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