昨天我們學完了 Kubernetes 儲存三兄弟 —— PV、PVC、StorageClass,讓儲存資源可以自動建立並完成綁定。
但到目前為止,我們使用的多半都是「無狀態」應用,例如 Nginx。
這類應用通常不依賴特定 Pod 的身份,即使 Pod 被刪除後重新建立,只要 Service 能把流量導向新的 Pod,應用通常還是可以正常運作。
可是真實世界中,有很多應用其實是有狀態的:
如果你用 Deployment 來跑這些應用,會發生什麼事?
app-7f8b9c6d4-xk2zp,Pod 被重建後名稱可能改變Kubernetes 的解法就是 StatefulSet —— 專門用來管理有狀態應用的控制器。
它可以讓每個 Pod 擁有穩定的名稱、穩定的網路身份,並搭配 volumeClaimTemplates 為每個 Pod 建立各自獨立的持久化儲存。
今天會學:
以下操作皆在 master 節點 執行。
用一個比喻來理解:
seat-0、seat-1、seat-2,也有專屬的餐具(持久化儲存)。即使客人暫時離開再回來,仍然會回到原本的座位,繼續使用自己的那套餐具。| 比較項目 | Deployment | StatefulSet |
|---|---|---|
| Pod 命名 | 名稱包含隨機產生的字串,例如 app-7f8b9c-xk2zp |
有固定序號,例如 app-0、app-1、app-2 |
| 啟動順序 | 沒有固定的啟動順序保證 | 預設依序啟動(0 → 1 → 2) |
| 刪除順序 | 沒有固定的刪除順序保證 | 預設反序終止(2 → 1 → 0) |
| 網路身份 | Pod 本身沒有固定的網路身份,重建後 Pod IP 可能改變 | 可搭配 Headless Service,讓每個 Pod 擁有穩定的 DNS 名稱 |
| 儲存 | 不會自動為每個 Pod 建立並維持專屬 PVC | 可透過 volumeClaimTemplates 為每個 Pod 建立專屬 PVC |
| 適用場景 | 無狀態應用,例如 Web Server、API | 有狀態應用,例如資料庫、MQ、分散式系統 |
💡 什麼時候該用 StatefulSet?
可以先問自己三個問題:
- 每個 Pod 是否需要各自獨立的持久化儲存?
- Pod 之間是否需要透過固定名稱或穩定的網路身份互相溝通?
- 啟動、更新或終止時,是否有順序上的要求?
如果其中任何一個需求很重要,就可以考慮使用 StatefulSet。
Deployment 建立的 Pod 名稱通常像這樣:
nginx-deployment-7f8b9c6d4-xk2zp
nginx-deployment-7f8b9c6d4-m3n7p
StatefulSet 建立的 Pod 名稱則會有固定序號:
web-0
web-1
web-2
名稱格式為 <StatefulSet 名稱>-<序號>,序號會從 0 開始遞增。
即使某個 Pod 被刪除後重新建立,它仍然會保留原本的名稱。例如 web-0 被重建後,名稱依然是 web-0。
這種穩定的命名方式,讓每個 Pod 都能擁有固定且可辨識的身份。
搭配 Headless Service,每個 Pod 會有一個固定的 DNS 名稱:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
例如:
web-0.nginx-headless.default.svc.cluster.local
web-1.nginx-headless.default.svc.cluster.local
web-2.nginx-headless.default.svc.cluster.local
這表示其他 Pod 可以透過固定的 DNS 名稱找到特定的 Pod。
即使 Pod 被刪除後重新建立、Pod IP 發生變化,只要 StatefulSet 中的身份沒有改變,對應的 DNS 名稱也會維持不變。
StatefulSet 可以透過 volumeClaimTemplates,為每個 Pod 自動建立各自專屬的 PVC:
data-web-0 → 自動建立的 PV-A
data-web-1 → 自動建立的 PV-B
data-web-2 → 自動建立的 PV-C
每個 Pod 都會使用自己的持久化儲存,彼此之間不會共用同一份資料。
即使 web-0 被刪除後重新建立,它仍然會重新掛載原本的 data-web-0,因此原本儲存在該 PVC 中的資料可以繼續使用。
volumeClaimTemplates就是 Day 10 的延伸還記得 Day 10 我們手動建立 PVC,並透過
storageClassName指定 StorageClass 嗎?
volumeClaimTemplates做的事情很類似,只是它會根據範本,自動為 StatefulSet 中的每個 Pod 建立一份專屬 PVC。PVC 的命名規則通常是:
<template-name>-<pod-name>例如:
data-web-0 data-web-1 data-web-2
在繼續實作 StatefulSet 之前,我們需要先理解 Headless Service。
一般的 Service(ClusterIP)會取得一個 ClusterIP,Client 存取這個 Service 時,流量會根據 kube-proxy 建立的轉送規則導向其中一個後端 Pod:
Client → Service IP(10.96.0.100)→ 隨機分配到 Pod-A 或 Pod-B
但有狀態應用有時需要的不是「任意一個 Pod」,而是要能夠直接找到某個特定的 Pod。
這時就可以使用 Headless Service。
Headless Service 不會建立 ClusterIP,而是透過 DNS 提供後端 Pod 的位址:
Client → DNS 查詢 → 直接拿到每個 Pod 的 IP
搭配 StatefulSet 後,每個 Pod 還可以擁有固定的 DNS 名稱,例如:
web-0.nginx-headless.default.svc.cluster.local
web-1.nginx-headless.default.svc.cluster.local
建立 Headless Service 的關鍵,就是將 clusterIP 設為 None:
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
clusterIP: None # 這就是 Headless 的關鍵
selector:
app: nginx
ports:
- port: 80
targetPort: 80
| 比較項目 | 一般 Service(ClusterIP) | Headless Service |
|---|---|---|
| clusterIP | 有(系統分配) | None |
| DNS 回傳 | Service 的 ClusterIP | 後端 Pod 的 IP |
| 流量轉送 | 透過 kube-proxy 建立的規則導向後端 Pod | 不經過 Service ClusterIP,由 Client 直接連線到 Pod |
| 用途 | 提供統一入口,將流量導向後端 Pod | 讓 Client 能直接找到特定 Pod,常搭配有狀態應用 |
💡 為什麼 StatefulSet 通常要搭配 Headless Service?
StatefulSet 的穩定網路身份,是透過 固定的 Pod 名稱 + Headless Service 提供的 DNS 記錄來實現。
搭配 Headless Service 後,每個 Pod 都能擁有固定且可預期的 DNS 名稱,例如:
web-0.nginx-headless.default.svc.cluster.local即使 Pod 被刪除後重新建立、IP 發生變化,其他 Pod 仍然可以透過相同的 DNS 名稱找到它。
現在來完整實作一個 StatefulSet,並搭配 Day 10 已經安裝好的 local-path StorageClass:
vim headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
clusterIP: None
selector:
app: nginx-sts
ports:
- port: 80
targetPort: 80
kubectl apply -f headless-svc.yaml
vim statefulset-demo.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: nginx-headless # 必須指定對應的 Headless Service
replicas: 3
selector:
matchLabels:
app: nginx-sts
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # 每個 Pod 自動建立專屬 PVC
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
kubectl apply -f statefulset-demo.yaml
⚠️注意
serviceName欄位
serviceName是 StatefulSet 用來指定「負責提供網路身份的 Service」的欄位,通常會填入前面建立的 Headless Service 名稱。Kubernetes 會根據這個 Service 名稱,搭配 Pod 名稱產生穩定的 DNS 身份。
例如:
web-0.nginx-headless.default.svc.cluster.local因此
serviceName必須和對應的 Headless Service 名稱一致,否則 Pod 的 DNS 名稱就無法按照預期運作。
kubectl get pods -w -l app=nginx-sts
會看到 Pod 依序啟動:

