跳至主要内容

Basic - Creating a StatefulSet

什麼是 StatefulSet

StatefulSet 是 Kubernetes 中的一種工作負載資源,用於管理具有穩定標識(Stable Identity)和持久化存儲的應用程式。 它主要適用於需要有序部署和縮放的應用,例如資料庫或分散式系統。


StatefulSet 的應用價值

StatefulSet 對於需要滿足以下一個或多個需求的應用程序非常有價值:

  1. 穩定的、唯一的網路識別子
    • 每個 Pod 都有穩定的網路名稱(例如 my-app-0my-app-1),允許應用程序在 Pod 重啟或遷移後仍能透過固定的網路標識進行通信。這對需要點對點通信的分散式系統特別重要。
  2. 穩定的、持久的儲存
    • 每個 Pod 都關聯到特定的 PersistentVolume(PV),即使 Pod 被刪除或重新部署,數據依然保持穩定和持久性。這對需要數據一致性的應用(如資料庫)非常有幫助。
  3. 有序的、優雅的部屬和縮放
    • StatefulSet 確保 Pod 的部署和縮放按照固定的順序進行,例如先啟動 my-app-0,然後依次啟動其他 Pod。縮放時,Pod 也會依照順序進行刪除或啟動。
  4. 有序的、自動滾動更新
    • 當 StatefulSet 的配置更新時,會按照 Pod 的序列進行滾動更新,確保在更新過程中系統穩定性不受影響。

與 Deployment 的差異

StatefulSet 與 Deployment 類似,都可以用來管理 Pod,但有以下重要差異:

  1. 穩定的網路標識
    • 每個 Pod 都有固定的名稱,格式為 pod-name-index,如 my-app-0my-app-1
  2. 穩定的存儲
    • 每個 Pod 都會綁定到特定的 PersistentVolume(PV),即使 Pod 被刪除或重新調度,數據仍然保持一致。
  3. 有序部署和刪除
    • Pod 的部署、更新和刪除按照固定順序進行。例如,只有在 my-app-0 準備好後,my-app-1 才會開始部署。

StatefulSet 中的有序命名及網路 ID

有序命名(Ordinal Index)

StatefulSet 的每個 Pod 都具有穩定且有序的名稱,這些名稱由以下組成:

  • StatefulSet 名稱
  • 有序索引(Ordinal Index)

Pod 的命名規則為:<StatefulSet 名稱>-<Ordinal Index>

網路 ID(Stable Network Identity)

每個 Pod 都會分配一個穩定的網路標識,確保其在集群中的網路名稱不會因重啟或重新調度而改變。網路標識由以下幾部分組成:

  1. Pod 名稱
    • 每個 Pod 的網路名稱與其有序命名一致,例如 my-app-0
  2. Headless Service
    • StatefulSet 通常與一個無頭服務(Headless Service)結合使用。Headless Service 不會提供負載均衡功能,而是直接將流量引導到特定的 Pod。

DNS 的命名規則:<Pod 名稱>.<Headless Service 名稱>.<Namespace>.svc.cluster.local


StatefulSet 中的穩定儲存

在 Kubernetes 中,StatefulSet 通過 PersistentVolume(PV)和 PersistentVolumeClaim(PVC)為每個 Pod 提供穩定的儲存資源。 這意味著即使 Pod 被刪除或重新調度,其對應的儲存數據仍然保持不變,並且在重新創建 Pod 時可以重新掛載原有的數據。


StatefulSet 中的 Headless Services

什麼是 Headless Service?

Headless Service 是 Kubernetes 中的一種特殊服務類型,用於直接將流量引導到特定的 Pod,而不進行負載均衡。 當與 StatefulSet 搭配使用時,Headless Service 為 StatefulSet 的每個 Pod 提供穩定的 DNS 條目,允許應用程序通過固定的網路標識與 Pod 通信。

Headless Service 的特點是:

  • 沒有 Cluster IP(spec.clusterIP 設置為 None)。
  • 不進行流量分配,只創建 Pod 的 DNS 條目。

為什麼 StatefulSet 需要 Headless Service?

