中断

本指南适用于希望构建高可用应用并需要了解 Pod 可能发生的各类中断的应用所有者。

本指南也适用于希望执行自动化集群操作(例如升级和扩缩集群)的集群管理员。

自愿和非自愿中断

Pod 在被某人(一个人或一个控制器)销毁之前,或者出现不可避免的硬件或系统软件错误之前不会消失。

我们将这些不可避免的情况称为应用的非自愿中断。例如:

  • 节点背后物理机的硬件故障
  • 集群管理员误删虚拟机(实例)
  • 云厂商或虚拟化层故障导致虚拟机消失
  • 内核 panic
  • 由于集群网络分区,节点从集群中消失
  • 由于节点资源不足而逐出 Pod。

除了资源不足的情况外,所有这些情况对大多数用户来说都应该很熟悉;它们并非 Kubernetes 特有。

我们将其他情况称为自愿中断。这包括应用所有者发起的操作和集群管理员发起的操作。典型的应用所有者操作包括:

  • 删除管理 Pod 的 Deployment 或其他控制器
  • 更新 Deployment 的 Pod 模板导致重启
  • 直接删除 Pod(例如误操作)

集群管理员操作包括:

  • 为了修复或升级而排空节点
  • 从集群中排空节点以缩减集群(了解节点自动扩缩)。
  • 从节点中移除 Pod 以便其他 Pod 可以调度到该节点。

这些操作可能由集群管理员直接执行,或由集群管理员运行的自动化工具执行,或由你的集群托管提供商执行。

请咨询你的集群管理员或查阅你的云厂商或分发版文档,以确定你的集群是否启用了任何自愿中断源。如果未启用,则可以跳过创建 PodDisruptionBudget。

处理中断

以下是缓解非自愿中断的一些方法:

  • 确保你的 Pod 请求所需的资源
  • 如果需要更高的可用性,复制你的应用。(了解如何运行副本化的无状态有状态应用。)
  • 对于运行副本化应用时需要更高可用性的场景,将应用分散到不同的机架(使用反亲和性)或不同的区域(如果使用多区域集群)。

自愿中断的频率各不相同。在一个基本的 Kubernetes 集群中,没有自动的自愿中断(只有用户触发的中断)。然而,你的集群管理员或托管提供商可能会运行一些额外的服务,这些服务会导致自愿中断。例如,滚动升级节点软件可能会导致自愿中断。此外,一些集群(节点)自动扩缩的实现可能会导致自愿中断,以对节点进行碎片整理和压缩。你的集群管理员或托管提供商应该已经记录了可能发生的自愿中断级别(如果有)。某些配置选项,例如在你的 Pod Spec 中使用 PriorityClass,也可能导致自愿(和非自愿)中断。

Pod 中断预算

特性状态: Kubernetes v1.21 [stable]

即使引入频繁的自愿中断,Kubernetes 也提供了帮助你运行高可用应用的功能。

作为应用所有者,你可以为每个应用创建一个 PodDisruptionBudget (PDB)。PDB 限制了在自愿中断期间副本化应用同时宕机的 Pod 的数量。例如,基于多数派(quorum-based)的应用希望确保运行中的副本数量永远不会低于多数派所需的数量。Web 前端可能希望确保正在提供负载的副本数量永远不会低于总副本数的某个百分比。

集群管理员和托管提供商应该使用遵循 PodDisruptionBudget 的工具,这些工具应该通过调用驱逐 API 而不是直接删除 Pod 或 Deployment 来实现。

例如,kubectl drain 子命令允许你将节点标记为停止服务。当你运行 kubectl drain 时,该工具会尝试驱逐你要停止服务的节点上的所有 Pod。kubectl 代表你提交的驱逐请求可能会被临时拒绝,因此该工具会周期性地重试所有失败的请求,直到目标节点上的所有 Pod 都被终止,或者直到达到一个可配置的超时时间。

PDB 指定了应用可以容忍的最大副本不可用数量,相对于其期望的副本数量。例如,一个具有 .spec.replicas: 5 的 Deployment 应该在任何给定时间有 5 个 Pod。如果其 PDB 允许同时有 4 个 Pod 可用,那么驱逐 API 将允许一次自愿中断一个(而不是两个)Pod。

构成应用的 Pod 组是使用标签选择器指定的,与应用控制器(Deployment、StatefulSet 等)使用的标签选择器相同。

“期望的” Pod 数量是根据管理这些 Pod 的工作负载资源的 .spec.replicas 计算得出的。控制平面通过检查 Pod 的 .metadata.ownerReferences 来发现拥有该 Pod 的工作负载资源。

非自愿中断不能通过 PDB 来阻止;但是它们会计入预算。

由于应用的滚动升级而被删除或不可用的 Pod 确实会计入中断预算,但是工作负载资源(如 Deployment 和 StatefulSet)在执行滚动升级时不受 PDB 的限制。相反,应用更新期间的故障处理在特定工作负载资源的 Spec 中配置。

建议将 PodDisruptionBudget 中的不健康 Pod 驱逐策略设置为 AlwaysAllow,以便在节点排空期间支持驱逐异常行为的应用。默认行为是等待应用 Pod 变为健康后才能继续排空操作。

当使用驱逐 API 驱逐 Pod 时,它会优雅地终止,并遵循其 PodSpecterminationGracePeriodSeconds 的设置。

PodDisruptionBudget 示例

考虑一个有 3 个节点的集群,从 node-1node-3。集群正在运行几个应用。其中一个应用最初有 3 个副本,分别称为 pod-apod-bpod-c。图中还显示了另一个与此无关的、没有 PDB 的 Pod,称为 pod-x。最初,Pod 的分布如下:

node-1node-2node-3
pod-a 可用pod-b 可用pod-c 可用
pod-x 可用

