本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
Kubernetes 1.24:StatefulSet 的最大不可用副本數
Kubernetes StatefulSet 自 1.5 版本引入並在 1.9 版本穩定以來,已被廣泛用於執行有狀態應用。它們提供穩定的 Pod 標識、每個 Pod 的持久化儲存以及有序的優雅部署、擴縮和滾動更新。你可以將 StatefulSet 視為運行復雜有狀態應用的原子構建塊。隨著 Kubernetes 的使用越來越廣泛,需要 StatefulSet 的場景也越來越多。其中許多場景需要比當前支援的“一次一個 Pod”更新更快的滾動更新,特別是在你為 StatefulSet 使用 OrderedReady
Pod 管理策略時。
以下是一些例子
我正在使用 StatefulSet 來編排一個多例項、基於快取的應用,其中快取的規模很大。快取從冷啟動開始,需要相當長的時間才能啟動容器。可能還需要執行更多初始啟動任務。對此 StatefulSet 進行滾動更新(RollingUpdate)會花費大量時間才能完全更新應用。如果 StatefulSet 支援一次更新多個 Pod,更新速度會快得多。
我的有狀態應用由領導者和跟隨者,或者一個寫入者和多個讀取者組成。我有多個讀取者或跟隨者,我的應用可以容忍多個 Pod 同時宕機。我希望一次更新多個 Pod,以便快速推出新的更新,特別是當我的應用例項數量很大時。請注意,我的應用仍然需要每個 Pod 具有唯一的標識。
為了支援這類場景,Kubernetes 1.24 引入了一個新的 Alpha 功能。在使用這個新功能之前,你必須啟用 MaxUnavailableStatefulSet
特性門控。啟用後,你可以在 StatefulSet 的 spec
中指定一個新欄位 maxUnavailable
。例如:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: default
spec:
podManagementPolicy: OrderedReady # you must set OrderedReady
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
# image changed since publication (previously used registry "k8s.gcr.io")
- image: registry.k8s.io/nginx-slim:0.8
imagePullPolicy: IfNotPresent
name: nginx
updateStrategy:
rollingUpdate:
maxUnavailable: 2 # this is the new alpha field, whose default value is 1
partition: 0
type: RollingUpdate
如果你啟用了這個新功能,但沒有在 StatefulSet 中為 maxUnavailable
指定值,Kubernetes 會應用預設值 maxUnavailable: 1
。這與未啟用新功能時的行為一致。
我將透過一個基於該示例清單的場景來演示此功能的工作原理。我將部署一個 StatefulSet,它有 5 個副本,maxUnavailable
設定為 2,partition
設定為 0。
我可以透過將映象更改為 registry.k8s.io/nginx-slim:0.9
來觸發滾動更新。一旦我啟動滾動更新,我可以觀察到 Pod 每次更新 2 個,因為當前 maxUnavailable 的值為 2。以下輸出顯示了一段時間內的情況,並不完整。maxUnavailable 可以是一個絕對數(例如 2),也可以是所需 Pods 的百分比(例如 10%)。絕對數是透過將百分比向上取整到最接近的整數計算得出的。
kubectl get pods --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 85s
web-1 1/1 Running 0 2m6s
web-2 1/1 Running 0 106s
web-3 1/1 Running 0 2m47s
web-4 1/1 Running 0 2m27s
web-4 1/1 Terminating 0 5m43s ----> start terminating 4
web-3 1/1 Terminating 0 6m3s ----> start terminating 3
web-3 0/1 Terminating 0 6m7s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-4 0/1 Terminating 0 5m48s
web-4 0/1 Terminating 0 5m48s
web-3 0/1 ContainerCreating 0 2s
web-3 1/1 Running 0 2s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-4 0/1 ContainerCreating 0 0s
web-4 1/1 Running 0 1s
web-2 1/1 Terminating 0 5m46s ----> start terminating 2 (only after both 4 and 3 are running)
web-1 1/1 Terminating 0 6m6s ----> start terminating 1
web-2 0/1 Terminating 0 5m47s
web-1 0/1 Terminating 0 6m7s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 2s
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
web-0 1/1 Terminating 0 6m6s ----> start terminating 0 (only after 2 and 1 are running)
web-0 0/1 Terminating 0 6m7s
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
請注意,滾動更新一開始,序號最高的兩個 Pod 4 和 3 會同時開始終止。序號為 4 和 3 的 Pod 可能會按各自的節奏就緒。一旦 Pod 4 和 3 都就緒,Pod 2 和 1 就會同時開始終止。當 Pod 2 和 1 都執行並就緒後,Pod 0 開始終止。
在 Kubernetes 中,StatefulSet 的更新在更新 Pod 時遵循嚴格的順序。在這個例子中,更新從副本 4 開始,然後是副本 3,接著是副本 2,依此類推,一次一個 Pod。當一次只更新一個 Pod 時,3 不可能在 4 之前執行並就緒。當 maxUnavailable
大於 1 時(在示例場景中,我將 maxUnavailable
設定為 2),副本 3 可能會在副本 4 就緒之前就緒並執行——這是可以的。如果你是開發人員,並且將 maxUnavailable
設定為大於 1,你應該知道這種結果是可能發生的,並且你必須確保你的應用能夠處理任何可能出現的此類排序問題。當你將 maxUnavailable
設定為大於 1 時,更新順序在每批 Pod 之間是有保證的。這個保證意味著更新批次 2(副本 2 和 1)中的 Pod 在批次 0(副本 4 和 3)的 Pods 就緒之前不能開始更新。
儘管 Kubernetes 將這些稱為**副本(replicas)**,但你的有狀態應用可能有不同的視角,StatefulSet 的每個 Pod 可能持有與其他 Pod 完全不同的資料。這裡的重點是,StatefulSet 的更新是分批進行的,現在你可以擁有大於 1 的批次大小(作為一個 Alpha 功能)。
另請注意,上述行為是在 podManagementPolicy: OrderedReady
的情況下。如果你將 StatefulSet 定義為 podManagementPolicy: Parallel
,不僅會有 maxUnavailable
數量的副本同時被終止,同樣數量的副本也會同時進入 ContainerCreating
階段。這被稱為突發(bursting)。
那麼,現在你可能會有很多關於以下方面的問題:
- 當你設定
podManagementPolicy: Parallel
時,行為是怎樣的? - 當
partition
設定為非0
的值時,行為又是怎樣的?
最好還是親自嘗試一下。這是一個 Alpha 功能,Kubernetes 的貢獻者們正在尋求對此功能的反饋。這個功能是否幫助你實現了你的有狀態場景?你是否發現了 bug,或者你認為當前的實現行為不直觀,可能會破壞應用或讓它們措手不及?請提交一個 issue 讓我們知道。