StatefulSet 使用 Headless Service 提供穩定的網路標識,解決以下問題:

  1. 穩定的 DNS 條目
    • 每個 Pod 都有一個固定的 DNS 名稱,讓應用程序可以輕鬆地與特定 Pod 進行通信。
  2. 去中心化的流量管理
    • Headless Service 允許流量直接路由到指定的 Pod,特別適合需要點對點通信的分散式系統。
  3. 有序初始化支持
    • StatefulSet 配合 Headless Service 的穩定 DNS 條目,支持分散式應用在初始化時的節點自我發現和註冊。

StatefulSet 的部署與擴縮保證

部署保障

  1. 有序部署:Pod 按索引順序創建(如 Pod-0Pod-1),每個 Pod 初始化完成後才會創建下一個。
  2. 穩定性:適用於需要有序啟動的應用(如分散式數據庫)。

擴縮保障

  1. 有序擴展:新增 Pod 時,分配唯一名稱和網路標識(如 Pod-2Pod-3)。
  2. 有序縮減:刪除 Pod 時按索引倒序進行(如 Pod-3Pod-2)。
  3. 數據完整性:縮減時保留對應的存儲卷,確保數據不丟失。

StatefulSet 的更新策略

StatefulSet 提供兩種更新策略,用於控制 Pod 的滾動更新方式。

  1. RollingUpdate(預設)
    • 按照 Pod 的索引從高到低逐一更新。
    • 每次僅更新一個 Pod,並在確保其穩定後更新下一個。
    • 適用於需要最小化服務中斷的場景。
  2. OnDelete
    • 不自動更新 Pod,需手動刪除舊 Pod 才會按新配置創建新 Pod。
    • 適用於需要手動控制更新時機的場景。

舉一個例子

SQL-Server-service

使用 StatefulSet 部署 SQL Server(3 個 Pod:1 個主節點,2 個副本節點)

  • 穩定的網路標識:StatefulSet 確保每個 Pod 擁有固定的網路標識,便於節點之間的通訊。
  • 有序部署與更新:Pod 的啟動、更新、與終止按照順序進行,確保系統穩定性。
  • 持久性存儲:每個 Pod 配備專屬的持久化存儲,保障數據安全。

節點角色與功能

  • 主節點 (Master):負責處理讀寫操作。
  • 副本節點 (Replicas):提升系統的可擴展性,並提供數據冗餘以增強可靠性。

控制終止

  • 防止數據丟失:StatefulSet 控制 Pod 的終止流程,確保縮減規模(Scaling Down)時不會導致數據遺失。

實作一個 StatefulSet

建立 StatefulSet 和 Headless Service

  1. 我們會使用到 StatefulSet 搭配 Headless Service 進行設定
headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: v1-service
labels:
app: v1
spec:
selector:
app: v1
ports:
- port: 80
name: http
clusterIP: None # 指定該 Service 為 Headless Service,不分配 ClusterIP。
statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: v1-statefulset
spec:
replicas: 3
updateStrategy:
type: RollingUpdate # 使用滾動更新策略,逐一更新 Pod,確保高可用性。
selector:
matchLabels:
app: v1
serviceName: "v1-service"
template:
metadata:
labels:
app: v1
spec:
terminationGracePeriodSeconds: 10 # 設定 Pod 結束時的寬限期,讓容器優雅地關閉。
containers:
- name: v1-container
image: nginx
ports:
- containerPort: 80 # 容器暴露的端口,這裡是 HTTP 的 80 端口。
name: http # 為端口指定名稱,便於識別。
volumeMounts:
- name: pvc # 對應的持久化卷名稱。
mountPath: /data # 容器內部掛載點,將持久化卷掛載到 /data 目錄。
volumeClaimTemplates:
- metadata:
name: pvc # 定義持久化卷聲明(PVC)的名稱,會為每個 Pod 自動創建 PVC。
spec:
accessModes: [ "ReadWriteOnce" ] # 設置訪問模式,Pod 可以以讀寫模式訪問卷。
storageClassName: "hostpath" # 存儲類型,這裡使用 `hostpath` 類型,適用於單節點部署。
resources:
requests:
storage: 1Gi # 設置每個 Pod 請求的存儲容量為 1Gi。
  1. 接著我們另外開兩個視窗觀察 pod 和 pvc 的狀態,使用-w(watch)來觀察他們,放著即可,若要結束請按 ctrl+c
kubectl.exe get pod -w
kubectl.exe get pvc -w
  1. 接著使用 kubectl 指令將 statefulset 和 headless-services 運行起來
