StatefulSet 概述
StatefulSet 是 Kubernetes 中用於管理有狀態應用程式的工作負載 API 物件。與 Deployment 不同,StatefulSet 為每個 Pod 維護一個固定的身份識別,這些 Pod 是根據相同的規格建立的,但不可互換:每個 Pod 都有一個持久的識別碼,在任何重新調度時都會保留。
StatefulSet 適用於需要以下一項或多項功能的應用程式:
- 穩定且唯一的網路識別碼
- 穩定的持久化儲存
- 有序的部署和擴展
- 有序的自動滾動更新
常見的 StatefulSet 使用場景包括:
- 資料庫叢集(MySQL、PostgreSQL、MongoDB)
- 訊息佇列(Kafka、RabbitMQ)
- 分散式儲存系統(Elasticsearch、Cassandra)
- 需要穩定網路識別的應用程式
Deployment vs StatefulSet
| 特性 | Deployment | StatefulSet |
|---|
| Pod 識別 | 隨機雜湊值 | 有序數字索引(0, 1, 2…) |
| Pod 名稱 | 隨機生成 | 固定格式:<statefulset-name>-<ordinal> |
| 網路識別 | 不穩定 | 穩定的 DNS 名稱 |
| 儲存 | 共享或無狀態 | 每個 Pod 獨立的持久化儲存 |
| 部署順序 | 並行部署 | 有序部署(0 → 1 → 2) |
| 刪除順序 | 並行刪除 | 逆序刪除(2 → 1 → 0) |
| 適用場景 | 無狀態應用 | 有狀態應用 |
StatefulSet 特性
穩定的網路識別
StatefulSet 中的每個 Pod 都會獲得一個基於 StatefulSet 名稱和 Pod 序號的穩定主機名稱。格式為:
1
| <statefulset-name>-<ordinal>.<service-name>.<namespace>.svc.cluster.local
|
例如,一個名為 mysql 的 StatefulSet 配合名為 mysql-headless 的 Headless Service,其 Pod DNS 名稱為:
1
2
3
| mysql-0.mysql-headless.default.svc.cluster.local
mysql-1.mysql-headless.default.svc.cluster.local
mysql-2.mysql-headless.default.svc.cluster.local
|
穩定的儲存
每個 StatefulSet Pod 都可以透過 volumeClaimTemplates 獲得自己專屬的 PersistentVolumeClaim(PVC)。當 Pod 被重新調度到其他節點時,它會重新掛載相同的 PVC,確保資料持久性。
有序部署與終止
- 部署順序:Pod 按照 0, 1, 2, …, N-1 的順序建立
- 終止順序:Pod 按照 N-1, …, 2, 1, 0 的逆序終止
- 在部署過程中,每個 Pod 必須處於 Running 和 Ready 狀態後,才會建立下一個 Pod
Headless Service 設定
StatefulSet 需要一個 Headless Service 來控制 Pod 的網路識別。Headless Service 的特點是 clusterIP: None。
1
2
3
4
5
6
7
8
9
10
11
12
13
| apiVersion: v1
kind: Service
metadata:
name: mysql-headless
labels:
app: mysql
spec:
ports:
- port: 3306
name: mysql
clusterIP: None
selector:
app: mysql
|
Headless Service 不會分配 Cluster IP,而是直接將 DNS 查詢解析到各個 Pod 的 IP 位址。這使得應用程式可以直接連接到特定的 Pod。
您也可以建立一個普通的 Service 來對外提供負載平衡:
1
2
3
4
5
6
7
8
9
10
11
12
| apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
name: mysql
selector:
app: mysql
|
StatefulSet YAML 範例
以下是一個完整的 MySQL StatefulSet 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
| apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
labels:
app: mysql
spec:
serviceName: mysql-headless
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
terminationGracePeriodSeconds: 30
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
- name: MYSQL_DATABASE
value: "myapp"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1000m"
memory: "2Gi"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
livenessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command:
- mysql
- -h
- localhost
- -e
- "SELECT 1"
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard"
resources:
requests:
storage: 10Gi
|
持久化儲存(volumeClaimTemplates)
volumeClaimTemplates 是 StatefulSet 特有的功能,它為每個 Pod 自動建立獨立的 PVC。
1
2
3
4
5
6
7
8
9
10
11
| volumeClaimTemplates:
- metadata:
name: data
labels:
app: mysql
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "fast-ssd"
resources:
requests:
storage: 20Gi
|
PVC 命名規則
自動建立的 PVC 名稱格式為:
1
| <volumeClaimTemplate-name>-<statefulset-name>-<ordinal>
|
例如:data-mysql-0、data-mysql-1、data-mysql-2
重要注意事項
- PVC 不會自動刪除:當 StatefulSet 被刪除時,相關的 PVC 不會自動刪除,以保護資料安全
- 手動清理:需要手動刪除 PVC 以釋放儲存空間
- 重新建立:如果重新建立同名的 StatefulSet,它會自動重新掛載現有的 PVC
Pod 管理策略
StatefulSet 支援兩種 Pod 管理策略,透過 spec.podManagementPolicy 設定:
OrderedReady(預設)
1
2
| spec:
podManagementPolicy: OrderedReady
|
- Pod 按順序建立(0, 1, 2…)
- 每個 Pod 必須 Running 和 Ready 後才建立下一個
- 按逆序終止
Parallel
1
2
| spec:
podManagementPolicy: Parallel
|
- 所有 Pod 同時建立或刪除
- 不等待其他 Pod 達到 Running/Ready 狀態
- 適用於不需要順序保證的場景
更新策略
StatefulSet 支援兩種更新策略,透過 spec.updateStrategy 設定:
RollingUpdate(預設)
1
2
3
4
5
6
| spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0
maxUnavailable: 1
|
- partition:只更新序號大於或等於此值的 Pod,可用於金絲雀發布
- maxUnavailable:更新期間最多可以有多少個 Pod 不可用(Kubernetes 1.24+)
分段更新範例
1
2
3
4
5
| spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2
|
當 partition: 2 時,只有 mysql-2 及更高序號的 Pod 會被更新,mysql-0 和 mysql-1 保持原有版本。
OnDelete
1
2
3
| spec:
updateStrategy:
type: OnDelete
|
- 不會自動更新 Pod
- 需要手動刪除 Pod 才會以新規格重建
- 提供完全的手動控制
擴展與縮減
擴展
1
2
3
4
5
| # 使用 kubectl scale
kubectl scale statefulset mysql --replicas=5
# 或使用 kubectl patch
kubectl patch statefulset mysql -p '{"spec":{"replicas":5}}'
|
擴展時,新的 Pod 會按照序號順序建立(例如:3, 4)。
縮減
1
| kubectl scale statefulset mysql --replicas=2
|
縮減時,Pod 會按照逆序終止(例如:4, 3, 2 依序終止)。
重要考量
- 資料備份:縮減前確保已備份將被移除 Pod 的資料
- PVC 保留:縮減不會刪除 PVC,需要手動清理
- 叢集狀態:對於資料庫叢集,確保縮減不會影響叢集健康狀態
完整部署範例
以下是一個包含所有相關資源的完整部署範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
| ---
# Secret
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
root-password: bXlzcWwtcm9vdC1wYXNzd29yZA==
---
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
my.cnf: |
[mysqld]
max_connections=200
innodb_buffer_pool_size=256M
---
# Headless Service
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
ports:
- port: 3306
name: mysql
clusterIP: None
selector:
app: mysql
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless
replicas: 3
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
volumeMounts:
- name: data
mountPath: /var/lib/mysql
- name: config
mountPath: /etc/mysql/conf.d
volumes:
- name: config
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard"
resources:
requests:
storage: 10Gi
|
常用指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 查看 StatefulSet 狀態
kubectl get statefulset mysql
# 查看 Pod 狀態
kubectl get pods -l app=mysql
# 查看 PVC
kubectl get pvc -l app=mysql
# 查看特定 Pod 的日誌
kubectl logs mysql-0
# 進入 Pod 執行指令
kubectl exec -it mysql-0 -- mysql -u root -p
# 查看 StatefulSet 詳細資訊
kubectl describe statefulset mysql
# 刪除 StatefulSet(保留 PVC)
kubectl delete statefulset mysql
# 刪除 StatefulSet 和 PVC
kubectl delete statefulset mysql
kubectl delete pvc -l app=mysql
|
參考資料