Advanced DaemonSet

这个控制器基于原生 DaemonSet 上增强了发布能力,比如 灰度分批、按 Node label 选择、暂停、热升级等。

如果你对原生 DaemonSet 不是很了解,我们强烈建议你先阅读它的文档(在学习 Advanced DaemonSet 之前):

注意 Advanced DaemonSet 是一个 CRD,kind 名字也是 DaemonSet,但是 apiVersion 是 apps.kruise.io/v1alpha1。 这个 CRD 的所有默认字段、默认行为与原生 DaemonSet 完全一致,除此之外还提供了一些 optional 字段来扩展增强的策略。

因此,用户从原生 DaemonSet 迁移到 Advanced DaemonSet,只需要把 apiVersion 修改后提交即可:

  1. - apiVersion: apps/v1
  2. + apiVersion: apps.kruise.io/v1alpha1
  3. kind: DaemonSet
  4. metadata:
  5. name: sample-ds
  6. spec:
  7. #...

增强策略

在 RollingUpdateDaemonSet 中我们新增了以下字段:

  1. const (
  2. + // StandardRollingUpdateType replace the old daemons by new ones using rolling update i.e replace them on each node one after the other.
  3. + // this is the default type for RollingUpdate.
  4. + StandardRollingUpdateType RollingUpdateType = "Standard"
  5. + // InplaceRollingUpdateType update container image without killing the pod if possible.
  6. + InplaceRollingUpdateType RollingUpdateType = "InPlaceIfPossible"
  7. )
  8. // Spec to control the desired behavior of daemon set rolling update.
  9. type RollingUpdateDaemonSet struct {
  10. + // Type is to specify which kind of rollingUpdate.
  11. + Type RollingUpdateType `json:"rollingUpdateType,omitempty" protobuf:"bytes,1,opt,name=rollingUpdateType"`
  12. // ...
  13. MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,2,opt,name=maxUnavailable"`
  14. // ...
  15. MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,7,opt,name=maxSurge"`
  16. + // A label query over nodes that are managed by the daemon set RollingUpdate.
  17. + // Must match in order to be controlled.
  18. + // It must match the node's labels.
  19. + Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,3,opt,name=selector"`
  20. + // The number of DaemonSet pods remained to be old version.
  21. + // Default value is 0.
  22. + // Maximum value is status.DesiredNumberScheduled, which means no pod will be updated.
  23. + // +optional
  24. + Partition *int32 `json:"partition,omitempty" protobuf:"varint,4,opt,name=partition"`
  25. + // Indicates that the daemon set is paused and will not be processed by the
  26. + // daemon set controller.
  27. + // +optional
  28. + Paused *bool `json:"paused,omitempty" protobuf:"varint,5,opt,name=paused"`
  29. }

升级方式

Advanced DaemonSet 在 spec.updateStrategy.rollingUpdate 中有一个 rollingUpdateType 字段,标识了如何进行滚动升级:

  • Standard: 对于每个 node,控制器会先删除旧的 daemon Pod,再创建一个新 Pod,和原生 DaemonSet 行为一致。

  • Surging: 对于每个 node,控制器会先创建一个新 Pod,等它 ready 之后再删除老 Pod。

  • Standard (默认): 控制器会重建升级 Pod,与原生 DaemonSet 行为一致。你可以通过 maxUnavailablemaxSurge 来控制重建新旧 Pod 的顺序。

  • InPlaceIfPossible: 控制器会尽量采用原地升级的方式,如果不行则重建升级。请阅读该文档了解更多原地升级的细节。 注意,在这个类型下,只能使用 maxUnavailable 而不能用 maxSurge

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: DaemonSet
  3. spec:
  4. # ...
  5. updateStrategy:
  6. type: RollingUpdate
  7. rollingUpdate:
  8. rollingUpdateType: Standard

Selector 标签选择升级

这个策略支持用户通过配置 node 标签的 selector,来指定灰度升级某些特定类型 node 上的 Pod。

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: DaemonSet
  3. spec:
  4. # ...
  5. updateStrategy:
  6. type: RollingUpdate
  7. rollingUpdate:
  8. selector:
  9. matchLabels:
  10. nodeType: canary

分批灰度升级或扩容

Partition 的语义是 保留旧版本 Pod 的数量,默认为 0。 如果在发布过程中设置了 partition,则控制器只会将 (status.DesiredNumberScheduled - partition) 数量的 Pod 更新到最新版本。

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: DaemonSet
  3. spec:
  4. # ...
  5. updateStrategy:
  6. type: RollingUpdate
  7. rollingUpdate:
  8. partition: 10

