Statefulset 的基本功能

statefulset 旨在与有状态的应用及分布式系统一起使用,statefulset 中的每个 pod 拥有一个唯一的身份标识,并且所有 pod 名都是按照 {0..N-1} 的顺序进行编号。本文会主要分析 statefulset controller 的设计与实现,在分析源码前先介绍一下 statefulset 的基本使用。

创建

对于一个拥有 N 个副本的 statefulset,pod 是按照 {0..N-1}的序号顺序创建的,并且会等待前一个 pod 变为 Running & Ready 后才会启动下一个 pod。

  1. $ kubectl create -f sts.yaml
  2. $ kubectl get pod -o wide -w
  3. NAME READY STATUS RESTARTS AGE IP NODE
  4. web-0 0/1 ContainerCreating 0 20s <none> minikube
  5. web-0 1/1 Running 0 3m1s 10.1.0.8 minikube
  6. web-1 0/1 Pending 0 0s <none> <none>
  7. web-1 0/1 ContainerCreating 0 2s <none> minikube
  8. web-1 1/1 Running 0 4s 10.1.0.9 minikube

扩容

statefulset 扩容时 pod 也是顺序创建的,编号与前面的 pod 相接。

  1. $ kubectl scale sts web --replicas=4
  2. statefulset.apps/web scaled
  3. $ kubectl get pod -o wide -w
  4. ......
  5. web-2 0/1 Pending 0 0s <none> <none>
  6. web-2 0/1 ContainerCreating 0 1s <none> minikube
  7. web-2 1/1 Running 0 4s 10.1.0.10 minikube
  8. web-3 0/1 Pending 0 0s <none> <none>
  9. web-3 0/1 ContainerCreating 0 1s <none> minikube
  10. web-3 1/1 Running 0 4s 10.1.0.11 minikube

缩容

缩容时控制器会按照与 pod 序号索引相反的顺序每次删除一个 pod,在删除下一个 pod 前会等待上一个被完全删除。

  1. $ kubectl scale sts web --replicas=2
  2. $ kubectl get pod -o wide -w
  3. ......
  4. web-3 1/1 Terminating 0 8m25s 10.1.0.11 minikube
  5. web-3 0/1 Terminating 0 8m27s <none> minikube
  6. web-2 1/1 Terminating 0 8m31s 10.1.0.10 minikube
  7. web-2 0/1 Terminating 0 8m33s 10.1.0.10 minikube

更新

更新策略由 statefulset 中的 spec.updateStrategy.type 字段决定,可以指定为 OnDelete 或者 RollingUpdate , 默认的更新策略为 RollingUpdate。当使用RollingUpdate 更新策略更新所有 pod 时采用与序号索引相反的顺序进行更新,即最先删除序号最大的 pod 并根据更新策略中的 partition 参数来进行分段更新,控制器会更新所有序号大于或等于 partition 的 pod,等该区间内的 pod 更新完成后需要再次设定 partition 的值以此来更新剩余的 pod,最终 partition 被设置为 0 时代表更新完成了所有的 pod。在更新过程中,如果一个序号小于 partition 的 pod 被删除或者终止,controller 依然会使用更新前的配置重新创建。

  1. // 使用 RollingUpdate 策略更新
  2. $ kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"nginx:1.16"}]'
  3. statefulset.apps/web patched
  4. $ kubectl rollout status sts/web
  5. Waiting for 1 pods to be ready...
  6. Waiting for partitioned roll out to finish: 1 out of 2 new pods have been updated...
  7. Waiting for 1 pods to be ready...
  8. partitioned roll out complete: 2 new pods have been updated...

如果 statefulset 的 .spec.updateStrategy.type 字段被设置为 OnDelete,在更新 statefulset 时,statefulset controller 将不会自动更新其 pod。你必须手动删除 pod,此时 statefulset controller 在重新创建 pod 时,使用修改过的 .spec.template 的内容创建新 pod。

  1. // 使用 OnDelete 方式更新
  2. $ kubectl patch statefulset nginx --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"nginx:1.9"}]'
  3. // 删除 web-1
  4. $ kubectl delete pod web-1
  5. // 查看 web-0 与 web-1 的镜像版本,此时发现 web-1 已经变为最新版本 nginx:1.9 了
  6. $ kubectl get pod -l app=nginx -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
  7. web-0 nginx:1.16
  8. web-1 nginx:1.9

使用滚动更新策略时你必须以某种策略不段更新 partition 值来进行升级,类似于金丝雀部署方式,升级对于 pod 名称来说是逆序。使用非滚动更新方式式,需要手动删除对应的 pod,升级可以是无序的。

回滚

statefulset 和 deployment 一样也支持回滚操作,statefulset 也保存了历史版本,和 deployment 一样利用.spec.revisionHistoryLimit 字段设置保存多少个历史版本,但 statefulset 的回滚并不是自动进行的,回滚操作也仅仅是进行了一次发布更新,和发布更新的策略一样,更新 statefulset 后需要按照对应的策略手动删除 pod 或者修改 partition 字段以达到回滚 pod 的目的。

  1. // 查看 sts 的历史版本
  2. $ kubectl rollout history statefulset web
  3. statefulset.apps/web
  4. REVISION
  5. 0
  6. 0
  7. 5
  8. 6
  9. $ kubectl get controllerrevision
  10. NAME CONTROLLER REVISION AGE
  11. web-6c4c79564f statefulset.apps/web 6 11m
  12. web-c47b9997f statefulset.apps/web 5 4h13m
  13. // 回滚至最近的一个版本
  14. $ kubectl rollout undo statefulset web --to-revision=5

因为 statefulset 的使用对象是有状态服务,大部分有状态副本集都会用到持久存储,statefulset 下的每个 pod 正常情况下都会关联一个 pv 对象,对 statefulset 对象回滚非常容易,但其使用的 pv 中保存的数据无法回滚,所以在生产环境中进行回滚时需要谨慎操作,statefulset、pod、pvc 和 pv 关系图如下所示:

statefulset controller 源码分析 - 图1

删除

statefulset 同时支持级联和非级联删除。使用非级联方式删除 statefulset 时,statefulset 的 pod 不会被删除。使用级联删除时,statefulset 和它关联的 pod 都会被删除。对于级联与非级联删除,在删除时需要指定删除选项(orphanbackground 或者 foreground)进行区分。

  1. // 1、非级联删除
  2. $ kubectl delete statefulset web --cascade=false
  3. // 删除 sts 后 pod 依然处于运行中
  4. $ kubectl get pod
  5. NAME READY STATUS RESTARTS AGE
  6. web-0 1/1 Running 0 4m38s
  7. web-1 1/1 Running 0 17m
  8. // 重新创建 sts 后,会再次关联所有的 pod
  9. $ kubectl create -f sts.yaml
  10. $ kubectl get sts
  11. NAME READY AGE
  12. web 2/2 28s

在级联删除 statefulset 时,会将所有的 pod 同时删掉,statefulset 控制器会首先进行一个类似缩容的操作,pod 按照和他们序号索引相反的顺序每次终止一个。在终止一个 pod 前,statefulset 控制器会等待 pod 后继者被完全终止。

  1. // 2、级联删除
  2. $ kubectl delete statefulset web
  3. $ kubectl get pod -o wide -w
  4. ......
  5. web-0 1/1 Terminating 0 17m 10.1.0.18 minikube <none> <none>
  6. web-1 1/1 Terminating 0 36m 10.1.0.15 minikube <none> <none>
  7. web-1 0/1 Terminating 0 36m 10.1.0.15 minikube <none> <none>
  8. web-0 0/1 Terminating 0 17m 10.1.0.18 minikube <none> <none>

Pod 管理策略

