中断
本指南适用于希望构建高可用性应用程序的应用程序所有者,因此需要了解 Pod 可能发生的哪些类型的中断。
它也适用于希望执行自动化集群操作(如升级和自动缩放集群)的集群管理员。
自愿和非自愿中断
在有人(人员或控制器)销毁 Pod,或者出现不可避免的硬件或系统软件错误之前,Pod 不会消失。
我们将这些不可避免的情况称为应用程序的非自愿中断。示例包括
- 支持节点的物理机器的硬件故障
- 集群管理员错误删除 VM(实例)
- 云提供商或虚拟机管理程序故障导致 VM 消失
- 内核崩溃
- 由于集群网络分区,节点从集群中消失
- 由于节点资源不足而驱逐 Pod。
除了资源不足的情况外,所有这些情况对大多数用户来说都应该很熟悉;它们不是 Kubernetes 特有的。
我们将其他情况称为自愿中断。这些既包括应用程序所有者发起的动作,也包括集群管理员发起的动作。典型的应用程序所有者操作包括
- 删除管理 Pod 的 deployment 或其他控制器
- 更新导致重启的 deployment 的 Pod 模板
- 直接删除 Pod(例如,意外地)
集群管理员操作包括
- 为了维修或升级而排空节点。
- 从集群中排空节点以缩小集群规模(了解集群自动缩放)。
- 从节点删除 Pod 以允许其他内容适合该节点。
这些操作可能由集群管理员直接执行,或由集群管理员运行的自动化执行,或由您的集群托管提供商执行。
请咨询您的集群管理员或查阅您的云提供商或分发文档,以确定是否为您的集群启用了任何自愿中断源。如果没有启用,您可以跳过创建 Pod 中断预算。
警告
并非所有的自愿中断都受 Pod 中断预算的约束。例如,删除 deployments 或 Pod 会绕过 Pod 中断预算。处理中断
以下是一些缓解非自愿中断的方法
- 确保您的 Pod 请求所需的资源。
- 如果您需要更高的可用性,请复制您的应用程序。(了解如何运行复制的无状态和有状态应用程序。)
- 为了在运行复制的应用程序时获得更高的可用性,请将应用程序分散在机架上(使用反亲和性)或跨区域(如果使用多区域集群)。
自愿中断的频率各不相同。在基本的 Kubernetes 集群上,没有自动的自愿中断(只有用户触发的中断)。但是,您的集群管理员或托管提供商可能会运行一些其他服务,从而导致自愿中断。例如,推出节点软件更新可能会导致自愿中断。此外,某些集群(节点)自动缩放的实现可能会导致自愿中断,以进行碎片整理和压缩节点。您的集群管理员或托管提供商应该记录了预期出现的自愿中断的级别(如果有)。某些配置选项(例如在 Pod 规范中使用 PriorityClasses)也可能导致自愿(和非自愿)中断。
Pod 中断预算
Kubernetes v1.21 [稳定]
Kubernetes 提供了相关功能,可帮助您即使在频繁引入自愿中断的情况下也能运行高可用性应用程序。
作为应用程序所有者,您可以为每个应用程序创建一个 PodDisruptionBudget (PDB)。PDB 限制了来自自愿中断的同时处于关闭状态的复制应用程序的 Pod 数量。例如,基于仲裁的应用程序希望确保运行的副本数永远不会低于仲裁所需的数量。Web 前端可能希望确保服务负载的副本数永远不会低于总数的某个百分比。
集群管理器和托管提供商应该使用尊重 PodDisruptionBudgets 的工具,通过调用驱逐 API而不是直接删除 Pod 或 deployments。
例如,kubectl drain
子命令允许您将节点标记为即将停止服务。当您运行 kubectl drain
时,该工具会尝试驱逐您要停止服务的节点上的所有 Pod。kubectl
代表您提交的驱逐请求可能会暂时被拒绝,因此该工具会定期重试所有失败的请求,直到目标节点上的所有 Pod 都终止,或者直到达到可配置的超时时间为止。
PDB 指定了应用程序可以容忍的副本数量(相对于它应该拥有的数量)。例如,.spec.replicas: 5
的 deployment 应该在任何给定时间有 5 个 Pod。如果其 PDB 允许一次有 4 个,则驱逐 API 将允许一次自愿中断一个(而不是两个)Pod。
组成应用程序的 Pod 组使用标签选择器指定,该标签选择器与应用程序的控制器(deployment、stateful-set 等)使用的标签选择器相同。
Pod 的“预期”数量是从管理这些 Pod 的工作负载资源的 .spec.replicas
计算得出的。控制平面通过检查 Pod 的 .metadata.ownerReferences
来发现所属的工作负载资源。
非自愿中断不能被 PDB 阻止;但是它们会影响预算。
由于对应用程序进行滚动升级而删除或不可用的 Pod 会影响中断预算,但在进行滚动升级时,工作负载资源(例如 Deployment 和 StatefulSet)不受 PDB 的限制。相反,应用程序更新期间的故障处理在特定工作负载资源的 spec 中配置。
建议将 PodDisruptionBudgets 的 AlwaysAllow
不健康 Pod 驱逐策略 设置为 AlwaysAllow
,以支持在节点排空期间驱逐行为不当的应用程序。默认行为是等待应用程序 Pod 变为健康状态,然后才能继续排空操作。
当使用驱逐 API 驱逐 Pod 时,Pod 会被优雅地终止,并遵循其 PodSpec 中的 terminationGracePeriodSeconds
设置。
PodDisruptionBudget 示例
假设有一个包含 3 个节点的集群,分别为 node-1
到 node-3
。该集群正在运行多个应用程序。其中一个应用程序最初有 3 个副本,分别称为 pod-a
、pod-b
和 pod-c
。另外还有一个不相关的、没有 PDB 的 Pod,称为 pod-x
。最初,这些 Pod 的布局如下:
node-1 | node-2 | node-3 |
---|---|---|
pod-a 可用 | pod-b 可用 | pod-c 可用 |
pod-x 可用 |
所有 3 个 Pod 都属于一个 Deployment,它们共同拥有一个 PDB,该 PDB 要求始终至少有 3 个 Pod 中的 2 个处于可用状态。
例如,假设集群管理员想要重启到新的内核版本以修复内核中的一个错误。集群管理员首先尝试使用 kubectl drain
命令排空 node-1
。该工具尝试驱逐 pod-a
和 pod-x
。此操作立即成功。两个 Pod 同时进入 terminating
状态。这使集群处于以下状态:
node-1 正在排空 | node-2 | node-3 |
---|---|---|
pod-a 正在终止 | pod-b 可用 | pod-c 可用 |
pod-x 正在终止 |
Deployment 注意到一个 Pod 正在终止,因此它创建了一个名为 pod-d
的替换 Pod。由于 node-1
已被隔离,因此它落在另一个节点上。另外,pod-y
也被创建为 pod-x
的替换 Pod。
(注意:对于 StatefulSet,pod-a
,它会被称为类似于 pod-0
的名称,需要在其替换 Pod 完全终止之前完成终止。替换 Pod 也称为 pod-0
,但具有不同的 UID。否则,该示例也适用于 StatefulSet。)
现在集群处于以下状态:
node-1 正在排空 | node-2 | node-3 |
---|---|---|
pod-a 正在终止 | pod-b 可用 | pod-c 可用 |
pod-x 正在终止 | pod-d 正在启动 | pod-y |
在某个时间点,Pod 终止,集群看起来像这样:
node-1 已排空 | node-2 | node-3 |
---|---|---|
pod-b 可用 | pod-c 可用 | |
pod-d 正在启动 | pod-y |
此时,如果急躁的集群管理员尝试排空 node-2
或 node-3
,则排空命令将阻塞,因为 Deployment 只有 2 个可用的 Pod,而其 PDB 要求至少 2 个。经过一段时间后,pod-d
变为可用状态。
现在集群状态如下:
node-1 已排空 | node-2 | node-3 |
---|---|---|
pod-b 可用 | pod-c 可用 | |
pod-d 可用 | pod-y |
现在,集群管理员尝试排空 node-2
。排空命令将尝试以某种顺序驱逐两个 Pod,例如先驱逐 pod-b
,然后再驱逐 pod-d
。它将成功驱逐 pod-b
。但是,当它尝试驱逐 pod-d
时,它将被拒绝,因为这将导致 Deployment 只有一个可用的 Pod。
Deployment 创建了一个名为 pod-e
的 pod-b
替换 Pod。由于集群中没有足够的资源来调度 pod-e
,因此排空将再次阻塞。集群可能会最终处于以下状态:
node-1 已排空 | node-2 | node-3 | 无节点 |
---|---|---|---|
pod-b 正在终止 | pod-c 可用 | pod-e 等待中 | |
pod-d 可用 | pod-y |
此时,集群管理员需要向集群添加一个节点才能继续升级。
您可以了解 Kubernetes 如何根据以下因素调整中断发生的速率:
- 应用程序需要的副本数量
- 优雅关闭实例所需的时间
- 启动新实例所需的时间
- 控制器的类型
- 集群的资源容量
Pod 中断条件
Kubernetes v1.31 [稳定]
(默认启用:true)添加了一个专用的 Pod DisruptionTarget
条件,以指示 Pod 即将由于中断而被删除。条件的 reason
字段还指示 Pod 终止的以下原因之一:
PreemptionByScheduler
- Pod 由于调度程序抢占而被删除,以便容纳具有更高优先级的新 Pod。有关更多信息,请参阅Pod 优先级抢占。
DeletionByTaintManager
- Pod 由于 Pod 不容忍的
NoExecute
污点而将被污点管理器(它是kube-controller-manager
中的节点生命周期控制器的一部分)删除;请参阅基于污点的驱逐。 EvictionByEvictionAPI
- Pod 已被标记为使用 Kubernetes API 进行驱逐。
DeletionByPodGC
- 绑定到不再存在的节点的 Pod 将由 Pod 垃圾回收删除。
TerminationByKubelet
- Pod 已被 kubelet 终止,原因可能是节点压力驱逐、优雅节点关闭,或者为了系统关键 Pod 而进行的抢占。
在所有其他中断场景中,例如由于超出Pod 容器限制而导致的驱逐,Pod 不会收到 DisruptionTarget
条件,因为中断可能是由 Pod 引起的,并且会在重试时再次发生。
注意
Pod 中断可能会被中断。控制平面可能会重新尝试继续中断同一 Pod,但这不保证。因此,DisruptionTarget
条件可能会添加到 Pod,但该 Pod 可能实际上不会被删除。在这种情况下,一段时间后,Pod 中断条件将被清除。在清理 Pod 的同时,如果 Pod 处于非终止阶段,Pod 垃圾回收器 (PodGC) 也会将它们标记为失败(另请参阅Pod 垃圾回收)。
当使用 Job(或 CronJob)时,您可能希望将这些 Pod 中断条件用作 Job 的Pod 失败策略的一部分。
分离集群所有者和应用程序所有者角色
通常,将集群管理器和应用程序所有者视为具有彼此有限了解的独立角色是有用的。在以下情况下,这种职责分离可能是有意义的:
- 当有多个应用程序团队共享一个 Kubernetes 集群时,并且存在自然的角色专业化
- 当使用第三方工具或服务来自动化集群管理时
PodDisruptionBudgets 通过提供角色之间的接口来支持这种角色分离。
如果您的组织中没有这种职责分离,则可能不需要使用 PodDisruptionBudgets。
如何在集群上执行中断操作
如果您是集群管理员,并且需要在集群中的所有节点上执行中断操作,例如节点或系统软件升级,以下是一些选项:
- 在升级期间接受停机。
- 故障转移到另一个完整的副本集群。
- 没有停机时间,但对于重复的节点和协调切换的人力来说,可能代价高昂。
- 编写容忍中断的应用程序并使用 PDB。
- 没有停机时间。
- 最小的资源重复。
- 允许更多集群管理自动化。
- 编写容忍中断的应用程序很棘手,但是容忍自愿中断的工作与支持自动缩放和容忍非自愿中断的工作很大程度上重叠。
下一步是什么
按照步骤配置 Pod 中断预算来保护您的应用程序。
了解有关排空节点的更多信息
了解有关更新 Deployment 的信息,包括在滚动更新期间保持其可用性的步骤。