这 3 个 Pod 都是一个 Deployment 的一部分,它们共同拥有一个 PDB,该 PDB 要求这 3 个 Pod 中至少有 2 个始终可用。

例如,假设集群管理员想重启到新的内核版本以修复内核中的 bug。集群管理员首先尝试使用 kubectl drain 命令排空 node-1。该工具尝试驱逐 pod-apod-x。这立即成功了。两个 Pod 同时进入 terminating 状态。这将集群置于以下状态:

node-1 正在排空node-2node-3
pod-a 正在终止pod-b 可用pod-c 可用
pod-x 正在终止

Deployment 注意到其中一个 Pod 正在终止,因此它创建了一个替代 Pod,名为 pod-d。由于 node-1 已被封锁,pod-d 会落在另一个节点上。同时,系统也为 pod-x 创建了一个替代 Pod pod-y

(注意:对于 StatefulSet,pod-a,其名称可能类似于 pod-0,需要完全终止后,其替代 Pod(名称也为 pod-0 但 UID 不同)才能被创建。否则,该示例也适用于 StatefulSet。)

现在集群处于以下状态:

node-1 正在排空node-2node-3
pod-a 正在终止pod-b 可用pod-c 可用
pod-x 正在终止pod-d 正在启动pod-y

某个时刻,这些 Pod 终止了,集群看起来像这样:

node-1 已排空node-2node-3
pod-b 可用pod-c 可用
pod-d 正在启动pod-y

此时,如果一个不耐烦的集群管理员尝试排空 node-2node-3,排空命令将会阻塞,因为 Deployment 只有 2 个可用 Pod,而其 PDB 要求至少有 2 个。过了一段时间,pod-d 变为可用。

现在集群状态如下:

node-1 已排空node-2node-3
pod-b 可用pod-c 可用
pod-d 可用pod-y

现在,集群管理员尝试排空 node-2。排空命令会尝试按某种顺序驱逐这两个 Pod,例如先驱逐 pod-b 再驱逐 pod-d。它会成功驱逐 pod-b。但是,当它尝试驱逐 pod-d 时,将会被拒绝,因为那样会导致 Deployment 只剩下一个可用 Pod。

Deployment 为 pod-b 创建了一个替代 Pod pod-e。由于集群中没有足够的资源来调度 pod-e,排空操作将再次阻塞。集群可能最终处于以下状态:

node-1 已排空node-2node-3无节点
pod-b 正在终止pod-c 可用pod-e Pending
pod-d 可用pod-y

此时,集群管理员需要在集群中添加一个节点以继续升级。

你可以看到 Kubernetes 如何根据以下因素来调整中断发生的速率:

  • 应用所需的副本数量
  • 优雅关闭实例所需的时间
  • 新实例启动所需的时间
  • 控制器类型
  • 集群的资源容量

Pod 中断状况

特性状态: Kubernetes v1.31 [stable](默认启用:true)

添加了一个专用的 Pod DisruptionTarget 状况,用于指示该 Pod 即将因中断而被删除。该状况的 reason 字段额外指示了 Pod 终止的以下原因之一:

调度器抢占
Pod 即将因调度器抢占而被删除,以便容纳具有更高优先级的 Pod。更多信息,请参阅Pod 优先级和抢占
由 Taint Manager 删除
Pod 即将因 Taint Manager(它是 kube-controller-manager 中节点生命周期控制器的一部分)而被删除,原因是 Pod 不容忍某个 NoExecute 污点;参见基于污点的驱逐。
由驱逐 API 驱逐
Pod 已被标记,准备使用 Kubernetes API 驱逐
由 Pod GC 删除
Pod 绑定到了一个不再存在的节点,即将被Pod 垃圾收集器删除。
由 Kubelet 终止
Pod 已被 kubelet 终止,原因可能是节点压力驱逐节点优雅关机或对系统关键 Pod 的抢占。

在所有其他中断场景中,例如由于超出Pod 容器限制而导致的驱逐,Pod 不会收到 DisruptionTarget 状况,因为这些中断很可能是由 Pod 自身引起的,并且重试后会再次发生。

除了清理 Pod,如果 Pod 处于非终止阶段(参见Pod 垃圾收集),Pod 垃圾收集器 (PodGC) 也会将其标记为 Failed。

使用 Job(或 CronJob)时,你可能希望将这些 Pod 中断状况作为 Job 的Pod 失败策略的一部分。

区分集群所有者和应用所有者角色

通常,将集群管理员和应用所有者视为两个独立的、彼此了解有限的角色是很有用的。这种职责分离在以下场景中可能很有意义:

  • 当许多应用团队共享一个 Kubernetes 集群,并且存在自然的职责分工时
  • 当使用第三方工具或服务来自动化集群管理时

PodDisruptionBudget 通过在这些角色之间提供接口来支持这种职责分离。

如果你的组织中没有这种职责分离,你可能不需要使用 PodDisruptionBudget。

如何在集群中执行中断性操作

如果你是集群管理员,并且需要在集群中的所有节点上执行中断性操作,例如节点或系统软件升级,以下是一些选项:

  • 接受升级期间的停机。
  • 故障切换到另一个完整的副本集群。
    • 没有停机,但可能在重复的节点资源和人工协调切换方面成本较高。
  • 编写具备中断容忍能力的应用并使用 PDB。
    • 没有停机。
    • 最小化资源重复。
    • 允许更多的集群管理自动化。
    • 编写具备中断容忍能力的应用很棘手,但容忍自愿中断的工作很大程度上与支持自动扩缩和容忍非自愿中断的工作重叠。

下一步

最后修改于 2024 年 4 月 26 日下午 3:39(太平洋标准时间):cluster-autoscaling -> node-autoscaling 清理 (dc530ffd6a)