Kubernetes StatefulSet - 有狀態應用程式部署

StatefulSet 是專門用來管理 有狀態 (Stateful) 應用程式的 Workload API 物件。 與 Deployment 不同,StatefulSet 會為每個 Pod 維護一個 固定的識別符 (Sticky Identity)

什麼是「有狀態」應用?

  • 無狀態 (Stateless): 如 Nginx, API Server。隨時可以被殺掉、重啟,不需要保留資料,也不在乎這台機器是誰。Deployment 專門處理這種。
  • 有狀態 (Stateful): 如 MySQL, Redis, ZooKeeper。它們需要:
    1. 穩定的網路識別符: 例如 mysql-0, mysql-1,不能隨便變動。
    2. 穩定的儲存: mysql-0 掛掉重啟後,必須接回原本那顆硬碟 (PVC),資料不能丟。
    3. 有序部署與縮放: 必須先啟動主節點 (Master),再啟動從節點 (Slave)。

定義 StatefulSet

建立 StatefulSet 通常需要配合 Headless Service (ClusterIP: None) 來控制網路網域名稱。

# nginx-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
    - port: 80
      name: web
  clusterIP: None # 關鍵:Headless Service,不分配 Cluster IP
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: 'nginx' # 必須匹配 Headless Service 名稱
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: registry.k8s.io/nginx-slim:0.8
          ports:
            - containerPort: 80
              name: web
          volumeMounts: # 掛載儲存卷
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates: # 自動建立 PVC 的模板
    - metadata:
        name: www
      spec:
        accessModes: ['ReadWriteOnce']
        storageClassName: 'standard' # 指定 StorageClass,若不指定則使用叢集預設值
        resources:
          requests:
            storage: 1Gi

StatefulSet 的特性

1. 穩定的網路 ID

StatefulSet 建立的 Pod 名稱是固定的,格式為 <statefulset-name>-<ordinal>。 例如上面的範例會依序建立:

  • web-0
  • web-1
  • web-2

它們的 DNS 名稱也會固定:web-0.nginx.default.svc.cluster.local。這讓叢集內的節點可以互相找到彼此(Discovery)。

2. 有序部署與擴展

  • 部署: 0 -> 1 -> 2。前一個 Ready 之後,才會建立下一個。
  • 縮減: 2 -> 1 -> 0。從最後一個開始刪除。

3. 穩定的儲存 (volumeClaimTemplates)

StatefulSet 可以透過 volumeClaimTemplates 自動為每個 Pod 申請一個專屬的 PVC (PersistentVolumeClaim)。 每個 Pod 會綁定一個對應編號的 PVC(例如 www-web-0, www-web-1)。

重點: 當 Pod 被刪除或重啟時,它會重新掛載原本的 PVC,確保資料(如資料庫檔案)不會遺失。即便 Pod 被調度到不同節點,PVC 依然會跟隨(由 Kubernetes PV 系統保證)。

StatefulSet 操作實務

擴展與縮減 (Scaling)

擴展 StatefulSet 時,它會依序建立 Pod (0 -> 1 -> 2)。縮減時,則會依序刪除 (2 -> 1 -> 0),這對分散式資料庫的數據同步非常重要。

# 擴展到 5 個副本
kubectl scale statefulset web --replicas=5

# 縮減回 3 個副本
kubectl scale statefulset web --replicas=3

更新策略 (Update Strategy)

StatefulSet 預設使用 RollingUpdate 策略。更新時會以 逆序 (Reverse Ordinal) 方式逐一更新 Pod (2 -> 1 -> 0)。確保在更新任何一個 Pod 之前,其後的 Pod 都已經 Ready 並且 Running。

Partition (分區更新):

如果你只想更新部分的 Pod(例如做金絲雀佈署 Canary Release),可以使用 partition 參數。

updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    partition: 2

設定 partition: 2 表示只有 web-2 (及以上) 的 Pod 會被更新,web-0web-1 會保持舊版本。這個功能常用於大型資料庫的灰度升級。

刪除 StatefulSet 與 PVC 保留

這是 StatefulSet 最獨特的設計之一。當你刪除 StatefulSet 時,PVC 不會被自動刪除。這是為了防止意外誤刪導致珍貴的資料遺失。

# 刪除 StatefulSet (Pod 會被終止)
kubectl delete statefulset web

刪除後,你依然會看到 PVC 存在:

kubectl get pvc
# 輸出範例:
# NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
# www-web-0   Bound    pvc-12345678-1234-1234-1234-1234567890ab   1Gi        RWO            standard       10m
# www-web-1   Bound    pvc-87654321-4321-4321-4321-ba0987654321   1Gi        RWO            standard       10m

如果你確定要刪除資料,必須手動刪除 PVC:

kubectl delete pvc www-web-0 www-web-1 www-web-2

何時使用 StatefulSet?

  • 資料庫 (Database): MySQL, PostgreSQL, MongoDB, Cassandra
  • 分散式協調系統: ZooKeeper, Etcd
  • 訊息佇列: Kafka, RabbitMQ

如果你的應用不需要這些特性,請優先使用 Deployment