k8s-statefulset

張鐵牛發表於2021-12-22

1. 簡介

StatefulSet 是用來管理有狀態應用的工作負載Api物件。

StatefulSet 用來管理某 Pod 集合的部署和擴縮, 併為這些 Pod 提供持久儲存和持久識別符號。

Deployment 類似, StatefulSet 管理基於相同容器規約的一組 Pod。

但和 Deployment 不同的是, StatefulSet 為它們的每個 Pod 維護了一個有粘性的 ID。這些 Pod 是基於相同的規約來建立的, 但是不能相互替換:無論怎麼排程,每個 Pod 都有一個永久不變的 ID。

如果希望使用儲存卷為工作負載提供持久儲存,可以使用 StatefulSet 作為解決方案的一部分。 儘管 StatefulSet 中的單個 Pod 仍可能出現故障, 但持久的 Pod 識別符號使得將現有卷與替換已失敗 Pod 的新 Pod 相匹配變得更加容易。

2. 什麼時候使用StatefulSet

StatefulSets 對於需要滿足以下一個或多個需求的應用程式很有價值:

  • 穩定的、唯一的網路識別符號。
  • 穩定的、持久的儲存。
  • 有序的、優雅的部署和縮放。
  • 有序的、自動的滾動更新。

在上面描述中,“穩定的”意味著 Pod 排程或重排程的整個過程是有永續性的。 如果應用程式不需要任何穩定的識別符號或有序的部署、刪除或伸縮,則應該使用 由一組無狀態的副本控制器提供的工作負載來部署應用程式,比如 Deployment 能更適用於你的無狀態應用部署需要。

3. 限制

  • 給定 Pod 的儲存必須由 PersistentVolume 驅動基於所請求的 storage class 來提供,或者由管理員預先提供。
  • 刪除或者收縮 StatefulSet 並不會刪除它關聯的儲存卷。 這樣做是為了保證資料安全,它通常比自動清除 StatefulSet 所有相關的資源更有價值。
  • StatefulSet 當前需要 無頭服務 來負責 Pod 的網路標識。你需要負責建立此服務。
  • 當刪除 StatefulSets 時,StatefulSet 不提供任何終止 Pod 的保證。 為了實現 StatefulSet 中的 Pod 可以有序地且體面地終止,可以在刪除之前將 StatefulSet 縮放為 0。
  • 在預設 Pod 管理策略(OrderedReady) 時使用 滾動更新,可能進入需要人工干預 才能修復的損壞狀態。

4. StatefulSet 原理

StatefulSet由Service和volumeClaimTemplates組成。Service中的多個Pod將會被分別編號,並掛載volumeClaimTemplates中宣告的PV。

注意: 官方提示 StatefulSets 在1.9中是穩定的

StatefulSet 除了要與PV卷捆綁使用以儲存Pod的狀態資料,還要與Headless Service配合使用,在每個StatfulSet的定義中要宣告它屬於哪個Headless Service.Headless Service 與普通Service的關鍵區別在於, Headless Service它沒有Cluster IP,如果解析Headless Service的DNS域名,則返回的是該Service對應的全部Pod的Endpoint列表。

StatefulSet在Headless Service的基礎上又為StatefulSet控制的每個Pod實力建立了一個DNS域名,這個域名的格式為: ${podname}.${headless service name}

比如一個3節點的Kafka的StatefulSet叢集,對應的Headless Service的名字為kafka,StatefulSet的名字為kafka,則StatefulSet 裡面的3個Pod的DNS分別為kafka-0.kafka、kafka-1.kafka、kafka-3.kafka,這些DNS名稱可以直接在叢集的配置檔案中固定下來。

簡單點說:沒有部署HeadlessService的話,PetSet/StatefulSet下的pod,無法通過域名進行訪問。

5. quick start

5.1 建立sts

建立資源模板資訊 web.yaml 其中包括 一個 headless-service(無頭服務)和 一個 StatefulSet

經測試 svc 的 name 必須與 sts 的 spec.serviceName 相等,否則無法通過路由規則找到容器服務

這裡為了方便就掛載了一個空目錄,如果有自己的 storageClass 也可以使用

apiVersion: v1
kind: Service
metadata:
  # must be equal sts .spec.serviceName
  name: nginx-sn
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  # must be equal headless-svc metadata.name
  serviceName: "nginx-sn"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: nginx-volume
          mountPath: /usr/share/nginx/html
      volumes:
      - name: nginx-volume
        # 為了演示方便,掛載個空目錄
        emptyDir: {}
  # 如果有自己的 storageClass 可以使用以下的方式
  #volumeClaimTemplates:
  #- metadata:
  #    name: www
  #  spec:
  #    accessModes: [ "ReadWriteOnce" ]
  #    storageClassName: "your-storage-class"
  #    resources:
  #      requests:
  #        storage: 1Gi      

建立資源

$ kubectl create -f web.yaml

5.2 檢視sts

檢視sts 資訊

$ kubectl get -f web.yaml -owide
# 輸出內容如下
# headless svc
NAME               TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service/nginx-sn   ClusterIP   None         <none>        80/TCP    20m   app=nginx
# sts
NAME                   READY   AGE   CONTAINERS   IMAGES
statefulset.apps/web   2/2     20m   nginx        nginx:latest

也可以使用命令分開查詢

檢視svc

$ kubectl get svc/nginx-sn -owide
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
nginx-sn   ClusterIP   None         <none>        80/TCP    22m   app=nginx

檢視sts

$ kubectl get sts -owide
NAME   READY   AGE   CONTAINERS   IMAGES
web    2/2     22m   nginx        nginx:latest

也可以檢視endpoints 列表資訊,可以看到繫結到了兩個pod

kubectl get endpoints/nginx-sn -owide
NAME       ENDPOINTS                             AGE
nginx-sn   10.100.132.137:80,10.100.132.142:80   23m

pod資訊如下

$ kubectl get po -owide
NAME                          READY   STATUS    RESTARTS   AGE    IP               NODE           NOMINATED NODE   READINESS GATES
web-0                         1/1     Running   0          24m    10.100.132.137   k8s-woker-01   <none>           <none>
web-1                         1/1     Running   0          24m    10.100.132.142   k8s-woker-01   <none>           <none>

5.3 修改/刪除 sts

sts 修改刪除沒什麼特別的,操作基本和deploy一樣,這裡就不再贅述,說幾點驗證過的結論。

  1. 直接刪除sts對應的pods時,sts會重新排程生成一個新的pod(pod ip會變,但是編號不變)。比如刪除的是po/web-0,sts會重新排程生成一個web-0。
  2. sts 也可以修改對應的副本數,新生成的pod 編號會依次遞增。
  3. 假設現在有三個pod 分別為:web-0、web-1、web-2,如果刪除web-1,sts會重新排程生成一個新的web-1,之前的pod保持不變

6. 小結

6.1 有序索引

對於具有 N 個副本的 StatefulSet,StatefulSet 中的每個 Pod 將被分配一個整數序號, 從 0 到 N-1,該序號在 StatefulSet 上是唯一的。比如``sts name=nginx ,replicas=3 則對應的pod名稱依次為nginx-0、nginx-1、nginx-2`,

6.2 穩定的網路 ID

StatefulSet 可以使用 "無頭服務" 控制它的 Pod 的網路域。管理域的這個服務的格式為: $(服務名稱).$(名稱空間).svc.cluster.local,其中 cluster.local 是叢集域。 一旦每個 Pod 建立成功,就會得到一個匹配的 DNS 子域,格式為: $(pod 名稱).$(所屬服務的 DNS 域名),其中所屬服務由 StatefulSet 的 serviceName 域來設定。

下面給出一些選擇叢集域、服務名、StatefulSet 名、及其怎樣影響 StatefulSet 的 Pod 上的 DNS 名稱的示例:

叢集域名 服務(名稱空間/名字) StatefulSet(名字空間/名字) StatefulSet 域名 Pod DNS Pod 主機名
cluster.local default/nginx default/web nginx.default.svc.cluster.local web-{0..N-1}.nginx.default.svc.cluster.local web-{0..N-1}
cluster.local foo/nginx foo/web nginx.foo.svc.cluster.local web-{0..N-1}.nginx.foo.svc.cluster.local web-{0..N-1}
kube.local foo/nginx foo/web nginx.foo.svc.kube.local web-{0..N-1}.nginx.foo.svc.kube.local web-{0..N-1}

說明: 叢集域會被設定為 cluster.local,除非有其他配置

6.3 部署和擴縮保證

  • 對於包含 N 個 副本的 StatefulSet,當部署 Pod 時,它們是依次建立的,順序為 0..N-1
  • 當刪除 Pod 時,它們是逆序終止的,順序為 N-1..0
  • 在將縮放操作應用到 Pod 之前,它前面的所有 Pod 必須是 Running 和 Ready 狀態。
  • 在 Pod 終止之前,所有的繼任者必須完全關閉。

StatefulSet 不應將 pod.Spec.TerminationGracePeriodSeconds 設定為 0。 這種做法是不安全的,要強烈阻止。如果要強制刪除pod使用以下命令:

$ kubectl delete pods <pod> --grace-period=0 --force

在上面的 nginx 示例被建立後,會按照 web-0、web-1、web-2 的順序部署三個 Pod。 在 web-0 進入 Running 和 Ready狀態前不會部署 web-1。在 web-1 進入 Running 和 Ready 狀態前不會部署 web-2。 如果 web-1 已經處於 Running 和 Ready 狀態,而 web-2 尚未部署,在此期間發生了 web-0 執行失敗,那麼 web-2 將不會被部署,要等到 web-0 部署完成並進入 Running 和 Ready 狀態後,才會部署 web-2。

如果使用者想將示例中的 StatefulSet 收縮為 replicas=1,首先被終止的是 web-2。 在 web-2 沒有被完全停止和刪除前,web-1 不會被終止。 當 web-2 已被終止和刪除、web-1 尚未被終止,如果在此期間發生 web-0 執行失敗, 那麼就不會終止 web-1,必須等到 web-0 進入 Running 和 Ready 狀態後才會終止 web-1。

6.4 穩定的儲存

對於 StatefulSet 中定義的每個 VolumeClaimTemplate,每個 Pod 接收到一個 PersistentVolumeClaim。 當一個 Pod 被排程(重新排程)到節點上時,它的 volumeMounts 會掛載與其 PersistentVolumeClaims 相關聯的 PersistentVolume。 請注意,當 Pod 或者 StatefulSet 被刪除時,與 PersistentVolumeClaims 相關聯的 PersistentVolume 並不會被刪除。要刪除它必須通過手動方式來完成。