web-0 進入 Ready 狀態後,才會開始建立 web-1;web-1 Ready 後,才會建立 web-2。
這就是 StatefulSet 預設的有序部署行為。
(按 Ctrl+C 結束觀察)
每個 Pod 都有自己的 PVC,查看一下:
kubectl get pvc
會看到三個自動建立的 PVC:

命名規則:<volumeClaimTemplate名稱>-<Pod名稱>
現在讓每個 Pod 寫入不同的資料:
kubectl exec web-0 -- sh -c 'echo "我是 web-0 的資料" > /usr/share/nginx/html/index.html'
kubectl exec web-1 -- sh -c 'echo "我是 web-1 的資料" > /usr/share/nginx/html/index.html'
kubectl exec web-2 -- sh -c 'echo "我是 web-2 的資料" > /usr/share/nginx/html/index.html'
驗證各自的資料:
kubectl exec web-0 -- cat /usr/share/nginx/html/index.html
kubectl exec web-1 -- cat /usr/share/nginx/html/index.html
kubectl exec web-2 -- cat /usr/share/nginx/html/index.html
每個 Pod 讀到的是自己的資料,互不干擾。
用一個臨時 Pod 來測試 DNS:
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- nslookup nginx-headless
會看到 DNS 回傳所有 Pod 的 IP(而不是一個虛擬 IP)。

測試單一 Pod 的 DNS:
kubectl run dns-test2 --image=busybox:1.36 --rm -it --restart=Never -- nslookup web-0.nginx-headless.default.svc.cluster.local
會看到 web-0.nginx-headless.default.svc.cluster.local 解析到 web-0 的 Pod IP。

⚠️ 注意:DNS 查詢失敗時,可以改用 FQDN 測試
在 Kubernetes Pod 中,短名稱通常會搭配
/etc/resolv.conf裡的searchdomain 進行解析。如果使用 BusyBox 的
nslookup查詢:web-0.nginx-headless出現
NXDOMAIN或查詢失敗,可以先改用完整的 FQDN:web-0.nginx-headless.default.svc.cluster.local如果完整名稱可以正常解析,再進一步檢查 Pod 內的
/etc/resolv.conf、DNS Policy 與 search domain 設定。
刪掉 web-1,看看重建後會怎樣:
kubectl delete pod web-1
kubectl get pods -w -l app=nginx-sts

會看到新的 web-1 被自動重建(名稱一樣),驗證資料:
kubectl exec web-1 -- cat /usr/share/nginx/html/index.html
輸出依然是「我是 web-1 的資料」— 就代表 web-1 重建後,仍然重新掛載了原本對應的 PVC。

名稱保持不變、儲存也能接回原本的資料,這就是 StatefulSet 很重要的核心特性。
⚠️ 錯誤 1:刪除 StatefulSet 不會自動刪除 PVC
這是 StatefulSet 用來保護資料的重要設計之一。
即使刪除整個 StatefulSet:
kubectl delete statefulset web由
volumeClaimTemplates建立的 PVC 預設仍然會保留下來。如果之後重新建立相同名稱與相同 Volume Claim Template 的 StatefulSet,對應的 Pod 通常會重新使用原本的 PVC,因此原本的資料可以繼續保留。
如果確定這些資料已經不再需要,就必須另外手動刪除 PVC:
kubectl delete pvc data-web-0 data-web-1 data-web-2刪除 PVC 前要特別小心,後續是否連底層 PV 與資料一起刪除,還會受到 StorageClass 與
reclaimPolicy的影響。
⚠️ 錯誤 2:忘記建立 Headless Service
如果沒有建立 StatefulSet
serviceName所對應的 Headless Service,Pod 本身仍然可能正常啟動,但預期的穩定 DNS 名稱就無法正常使用。例如其他 Pod 可能無法透過:
web-0.nginx-headless找到特定的 StatefulSet Pod。
可以先檢查 Service 是否存在:
kubectl get svc nginx-headless並確認
CLUSTER-IP欄位顯示:None這表示它是一個 Headless Service。
podManagementPolicy:控制 Pod 的建立與終止順序StatefulSet 可以透過
podManagementPolicy控制 Pod 的管理方式:
OrderedReady(預設):依照序號建立 Pod(0 → 1 → 2),前一個 Pod 進入 Ready 狀態後,才會繼續建立下一個Parallel:建立或終止 Pod 時,不等待前一個 Pod 完成,也不要求依照序號順序處理如果應用程式不需要嚴格的啟動順序,只需要穩定的 Pod 身份與持久化儲存,可以使用
Parallel加快 Pod 的建立速度:spec: podManagementPolicy: Parallel
更新策略:
RollingUpdate與partitionStatefulSet 預設使用
RollingUpdate更新策略。更新 Pod Template 後,StatefulSet 會依照序號由大到小逐一更新,例如:
web-2 → web-1 → web-0也可以搭配
partition控制只更新部分 Pod:spec: updateStrategy: type: RollingUpdate rollingUpdate: partition: 2
partition: 2表示只有序號 大於或等於2的 Pod 會套用新的 Pod Template。以三個副本為例:
web-0 保持原版本 web-1 保持原版本 web-2 更新成新版本這種方式很適合先更新部分 Pod 進行驗證,再逐步降低
partition,完成分階段更新。
今天我們學會了 StatefulSet —— Kubernetes 為有狀態應用設計的控制器:
| 核心特性 | 說明 | 對比 Deployment |
|---|---|---|
| 穩定的 Pod 名稱 | web-0、web-1、web-2,Pod 被重建後名稱仍維持不變 |
Pod 名稱通常包含隨機字串,重建後可能改變 |
| 穩定的網路身份 | 搭配 Headless Service,可為每個 Pod 提供固定的 DNS 名稱 | Pod 本身沒有固定的網路身份,通常透過 Service 統一存取 |
| 專屬持久化儲存 | 可透過 volumeClaimTemplates 為每個 Pod 建立專屬 PVC |
不會自動為每個 Pod 建立並維持專屬 PVC |
| 有序部署與終止 | 預設依序建立(0 → 1 → 2),反序終止(2 → 1 → 0) | 沒有固定的建立與終止順序保證 |
回顧一下我們的學習路線:
到這裡,我們已經能處理有狀態應用的基本需求了。
但你有沒有想過:當 Kubernetes 建立這些 Pod 時,是怎麼決定它們要跑在哪個 Node 上的?如果 Node 的 CPU 或記憶體資源不夠,又會發生什麼事?
明天我們來學 Kubernetes 的排程與資源管理,看看 Scheduler 如何替 Pod 挑選合適的 Node,以及 Kubernetes 如何管理 CPU 與 Memory 資源!