Kubernetes StatefulSet 有狀態應用部署

Kubernetes StatefulSet for Stateful Applications

StatefulSet 概述

StatefulSet 是 Kubernetes 中用於管理有狀態應用程式的工作負載 API 物件。與 Deployment 不同,StatefulSet 為每個 Pod 維護一個固定的身份識別,這些 Pod 是根據相同的規格建立的,但不可互換:每個 Pod 都有一個持久的識別碼,在任何重新調度時都會保留。

StatefulSet 適用於需要以下一項或多項功能的應用程式:

  • 穩定且唯一的網路識別碼
  • 穩定的持久化儲存
  • 有序的部署和擴展
  • 有序的自動滾動更新

常見的 StatefulSet 使用場景包括:

  • 資料庫叢集(MySQL、PostgreSQL、MongoDB)
  • 訊息佇列(Kafka、RabbitMQ)
  • 分散式儲存系統(Elasticsearch、Cassandra)
  • 需要穩定網路識別的應用程式

Deployment vs StatefulSet

特性DeploymentStatefulSet
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-0data-mysql-1data-mysql-2

重要注意事項

  1. PVC 不會自動刪除:當 StatefulSet 被刪除時,相關的 PVC 不會自動刪除,以保護資料安全
  2. 手動清理:需要手動刪除 PVC 以釋放儲存空間
  3. 重新建立:如果重新建立同名的 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-0mysql-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 依序終止)。

重要考量

  1. 資料備份:縮減前確保已備份將被移除 Pod 的資料
  2. PVC 保留:縮減不會刪除 PVC,需要手動清理
  3. 叢集狀態:對於資料庫叢集,確保縮減不會影響叢集健康狀態

完整部署範例

以下是一個包含所有相關資源的完整部署範例:

 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

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy