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一樣,這裡就不再贅述,說幾點驗證過的結論。
- 直接刪除sts對應的pods時,sts會重新排程生成一個新的pod(pod ip會變,但是編號不變)。比如刪除的是po/web-0,sts會重新排程生成一個web-0。
- sts 也可以修改對應的副本數,新生成的pod 編號會依次遞增。
- 假設現在有三個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 並不會被刪除。要刪除它必須通過手動方式來完成。