kubectl.exe apply -f statefulset.yaml,headless-service.yaml
---

statefulset.apps/v1-statefulset created
service/v1-service created
  1. 從第二點的觀察,我們可以看到 pod 是依序產生,當第一個 run 才建立第二個,以此類推,另外,pvc 也是依序增長
kubectl.exe get pod -w
---

NAME READY STATUS RESTARTS AGE
v1-statefulset-0 0/1 Pending 0 0s
v1-statefulset-0 0/1 Pending 0 0s
v1-statefulset-0 0/1 Pending 0 1s
v1-statefulset-0 0/1 ContainerCreating 0 1s
v1-statefulset-0 1/1 Running 0 4s
v1-statefulset-1 0/1 Pending 0 0s
v1-statefulset-1 0/1 Pending 0 0s
v1-statefulset-1 0/1 Pending 0 2s
v1-statefulset-1 0/1 ContainerCreating 0 2s
v1-statefulset-1 1/1 Running 0 5s
v1-statefulset-2 0/1 Pending 0 0s
v1-statefulset-2 0/1 Pending 0 1s
v1-statefulset-2 0/1 Pending 0 2s
v1-statefulset-2 0/1 ContainerCreating 0 2s
v1-statefulset-2 1/1 Running 0 6s
kubectl.exe get pvc -w
---

NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-v1-statefulset-0 Pending hostpath 0s
pvc-v1-statefulset-0 Pending hostpath 0s
pvc-v1-statefulset-0 Pending pvc-77cd1a09-bca9-4653-aabb-91573ea8fc1c 0 hostpath 0s
pvc-v1-statefulset-0 Bound pvc-77cd1a09-bca9-4653-aabb-91573ea8fc1c 1Gi RWO hostpath 0s
pvc-v1-statefulset-1 Pending hostpath 0s
pvc-v1-statefulset-1 Pending hostpath 0s
pvc-v1-statefulset-1 Pending pvc-03c32168-b694-4adc-ae2c-4389640b7439 0 hostpath 0s
pvc-v1-statefulset-1 Bound pvc-03c32168-b694-4adc-ae2c-4389640b7439 1Gi RWO hostpath 0s
pvc-v1-statefulset-2 Pending hostpath 0s
pvc-v1-statefulset-2 Pending hostpath 0s
pvc-v1-statefulset-2 Pending pvc-df41ebad-bf31-4151-894a-2afbb4fc4985 0 hostpath 1s
pvc-v1-statefulset-2 Bound pvc-df41ebad-bf31-4151-894a-2afbb4fc4985 1Gi RWO hostpath 1s
  1. 我們可以隨機進入一個 pod 裡面,然後透過 DNS 的方式呼叫看看其他的 pod,會發現可以連通
kubectl.exe exec -it v1-statefulset-0 -- bash
---

root@v1-statefulset-0:/# curl v1-statefulset-1.v1-service
---

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...中間省略...
</body>
</html>
  1. 輸入 exit 離開

重新啟動 StatefulSet

  1. 我們首先執行 get pod -w,用來觀察重新啟動時 pod 會怎麼被重新建立
kubectl.exe get pod -w
  1. 在另外的視窗,執行 rollout restart,讓 StatefulSet 重新建立
kubectl.exe rollout restart statefulset v1-statefulset
---

statefulset.apps/v1-statefulset restarted
  1. 我們可以看到 pod 是從最後開始,而且下一個 pod 必須等到前一個 Pod 關閉重起到 Running,才會開始更新
kubectl.exe get pod -w
---