statefulset 的默认管理策略是 OrderedReady,该策略遵循上文展示的顺序性保证。statefulset 还有另外一种管理策略 ParallelParallel 管理策略告诉 statefulset 控制器并行的终止所有 pod,在启动或终止另一个 pod 前,不必等待这些 pod 变成 Running & Ready 或者完全终止状态,但是 Parallel 仅仅支持在 OnDelete 策略下生效,下文会在源码中具体分析。

StatefulSetController 源码分析

kubernetes 版本:v1.16

startStatefulSetController 是 statefulSetController 的启动方法,其中调用 NewStatefulSetController 进行初始化 controller 对象然后调用 Run 方法启动 controller。其中 ConcurrentStatefulSetSyncs 默认值为 5。

k8s.io/kubernetes/cmd/kube-controller-manager/app/apps.go:55

  1. func startStatefulSetController(ctx ControllerContext) (http.Handler, bool, error) {
  2. if !ctx.AvailableResources[schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"}] {
  3. return nil, false, nil
  4. }
  5. go statefulset.NewStatefulSetController(
  6. ctx.InformerFactory.Core().V1().Pods(),
  7. ctx.InformerFactory.Apps().V1().StatefulSets(),
  8. ctx.InformerFactory.Core().V1().PersistentVolumeClaims(),
  9. ctx.InformerFactory.Apps().V1().ControllerRevisions(),
  10. ctx.ClientBuilder.ClientOrDie("statefulset-controller"),
  11. ).Run(int(ctx.ComponentConfig.StatefulSetController.ConcurrentStatefulSetSyncs), ctx.Stop)
  12. return nil, true, nil
  13. }

当 controller 启动后会通过 informer 同步 cache 并监听 pod 和 statefulset 对象的变更事件,informer 的处理流程此处不再详细讲解,最后会执行 sync 方法,sync 方法是每个 controller 的核心方法,下面直接看 statefulset controller 的 sync 方法。

sync

sync 方法的主要逻辑为:

  • 1、根据 ns/name 获取 sts 对象;
  • 2、获取 sts 的 selector;
  • 3、调用 ssc.adoptOrphanRevisions 检查是否有孤儿 controllerrevisions 对象,若有且能匹配 selector 的则添加 ownerReferences 进行关联,已关联但 label 不匹配的则进行释放;
  • 4、调用 ssc.getPodsForStatefulSet 通过 selector 获取 sts 关联的 pod,若有孤儿 pod 的 label 与 sts 的能匹配则进行关联,若已关联的 pod label 有变化则解除与 sts 的关联关系;
  • 5、最后调用 ssc.syncStatefulSet 执行真正的 sync 操作;

k8s.io/kubernetes/pkg/controller/statefulset/stateful_set.go:408

  1. func (ssc *StatefulSetController) sync(key string) error {
  2. ......
  3. namespace, name, err := cache.SplitMetaNamespaceKey(key)
  4. if err != nil {
  5. return err
  6. }
  7. // 1、获取 sts 对象
  8. set, err := ssc.setLister.StatefulSets(namespace).Get(name)
  9. ......
  10. selector, err := metav1.LabelSelectorAsSelector(set.Spec.Selector)
  11. ......
  12. // 2、关联以及释放 sts 的 controllerrevisions
  13. if err := ssc.adoptOrphanRevisions(set); err != nil {
  14. return err
  15. }
  16. // 3、获取 sts 所关联的 pod
  17. pods, err := ssc.getPodsForStatefulSet(set, selector)
  18. if err != nil {
  19. return err
  20. }
  21. return ssc.syncStatefulSet(set, pods)
  22. }

syncStatefulSet

syncStatefulSet 中仅仅是调用了 ssc.control.UpdateStatefulSet 方法进行处理。ssc.control.UpdateStatefulSet 会调用 defaultStatefulSetControlUpdateStatefulSet 方法,defaultStatefulSetControl 是 statefulset controller 中另外一个对象,主要负责处理 statefulset 的更新。

k8s.io/kubernetes/pkg/controller/statefulset/stateful_set.go:448

  1. func (ssc *StatefulSetController) syncStatefulSet(set *apps.StatefulSet, pods []*v1.Pod) error {
  2. ......
  3. if err := ssc.control.UpdateStatefulSet(set.DeepCopy(), pods); err != nil {
  4. return err
  5. }
  6. ......
  7. return nil
  8. }

UpdateStatefulSet 方法的主要逻辑如下所示:

  • 1、获取历史 revisions;
  • 2、计算 currentRevisionupdateRevision,若 sts 处于更新过程中则 currentRevisionupdateRevision 值不同;
  • 3、调用 ssc.updateStatefulSet 执行实际的 sync 操作;
  • 4、调用 ssc.updateStatefulSetStatus 更新 status subResource;
  • 5、根据 sts 的 spec.revisionHistoryLimit字段清理过期的 controllerrevision

在基本操作的回滚阶段提到了过,sts 通过 controllerrevision 保存历史版本,类似于 deployment 的 replicaset,与 replicaset 不同的是 controllerrevision 仅用于回滚阶段,在 sts 的滚动升级过程中是通过 currentRevisionupdateRevision来进行控制并不会用到 controllerrevision

k8s.io/kubernetes/pkg/controller/statefulset/stateful_set_control.go:75

  1. func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, pods []*v1.Pod) error {
  2. // 1、获取历史 revisions
  3. revisions, err := ssc.ListRevisions(set)
  4. if err != nil {
  5. return err
  6. }
  7. history.SortControllerRevisions(revisions)
  8. // 2、计算 currentRevision 和 updateRevision
  9. currentRevision, updateRevision, collisionCount, err := ssc.getStatefulSetRevisions(set, revisions)
  10. if err != nil {
  11. return err
  12. }
  13. // 3、执行实际的 sync 操作
  14. status, err := ssc.updateStatefulSet(set, currentRevision, updateRevision, collisionCount, pods)
  15. if err != nil {
  16. return err
  17. }
  18. // 4、更新 sts 状态
  19. err = ssc.updateStatefulSetStatus(set, status)
  20. if err != nil {
  21. return err
  22. }
  23. ......
  24. // 5、清理过期的历史版本
  25. return ssc.truncateHistory(set, pods, revisions, currentRevision, updateRevision)
  26. }

updateStatefulSet

updateStatefulSet 是 sync 操作中的核心方法,对于 statefulset 的创建、扩缩容、更新、删除等操作都会在这个方法中完成,以下是其主要逻辑:

  • 1、分别获取 currentRevisionupdateRevision 对应的的 statefulset object;
  • 2、构建 status 对象;
  • 3、将 statefulset 的 pods 按 ord(ord 为 pod name 中的序号)的值分到 replicas 和 condemned 两个数组中,0 <= ord < Spec.Replicas 的放到 replicas 组,ord >= Spec.Replicas 的放到 condemned 组,replicas 组代表可用的 pod,condemned 组是需要删除的 pod;
  • 4、找出 replicas 和 condemned 组中的 unhealthy pod,healthy pod 指 running & ready 并且不处于删除状态;
  • 5、判断 sts 是否处于删除状态;
  • 6、遍历 replicas 数组,确保 replicas 数组中的容器处于 running & ready状态,其中处于 failed 状态的容器删除重建,未创建的容器则直接创建,最后检查 pod 的信息是否与 statefulset 的匹配,若不匹配则更新 pod 的状态。在此过程中每一步操作都会检查 monotonic 的值,即 sts 是否设置了 Parallel 参数,若设置了则循环处理 replicas 中的所有 pod,否则每次处理一个 pod,剩余 pod 则在下一个 syncLoop 继续进行处理;
  • 7、按 pod 名称逆序删除 condemned 数组中的 pod,删除前也要确保 pod 处于 running & ready状态,在此过程中也会检查 monotonic 的值,以此来判断是顺序删除还是在下一个 syncLoop 中继续进行处理;
  • 8、判断 sts 的更新策略 .Spec.UpdateStrategy.Type,若为 OnDelete 则直接返回;
  • 9、此时更新策略为 RollingUpdate,更新序号大于等于 .Spec.UpdateStrategy.RollingUpdate.Partition 的 pod,在 RollingUpdate 时,并不会关注 monotonic 的值,都是顺序进行处理且等待当前 pod 删除成功后才继续删除小于上一个 pod 序号的 pod,所以 Parallel 的策略在滚动更新时无法使用。

updateStatefulSet 这个方法中包含了 statefulset 的创建、删除、扩若容、更新等操作,在源码层面对于各个功能无法看出明显的界定,没有 deployment sync 方法中写的那么清晰,下面还是按 statefulset 的功能再分析一下具体的操作:

  • 创建:在创建 sts 后,sts 对象已被保存至 etcd 中,此时 sync 操作仅仅是创建出需要的 pod,即执行到第 6 步就会结束;
  • 扩缩容:对于扩若容操作仅仅是创建或者删除对应的 pod,在操作前也会判断所有 pod 是否处于 running & ready状态,然后进行对应的创建/删除操作,在上面的步骤中也会执行到第 6 步就结束了;
  • 更新:可以看出在第六步之后的所有操作就是与更新相关的了,所以更新操作会执行完整个方法,在更新过程中通过 pod 的 currentRevisionupdateRevision 来计算 currentReplicasupdatedReplicas 的值,最终完成所有 pod 的更新;
  • 删除:删除操作就比较明显了,会止于第五步,但是在此之前检查 pod 状态以及分组的操作确实是多余的;

k8s.io/kubernetes/pkg/controller/statefulset/stateful_set_control.go:255

  1. func (ssc *defaultStatefulSetControl) updateStatefulSet(......) (*apps.StatefulSetStatus, error) {
  2. // 1、分别获取 currentRevision 和 updateRevision 对应的的 statefulset object
  3. currentSet, err := ApplyRevision(set, currentRevision)
  4. if err != nil {
  5. return nil, err
  6. }
  7. updateSet, err := ApplyRevision(set, updateRevision)
  8. if err != nil {
  9. return nil, err
  10. }
  11. // 2、计算 status
  12. status := apps.StatefulSetStatus{}
  13. status.ObservedGeneration = set.Generation
  14. status.CurrentRevision = currentRevision.Name
  15. status.UpdateRevision = updateRevision.Name
  16. status.CollisionCount = new(int32)
  17. *status.CollisionCount = collisionCount
  18. // 3、将 statefulset 的 pods 按 ord(ord 为 pod name 中的序数)的值
  19. // 分到 replicas 和 condemned 两个数组中
  20. replicaCount := int(*set.Spec.Replicas)
  21. replicas := make([]*v1.Pod, replicaCount)
  22. condemned := make([]*v1.Pod, 0, len(pods))
  23. unhealthy := 0
  24. firstUnhealthyOrdinal := math.MaxInt32
  25. var firstUnhealthyPod *v1.Pod
  26. // 4、计算 status 字段中的值,将 pod 分配到 replicas和condemned两个数组中
  27. for i := range pods {
  28. status.Replicas++
  29. if isRunningAndReady(pods[i]) {
  30. status.ReadyReplicas++
  31. }
  32. if isCreated(pods[i]) && !isTerminating(pods[i]) {
  33. if getPodRevision(pods[i]) == currentRevision.Name {
  34. status.CurrentReplicas++
  35. }
  36. if getPodRevision(pods[i]) == updateRevision.Name {
  37. status.UpdatedReplicas++
  38. }
  39. }
  40. if ord := getOrdinal(pods[i]); 0 <= ord && ord < replicaCount {
  41. replicas[ord] = pods[i]
  42. } else if ord >= replicaCount {
  43. condemned = append(condemned, pods[i])
  44. }
  45. }
  46. // 5、检查 replicas数组中 [0,set.Spec.Replicas) 下标是否有缺失的 pod,若有缺失的则创建对应的 pod object
  47. // 在 newVersionedStatefulSetPod 中会判断是使用 currentSet 还是 updateSet 来创建
  48. for ord := 0; ord < replicaCount; ord++ {
  49. if replicas[ord] == nil {
  50. replicas[ord] = newVersionedStatefulSetPod(
  51. currentSet,
  52. updateSet,
  53. currentRevision.Name,
  54. updateRevision.Name, ord)
  55. }
  56. }
  57. // 6、对 condemned 数组进行排序
  58. sort.Sort(ascendingOrdinal(condemned))
  59. // 7、根据 ord 在 replicas 和 condemned 数组中找出 first unhealthy Pod
  60. for i := range replicas {
  61. if !isHealthy(replicas[i]) {
  62. unhealthy++
  63. if ord := getOrdinal(replicas[i]); ord < firstUnhealthyOrdinal {
  64. firstUnhealthyOrdinal = ord
  65. firstUnhealthyPod = replicas[i]
  66. }
  67. }
  68. }
  69. for i := range condemned {
  70. if !isHealthy(condemned[i]) {
  71. unhealthy++
  72. if ord := getOrdinal(condemned[i]); ord < firstUnhealthyOrdinal {
  73. firstUnhealthyOrdinal = ord
  74. firstUnhealthyPod = condemned[i]
  75. }
  76. }
  77. }
  78. ......
  79. // 8、判断是否处于删除中
  80. if set.DeletionTimestamp != nil {
  81. return &status, nil
  82. }
  83. // 9、默认设置为非并行模式
  84. monotonic := !allowsBurst(set)
  85. // 10、确保 replicas 数组中所有的 pod 是 running 的
  86. for i := range replicas {
  87. // 11、对于 failed 的 pod 删除并重新构建 pod object
  88. if isFailed(replicas[i]) {
  89. ......
  90. if err := ssc.podControl.DeleteStatefulPod(set, replicas[i]); err != nil {
  91. return &status, err
  92. }
  93. if getPodRevision(replicas[i]) == currentRevision.Name {
  94. status.CurrentReplicas--
  95. }
  96. if getPodRevision(replicas[i]) == updateRevision.Name {
  97. status.UpdatedReplicas--
  98. }
  99. status.Replicas--
  100. replicas[i] = newVersionedStatefulSetPod(
  101. currentSet,
  102. updateSet,
  103. currentRevision.Name,
  104. updateRevision.Name,
  105. i)
  106. }
  107. // 12、如果 pod.Status.Phase 不为“” 说明该 pod 未创建,则直接重新创建该 pod
  108. if !isCreated(replicas[i]) {
  109. if err := ssc.podControl.CreateStatefulPod(set, replicas[i]); err != nil {
  110. return &status, err
  111. }
  112. status.Replicas++
  113. if getPodRevision(replicas[i]) == currentRevision.Name {
  114. status.CurrentReplicas++
  115. }
  116. if getPodRevision(replicas[i]) == updateRevision.Name {
  117. status.UpdatedReplicas++
  118. }
  119. // 13、如果为Parallel,直接return status结束;如果为OrderedReady,循环处理下一个pod。
  120. if monotonic {
  121. return &status, nil
  122. }
  123. continue
  124. }
  125. // 14、如果pod正在删除(pod.DeletionTimestamp不为nil),且Spec.PodManagementPolicy不
  126. // 为Parallel,直接return status结束,结束后会在下一个 syncLoop 继续进行处理,
  127. // pod 状态的改变会触发下一次 syncLoop
  128. if isTerminating(replicas[i]) && monotonic {
  129. ......
  130. return &status, nil
  131. }
  132. // 15、如果pod状态不是Running & Ready,且Spec.PodManagementPolicy不为Parallel,
  133. // 直接return status结束
  134. if !isRunningAndReady(replicas[i]) && monotonic {
  135. ......
  136. return &status, nil
  137. }
  138. // 16、检查 pod 的信息是否与 statefulset 的匹配,若不匹配则更新 pod 的状态
  139. if identityMatches(set, replicas[i]) && storageMatches(set, replicas[i]) {
  140. continue
  141. }
  142. replica := replicas[i].DeepCopy()
  143. if err := ssc.podControl.UpdateStatefulPod(updateSet, replica); err != nil {
  144. return &status, err
  145. }
  146. }
  147. // 17、逆序处理 condemned 中的 pod
  148. for target := len(condemned) - 1; target >= 0; target-- {
  149. // 18、如果pod正在删除,检查 Spec.PodManagementPolicy 的值,如果为Parallel,
  150. // 循环处理下一个pod 否则直接退出
  151. if isTerminating(condemned[target]) {
  152. ......
  153. if monotonic {
  154. return &status, nil
  155. }
  156. continue
  157. }
  158. // 19、不满足以下条件说明该 pod 是更新前创建的,正处于创建中
  159. if !isRunningAndReady(condemned[target]) && monotonic && condemned[target] != firstUnhealthyPod {
  160. ......
  161. return &status, nil
  162. }
  163. // 20、否则直接删除该 pod
  164. if err := ssc.podControl.DeleteStatefulPod(set, condemned[target]); err != nil {
  165. return &status, err
  166. }
  167. if getPodRevision(condemned[target]) == currentRevision.Name {
  168. status.CurrentReplicas--
  169. }
  170. if getPodRevision(condemned[target]) == updateRevision.Name {
  171. status.UpdatedReplicas--
  172. }
  173. // 21、如果为 OrderedReady 方式则返回否则继续处理下一个 pod
  174. if monotonic {
  175. return &status, nil
  176. }
  177. }
  178. // 22、对于 OnDelete 策略直接返回
  179. if set.Spec.UpdateStrategy.Type == apps.OnDeleteStatefulSetStrategyType {
  180. return &status, nil
  181. }
  182. // 23、若为 RollingUpdate 策略,则倒序处理 replicas数组中下标大于等于
  183. // Spec.UpdateStrategy.RollingUpdate.Partition 的 pod
  184. updateMin := 0
  185. if set.Spec.UpdateStrategy.RollingUpdate != nil {
  186. updateMin = int(*set.Spec.UpdateStrategy.RollingUpdate.Partition)
  187. }
  188. for target := len(replicas) - 1; target >= updateMin; target-- {
  189. // 24、如果Pod的Revision 不等于 updateRevision,且 pod 没有处于删除状态则直接删除 pod
  190. if getPodRevision(replicas[target]) != updateRevision.Name && !isTerminating(replicas[target]) {
  191. ......
  192. err := ssc.podControl.DeleteStatefulPod(set, replicas[target])
  193. status.CurrentReplicas--
  194. return &status, err
  195. }
  196. // 25、如果 pod 非 healthy 状态直接返回
  197. if !isHealthy(replicas[target]) {
  198. return &status, nil
  199. }
  200. }
  201. return &status, nil
  202. }

总结

本文分析了 statefulset controller 的主要功能,statefulset 在设计上有很多功能与 deployment 是类似的,但其主要是用来部署有状态应用的,statefulset 中的 pod 名称存在顺序性和唯一性,同时每个 pod 都使用了 pv 和 pvc 来存储状态,在创建、删除、更新操作中都会按照 pod 的顺序进行。

参考:

https://github.com/kubernetes/kubernetes/issues/78007

https://github.com/kubernetes/kubernetes/issues/67250

https://www.cnblogs.com/linuxk/p/9767736.html