返回文章列表

Kubernetes StatefulSet 擴充套件與更新策略詳解

本文探討 Kubernetes StatefulSet 的擴充套件和更新機制,包含 Pod 的生命週期管理、持久化儲存的處理、RollingUpdate 和 OnDelete 策略的應用,以及如何使用 kubectl 命令進行操作和監控。

Kubernetes 容器技術

StatefulSet 的擴充套件和縮減操作與 Deployment 不同,Pod 會按照預定的順序建立和終止,確保應用程式狀態的一致性。擴充套件時,新 Pod 會依次建立,直到所有前置 Pod 處於 Running 和 Ready 狀態。縮減時,Pod 也會依次終止,同樣需要等待所有前置 Pod 完全終止。此外,podManagementPolicy 引數控制 Pod 的管理策略,OrderedReady 保證順序執行,而 Parallel 則允許平行操作。更新 StatefulSet 時,RollingUpdate 策略會按照逆序逐一更新 Pod,確保應用程式在更新過程中保持可用性。OnDelete 策略則需要手動刪除 Pod 以觸發更新,適用於需要額外驗證或手動干預的場景。在刪除 StatefulSet 時,可以選擇是否保留 Pod,但 PVC 和 PV 預設不會被刪除,需要手動清理以釋放儲存資源。

擴充套件 StatefulSet

在 StatefulSet 的情況下,您可以透過更改規格中的副本數量或使用 kubectl scale 命令來執行與 Deployment 物件類別似的擴充套件操作。新的 Pod 將自動被發現為 Service 的新端點(Endpoints),當您擴充套件時,或者在縮減時自動從端點列表中移除。

然而,與 Deployment 物件相比,有一些差異:

  • 當您佈署一個具有 N 個副本的 StatefulSet 物件時,在佈署期間,Pod 會按照順序從 0 到 N-1 建立。在我們的例子中,在建立具有三個副本的 StatefulSet 物件期間,首先建立 mysql-stateful-0 Pod,然後是 mysql-stateful-1,最後是 mysql-stateful-2
  • 當您擴充套件 StatefulSet 時,新的 Pod 也會按照順序建立。
  • 當您縮減 StatefulSet 時,Pod 會按照相反的順序,從 N-1 到 0 終止。在我們的例子中,當縮減 StatefulSet 物件到零個副本時,mysql-stateful-2 Pod 首先被終止,然後是 mysql-stateful-1,最後是 mysql-stateful-0
  • 在擴充套件 StatefulSet 物件期間,在建立下一個 Pod 之前,它的所有前驅 Pod 必須正在執行並且就緒。
  • 在縮減 StatefulSet 物件期間,在終止下一個 Pod 之前,它的所有前驅 Pod 必須完全終止並刪除。
  • 此外,通常在對 StatefulSet 物件中的 Pod 執行任何擴充套件操作之前,它的所有前驅 Pod 必須正在執行並且就緒。這意味著,如果在從四個副本縮減到一個副本的過程中,mysql-stateful-0 Pod 突然失敗,則不會對 mysql-stateful-1mysql-stateful-2mysql-stateful-3 Pod 執行進一步的擴充套件操作。當 mysql-stateful-0 Pod 再次就緒時,擴充套件操作將還原。

具備了這些知識後,讓我們透過命令列示範如何快速擴充套件我們的 StatefulSet:

  1. 使用以下命令擴充套件 StatefulSet:
$ kubectl scale statefulset -n mysql mysql-stateful --replicas 4
statefulset.apps/mysql-stateful scaled

這種順序的擴充套件行為可以透過更改規格中的 .spec.podManagementPolicy 欄位來放鬆。預設值是 OrderedReady。如果將其更改為 Parallel,則擴充套件操作將與 Deployment 物件類別似地平行執行。

  1. 如果您現在使用 kubectl get pods 命令檢查 Pod,您將看到新的 Pod 按順序建立:
$ kubectl get pod -n mysql
NAME READY STATUS RESTARTS AGE
k8sutils 1/1 Running 0 56m
mysql-stateful-0 1/1 Running 0 9m13s
mysql-stateful-1 1/1 Running 0 9m12s
mysql-stateful-2 1/1 Running 0 9m10s
mysql-stateful-3 1/1 Running 0 4s

同樣,如果您檢查 StatefulSet 物件的 kubectl describe 命令輸出,您將在事件中看到以下內容:

$ kubectl describe sts -n mysql mysql-stateful
Name: mysql-stateful
Namespace: mysql
...<removed for brevity>...
Events:
Type Reason Age From Message
---
- 
---
---
 
---
- 
---
-
---
-
---
Normal SuccessfulCreate 23m (x2 over 75m) statefulset-controller create Pod mysql-stateful-0 in StatefulSet mysql-stateful successful
Normal RecreatingTerminatedPod 11m (x13 over 23m) statefulset-controller StatefulSet mysql/mysql-stateful is recreating terminated Pod mysql-stateful-0
Normal SuccessfulDelete 11m (x13 over 23m) statefulset-controller delete Pod mysql-stateful-0 in StatefulSet mysql-stateful successful
Normal SuccessfulCreate 2m28s statefulset-controller create Claim mysql-data-mysql-stateful-3 Pod mysql-stateful-3 in StatefulSet mysql-stateful success
Normal SuccessfulCreate 2m28s statefulset-controller create Pod mysql-stateful-3 in StatefulSet mysql-stateful successful

讓我們使用命令列示範如何縮減我們的 StatefulSet 物件並檢查 Pod:

$ kubectl scale statefulset -n mysql mysql-stateful --replicas 2
statefulset.apps/mysql-stateful scaled

您可以看到,最後兩個 Pod – mysql-stateful-3mysql-stateful-2 – 按順序被刪除。現在,讓我們檢查 StatefulSet 中的 Pod:

$ kubectl get pod -n mysql
NAME READY STATUS RESTARTS AGE
k8sutils 1/1 Running 0 61m
mysql-stateful-0 1/1 Running 0 15m
mysql-stateful-1 1/1 Running 0 15m
  1. 現在檢查 PVC,您將看到 PVC 仍然存在。這是 StatefulSet 的預期行為,正如我們之前所學到的:
$ kubectl get pvc -n mysql
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
mysql-data-mysql-stateful-0 Bound pvc-453dbfee-6076-48b9-8878-e7ac6f79d271 1Gi RWO standard <unset> 79m
mysql-data-mysql-stateful-1 Bound pvc-36494153-3829-42aa-be6d-4dc63163ea38 1Gi RWO standard <unset> 79m
mysql-data-mysql-stateful-2 Bound pvc-6730af33-f0b6-445d-841b-4fbad5732cde 1Gi RWO standard <unset> 79m
mysql-data-mysql-stateful-3 Bound pvc-6ec1ee2a-5be3-4bf9-84e5-4f5aee566c11 1Gi RWO standard <unset> 7m4s

程式碼解密:

上述範例展示瞭如何使用 kubectl scale 命令來擴充套件和縮減 StatefulSet。以下是關鍵步驟的解析:

  • 使用 kubectl scale 命令更改 StatefulSet 的副本數量,以擴充套件或縮減 Pod 的數量。
  • StatefulSet 中的 Pod 是按順序建立或終止的,這與 Deployment 物件不同。
  • 當擴充套件或縮減 StatefulSet 時,需要考慮 .spec.podManagementPolicy 的設定,以決定是否按順序或平行執行操作。

刪除 StatefulSet

要刪除 StatefulSet,有兩種可能性:

  • 刪除 StatefulSet 以及它擁有的 Pod。
  • 刪除 StatefulSet 但保留 Pod 不變。

在這兩種情況下,預設情況下不會刪除使用 volumeClaimTemplates 為 Pod 建立的 PVC 和 PV。這樣可以確保狀態資料不會意外丟失,除非您明確清理 PVC 和 PV。

程式碼解密:

刪除 StatefulSet 時需要注意以下事項:

  • 可以選擇是否刪除 StatefulSet 所擁有的 Pod。
  • PVC 和 PV 不會被自動刪除,以保留狀態資料。
  • 在使用 Horizontal Pod Autoscaler(HPA)等水平擴充套件工具管理 StatefulSet 時,應避免在 manifest 中指定 .spec.replicas 的值,而應讓 Kubernetes 控制平面根據資源需求動態調整副本數量。

StatefulSet:佈署有狀態應用程式

在 Kubernetes 中,StatefulSet 是一種用於管理有狀態應用程式的控制器。與 Deployment 不同,StatefulSet 能夠保證 Pod 的身份和順序,這對於需要持久化儲存和有序佈署的應用程式來說至關重要。

管理 StatefulSet 的生命週期

在最新的 Kubernetes 版本(從 v1.27 開始),可以使用 .spec.persistentVolumeClaimRetentionPolicy 欄位來控制 StatefulSet 生命週期中 PVC 的刪除。

apiVersion: apps/v1
kind: StatefulSet
...
spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Delete
...

內容解密:

  • apiVersionkind 定義了 Kubernetes 資源的版本和型別。
  • .spec.persistentVolumeClaimRetentionPolicy 控制 PVC 在 StatefulSet 被刪除或縮減時的行為。
  • whenDeleted: Retain 表示當 StatefulSet 被刪除時,保留 PVC。
  • whenScaled: Delete 表示當 StatefulSet 縮減時,刪除多餘的 PVC。

要刪除 StatefulSet 物件及其 Pod,可以使用 kubectl delete 命令:

$ kubectl delete sts -n mysql mysql-stateful
statefulset.apps "mysql-stateful" deleted

這將首先終止 Pod,然後刪除 StatefulSet 物件。

刪除 StatefulSet 物件但保留 Pod

如果只想刪除 StatefulSet 物件而保留 Pod,可以使用 --cascade=orphan 選項:

$ kubectl delete sts -n mysql mysql-stateful --cascade=orphan

執行此命令後,Pod 將繼續存在,但不再受 StatefulSet 管理。

清理 PVC 和 PV

刪除 StatefulSet 物件後,需要手動清理 PVC 和 PV:

$ kubectl delete -n mysql pvc mysql-data-mysql-stateful-0 mysql-data-mysql-stateful-1 mysql-data-mysql-stateful-2 mysql-data-mysql-stateful-3
persistentvolumeclaim "mysql-data-mysql-stateful-0" deleted
persistentvolumeclaim "mysql-data-mysql-stateful-1" deleted
persistentvolumeclaim "mysql-data-mysql-stateful-2" deleted
persistentvolumeclaim "mysql-data-mysql-stateful-3" deleted

內容解密:

  • 此命令刪除了與 StatefulSet 關聯的 PVC,從而進一步清理了相關的 PV。

發布新版本的 StatefulSet 應用程式

StatefulSet 支援兩種更新策略:RollingUpdateOnDelete

RollingUpdate 策略

RollingUpdate 是預設的更新策略,它允許以可控的方式推出新版本的應用程式。與 Deployment 的 RollingUpdate 不同,StatefulSet 的 RollingUpdate 將按照逆序(例如,從最大的索引到最小的索引)逐一終止並重新建立 Pod。

OnDelete 策略

OnDelete 策略要求手動刪除 Pod 以觸發更新。這在需要對更新進行額外驗證或手動干預時非常有用。

使用 RollingUpdate 策略更新 StatefulSet

當使用 RollingUpdate 策略時,StatefulSet 將按照逆序更新 Pod,並且會等待每個 Pod 處於 Ready 狀態後再繼續更新下一個 Pod。

spec:
  updateStrategy:
    type: RollingUpdate

內容解密:

  • .spec.updateStrategy.type 定義了更新策略。
  • RollingUpdate 將按照逆序更新 Pod,並確保每個 Pod 在繼續之前處於 Ready 狀態。

請注意,在進行新版本滾動更新後,如果需要驗證狀態永續性,請不要刪除 PVC,以避免丟失儲存在 PV 中的資料。

StatefulSet – 佈署有狀態應用程式

StatefulSet 是 Kubernetes 中用於管理有狀態應用程式的重要資源物件。與 Deployment 不同,StatefulSet 能夠保證 Pod 的身份識別和順序,並且支援有序的佈署、擴充套件和更新。

RollingUpdate 策略

在 StatefulSet 中,RollingUpdate 策略允許您逐步更新 Pod 的版本,同時保持應用程式的可用性和一致性。如果更新過程中出現錯誤,StatefulSet 控制器將嘗試還原失敗的 Pod 至其當前版本。這意味著已經成功更新的 Pod 將保持當前版本,而尚未更新的 Pod 將保持在先前的版本。

然而,這種機制也可能導致更新過程被中斷。如果其中一個 Pod 副本無法變為 Running 和 Ready 狀態,StatefulSet 將停止更新並等待手動干預。簡單地將 StatefulSet 的範本更新為先前的版本是不夠的,因為 StatefulSet 將等待失敗的 Pod 變為 Ready 狀態。唯一的解決方案是手動刪除失敗的 Pod,然後讓 StatefulSet 套用先前的 Pod 範本版本。

此外,RollingUpdate 策略還提供了一個 partition 欄位,用於控制更新的粒度。透過設定 partition 的值,您可以指定哪些 Pod 應該被更新,而哪些應該保持在先前的版本。例如,如果 partition 設定為 1,則只有序號大於或等於 1 的 Pod 會被更新,而序號小於 1 的 Pod 將保持不變。

更新 StatefulSet

現在,我們將示範如何使用 RollingUpdate 策略來更新 StatefulSet 中的 MySQL 容器映像版本。

首先,複製先前的 YAML 清單檔:

$ cp mysql-statefulset.yaml mysql-statefulset-rolling-update.yaml

然後,確保 YAML 清單檔中的 updateStrategy 設定為 RollingUpdate,並且 partition 設定為 0:

# mysql-statefulset-rolling-update.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql-stateful
  labels:
    app: mysql
  namespace: mysql
spec:
  serviceName: mysql-headless
  podManagementPolicy: OrderedReady
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0
  replicas: 3
  ...

接下來,將清單檔套用到叢集中:

$ kubectl apply -f mysql-statefulset-rolling-update.yaml
statefulset.apps/mysql-stateful created

等待 Pod 變為 Running 狀態後,我們可以驗證 StatefulSet 中的 Pod:

$ kubectl get pods -n mysql
NAME              READY   STATUS    RESTARTS   AGE
k8sutils          1/1     Running   1          23h
mysql-stateful-0  1/1     Running   0          65s
mysql-stateful-1  1/1     Running   0          62s
mysql-stateful-2  1/1     Running   0          58s

建立新資料函式庫

在 StatefulSet 中建立一個新的資料函式庫:

$ kubectl exec -it -n mysql k8sutils -- /bin/bash
root@k8sutils:/# mysql -u root -p -h mysql-stateful-0.mysql-headless
Enter password: <mysqlroot>
Welcome to the MariaDB monitor. Commands end with ; or \g.
...
MySQL [(none)]> create database stsrolling;
Query OK, 1 row affected (0.027 sec)
MySQL [(none)]> exit;

更新 MySQL 容器映像版本

現在,我們將更新 MySQL 容器映像版本至 mysql:8.3.0

# mysql-statefulset-rolling-update.yaml
...
spec:
  containers:
  - name: mysql
    image: mysql:8.3.0
...

將變更套用到叢集中:

$ kubectl apply -f mysql-statefulset-rolling-update.yaml
statefulset.apps/mysql-stateful configured

使用 kubectl rollout status 命令觀察更新進度:

$ kubectl rollout status statefulset -n mysql
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
...
partitioned roll out complete: 3 new pods have been updated...

檢視更新事件

使用 kubectl describe 命令檢視 StatefulSet 的事件:

$ kubectl describe sts -n mysql mysql-stateful
Name: mysql-stateful
...

程式碼解密:

以上範例程式碼演示瞭如何使用 RollingUpdate 策略來更新 StatefulSet 中的 MySQL 容器映像版本。其中,updateStrategy 設定為 RollingUpdate,並且 partition 設定為 0,以確保所有 Pod 都會被更新。接下來,我們建立了一個新的資料函式庫,並將 MySQL 容器映像版本更新至 mysql:8.3.0。最後,我們使用 kubectl rollout status 命令觀察更新進度,並使用 kubectl describe 命令檢視 StatefulSet 的事件。

此圖示顯示了 StatefulSet 更新過程中的事件順序:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Kubernetes StatefulSet 擴充套件與更新策略詳解

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

此圖示說明瞭 StatefulSet 更新過程中的事件順序,包括檢查 partition 值、更新 Pod 和等待 Pod 變為 Ready 狀態等步驟。