參考:https://kubernetes.io/zh/docs/concepts/workloads/controllers/statefulset/
https://www.kubernetes.org.cn/deployment
StatefulSet
StatefulSet是為了解決有狀態服務的問題(對應Deployments和ReplicaSets是為無狀態服務而設計),其應用場景包括
- 穩定的持久化存儲,即Pod重新調度后還是能訪問到相同的持久化數據,基於PVC來實現
- 穩定的網絡標志,即Pod重新調度后其PodName和HostName不變,基於Headless Service(即沒有Cluster IP的Service)來實現
- 有序部署,有序擴展,即Pod是有順序的,在部署或者擴展的時候要依據定義的順序依次依次進行(即從0到N-1,在下一個Pod運行之前所有之前的Pod必須都是Running和Ready狀態),基於init containers來實現
- 有序收縮,有序刪除(即從N-1到0)
從上面的應用場景可以發現,StatefulSet由以下幾個部分組成:
- 用於定義網絡標志(DNS domain)的Headless Service
- 用於創建PersistentVolumes的volumeClaimTemplates
- 定義具體應用的StatefulSet
StatefulSet中每個Pod的DNS格式為statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
,其中
serviceName
為Headless Service的名字0..N-1
為Pod所在的序號,從0開始到N-1statefulSetName
為StatefulSet的名字namespace
為服務所在的namespace,Headless Servic和StatefulSet必須在相同的namespace.cluster.local
為Cluster Domain 默認域就是cluster.local
限制
- 給定 Pod 的存儲必須由 PersistentVolume 驅動 基於所請求的
storage class
來提供,由管理員預先提供。 - 刪除或者收縮 StatefulSet 並不會刪除它關聯的存儲卷。這樣做是為了保證數據安全,它通常比自動清除 StatefulSet 所有相關的資源更有價值。
- StatefulSet 當前需要 headless 服務 來負責 Pod 的網絡標識。您需要負責創建此服務。
- 當刪除 StatefulSets 時,StatefulSet 不提供任何終止 Pod 的保證。為了實現 StatefulSet 中的 Pod 可以有序和優雅的終止,可以在刪除之前將 StatefulSet 縮放為 0。
- 在默認 Pod 管理策略(
OrderedReady
) 時使用 滾動更新,可能進入需要 人工干預 才能修復的損壞狀態。
組件
下面的示例演示了 StatefulSet 的組件。
# cat web.yaml apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels serviceName: "nginx" replicas: 3 # by default is 1 template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: nginx #image: k8s.gcr.io/nginx-slim:0.8 image: lowyard/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "managed-nfs-storage" resources: requests: storage: 1Gi
- 名為
nginx
的 Headless Service 用來控制網絡域名。 - 名為web的StatefulSet .spec.replicas表明獨立的3個Pod副本啟動nginx容器
volumeClaimTemplates
將通過 PersistentVolumes 驅動提供的 PersistentVolumes 來提供穩定的存儲。需要由管理員事先創建,本次創建的是NFS存儲創建參考:https://www.cnblogs.com/minseo/p/12456987.html
Pod選擇器
必須設置StatefulSet的.spec.selector字段,使之匹配其在.spec.template.metadata.labels
中設置的標簽。在 Kubernetes 1.8 版本之前,被忽略 .spec.selector
字段會獲得默認設置值。在 1.8 和以后的版本中,未指定匹配的 Pod 選擇器將在創建 StatefulSet 期間導致驗證錯誤。
Pod標識
StatefulSet Pod 具有唯一的標識,該標識包括順序標識、穩定的網絡標識和穩定的存儲。該標識和 Pod 是綁定的,不管它被調度在哪個節點上。
有序索引
對於具有 N 個副本的 StatefulSet,StatefulSet 中的每個 Pod 將被分配一個整數序號,從 0 到 N-1,該序號在 StatefulSet 上是唯一的。
穩定的網絡 ID
StatefulSet 中的每個 Pod 根據 StatefulSet 的名稱和 Pod 的序號派生出它的主機名。組合主機名的格式為$(StatefulSet 名稱)-$(序號)
。上例將會創建三個名稱分別為 web-0、web-1、web-2
的 Pod
StatefulSet 可以使用 headless 服務 控制它的 Pod 的網絡域。管理域的這個服務的格式為: $(服務名稱).$(命名空間).svc.cluster.local
,其中 cluster.local
是集群域。 一旦每個 Pod 創建成功,就會得到一個匹配的 DNS 子域,格式為:$(pod 名稱).$(所屬服務的 DNS 域名)
,其中所屬服務由 StatefulSet 的 serviceName
域來設定。
下面給出一些選擇集群域、服務名、StatefulSet 名、及其怎樣影響 StatefulSet 的 Pod 上的 DNS 名稱的示例:
Cluster Domain | Service (ns/name) | StatefulSet (ns/name) | StatefulSet Domain | Pod DNS | Pod Hostname |
---|---|---|---|---|---|
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,除非有其他配置。
穩定的存儲
Kubernetes 為每個 VolumeClaimTemplate 創建一個 PersistentVolume。在上面的 nginx 示例中,每個 Pod 將會得到基於 StorageClass my-storage-class
提供的 1 Gib 的 PersistentVolume。如果沒有聲明 StorageClass,就會使用默認的 StorageClass。當一個 Pod 被調度(重新調度)到節點上時,它的 volumeMounts
會掛載與其 PersistentVolumeClaims 相關聯的 PersistentVolume。請注意,當 Pod 或者 StatefulSet 被刪除時,與 PersistentVolumeClaims 相關聯的 PersistentVolume 並不會被刪除。要刪除它必須通過手動方式來完成。
Pod名稱標簽
當 StatefulSet 控制器 創建 Pod 時,它會添加一個標簽 statefulset.kubernetes.io/pod-name
,該標簽設置為 Pod 名稱。這個標簽允許您給 StatefulSet 中的特定 Pod 綁定一個 Service。
查看該標簽
# kubectl get pod --show-labels NAME READY STATUS RESTARTS AGE LABELS db-0 0/1 CrashLoopBackOff 13 68m controller-revision-hash=db-86458bcd44,project=java-demo,run=mysql,statefulset.kubernetes.io/pod-name=db-0 nfs-client-provisioner-7db87779-jdp99 1/1 Running 0 71m app=nfs-client-provisioner,pod-template-hash=7db87779 web-0 1/1 Running 0 56m app=nginx,controller-revision-hash=web-5d84f996d,statefulset.kubernetes.io/pod-name=web-0 web-1 1/1 Running 0 56m app=nginx,controller-revision-hash=web-5d84f996d,statefulset.kubernetes.io/pod-name=web-1 web-2 1/1 Running 0 56m app=nginx,controller-revision-hash=web-5d84f996d,statefulset.kubernetes.io/pod-name=web-2
部署和擴縮保證
- 對於包含 N 個 副本的 StatefulSet,當部署 Pod 時,它們是依次創建的,順序為
0..N-1
。 - 當刪除 Pod 時,它們是逆序終止的,順序為
N-1..0
。 - 在將縮放操作應用到 Pod 之前,它前面的所有 Pod 必須是 Running 和 Ready 狀態。
- 在 Pod 終止之前,所有的繼任者必須完全關閉。
StatefulSet 不應將 pod.Spec.TerminationGracePeriodSeconds
設置為 0。這種做法是不安全的,要強烈阻止。
在上面的 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。
Pod管理策略
在 Kubernetes 1.7 及以后的版本中,StatefulSet 允許您不要求其排序保證,同時通過它的 .spec.podManagementPolicy
域保持其唯一性和身份保證。 在 Kubernetes 1.7 及以后的版本中,StatefulSet 允許您放寬其排序保證,同時通過它的 .spec.podManagementPolicy
域保持其唯一性和身份保證。
OrderedReady Pod 管理
OrderedReady
Pod 管理是 StatefulSet 的默認設置。它實現了上面描述的功能。
Parallel Pod 管理
Parallel
Pod 管理讓 StatefulSet 控制器並行的啟動或終止所有的 Pod,啟動或者終止其他 Pod 前,無需等待 Pod 進入 Running 和 ready 或者完全停止狀態。
更新策略
在 Kubernetes 1.7 及以后的版本中,StatefulSet 的 .spec.updateStrategy
字段讓您可以配置和禁用掉自動滾動更新 Pod 的容器、標簽、資源請求或限制、以及注解。
關於刪除策略
OnDelete
更新策略實現了 1.6 及以前版本的歷史遺留行為。當 StatefulSet 的 .spec.updateStrategy.type
設置為 OnDelete
時,它的控制器將不會自動更新 StatefulSet 中的 Pod。用戶必須手動刪除 Pod 以便讓控制器創建新的 Pod,以此來對 StatefulSet 的 .spec.template
的變動作出反應。
滾動更新
RollingUpdate
更新策略對 StatefulSet 中的 Pod 執行自動的滾動更新。在沒有聲明 .spec.updateStrategy
時,RollingUpdate
是默認配置。 當 StatefulSet 的 .spec.updateStrategy.type
被設置為 RollingUpdate
時,StatefulSet 控制器會刪除和重建 StatefulSet 中的每個 Pod。 它將按照與 Pod 終止相同的順序(從最大序號到最小序號)進行,每次更新一個 Pod。它會等到被更新的 Pod 進入 Running 和 Ready 狀態,然后再更新其前身。
分區
通過聲明 .spec.updateStrategy.rollingUpdate.partition
的方式,RollingUpdate
更新策略可以實現分區。如果聲明了一個分區,當 StatefulSet 的 .spec.template
被更新時,所有序號大於等於該分區序號的 Pod 都會被更新。所有序號小於該分區序號的 Pod 都不會被更新,並且,即使他們被刪除也會依據之前的版本進行重建。如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition
大於它的 .spec.replicas
,對它的 .spec.template
的更新將不會傳遞到它的 Pod。 在大多數情況下,您不需要使用分區,但如果您希望進行階段更新、則這些分區會非常有用。
強制回滾
在默認 Pod 管理策略(OrderedReady
) 時使用 滾動更新 ,可能進入需要人工干預才能修復的損壞狀態。
如果更新后 Pod 模板配置進入無法運行或就緒的狀態(例如,由於錯誤的二進制文件或應用程序級配置錯誤),StatefulSet 將停止回滾並等待。
在這種狀態下,僅將 Pod 模板還原為正確的配置是不夠的。由於已知問題,StatefulSet 將繼續等待損壞狀態的 Pod 准備就緒(永遠不會發生),然后再嘗試將其恢復為正常工作配置。
恢復模板后,還必須刪除 StatefulSet 嘗試使用錯誤的配置來運行的 Pod。這樣,StatefulSet 才會開始使用被還原的模板來重新創建 Pod。
部署StatefulSet
參考:https://kubernetes.io/zh/docs/tutorials/stateful-application/basic-stateful-set/
目標
StatefulSets 旨在與有狀態的應用及分布式系統一起使用。然而在 Kubernetes 上管理有狀態應用和分布式系統是一個寬泛而復雜的話題。為了演示 StatefulSet 的基本特性,並且不使前后的主題混淆,你將會使用 StatefulSet 部署一個簡單的 web 應用。
在閱讀本教程后,你將熟悉以下內容:
- 如何創建StatefulSet
- StatefulSet怎樣管理它的Pods
- 如何刪除StatefulSet
- 如何對StatefulSet進行擴容/縮容
- 如果更新一個StatefulSet的Pods
創建StatefulSet
需要有動態存儲卷本次私有NFS搭建,搭建完畢以后使用命令查看
# kubectl get storageClass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storage fuseim.pri/ifs Delete Immediate false 120m
需要使用兩個終端窗口。在一個終端中使用kubectl get來檢查StatefulSet創建Pods情況
kubectl get pods -w -l app=nginx
在另一個終端中,使用 kubectl apply
來創建定義在 web.yaml
中的 Headless Service 和 StatefulSet。
kubectl apply -f web.yaml service/nginx created statefulset.apps/web created
上面的命令創建了兩個 Pod,每個都運行了一個 NGINX web 服務器。獲取 nginx
Service 和 web
StatefulSet 來驗證是否成功的創建了它們。
kubectl get service nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP None <none> 80/TCP 2m8s kubectl get statefulset web NAME READY AGE web 2/2 2m10s
順序創建Pod
對於一個擁有 N 個副本的 StatefulSet,Pod 被部署時是按照 {0 …… N-1} 的序號順序創建的。在第一個終端中使用 kubectl get
檢查輸出。這個輸出最終將看起來像下面的樣子。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 2s web-0 0/1 ContainerCreating 0 2s web-0 1/1 Running 0 4s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 2s web-1 0/1 ContainerCreating 0 2s web-1 1/1 Running 0 4s
請注意在 web-0
Pod 處於 Running和Ready 狀態后 web-1
Pod 才會被啟動。
StatefulSet中的Pod
StatefulSet終端Pod擁有一個唯一的順序索引和穩定的網絡身份標識。
檢查Pod順序索引
#參數-l更加標簽進行匹配 kubectl get pods -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 4m6s web-1 1/1 Running 0 4m1s
如同 StatefulSets 概念中所提到的,StatefulSet 中的 Pod 擁有一個具有黏性的、獨一無二的身份標志。這個標志基於 StatefulSet 控制器分配給每個 Pod 的唯一順序索引。Pod 的名稱的形式為<statefulset name>-<ordinal index>
。web
StatefulSet 擁有兩個副本,所以它創建了兩個 Pod:web-0
和web-1
。
本次示例的statefulset name為web
序號從0開始至設定的副本數n-1
使用穩定的網絡身份標識
每個Pod都擁有一個基於其順序索引的穩定的主機名。
for i in 0 1;do kubectl exec web-$i -- sh -c 'hostname';done web-0 web-1
使用 kubectl run
運行一個提供 nslookup
命令的容器,該命令來自於 dnsutils
包。通過對 Pod 的主機名執行 nslookup
,你可以檢查他們在集群內部的 DNS 地址。
kubectl run -it --image busybox:1.28 dns-test --restart=Never --rm nslookup web-0.nginx Server: 10.0.0.2 Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.17.71.3 web-0.nginx.default.svc.cluster.local
DNS格式為 $(Pod名).$(所屬服務的 DNS 域名).$(命名空間).svc.cluster.local
所屬服務的 DNS 域名由StatefulSet的serviceName設置
其中域設置默認為cluster.local
在一個終端查看StatefulSet的Pods
kubectl get pods -w -l app=nginx
在另一個終端刪除StatefulSet中的Pods
kubectl delete pod -l app=nginx pod "web-0" deleted pod "web-1" deleted
等待 StatefulSet 重啟它們,並且兩個 Pod 都變成 Running 和 Ready 狀態。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 19m web-1 1/1 Running 0 19m web-0 1/1 Terminating 0 20m web-1 1/1 Terminating 0 20m web-1 0/1 Terminating 0 20m web-0 0/1 Terminating 0 20m web-1 0/1 Terminating 0 20m web-1 0/1 Terminating 0 20m web-0 0/1 Terminating 0 20m web-0 0/1 Terminating 0 20m web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 1s
使用 kubectl exec
和 kubectl run
查看 Pod 的主機名和集群內部的 DNS 表項。
for i in 0 1;do kubectl exec web-$i -- sh -c 'hostname';done web-0 web-1
kubectl run -it --image busybox:1.28 dns-test --restart=Never --rm If you don't see a command prompt, try pressing enter. / # nslookup web-0.nginx Server: 10.0.0.2 Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.17.71.3 web-0.nginx.default.svc.cluster.local
Pod 的序號、主機名、SRV 條目和記錄名稱沒有改變,但和 Pod 相關聯的 IP 地址可能發生了改變。本次沒有改變。這就是為什么不要在其他應用中使用 StatefulSet 中的 Pod 的 IP 地址進行連接,這點很重要。
如果你需要查找並連接一個 StatefulSet 的活動成員,你應該查詢 Headless Service 的 CNAME。和 CNAME 相關聯的 SRV 記錄只會包含 StatefulSet 中處於 Running 和 Ready 狀態的 Pod。
如果你的應用已經實現了用於測試 liveness 和 readiness 的連接邏輯,你可以使用 Pod 的 SRV 記錄(web-0.nginx.default.svc.cluster.local
, web-1.nginx.default.svc.cluster.local
)。因為他們是穩定的,並且當你的 Pod 的狀態變為 Running 和 Ready 時,你的應用就能夠發現它們的地址。
寫入穩定的存儲
獲取 web-0
和 web-1
的 PersistentVolumeClaims。
kubectl get pvc -l app=nginx NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound pvc-bbc399de-fac4-4f10-bd77-fc40fbf949a9 1Gi RWO managed-nfs-storage 131m www-web-1 Bound pvc-4d147825-2087-41d8-8fc1-a174de4c3bf6 1Gi RWO managed-nfs-storage
StatefulSet 控制器創建了兩個 PersistentVolumeClaims,綁定到兩個 PersistentVolumes。由於本教程使用的集群配置為動態提供 PersistentVolume,所有的 PersistentVolume 都是自動創建和綁定的。
NGINX web 服務器默認會加載位於 /usr/share/nginx/html/index.html
的 index 文件。StatefulSets spec
中的 volumeMounts
字段保證了 /usr/share/nginx/html
文件夾由一個 PersistentVolume 支持。
將 Pod 的主機名寫入它們的index.html
文件並驗證 NGINX web 服務器使用該主機名提供服務。
for i in 0 1; do kubectl exec web-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
注意: 請注意,如果你看見上面的 curl 命令返回了 403 Forbidden 的響應,你需要像這樣修復使用 volumeMounts(due to a bug when using hostPath volumes)掛載的目錄的權限: for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done 在你重新嘗試上面的 curl 命令之前。
在一個終端查看 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一個終端刪除 StatefulSet 所有的 Pod。
kubectl delete pod -l app=nginx pod "web-0" deleted pod "web-1" deleted
在第一個終端里檢查kubectl get命令的輸出,等待所有Pod變成Running和Ready狀態
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 117m web-1 1/1 Running 0 117m web-0 1/1 Terminating 0 118m web-1 1/1 Terminating 0 118m web-1 0/1 Terminating 0 118m web-0 0/1 Terminating 0 118m web-0 0/1 Terminating 0 118m web-0 0/1 Terminating 0 118m web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 0/1 Terminating 0 118m web-1 0/1 Terminating 0 118m web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 0s
驗證所有 web 服務器在繼續使用它們的主機名提供服務。
for i in 0 1;do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
雖然 web-0
和 web-1
被重新調度了,但它們仍然繼續監聽各自的主機名,因為和它們的 PersistentVolumeClaim 相關聯的 PersistentVolume 被重新掛載到了各自的 volumeMount
上。不管 web-0
和 web-1
被調度到了哪個節點上,它們的 PersistentVolumes 將會被掛載到合適的掛載點上。
擴容/縮容StatefulSet
擴容/縮容 StatefulSet 指增加或減少它的副本數。這通過更新 replicas
字段完成。你可以使用kubectl scale
或者kubectl patch
來擴容/縮容一個 StatefulSet。
擴容
在一個終端窗口觀察 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一個終端窗口使用 kubectl scale
擴展副本數為 5。
kubectl scale sts web --replicas=5 statefulset.apps/web scaled
在第一個 終端中檢查 kubectl get
命令的輸出,等待增加的 3 個 Pod 的狀態變為 Running 和 Ready。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 6m17s web-1 1/1 Running 0 6m12s web-2 0/1 Pending 0 0s web-2 0/1 Pending 0 0s web-2 0/1 Pending 0 1s web-2 0/1 ContainerCreating 0 1s web-2 1/1 Running 0 3s web-3 0/1 Pending 0 0s web-3 0/1 Pending 0 0s web-3 0/1 Pending 0 1s web-3 0/1 ContainerCreating 0 1s web-3 1/1 Running 0 2s web-4 0/1 Pending 0 0s web-4 0/1 Pending 0 0s web-4 0/1 Pending 0 1s web-4 0/1 ContainerCreating 0 1s web-4 1/1 Running 0 3s
縮容
在一個終端觀察 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一個終端使用 kubectl patch
將 StatefulSet 縮容回三個副本。
kubectl patch sts web -p '{"spec":{"replicas":3}}' statefulset.apps/web patched
等待 web-4
和 web-3
狀態變為 Terminating。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 9m49s web-1 1/1 Running 0 9m44s web-2 1/1 Running 0 2m37s web-3 1/1 Running 0 9s web-4 1/1 Running 0 8s web-4 1/1 Terminating 0 52s web-4 0/1 Terminating 0 53s web-4 0/1 Terminating 0 59s web-4 0/1 Terminating 0 59s web-3 1/1 Terminating 0 60s web-3 0/1 Terminating 0 61s web-3 0/1 Terminating 0 65s web-3 0/1 Terminating 0 65s
順序終止Pod
控制器會按照與 Pod 序號索引相反的順序每次刪除一個 Pod。在刪除下一個 Pod 前會等待上一個被完全關閉。
獲取 StatefulSet 的 PersistentVolumeClaims。
kubectl get pvc -l app=nginx NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound pvc-bbc399de-fac4-4f10-bd77-fc40fbf949a9 1Gi RWO managed-nfs-storage 151m www-web-1 Bound pvc-4d147825-2087-41d8-8fc1-a174de4c3bf6 1Gi RWO managed-nfs-storage 151m www-web-2 Bound pvc-28fb3584-78ce-44a3-9523-6e89907cb9fc 1Gi RWO managed-nfs-storage 5m22s www-web-3 Bound pvc-256cfeec-46d5-4b35-9357-acd12ef8ee9c 1Gi RWO managed-nfs-storage 5m19s www-web-4 Bound pvc-26beab6f-852d-46fd-ac32-35632b1dc65e 1Gi RWO managed-nfs-storage 5m17s
五個 PersistentVolumeClaims 和五個 PersistentVolumes 仍然存在。查看 Pod 的 穩定存儲,我們發現當刪除 StatefulSet 的 Pod 時,掛載到 StatefulSet 的 Pod 的 PersistentVolumes 不會被刪除。當這種刪除行為是由 StatefulSet 縮容引起時也是一樣的。
更新StatefulSet
Kubernetes1.7版本的StatefulSet控制器支持自動更新。更新策略有StatefulSet API Object的spec.updateSreategy字段決定。這個特性能夠用來更新一個StatefulSet中的Pod的container images,resource requests,limits和annotaions。RollingUpdate滾動更新是StatefulSet默認策略。
Rolling Update 策略
RollingUpdate
更新策略會更新一個 StatefulSet 中所有的 Pod,采用與序號索引相反的順序並遵循 StatefulSet 的保證。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}' statefulset.apps/web patched (no change)
因為默認的更新策略是RollingUpdate所以提示沒有修改
在一個終端窗口中 patch web
StatefulSet 來再次的改變容器鏡像。
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"nginx"}]'
把鏡像值修改成nginx,該鏡像需要是可以下載的否則更新時會出現下載鏡像錯誤提示ErrImagePull可以通過命令查看是否更新
kubectl edit sts web
在另一個終端監控 StatefulSet 中的 Pod。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-2 0/1 Pending 0 0s web-2 0/1 Pending 0 0s web-2 0/1 ContainerCreating 0 0s web-2 1/1 Running 0 5s web-1 1/1 Terminating 0 5s web-1 0/1 Terminating 0 6s web-1 0/1 Terminating 0 10s web-1 0/1 Terminating 0 10s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 2s web-0 1/1 Terminating 0 14s web-0 0/1 Terminating 0 14s web-0 0/1 Terminating 0 15s web-0 0/1 Terminating 0 15s web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s
StatefulSet 里的 Pod 采用和序號相反的順序更新。在更新下一個 Pod 前,StatefulSet 控制器終止每個 Pod 並等待它們變成 Running 和 Ready。請注意,雖然在順序后繼者變成 Running 和 Ready 之前 StatefulSet 控制器不會更新下一個 Pod,但它仍然會重建任何在更新過程中發生故障的 Pod,使用的是它們當前的版本。已經接收到更新請求的 Pod 將會被恢復為更新的版本,沒有收到請求的 Pod 則會被恢復為之前的版本。像這樣,控制器嘗試繼續使應用保持健康並在出現間歇性故障時保持更新的一致性。
獲取 Pod 來查看他們的容器鏡像。
for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done nginx nginx nginx
小竅門:你還可以使用 kubectl rollout status sts/<name>
來查看 rolling update 的狀態。
分段更新
你可以使用 RollingUpdate
更新策略的 partition
參數來分段更新一個 StatefulSet。分段的更新將會使 StatefulSet 中的其余所有 Pod 保持當前版本的同時僅允許改變 StatefulSet 的 .spec.template
。
Patch web
StatefulSet 來對 updateStrategy
字段添加一個分區。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
再次 Patch StatefulSet 來改變容器鏡像。
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"lowyard/nginx-slim:0.8"}]'
刪除 StatefulSet 中的 Pod。
kubectl delete pod web-2 pod "web-2" deleted
等待 Pod 變成 Running 和 Ready。
獲取Pod容器鏡像
kubectl get po web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' nginx
請注意,雖然更新策略是 RollingUpdate
,StatefulSet 控制器還是會使用原始的容器恢復 Pod。這是因為 Pod 的序號比 updateStrategy
指定的 partition
更小。
灰度擴容
把分段數改成2
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}' statefulset.apps/web patched
刪除web-2以后就使用新的鏡像更新了
kubectl get po web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' lowyard/nginx-slim:0.8
當你改變 partition
時,StatefulSet 會自動的更新 web-2
Pod,這是因為 Pod 的序號小於或等於 partition
。
刪除web-1等待running
kubectl delete pod web-1
查看鏡像還是舊的鏡像
kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' nginx
web-1
被按照原來的配置恢復,因為 Pod 的序號小於分區。當指定了分區時,如果更新了 StatefulSet 的 .spec.template
,則所有序號大於或等於分區的 Pod 都將被更新。如果一個序號小於分區的 Pod 被刪除或者終止,它將被按照原來的配置恢復。
分階段擴容
你可以使用類似灰度擴容的方法執行一次分階段的擴容(例如一次線性的、等比的或者指數形式的擴容)。要執行一次分階段的擴容,你需要設置 partition
為希望控制器暫停更新的序號。
分區當前為2
。請將分區設置為0
。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}' statefulset.apps/web patched
等待 StatefulSet 中的所有 Pod 變成 Running 和 Ready。
獲取Pod的容器
for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done lowyard/nginx-slim:0.8 lowyard/nginx-slim:0.8 lowyard/nginx-slim:0.8
PS:一般擴容都是整體擴容,分階段擴容用的較少,但在某些特定場合適用
刪除StatefulSet
StatefulSet 同時支持級聯和非級聯刪除。使用非級聯方式刪除 StatefulSet 時,StatefulSet 的 Pod 不會被刪除。使用級聯刪除時,StatefulSet 和它的 Pod 都會被刪除。
非級聯刪除
在一個終端窗口查看 StatefulSet 中的 Pod。
kubectl get pods -w -l app=nginx
使用 kubectl delete
刪除 StatefulSet。請確保提供了 --cascade=false
參數給命令。這個參數告訴 Kubernetes 只刪除 StatefulSet 而不要刪除它的任何 Pod。
kubectl delete statefulset web --cascade=false statefulset.apps "web" deleted
獲取pod狀態
# kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 3m41s web-1 1/1 Running 0 3m46s web-2 1/1 Running 0 7m51s web-0 1/1 Running 0 4m36s web-2 1/1 Running 0 8m46s web-1 1/1 Running 0 4m41s
雖然 web
已經被刪除了,但所有 Pod 仍然處於 Running 和 Ready 狀態。 刪除 web-0
。
kubectl delete pod web-0
獲取 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-1 1/1 Running 0 6m13s web-2 1/1 Running 0 10m
由於 web
StatefulSet 已經被刪除,web-0
沒有被重新啟動。
在一個終端監控 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一個終端里重新創建 StatefulSet。請注意,除非你刪除了 nginx
Service (你不應該這樣做),你將會看到一個錯誤,提示 Service 已經存在。
kubectl apply -f web.yaml service/nginx unchanged statefulset.apps/web created
在第一個終端中運行並檢查 kubectl get
命令的輸出。
kubectl get pods -w -l app=nginx
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 14m
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 14m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
web-2 1/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
當重新創建 web
StatefulSet 時,web-0
被第一個重新啟動。由於 web-1
已經處於 Running 和 Ready 狀態,當 web-0
變成 Running 和 Ready 時,StatefulSet 會直接接收這個 Pod。由於你重新創建的 StatefulSet 的 replicas
等於 2,一旦 web-0
被重新創建並且 web-1
被認為已經處於 Running 和 Ready 狀態時,web-2
將會被終止。
讓我們再看看被 Pod 的 web 服務器加載的 index.html
的內容。
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
盡管你同時刪除了 StatefulSet 和 web-0
Pod,但它仍然使用最初寫入 index.html
文件的主機名進行服務。這是因為 StatefulSet 永遠不會刪除和一個 Pod 相關聯的 PersistentVolumes。當你重建這個 StatefulSet 並且重新啟動了 web-0
時,它原本的 PersistentVolume 會被重新掛載。
級聯刪除
在一個終端窗口觀察 StatefulSet 里的 Pod。
kubectl get pods -w -l app=nginx
另一個窗口中再次刪除這個 StatefulSet。這次省略 --cascade=false
參數。
kubectl delete statefulset web statefulset.apps "web" deleted
在第一個終端檢查 kubectl get
命令的輸出,並等待所有的 Pod 變成 Terminating 狀態。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 114s web-1 1/1 Running 0 12m web-0 1/1 Terminating 0 2m30s web-1 1/1 Terminating 0 12m web-0 0/1 Terminating 0 2m31s web-1 0/1 Terminating 0 12m web-0 0/1 Terminating 0 2m32s web-0 0/1 Terminating 0 2m32s
如同你在縮容一節看到的,Pod 按照和他們序號索引相反的順序每次終止一個。在終止一個 Pod 前,StatefulSet 控制器會等待 Pod 后繼者被完全終止。
請注意,雖然級聯刪除會刪除 StatefulSet 和它的 Pod,但它並不會刪除和 StatefulSet 關聯的 Headless Service。你必須手動刪除nginx
Service。
kubectl delete svc nginx service "nginx" deleted
再一次重新創建 StatefulSet 和 Headless Service。
kubectl apply -f web.yaml service/nginx created statefulset.apps/web created
當 StatefulSet 所有的 Pod 變成 Running 和 Ready 時,獲取它們的 index.html
文件的內容。
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
即使你已經刪除了 StatefulSet 和它的全部 Pod,這些 Pod 將會被重新創建並掛載它們的 PersistentVolumes,並且 web-0
和 web-1
將仍然使用它們的主機名提供服務。
Pod管理策略
對於某些分布式系統來說,StatefulSet 的順序性保證是不必要和/或者不應該的。這些系統僅僅要求唯一性和身份標志。為了解決這個問題,在 Kubernetes 1.7 中我們針對 StatefulSet API Object 引入了 .spec.podManagementPolicy
。
OrderedReady Pod 管理策略
OrderedReady
pod 管理策略是 StatefulSets 的默認選項。它告訴 StatefulSet 控制器遵循上文展示的順序性保證。
Parallel Pod 管理策略
Parallel
pod 管理策略告訴 StatefulSet 控制器並行的終止所有 Pod,在啟動或終止另一個 Pod 前,不必等待這些 Pod 變成 Running 和 Ready 或者完全終止狀態。
# cat web-parallel.yaml apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels serviceName: "nginx" podManagementPolicy: "Parallel" replicas: 2 # by default is 1 template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: nginx #image: k8s.gcr.io/nginx-slim:0.8 image: lowyard/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "managed-nfs-storage" resources: requests: storage: 1Gi
這份清單和你在上文下載的完全一樣,只是 web
StatefulSet 的 .spec.podManagementPolicy
設置成了 Parallel
。
在另一個終端窗口創建清單中的 StatefulSet 和 Service。
kubectl apply -f web-parallel.yaml service/nginx created statefulset.apps/web created
查看你在第一個終端中運行的 kubectl get
命令的輸出。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-1 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 1/1 Running 0 2s
StatefulSet 控制器同時啟動了 web-0
和 web-1
。
保持第二個終端打開,並在另一個終端窗口中擴容 StatefulSet。
kubectl scale statefulset/web --replicas=4 statefulset.apps/web scaled
在 kubectl get
命令運行的終端里檢查它的輸出。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-1 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 1/1 Running 0 2s web-2 0/1 Pending 0 0s web-3 0/1 Pending 0 0s web-2 0/1 Pending 0 0s web-3 0/1 Pending 0 0s web-2 0/1 ContainerCreating 0 0s web-3 0/1 ContainerCreating 0 0s web-3 1/1 Running 0 1s web-2 1/1 Running 0 2s
StatefulSet 控制器啟動了兩個新的 Pod,而且在啟動第二個之前並沒有等待第一個變成 Running 和 Ready 狀態。
保持這個終端打開,並在另一個終端刪除 web
StatefulSet。
kubectl delete sts web statefulset.apps "web" deleted
在另一個終端里再次檢查 kubectl get
命令的輸出。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2m33s web-1 1/1 Running 0 2m33s web-2 1/1 Running 0 91s web-3 1/1 Running 0 91s web-2 1/1 Terminating 0 98s web-1 1/1 Terminating 0 2m40s web-3 1/1 Terminating 0 98s web-0 1/1 Terminating 0 2m40s web-1 0/1 Terminating 0 2m41s web-2 0/1 Terminating 0 99s web-0 0/1 Terminating 0 2m42s web-2 0/1 Terminating 0 100s web-2 0/1 Terminating 0 100s web-1 0/1 Terminating 0 2m42s web-2 0/1 Terminating 0 100s web-3 0/1 Terminating 0 100s web-0 0/1 Terminating 0 2m48s web-0 0/1 Terminating 0 2m48s web-1 0/1 Terminating 0 2m53s web-1 0/1 Terminating 0 2m53s web-3 0/1 Terminating 0 111s web-3 0/1 Terminating 0 111s
StatefulSet 控制器將並發的刪除所有 Pod,在刪除一個 Pod 前不會等待它的順序后繼者終止。