淺析KubernetesStatefulSet

清俠發表於2018-06-26

StatefulSet和Deployment的區別

“Deployment用於部署無狀態服務,StatefulSet用來部署有狀態服務”。

具體的,什麼場景需要使用StatefulSet呢?官方給出的建議是,如果你部署的應用滿足以下一個或多個部署需求,則建議使用StatefulSet。

  • 穩定的、唯一的網路標識。
  • 穩定的、持久的儲存。
  • 有序的、優雅的部署和伸縮。
  • 有序的、優雅的刪除和停止。
  • 有序的、自動的滾動更新。

穩定的主要是針對Pod發生re-schedule後仍然要保持之前的網路標識和持久化儲存。這裡所說的網路標識包括hostname、叢集內DNS中該Pod對應的A Record,並不能保證Pod re-schedule之後IP不變。要想保持Pod IP不變,我們可以藉助穩定的Pod hostname定製IPAM獲取固定的Pod IP。藉助StatefulSet的穩定的唯一的網路標識特性,我們能比較輕鬆的實現Pod的固定IP需求,然後如果使用Deployment,那麼將會複雜的多,你需要考慮滾動更新的過程中的引數控制(maxSurge、maxUnavailable)、每個應用的IP池預留造成的IP浪費等等問題。

因此,我想再加一個StatefulSet的使用場景:

  • 實現固定的Pod IP方案, 可以優先考慮基於StatefulSet

最佳實踐

  • StatefulSet對應Pod的儲存最好通過StorageClass來動態建立:每個Pod都會根據StatefulSet中定義的VolumeClaimTemplate來建立一個對應的PVC,然後PVS通過StorageClass自動建立對應的PV,並掛載給Pod。所以這種方式,需要你事先建立好對應的StorageClass。當然,你也可以通過預先由管理員手動建立好對應的PV,只要能保證自動建立的PVC能和這些PV匹配上。
  • 為了資料安全,當刪除StatefulSet中Pods或者對StatefulSet進行縮容時,Kubernetes並不會自動刪除StatefulSet對應的PV,而且這些PV預設也不能被其他PVC Bound。當你確認資料無用之後再手動去刪除PV的時候,資料是否刪除取決於PV的ReclaimPolicy配置。Reclaim Policy支援以下三種:

    • Retain,意味著需要你手動清理;
    • Recycle,等同於rm -rf /thevolume/*
    • Delete,預設值,依賴於後端的儲存系統自己實現。

      > 注意:
      > - 目前只有NFS和HostPath支援Recycle;
      > - EBS,GCE PD, Azure Disk,Openstack Cinder支援Delete。
  • 請小心刪除StatefulSet對應的PVC,首先確保Pods已經完全Terminate,然後確定不需要Volume中的資料後,再考慮刪除PV。因為刪除PVC可能觸發對應PV的自動刪除,並根據StorageClass中的recalimPolicy配置可能造成volume中的資料丟失。
  • 因為部署的是有狀態應用,我們需要自己建立對應的Headless Service,注意Label要和StatefulSet中Pods的Label匹配。Kubernetes會為該Headless Service建立對應SRV Records,包含所有的後端Pods,KubeDNS會通過Round Robin演算法進行選擇。
  • 在Kubernetes 1.8+中,你必須保證StatefulSet的spec.selector能匹配.spec.template.metadata.labels,否則會導致StatefulSet建立失敗。在Kubernetes 1.8之前,StatefulSet的spec.selector如果沒指定則預設會等同於.spec.template.metadata.labels。
  • 對StatefulSet進行縮容前,你需要確認對應的Pods都是Ready的,否則即使你觸發了縮容操作,Kubernetes也不會真的進行縮容操作。

如何理解穩定的網路標識

StatefulSet中反覆強調的“穩定的網路標識”,主要指Pods的hostname以及對應的DNS Records。

  • HostName:StatefulSet的Pods的hostname按照這種格式生成:$(statefulset name)-$(ordinal), ordinal0 ~ N-1(N為期望副本數)。

    • StatefulSet Controller在建立pods時,會給pod加上一個pod name label:statefulset.kubernetes.io/pod-name, 然後設定到Pod的pod name和hostname中。
    • pod name label有啥用呢?我們可以建立獨立的Service匹配到這個指定的pod,然後方便我們單獨對這個pod進行debug等處理。
  • DNS Records

    • Headless Service的DNS解析:$(service name).$(namespace).svc.cluster.local 通過DNS RR解析到後端其中一個Pod。SRV Records只包含對應的Running and Ready的Pods,不Ready的Pods不會在對應的SRV Records中。
    • Pod的DNS解析:$(hostname).$(service name).$(namespace).svc.cluster.local解析到對應hostname的Pod。

如何理解穩定的持久化儲存

  • 每個Pod對應一個PVC,PVC的名稱是這樣組成的:$(volumeClaimTemplates.name)-$(pod`s hostname),跟對應的Pod是一一對應的。
  • 當Pod發生re-schedule(其實是recreate)後,它所對應的PVC所Bound的PV仍然會自動的掛載到新的Pod中。
  • Kubernetes會按照VolumeClaimTemplate建立N(N為期望副本數)個PVC,由PVCs根據指定的StorageClass自動建立PVs。
  • 當通過級聯刪除StatefulSet時並不會自動刪除對應的PVCs,所以PVC需要手動刪除。
  • 當通過級聯刪除StatefulSet或者直接刪除對應Pods時,對應的PVs並不會自動刪除。需要你手動的去刪除PV。

部署和伸縮時與Deployment的區別

  • 當部署有N個副本的StatefulSet應用時,嚴格按照index從0到N-1的遞增順序建立,下一個Pod建立必須是前一個Pod Ready為前提。
  • 當刪除有N個副本的StatefulSet應用時,嚴格按照index從N-1到0的遞減順序刪除,下一個Pod刪除必須是前一個Pod shutdown並完全刪除為前提。
  • 當擴容StatefulSet應用時,每新增一個Pod必須是前一個Pod Ready為前提。
  • 當縮容StatefulSet應用時,沒刪除一個Pod必須是前一個Pod shutdown併成功刪除為前提。
  • 注意StatefulSet的pod.Spec.TerminationGracePeriodSeconds不要設定為0。

Node網路異常等情況下該如何處理

  • 正常情況下,StatefulSet Controller會保證叢集內同一namespace下不會出現多個相同network identity的StatefulSet Pods。
  • 如果叢集內出現以上情況,那麼有可能導致該有狀態應用不能正常工作、甚至出現資料丟失等致命問題。

那麼什麼情況下會導致出現同一namespace下會出現多個相同network identity的StatefulSet Pods呢?我們考慮下Node出現網路Unreachable的情況:

  • 如果你使用Kubernetes 1.5之前的版本,當Node Condition是NetworkUnavailable時,node controller會強制從apiserver中刪除這個Node上的這些pods物件,這時StatefulSet Controller就會自動在其他Ready Nodes上recreate同identity的Pods。這樣做其實風險是很大的,可能會導致有一段時間有多個相同network identity的StatefulSet Pods,可能會導致該有狀態應用不能正常工作。所以儘量不要在Kubernetes 1.5之前的版本中使用StatefulSet,或者你明確知道這個風險並且無視它。
  • 如果你使用Kubernetes 1.5+的版本,當Node Condition是NetworkUnavailable時,node controller不會強制從apiserver中刪除這個Node上的這些pods物件,這些pods的state在apiserver中被標記為Terminating或者Unknown,因此StatefulSet Controller並不會在其他Node上再recreate同identity的Pods。當你確定了這個Node上的StatefulSet Pods shutdown或者無法和該StatefulSet的其他Pods網路不同時,接下來就需要強制刪除apiserver中這些unreachable pods object,然後StatefulSet Controller就能在其他Ready Nodes上recreate同identity的Pods,使得StatefulSet繼續健康工作。

那麼在Kubernetes 1.5+中,如何強制從apiserver中刪除該StatefulSet pods呢?有如下三種方法:

  • 如果Node永久的無法連線網路或者關機了,意味著能確定這個Node上的Pods無法與其他Pods通訊了,不會對StatefulSet應用的可用性造成影響,那麼建議手動從apiserver中刪除該NetworkUnavailable的Node,Kubernetes會自動從apiserver中刪除它上面的Pods object。
  • 如果Node是因為叢集網路腦裂導致的,則建議去檢查網路問題併成功恢復,因為Pods state已經是Terminating或者Unkown,所以kubelet從apiserver中獲取到這個資訊後就會自動刪除這些Pods。
  • 其他情況才考慮直接手動從apiserver中刪除這些Pods,因為這時你無法確定對應的Pods是否已經shutdown或者對StatefulSet應用無影響,強制刪除後就可能導致出現同一namespace下有多個相同network identity的StatefulSet Pods,所以儘量不要使用這種方法。

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

小知識:當前Node Condition有以下6種:

Node Condition Description
OutOfDisk True if there is insufficient free space on the node for adding new pods, otherwise False
Ready True if the node is healthy and ready to accept pods, False if the node is not healthy and is not accepting pods, and Unknown if the node controller has not heard from the node in the last 40 seconds
MemoryPressure True if pressure exists on the node memory – that is, if the node memory is low; otherwise False
DiskPressure True if pressure exists on the disk size – that is, if the disk capacity is low; otherwise False
NetworkUnavailable True if the network for the node is not correctly configured, otherwise False
ConfigOK True if the kubelet is correctly configured, otherwise False

[](https://kubernetes.io/docs/concepts/architecture/nodes/#node-condition)

StatefulSet的Pod管理策略

Kubernetes 1.7+,StatefulSet開始支援Pod Management Policy配置,提供以下兩種配置:

  • OrderedReady,StatefulSet的Pod預設管理策略,就是逐個的、順序的進行部署、刪除、伸縮,也是預設的策略。
  • Parallel,支援並行建立或者刪除同一個StatefulSet下面的所有Pods,並不會逐個的、順序的等待前一個操作確保成功後才進行下一個Pod的處理。其實用這種管理策略的場景非常少。

StatefulSet的更新策略

StatefulSet的更新策略(由.spec.updateStrategy.type指定)支援以下兩種:

  • OnDelete, 含義同Deployment的OnDelete策略,大家應該很熟悉了,不多介紹。
  • RollingUpdate,滾動更新過程也跟Deployment大致相同,區別在於:

    • 相當於Deployment的maxSurge=0,maxUnavailable=1(其實StatefulSet是不存在這兩個配置的)
    • 滾動更新的過程是有序的(逆序),index從N-1到0逐個依次進行,並且下一個Pod建立必須是前一個Pod Ready為前提,下一個Pod刪除必須是前一個Pod shutdown並完全刪除為前提。
    • 支援部分例項滾動更新,部分不更新,通過.spec.updateStrategy.rollingUpdate.partition來指定一個index分界點。

      • 所有ordinal大於等於partition指定的值的Pods將會進行滾動更新。
      • 所有ordinal小於partition指定的值得Pods將保持不變。即使這些Pods被recreate,也會按照原來的pod template建立,並不會更新到最新的版本。
      • 特殊地,如果partition的值大於StatefulSet的期望副本數N,那麼將不會觸發任何Pods的滾動更新。

思考:StatefulSet滾動更新時,如果某個Pod更新失敗,會怎麼辦呢?
先賣個關子,下一篇對StatefulSet Controller原始碼分析時我們再來回答。