主線
KAI K8s之路 07|StatefulSet 是什麼?副本不再只是「隨便換一個」
Deployment 把 Pod 當成可互換副本,但資料庫、broker 和有狀態叢集常常需要固定名字、獨立儲存與啟停順序。這篇用宿舍房號拆解 StatefulSet、Headless Service、ordinal、volumeClaimTemplates 與實務排查路徑。
StatefulSet 不是「有磁碟的 Deployment」;它替每個 Pod 保留可辨認的身份,讓名字、網路位置與獨立儲存能在重建後重新對上。
先判斷副本是否真的不可互換。需要固定 ordinal、穩定 DNS、每個副本獨立 PVC 或啟停順序時才選 StatefulSet;排查時沿 StatefulSet、Pod ordinal、Headless Service、PVC 與 Events 一層層看。
上一篇我們把 Volume、PV 和 PVC 想清楚了:
Pod 可以換,但有業務意義的資料不能跟著消失。
可是「資料留下來」只解決了一半。
假設現在有三個副本:
- 第一個負責 leader 工作
- 第二個保存 shard A
- 第三個保存 shard B
這時候你不能只說:
掛一顆磁碟給它就好。
因為新的問題是:哪個 Pod 應該接回哪一份資料?其他成員要怎麼找到它?啟動順序要不要管?
我會先記這句:
Deployment 管的是一群可互換副本;StatefulSet 管的是一群有名字、有位置、有自己資料的成員。
先不要把 StatefulSet 想成不會死的 Pod
StatefulSet 裡的 Pod 一樣會:
- crash
- 被 evict
- 被重新排程
- 因更新而重建
- 跟著 node 故障而消失
它沒有把 Pod 變成寵物,也沒有讓 process 永生。
它真正提供的是 sticky identity。
也就是:
Pod 可以被替換,但它代表的那個成員位置要能被重新認出來。
例如 web-0 掛了,controller 會建立新的 web-0。
新的 Pod IP 可能不同、所在 node 也可能不同,但它仍然可以:
- 使用
web-0這個名字 - 回到自己的 DNS 身份
- 重新掛上屬於
web-0的 PVC
所以 StatefulSet 的重點從來不是「不變」。
而是 變動之後,身份仍然對得回來。
用宿舍房號想 StatefulSet
我會把 Deployment 想成一群活動工作人員。
每個人穿同一件制服、做同一份工作;今天 A 休息,換 B 上場,外面的人通常不需要知道差別。
StatefulSet 比較像一排有房號的宿舍:
web-0是 0 號房web-1是 1 號房web-2是 2 號房- 每個房間有自己的信箱
- 每個房間有自己的置物櫃
住戶可以換,但房號不能亂。
0 號房的新住戶要接回 0 號房的信箱和置物櫃,不能隨手拿走 2 號房的東西。
這個比喻對應到 Kubernetes:
| 宿舍 | StatefulSet |
|---|---|
| 房號 | Pod ordinal,例如 web-0 |
| 信箱地址 | 穩定的 Pod DNS |
| 私人置物櫃 | 每個 Pod 自己的 PVC |
| 按房號安排入住 | ordered create / scale |
| 倒序退房 | reverse ordered scale-down |
真正要判斷的不是「這個 app 有沒有寫資料」。
而是:
這些副本是不是可互換?還是每個成員都必須被點名?
StatefulSet 穩定的是哪三件事
1) 穩定 ordinal
預設有 N 個副本時,Pod 會取得從 0 到 N-1 的 ordinal:
web-0
web-1
web-2
名字不是隨機 suffix。
這讓 application、監控和運維流程可以清楚知道自己面對哪個成員。
但不要過度解讀:web-0 不會自動變成 leader。
ordinal 只提供身份,leader election、replication 和 quorum 還是 application 自己的協議。
2) 穩定網路身份
StatefulSet 通常配一個 Headless Service:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
clusterIP: None
selector:
app: web
ports:
- name: http
port: 80
clusterIP: None 代表它不提供一般 Service 的單一 virtual IP load balancing。
它更像一份可查詢的成員名冊,讓 client 可以找到具體 Pod,例如:
web-0.web.default.svc.cluster.local
web-1.web.default.svc.cluster.local
這很適合需要 peer discovery 的系統。
但 Headless Service 不是公開流量入口,也不是 StatefulSet 自動送你的贈品;你仍要自己建立,selector 和 serviceName 也必須對得上。
3) 穩定獨立儲存
volumeClaimTemplates 會替每個 Pod 建立自己的 PVC。
例如:
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOncePod
storageClassName: fast
resources:
requests:
storage: 10Gi
配合三個副本,概念上會得到:
data-web-0 -> PVC/PV for web-0
data-web-1 -> PVC/PV for web-1
data-web-2 -> PVC/PV for web-2
web-0 被重建時,會重新引用 data-web-0。
這就是身份和資料能重新對上的關鍵。
預設情況下,刪除 Pod、縮容,甚至刪除 StatefulSet,都不會自動把相關 PVC 當垃圾清掉。
這是資料安全取向,不代表你永遠不用治理它們。
新版本 Kubernetes 可以用 .spec.persistentVolumeClaimRetentionPolicy 設定 StatefulSet 被刪除或縮容時是否保留或刪除 PVC;但你必須明確審查 cluster 版本、資料保留需求和底層 PV reclaim policy,不能只看到 Delete 就一路按下去。
一個夠用的 StatefulSet 範例
下面不是 production database manifest,只是把關係串起來:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
clusterIP: None
selector:
app: web
ports:
- name: http
port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
terminationGracePeriodSeconds: 30
containers:
- name: web
image: nginx:1.27
ports:
- name: http
containerPort: 80
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOncePod
storageClassName: fast
resources:
requests:
storage: 10Gi
責任切分是:
- Headless Service 管成員的網路 domain
serviceName把 StatefulSet 接到這個 domain- ordinal 給每個 Pod 穩定名字
volumeClaimTemplates給每個 Pod 自己的 claim- StorageClass / PV provisioner 提供真正 storage
- application 自己處理 replication、leader、quorum 和資料一致性
StatefulSet 管身份與編排,不替 application 發明分散式系統協議。
順序不是裝飾
預設 podManagementPolicy 是 OrderedReady。
建立三個副本時,通常會按這個順序:
web-0 Ready
-> web-1 Ready
-> web-2 Ready
縮容時,則反過來:
web-2
-> web-1
-> web-0
RollingUpdate 也會從最高 ordinal 往最低 ordinal 更新,並等待更新後的 Pod Running and Ready 才繼續。
這對需要 bootstrap 順序的 workload 有價值。
但它也帶來一個代價:
前面的成員不 Ready,後面的動作可能一起被卡住。
所以 StatefulSet 卡在 rollout 時,不要只看最後一個 Pod。
你要找出是哪個 ordinal 擋住了整條隊伍。
如果 application 不需要嚴格 scaling 順序,可以考慮 podManagementPolicy: Parallel。
但 Parallel 只放寬建立和縮容的等待方式,不會把成員身份變成可互換,也不等於所有 stateful system 都適合並行啟動。
還有一個重要邊界:直接刪除 StatefulSet 時,Kubernetes 不保證 Pod 會按 ordinal 優雅終止。如果 application 很在意有序 shutdown,應先縮到 0,確認資料層狀態,再刪除 StatefulSet。
Deployment 還是 StatefulSet
我會先問一句:
如果其中一個 Pod 消失,任何新副本都能無差別頂上嗎?
如果答案是「可以」,通常先用 Deployment。
如果答案是「不行,新副本必須知道自己是誰、接回哪份資料、被其他成員用固定地址找到」,才往 StatefulSet 看。
| 判斷 | Deployment | StatefulSet |
|---|---|---|
| 副本是否可互換 | 通常是 | 通常不是 |
| Pod 名稱 | 隨機 suffix | 穩定 ordinal |
| 個別 Pod DNS | 通常不重要 | 常是設計的一部分 |
| 每副本獨立 PVC | 需另外設計 | volumeClaimTemplates 原生支援 |
| 建立/縮容順序 | 不強調 | 預設有順序 |
| 典型 workload | web/API、stateless worker | database、broker、需要固定成員的叢集 |
「有 PVC」不是選 StatefulSet 的充分條件。
一個單副本 app 可以由 Deployment 掛 PVC;一個把 state 放在外部 database 的 app 也可能完全不需要 StatefulSet。
反過來,StatefulSet 也不是 database 的魔法保護罩。
它不自動提供:
- backup
- replication
- failover correctness
- data consistency
- quorum
- disaster recovery
新手最常踩的坑
1) 以為 StatefulSet Pod 不會被重建
會。
穩定的是 identity,不是 Pod UID、IP、node 或 process。
2) 以為 web-0 自動就是 leader
不是。
ordinal 可以讓 application 有穩定成員編號,但角色仍要由 application config、operator 或選舉協議決定。
3) 忘記建立 Headless Service
沒有 governing Headless Service,穩定 Pod DNS 這條路就不完整。
還要檢查:
- StatefulSet
.spec.serviceName - Service 名稱
- namespace
- selector
- Pod label
4) 以為刪 StatefulSet 會順便清乾淨資料
預設不會自動刪除關聯 PVC,這是為了避免資料事故。
清理前要同時看 PVC retention policy、PV reclaim policy、備份和實際 storage backend。
5) 直接 force delete 卡住的 Pod
StatefulSet 的身份必須保持唯一。
在 node 狀態不明時強制刪除,有機會讓同一 identity 同時出現兩個 instance,形成 split-brain 風險。
先確認舊 instance 真的不可能再運作,再處理強制刪除。
6) 把 OrderedReady 當成 application readiness
Kubernetes 只知道 probe 和 Pod 狀態。
如果 readiness probe 沒有反映真正的 replication、recovery 或 quorum 狀態,controller 看到 Ready,不代表資料層真的安全。
我會怎麼 inspect
看到 StatefulSet rollout 卡住、Pod identity 對不上或 storage 沒掛好,我會沿這條路走:
kubectl get statefulset -n <ns>
kubectl describe statefulset <name> -n <ns>
kubectl rollout status statefulset/<name> -n <ns>
kubectl get pods -n <ns> -l app=<label> -o wide
kubectl describe pod <name>-<ordinal> -n <ns>
kubectl get service <headless-service> -n <ns> -o yaml
kubectl get endpointslice -n <ns> -l kubernetes.io/service-name=<headless-service>
kubectl get pvc -n <ns>
kubectl describe pvc <claim-name> -n <ns>
kubectl get events -n <ns> --sort-by=.lastTimestamp
我會重點看:
READY/CURRENT/UPDATEDreplicas 是否一致- 哪個 ordinal 第一個不 Ready
- Pod 名稱、ownerReference 和 label 是否符合預期
- Headless Service 是否真的是
clusterIP: None serviceName、Service selector 和 Pod label 是否吻合- 每個 ordinal 是否拿到自己的 PVC
- PVC 是否
Bound - Events 裡有沒有 scheduling、attach、mount、probe 或 termination 問題
- update strategy、partition、pod management policy 是否阻擋 rollout
一句話版本:
先找哪個成員掉隊,再查它的名字、地址和資料有沒有重新對上。
我對 StatefulSet 的記法
我不會把 StatefulSet 記成「Kubernetes 跑 database 的物件」。
這個說法太粗,也很容易讓人以為用了它就自動安全。
我會這樣記:
StatefulSet 是一張固定座位表:人可以換,但座位號、聯絡地址和置物櫃不能亂。
Deployment 追求的是:
少一個副本,就補一個能工作的副本。
StatefulSet 追求的是:
少的是 1 號成員,就把 1 號成員帶著自己的身份和資料補回來。
這個差異,就是 stateless replica 和 stateful member 的分界。
這篇先記住三件事
- StatefulSet 保留的是 sticky identity,不是讓 Pod 永遠不死
- ordinal、Headless Service 與
volumeClaimTemplates把名字、網路身份和獨立儲存對在一起 - StatefulSet 只負責 Kubernetes 層的身份與順序;replication、leader、quorum、backup 仍是 application 的責任
下一篇會接著看:Job / CronJob 是什麼?不是每個 workload 都要一直活著,有些工作跑完就應該正式結束。
技術校對參考: