Pod 优先级和抢占
Kubernetes v1.14 [稳定]
Pod 可以具有*优先级*。优先级表示 Pod 相对于其他 Pod 的重要性。如果无法调度 Pod,则调度程序会尝试抢占(驱逐)优先级较低的 Pod,以便能够调度待处理的 Pod。
警告
在并非所有用户都可信的集群中,恶意用户可能会创建具有最高优先级的 Pod,从而导致其他 Pod 被驱逐/无法调度。管理员可以使用 ResourceQuota 来防止用户创建具有高优先级的 Pod。
有关详细信息,请参阅默认情况下限制优先级类的使用。
如何使用优先级和抢占
要使用优先级和抢占
添加一个或多个PriorityClass。
创建 Pod 时,将
priorityClassName
设置为已添加的 PriorityClass 之一。当然,您不需要直接创建 Pod;通常,您需要将priorityClassName
添加到集合对象(如 Deployment)的 Pod 模板中。
继续阅读以获取有关这些步骤的更多信息。
注意
Kubernetes 已经附带了两个 PriorityClass:system-cluster-critical
和 system-node-critical
。这些是常见的类,用于确保始终首先调度关键组件。PriorityClass
PriorityClass 是一个非命名空间对象,它定义了从优先级类名称到优先级整数值的映射。名称在 PriorityClass 对象元数据的 name
字段中指定。值在必需的 value
字段中指定。值越高,优先级越高。PriorityClass 对象的名称必须是有效的DNS 子域名,并且不能以 system-
为前缀。
PriorityClass 对象可以具有任何小于或等于 10 亿的 32 位整数值。这意味着 PriorityClass 对象的值范围是从 -2147483648 到 1000000000(含)。较大的数字保留给表示关键系统 Pod 的内置 PriorityClass。集群管理员应为他们想要的每个此类映射创建一个 PriorityClass 对象。
PriorityClass 还有两个可选字段:globalDefault
和 description
。globalDefault
字段指示应将此 PriorityClass 的值用于没有 priorityClassName
的 Pod。系统中只能存在一个 globalDefault
设置为 true 的 PriorityClass。如果没有设置 globalDefault
的 PriorityClass,则没有 priorityClassName
的 Pod 的优先级为零。
description
字段是一个任意字符串。它旨在告诉集群用户何时应使用此 PriorityClass。
关于 PodPriority 和现有集群的注意事项
如果您升级没有此功能的现有集群,则现有 Pod 的优先级实际上为零。
添加
globalDefault
设置为true
的 PriorityClass 不会更改现有 Pod 的优先级。此类 PriorityClass 的值仅用于在添加 PriorityClass 后创建的 Pod。如果删除 PriorityClass,则使用已删除 PriorityClass 名称的现有 Pod 将保持不变,但您无法创建更多使用已删除 PriorityClass 名称的 Pod。
PriorityClass 示例
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
非抢占式 PriorityClass
Kubernetes v1.24 [稳定]
preemptionPolicy: Never
的 Pod 将被放置在调度队列中,位于优先级较低的 Pod 之前,但它们不能抢占其他 Pod。等待调度的非抢占式 Pod 将保留在调度队列中,直到有足够的可用资源并且可以调度它为止。与其他 Pod 一样,非抢占式 Pod 也受调度程序退避的约束。这意味着,如果调度程序尝试调度这些 Pod 但无法调度,则会以较低的频率重试,从而允许在它们之前调度优先级较低的其他 Pod。
非抢占式 Pod 仍然可能会被其他高优先级 Pod 抢占。
preemptionPolicy
默认为 PreemptLowerPriority
,这将允许该 PriorityClass 的 Pod 抢占优先级较低的 Pod(与现有默认行为一样)。如果 preemptionPolicy
设置为 Never
,则该 PriorityClass 中的 Pod 将是非抢占式的。
一个用例示例是数据科学工作负载。用户可以提交他们希望优先于其他工作负载的作业,但不希望通过抢占正在运行的 Pod 来丢弃现有工作。具有 preemptionPolicy: Never
的高优先级作业将在其他排队的 Pod 之前进行调度,只要有足够的集群资源“自然地”可用即可。
非抢占式 PriorityClass 示例
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "This priority class will not cause other pods to be preempted."
Pod 优先级
拥有一个或多个 PriorityClass 后,您可以创建 Pod,并在其规范中指定其中一个 PriorityClass 名称。优先级准入控制器使用 priorityClassName
字段并填充优先级的整数值。如果未找到优先级类,则拒绝 Pod。
以下 YAML 是使用前面示例中创建的 PriorityClass 的 Pod 配置示例。优先级准入控制器检查规范并将 Pod 的优先级解析为 1000000。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
Pod 优先级对调度顺序的影响
启用 Pod 优先级后,调度程序会按优先级对待处理的 Pod 进行排序,并且待处理的 Pod 在调度队列中位于优先级较低的其他待处理 Pod 之前。因此,如果满足调度要求,则优先级较高的 Pod 可能会比优先级较低的 Pod 更早调度。如果无法调度此类 Pod,则调度程序将继续并尝试调度其他优先级较低的 Pod。
抢占
创建 Pod 后,它们会进入队列并等待调度。调度程序从队列中选择一个 Pod 并尝试将其调度到节点上。如果没有找到满足 Pod 所有指定要求的节点,则会为待处理的 Pod 触发抢占逻辑。我们将待处理的 Pod 称为 P。抢占逻辑尝试查找一个节点,在该节点上删除一个或多个优先级低于 P 的 Pod 将使 P 能够在该节点上调度。如果找到这样的节点,则会从该节点中驱逐一个或多个优先级较低的 Pod。Pod 消失后,即可在该节点上调度 P。
用户公开信息
当 Pod P 抢占节点 N 上的一个或多个 Pod 时,Pod P 状态的 nominatedNodeName
字段将设置为节点 N 的名称。此字段可帮助调度程序跟踪为 Pod P 保留的资源,并向用户提供有关集群中抢占的信息。
请注意,Pod P 不一定调度到“提名的节点”。调度程序始终会在迭代任何其他节点之前先尝试“提名的节点”。受害者 Pod 被抢占后,它们会获得宽限期。如果在调度程序等待受害者 Pod 终止时另一个节点可用,则调度程序可以使用另一个节点来调度 Pod P。因此,Pod 规范的 nominatedNodeName
和 nodeName
并不总是相同的。此外,如果调度程序抢占节点 N 上的 Pod,但随后出现比 Pod P 优先级更高的 Pod,则调度程序可能会将节点 N 分配给新的优先级更高的 Pod。在这种情况下,调度程序会清除 Pod P 的 nominatedNodeName
。通过这样做,调度程序使 Pod P 有资格抢占另一个节点上的 Pod。
抢占的限制
抢占受害者的宽限期终止
当 Pod 被抢占时,受害者会获得其宽限期。它们有足够的时间完成工作并退出。如果它们没有退出,则会被杀死。此宽限期会在调度程序抢占 Pod 的时间点与待处理 Pod (P) 可以在节点 (N) 上调度的时间之间创建一个时间差。在此期间,调度程序会继续调度其他待处理的 Pod。随着受害者退出或被终止,调度程序会尝试调度待处理队列中的 Pod。因此,在调度程序抢占受害者的时间点与 Pod P 被调度的时间之间通常存在时间差。为了最大程度地减少这种差距,可以将低优先级 Pod 的宽限期设置为零或一个较小的数字。
支持 PodDisruptionBudget,但不保证
PodDisruptionBudget (PDB) 允许应用程序所有者限制因自愿中断而同时关闭的复制应用程序 Pod 的数量。Kubernetes 在抢占 Pod 时支持 PDB,但尽力遵守 PDB。调度器会尝试查找其 PDB 不因抢占而违反的受害者,但如果找不到此类受害者,抢占仍会发生,并且尽管违反了其 PDB,也会删除优先级较低的 Pod。
低优先级 Pod 上的 Pod 间亲和性
仅当此问题的答案为“是”时,才会考虑对节点进行抢占:“如果从节点中删除所有优先级低于待处理 Pod 的 Pod,是否可以在节点上调度待处理 Pod?”
注意
抢占不一定会删除所有优先级较低的 Pod。如果可以通过删除少于所有低优先级 Pod 来调度待处理 Pod,则只会删除一部分低优先级 Pod。即便如此,前面问题的答案也必须是“是”。如果答案为“否”,则不会考虑对节点进行抢占。如果待处理 Pod 与节点上的一个或多个低优先级 Pod 之间存在 Pod 间 亲和性,则在缺少这些低优先级 Pod 的情况下,无法满足 Pod 间亲和性规则。在这种情况下,调度器不会抢占节点上的任何 Pod。相反,它会寻找另一个节点。调度器可能会找到合适的节点,也可能找不到。不能保证可以调度待处理 Pod。
我们针对此问题的建议解决方案是仅针对优先级相等或更高的 Pod 创建 Pod 间亲和性。
跨节点抢占
假设正在考虑对节点 N 进行抢占,以便可以在 N 上调度待处理 Pod P。只有在抢占另一个节点上的 Pod 时,P 才可能在 N 上变得可行。下面是一个例子
- 正在考虑将 Pod P 用于节点 N。
- Pod Q 正在与节点 N 位于同一区域的另一个节点上运行。
- Pod P 与 Pod Q 具有区域范围的反亲和性(
topologyKey: topology.kubernetes.io/zone
)。 - 在区域中,Pod P 与其他 Pod 之间没有其他反亲和性情况。
- 为了在节点 N 上调度 Pod P,可以抢占 Pod Q,但调度器不会执行跨节点抢占。因此,Pod P 将被视为在节点 N 上不可调度。
如果从其节点中删除 Pod Q,则 Pod 反亲和性冲突将消失,并且 Pod P 可能可以在节点 N 上调度。
如果需求足够大,并且我们找到性能合理的算法,我们可能会考虑在未来版本中添加跨节点抢占。
故障排除
Pod 优先级和抢占可能会产生不希望有的副作用。以下是潜在问题的一些示例以及处理这些问题的方法。
不必要地抢占 Pod
抢占会从资源压力下的集群中删除现有 Pod,以便为优先级更高的待处理 Pod 腾出空间。如果您错误地为某些 Pod 指定了高优先级,则这些意外的高优先级 Pod 可能会导致集群中出现抢占。Pod 优先级是通过在 Pod 的规范中设置 priorityClassName
字段来指定的。然后,将解析优先级的整数值并将其填充到 podSpec
的 priority
字段中。
要解决此问题,您可以更改这些 Pod 的 priorityClassName
以使用优先级较低的类,或者将该字段留空。默认情况下,空的 priorityClassName
将解析为零。
抢占 Pod 时,将为被抢占的 Pod 记录事件。仅当集群没有足够的资源用于 Pod 时,才会发生抢占。在这种情况下,仅当待处理 Pod(抢占者)的优先级高于受害者 Pod 时,才会发生抢占。如果没有待处理 Pod,或者待处理 Pod 的优先级等于或低于受害者,则不得发生抢占。如果在这种情况下载生抢占,请提交问题。
Pod 被抢占,但未调度抢占者
当 Pod 被抢占时,它们会收到其请求的优雅终止期限,默认情况下为 30 秒。如果受害者 Pod 在此期限内未终止,则会被强制终止。所有受害者都消失后,就可以调度抢占者 Pod。
当抢占者 Pod 等待受害者消失时,可能会创建一个适合同一节点的优先级更高的 Pod。在这种情况下,调度器将调度优先级更高的 Pod,而不是抢占者。
这是预期行为:优先级较高的 Pod 应取代优先级较低的 Pod。
在优先级较低的 Pod 之前抢占优先级较高的 Pod
调度器会尝试查找可以运行待处理 Pod 的节点。如果未找到节点,则调度器会尝试从任意节点中删除优先级较低的 Pod,以便为待处理 Pod 腾出空间。如果具有低优先级 Pod 的节点无法运行待处理 Pod,则调度器可以选择另一个具有更高优先级 Pod(与其他节点上的 Pod 相比)的节点进行抢占。受害者的优先级必须仍然低于抢占者 Pod。
当有多个节点可用于抢占时,调度器会尝试选择具有一组优先级最低的 Pod 的节点。但是,如果此类 Pod 具有 PodDisruptionBudget,并且如果抢占它们将违反该预算,则调度器可以选择另一个具有更高优先级 Pod 的节点。
当存在多个节点用于抢占并且上述情况均不适用时,调度器将选择优先级最低的节点。
Pod 优先级与服务质量之间的交互
Pod 优先级和 QoS 类 是两个正交特性,它们之间几乎没有交互,并且对根据 Pod 的 QoS 类设置 Pod 的优先级没有默认限制。调度器的抢占逻辑在选择抢占目标时不考虑 QoS。抢占会考虑 Pod 优先级,并尝试选择一组优先级最低的目标。仅当删除优先级最低的 Pod 不足以让调度器调度抢占者 Pod,或者优先级最低的 Pod 受 PodDisruptionBudget
保护时,才会考虑将优先级较高的 Pod 用于抢占。
kubelet 使用优先级来确定 节点压力驱逐 的 Pod 顺序。您可以使用 QoS 类来估计 Pod 最有可能被驱逐的顺序。kubelet 根据以下因素对 Pod 进行驱逐排名
- 不足资源的使用量是否超过请求量
- Pod 优先级
- 资源使用量相对于请求量的多少
有关更多详细信息,请参阅kubelet 驱逐的 Pod 选择。
当 Pod 的使用量未超过其请求量时,kubelet 节点压力驱逐不会驱逐 Pod。如果优先级较低的 Pod 未超过其请求量,则不会被驱逐。可能会驱逐另一个超过其请求量的优先级较高的 Pod。
后续步骤
- 阅读有关结合 PriorityClasses 使用 ResourceQuotas 的信息:默认情况下限制优先级类的消耗
- 了解 Pod 中断
- 了解 API 发起的驱逐
- 了解 节点压力驱逐