另外如果你在 Advanced DaemonSet 中定义了 daemonset.kruise.io/progressive-create-pod: "true" annotation, partition 同样会在扩容的时候控制创建出来 Pod 的数量。

暂停升级

用户可以通过设置 paused 为 true 暂停发布,不过控制器还是会做 replicas 数量管理:

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: DaemonSet
  3. spec:
  4. # ...
  5. updateStrategy:
  6. rollingUpdate:
  7. paused: true

升级镜像自动预热

FEATURE STATE: Kruise v1.3.0

如果你在安装或升级 Kruise 的时候启用了 PreDownloadImageForDaemonSetUpdate feature-gate, DaemonSet 控制器会自动在所有旧版本 pod 所在 node 节点上预热你正在灰度发布的新版本镜像。 这对于应用发布加速很有帮助。

默认情况下 DaemonSet 每个新镜像预热时的并发度都是 1,也就是一个个节点拉镜像。 如果需要调整,你可以通过 apps.kruise.io/image-predownload-parallelism annotation 来设置并发度。

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: DaemonSet
  3. metadata:
  4. annotations:
  5. apps.kruise.io/image-predownload-parallelism: "10"

生命周期钩子

FEATURE STATE: Kruise v1.1.0

CloneSet 提供的生命周期钩子 能力相似。

目前 Advanced DaemonSet 只支持 PreDelete hook,它允许用户在 daemon Pod 被删除前执行一些自定义的逻辑。

  1. type LifecycleStateType string
  2. // Lifecycle contains the hooks for Pod lifecycle.
  3. type Lifecycle struct {
  4. // PreDelete is the hook before Pod to be deleted.
  5. PreDelete *LifecycleHook `json:"preDelete,omitempty"`
  6. }
  7. type LifecycleHook struct {
  8. LabelsHandler map[string]string `json:"labelsHandler,omitempty"`
  9. FinalizersHandler []string `json:"finalizersHandler,omitempty"`
  10. /********************** FEATURE STATE: 1.2.0 ************************/
  11. // MarkPodNotReady = true means:
  12. // - Pod will be set to 'NotReady' at preparingDelete/preparingUpdate state.
  13. // - Pod will be restored to 'Ready' at Updated state if it was set to 'NotReady' at preparingUpdate state.
  14. // Default to false.
  15. MarkPodNotReady bool `json:"markPodNotReady,omitempty"`
  16. /*********************************************************************/
  17. }

例如:

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: DaemonSet
  3. spec:
  4. # define with label
  5. lifecycle:
  6. preDelete:
  7. labelsHandler:
  8. example.io/block-deleting: "true"

当 DaemonSet 删除一个 Pod 时(包括缩容和重建升级):

  • 如果没有定义 lifecycle hook 或者 Pod 不符合 preDelete 条件,则直接删除
  • 否则,会先将 Pod 更新为 PreparingDelete 状态,并等待用户的 controller 将 Pod 中关联的 label/finalizer 去除,再执行 Pod 删除
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. labels:
  5. example.io/block-deleting: "true" # the pod is hooked by PreDelete hook label
  6. lifecycle.apps.kruise.io/state: PreparingDelete # so we update it to `PreparingDelete` state and wait for user controller to do something and remove the label

删除 Pod 前将其置为 NotReady

FEATURE STATE: Kruise v1.2.0

  1. lifecycle:
  2. preDelete:
  3. markPodNotReady: true
  4. finalizersHandler:
  5. - example.io/unready-blocker

如果设置 preDelete.markPodNotReady=true, Kruise 将会在 Pod 进入 PreparingDelete 状态时,将 KruisePodReady 这个 Pod Condition 设置为 False, Pod 将变为 NotReady

用户可以利用这一特性,在容器真正被停止之前将 Pod 上的流量先行排除,防止流量损失。

注意: 该特性仅在 Pod 被注入 KruisePodReady 这个 ReadinessGate 时生效。

用户 controller 逻辑示例

与上述 yaml 例子类似,我们需要先将 example.io/block-deleting label 定义在 Advanced DaemonSet 的 template 和 lifecycle 中。

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: DaemonSet
  3. spec:
  4. template:
  5. metadata:
  6. labels:
  7. example.io/block-deleting: "true"
  8. # ...
  9. lifecycle:
  10. preDelete:
  11. labelsHandler:
  12. example.io/block-deleting: "true"

用户自定义 controller 的执行逻辑:

  • 当发现 Pod 进入 PreparingDelete 状态,检查它的节点是否存在,并执行一些处理逻辑(例如资源预留等),最后将 Pod 中的 example.io/block-deleting label 去掉。