Pod 优先级和抢占
Kubernetes v1.14 [稳定]Pod 可以具有优先级。优先级表示 Pod 相对于其他 Pod 的重要性。如果 Pod 无法被调度,调度器会尝试抢占(驱逐)优先级较低的 Pod,以便调度待定的 Pod。
警告
在并非所有用户都受信任的集群中,恶意用户可能会创建具有最高优先级的 Pod,导致其他 Pod 被驱逐/无法被调度。管理员可以使用 ResourceQuota 来阻止用户创建具有高优先级的 Pod。
有关详细信息,请参阅默认限制优先级类消耗。
如何使用优先级和抢占
要使用优先级和抢占
添加一个或多个PriorityClasses。
创建 Pod 时,将
priorityClassName设置为添加的 PriorityClasses 之一。当然,你不需要直接创建 Pod;通常,你会将priorityClassName添加到 Deployment 等集合对象的 Pod 模板中。
请继续阅读以获取有关这些步骤的更多信息。
注意
Kubernetes 已经附带了两个 PriorityClasses:system-cluster-critical 和 system-node-critical。这些是常见的类,用于确保关键组件始终首先被调度。PriorityClass
PriorityClass 是一个非命名空间对象,定义了从优先级类名称到优先级整数值的映射。该名称在 PriorityClass 对象的元数据的 name 字段中指定。该值在必需的 value 字段中指定。值越高,优先级越高。PriorityClass 对象的名称必须是有效的 DNS 子域名,并且不能以 system- 为前缀。
PriorityClass 对象可以具有小于或等于 10 亿的任何 32 位整数值。这意味着 PriorityClass 对象的值范围为 -2147483648 到 1000000000(包括)。较大的数字保留给表示关键系统 Pod 的内置 PriorityClasses。集群管理员应为他们想要的每个此类映射创建一个 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 优先级
在你拥有一个或多个 PriorityClasses 后,你可以创建 Pod,并在它们的规范中指定这些 PriorityClass 名称之一。优先级准入控制器使用 priorityClassName 字段,并填充优先级的整数值。如果找不到优先级类,则 Pod 将被拒绝。
以下 YAML 是 Pod 配置的示例,该配置使用前面示例中创建的 PriorityClass。优先级准入控制器检查规范并将 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 进行抢占,以便将待调度的 Pod P 调度到 N 上。只有在另一个节点上的 Pod 被抢占的情况下,P 才可能在 N 上可行。以下是一个示例:
- 正在考虑将 Pod P 调度到节点 N 上。
- Pod Q 正在与节点 N 位于同一区域的另一个节点上运行。
- Pod P 与 Pod Q 具有区域范围的反亲和性(
topologyKey: topology.kubernetes.io/zone)。 - Pod P 和该区域中的其他 Pod 之间没有其他反亲和性案例。
- 为了将 Pod P 调度到节点 N 上,可以抢占 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 类 是两个正交的功能,它们之间的交互很少,并且基于其 QoS 类设置 Pod 的优先级没有任何默认限制。调度器的抢占逻辑在选择抢占目标时不考虑 QoS。抢占会考虑 Pod 优先级,并尝试选择一组优先级最低的目标。只有在移除优先级最低的 Pod 不足以让调度器调度抢占者 Pod 时,或者如果优先级最低的 Pod 受 PodDisruptionBudget 保护时,才会考虑抢占更高优先级的 Pod。
kubelet 使用优先级来确定 节点压力驱逐 的 Pod 顺序。您可以使用 QoS 类来估计 Pod 最有可能被驱逐的顺序。kubelet 根据以下因素对 Pod 进行驱逐排名:
- 资源饥饿使用量是否超出请求
- Pod 优先级
- 相对于请求的资源使用量
有关更多详细信息,请参阅kubelet 驱逐的 Pod 选择。
当 Pod 的使用量未超过其请求时,kubelet 节点压力驱逐不会驱逐 Pod。如果优先级较低的 Pod 未超出其请求,则不会被驱逐。另一个超出其请求的优先级较高的 Pod 可能会被驱逐。
下一步
- 阅读有关将 ResourceQuotas 与 PriorityClasses 结合使用的信息:默认限制优先级类消耗
- 了解Pod 中断
- 了解API 发起的驱逐
- 了解节点压力驱逐