NAME READY STATUS RESTARTS AGE
v1-statefulset-0 1/1 Running 0 64m
v1-statefulset-1 1/1 Running 0 64m
v1-statefulset-2 1/1 Running 0 64m
v1-statefulset-2 1/1 Terminating 0 65m
v1-statefulset-2 0/1 Terminating 0 65m
v1-statefulset-2 0/1 Terminating 0 65m
v1-statefulset-2 0/1 Terminating 0 65m
v1-statefulset-2 0/1 Terminating 0 65m
v1-statefulset-2 0/1 Pending 0 0s
v1-statefulset-2 0/1 Pending 0 0s
v1-statefulset-2 0/1 ContainerCreating 0 0s
v1-statefulset-2 1/1 Running 0 4s
v1-statefulset-1 1/1 Terminating 0 65m
v1-statefulset-1 0/1 Terminating 0 65m
v1-statefulset-1 0/1 Terminating 0 65m
v1-statefulset-1 0/1 Terminating 0 65m
v1-statefulset-1 0/1 Terminating 0 65m
v1-statefulset-1 0/1 Pending 0 0s
v1-statefulset-1 0/1 Pending 0 0s
v1-statefulset-1 0/1 ContainerCreating 0 0s
v1-statefulset-1 1/1 Running 0 3s
v1-statefulset-0 1/1 Terminating 0 65m
v1-statefulset-0 0/1 Terminating 0 65m
v1-statefulset-0 0/1 Terminating 0 65m
v1-statefulset-0 0/1 Terminating 0 65m
v1-statefulset-0 0/1 Terminating 0 65m
v1-statefulset-0 0/1 Pending 0 0s
v1-statefulset-0 0/1 Pending 0 0s
v1-statefulset-0 0/1 ContainerCreating 0 0s
v1-statefulset-0 1/1 Running 0 3s

刪除 StatefulSet

  1. 首先我們刪除 Headless Service
kubectl.exe delete -f headless-service.yaml
---

service "v1-service" deleted
  1. 接著我們可以一樣用 get pod -w,用來觀察刪除時 pod 會怎麼被刪除
kubectl.exe get pod -w
  1. 我們可以像刪除 kubernetes 其他資源一樣刪除 StatefulSet,使用 kubectl delete 指令
kubectl.exe delete statefulsets v1-statefulset
---

statefulset.apps "v1-statefulset" deleted
警告

刪除 StatefulSet 管理的 Pod並不會刪除關聯的 PVC!這是為了確保你再刪除卷之前能有機會複製裡面的資料。

  1. 會發現刪除時,不會依照順序刪除,會是併行的刪除
kubectl.exe get pod -w
---

NAME READY STATUS RESTARTS AGE
v1-statefulset-0 1/1 Running 0 8m34s
v1-statefulset-1 1/1 Running 0 8m38s
v1-statefulset-2 1/1 Running 0 8m43s
v1-statefulset-2 1/1 Terminating 0 9m19s
v1-statefulset-1 1/1 Terminating 0 9m14s
v1-statefulset-0 1/1 Terminating 0 9m10s
v1-statefulset-2 0/1 Terminating 0 9m19s
v1-statefulset-0 0/1 Terminating 0 9m10s
v1-statefulset-1 0/1 Terminating 0 9m14s
v1-statefulset-1 0/1 Terminating 0 9m15s
v1-statefulset-1 0/1 Terminating 0 9m15s
v1-statefulset-1 0/1 Terminating 0 9m15s
v1-statefulset-2 0/1 Terminating 0 9m20s
v1-statefulset-2 0/1 Terminating 0 9m20s
v1-statefulset-2 0/1 Terminating 0 9m20s
v1-statefulset-0 0/1 Terminating 0 9m11s
v1-statefulset-0 0/1 Terminating 0 9m11s
v1-statefulset-0 0/1 Terminating 0 9m11s
  1. 此時觀察 pvc,會發現他們沒有被刪除掉,是保障你還有機會複製裡面的資料
kubectl.exe get pvc
---

NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-v1-statefulset-0 Bound pvc-77cd1a09-bca9-4653-aabb-91573ea8fc1c 1Gi RWO hostpath 77m
pvc-v1-statefulset-1 Bound pvc-03c32168-b694-4adc-ae2c-4389640b7439 1Gi RWO hostpath 76m
pvc-v1-statefulset-2 Bound pvc-df41ebad-bf31-4151-894a-2afbb4fc4985 1Gi RWO hostpath 76m
  1. 因此我們需要而外來處理由 StatefulSet 產生的儲存資源,進行刪除
kubectl.exe delete pvc pvc-v1-statefulset-0 pvc-v1-statefulset-1 pvc-v1-statefulset-2
---

persistentvolumeclaim "pvc-v1-statefulset-0" deleted
persistentvolumeclaim "pvc-v1-statefulset-1" deleted
persistentvolumeclaim "pvc-v1-statefulset-2" deleted
危險

這個步驟就正式刪除了喔!


延伸閱讀

  1. 使用 StatefulSet 來維護一對讀寫分離的 mysql,另外使用 xtrabackup 進行備份,請參考Kubernetes – StatefulSets