Karmada 在最近发布 v1.3.0 中更新了很多的新特性。其中对应用跨集群故障迁移功能进行了重新设计,实现了基于污点的故障驱逐机制,并提供平滑的故障迁移功能,可以有效保障服务迁移过程的连续性(不断服)。设计理念源于 K8s 的污点容忍机制,对于熟悉 K8s 的用户来说并没有过多的学习成本,很容易就能理解 Karmada 的 failover 的原理。Karmada 的控制器拥有众多的组件,每个组件各司其职。在 Karmada 原有的这套高度解耦的构架下很优雅地实现了 failover 的功能。
故障转移依赖 cluster-controller、cluster-stauts-controller、taint-manager、scheduler、graceful-eviction、webhook 等多个控制器协同完成故障迁移流程,其中 taint-manager 是全新的组件,它是其中的重要一环,它决定是否驱逐工作负载。我们可以把整个流程可以分为:“集群故障判定”,“负载预驱逐”,“重新调度”,“清理冗余负载”。
cluster-status 控制器来判断集群的健康状态,如果集群被判定为故障集群后,cluster 控制器会将集群标记 `effect` 为 “NoExecute” 的污点,随后 taint-manager 控制器根据容忍机制开始驱逐该该集群上的工作负载,最后调度器将重新调度被驱逐的工作负载到候选集群上。如果开启了优雅驱逐的特性,graceful-eviction 控制器会从队列中执行优雅驱逐的 task。
01
在 Karmada 中,无论是 Push 还是 Pull 模式的 member 集群,都是由 cluster status 控制器持续间隔时间来探测 cluster 是否健康,并将状态信息在 `cluster.status.condition` 中进行更新。集群状态的 condition 为 `Ready`, 每次检测集群的隔间时间为 10 秒,可以通过 --cluster-status-update-frequency 的 flag 来设置。集群 `Ready` 的状态为 false 可以分为两种情况:
ClusterNotReachableReason: 集群在一个时间周期内请求没有响应。
ClusterNotReady:集群请求正常响应,在一个时间周期内健康检测的 endpoint 响应不正常。
相应的时间周期我们可以通过 --cluster-failure-threshold 的 flag 进行设置,默认为 30 秒。只有当以上两种请求都能正常响应,我们才认为集群是健康的状态。下面是一个不健康的集群的案例:
Status:
Conditions:
Last Transition Time: 2022-10-31T03:36:08Z
Message: cluster is not reachable
Reason: ClusterNotReachable
Status: False
Type: Ready
目前 failover 的功能还处于 alpha 的阶段,想要使用该功能可以通过 `--feature-gates=Failover=true` flag 来开启。在 Karmada 中,集群的污点标记是自动的,cluster 的控制器会根据 cluster 的 condition 来为 cluster 标记污点。根据集群的状态可以分为下面几种类型的污点:
当集群的 `Ready` 的 condition 的状态为 "False" 时:
key: cluster.karmada.io/not-ready
effect: NoSchedule
当集群的 `Ready` 的 condition 的状态为 "Unknown" 时:
key: cluster.karmada.io/unreachable
effect: NoSchedule
以上的污点并不会被 taint-manager 处理,它只会处理带有 `effect` 为`NotExecute`污点的集群。为了增加集群容错的时间,集群在处于 `NoSchedule` 一定时间后,才会被设置为 `NoExecute`。可以通过 `--failover-eviction-timeout` 来设置时间间隔,默认时间为 5 分钟。
当集群被移除的时候,也会被设置为以下的污点:
key: cluster.karmada.io/terminating
effect: NoSchedule
taint-manager 是整个功能关键组件,当集群被标记 `effect` 为 `NoExecute` 污点后,并不会立马驱逐该集群上的工作负载。它实现了污点容忍的机制,它会根据该集群的 `RB`/`CRB` 资源上设置的容忍参数进行判断,具体可以分为三种情况:
需要立马驱逐:没有设置污点容忍或者没有容忍时间。
永不驱逐:容忍了所有的污点。
一定时间后进行驱逐:karmada-webhook 为每一个 PropagationPolicy/ClusterPropagationPolicy 自动设置默认的容忍时间,默认为 10 分钟。可以通过 `--default-not-ready-toleration-seconds` 和 `--default-unreachable-toleration-seconds` flag 自定义容忍时间。
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: nginx-propagation
namespace: default
spec:
placement:
clusterTolerations:
- effect: NoExecute
key: cluster.karmada.io/not-ready
operator: Exists
tolerationSeconds: 600
- effect: NoExecute
key: cluster.karmada.io/unreachable
operator: Exists
tolerationSeconds: 600
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: nginx
namespace: default
被驱逐的资源重新调度的流程,还是由 scheduler 来执行, taint-manager 在确定故障集群满足驱逐条件后,会在 RB/CRB 资源中把污点集群从可用集群列表中移除。Scheduler 控制器会重新触发调度流程。不过在 scheduler 中新增了 taint_toleration 插件,由这个插件来控制资源能否被污点集群所容忍。除此之外,还需要满足以下两点限制条件:
重新调度的资源,还是需要满足传播策略的限制,例如:ClusterAffinity 或者 SpreadConstraints。
在故障迁移整个过程中,之前已经被调度到 Ready 集群的资源会被保留。
在真实场景中,如果当集群为判断为故障集群,应用还没有被迁移到可用集群就被删除了,可能会造成服务不可用,这对用户的体验上是不友好的。为了避免这个问题,Karmada 设计了优雅驱逐功能,整个过程可以保障在驱逐过程中业务不中断,让用户无感知。目前这个功能还处于 alpha 的阶段,需要通过 `-–GracefulEviction=true` flag 开启。优雅驱逐由一个新的控制器 graceful-eviction 来完成的,它的执行过程可以分为:
taint-manager 通过污点容忍机制确定需要驱逐的应用,将创建一个 GracefulEviction 的 task,这个 task 将设置到 RB/CRB 的 `spec.GracefulEvictionTasks` 队列中。
scheduler 执行了重新调度的流程,在候选集群中选择集群去调度驱逐的工作负载。
graceful-eviction controller 确定了重新调度结果后,并检查资源的健康状态。在超过间隔时间后才将故障集群资源移除。这个间隔时间可以通过 `--graceful-eviction-timeout` 时间设置,默认 10 分钟。最后将这个 task 从队列移除。
在 Karmada 1.3 版本之前的 failover 功能,只依赖了 cluster-status 和 scheduler 两个控制器就完成了整个流程。Schduler 监听到 cluster 状态为 NotReady 后,就启动了重调度的流程。启动的时机取决于 cluster-status 的状态判断,从这个过程中我们可以看出它并没有任何的集群容错能力,这对于真实的生产环境可能会造成频繁跨集群迁移带来的诸多风险。
Karmada 基于污点的优雅驱逐机制的设计得非常合理,也非常符合 Karmada 当前的架构。不过目前还处于一个 alpha 的阶段,还需要一段时间来检验功能可靠性。
本文作者
陈文
现任「DaoCloud 道客」 云原生开发工程师
参考资料:
[1] https://karmada.io/docs/userguide/failover/#concept
[2] https://bbs.huaweicloud.com/blogs/374327
[3] https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration/