Kubernetes v1.14 [稳定]Pod 可以有优先级。优先级表示 Pod 相对于其他 Pod 的重要性。如果 Pod 无法被调度,调度程序会尝试抢占(驱逐)低优先级的 Pod,以使待处理的 Pod 有可能被调度。
在并非所有用户都受信任的集群中,恶意用户可能会创建具有最高优先级的 Pod,导致其他 Pod 被驱逐或无法被调度。管理员可以使用 ResourceQuota 来防止用户创建具有高优先级的 Pod。
有关详情,请参阅默认限制优先级类消费。
使用优先级和抢占的方法:
添加一个或多个 PriorityClasses。
创建 Pod,并将 priorityClassName 设置为其中一个已添加的 PriorityClass。当然,你无需直接创建 Pod;通常你会将 priorityClassName 添加到集合对象(如 Deployment)的 Pod 模板中。
继续阅读以了解有关这些步骤的更多信息。
system-cluster-critical 和 system-node-critical。这些是常见的类,用于确保关键组件始终优先被调度。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。
如果你升级了一个没有此功能的现有集群,现有 Pod 的优先级实际上为零。
添加一个 globalDefault 设置为 true 的 PriorityClass 不会改变现有 Pod 的优先级。此类 PriorityClass 的值仅适用于在 PriorityClass 添加之后创建的 Pod。
如果你删除了一个 PriorityClass,使用该已删除 PriorityClass 名称的现有 Pod 将保持不变,但你无法创建更多使用该已删除 PriorityClass 名称的 Pod。
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."
Kubernetes v1.24 [稳定]preemptionPolicy: Never 的 Pod 将被放置在调度队列中,排在较低优先级的 Pod 之前,但它们不能抢占其他 Pod。正在等待被调度的非抢占式 Pod 将保留在调度队列中,直到有足够的资源空闲,且该 Pod 可以被调度。非抢占式 Pod 和其他 Pod 一样,受调度程序回退机制的约束。这意味着如果调度程序尝试调度这些 Pod 但未能成功,它们将以较低的频率进行重试,从而允许优先级较低的其他 Pod 在它们之前被调度。
非抢占式 Pod 仍可能被其他高优先级 Pod 抢占。
preemptionPolicy 默认为 PreemptLowerPriority,它将允许该 PriorityClass 的 Pod 抢占低优先级 Pod(这是现有的默认行为)。如果 preemptionPolicy 设置为 Never,则该 PriorityClass 中的 Pod 将是非抢占式的。
一个示例用例是数据科学工作负载。用户可能提交一个他们希望优先于其他工作负载执行的作业,但不希望通过抢占正在运行的 Pod 来丢弃现有工作。设置了 preemptionPolicy: Never 的高优先级作业将在集群资源“自然”空闲且足够时,先于其他排队的 Pod 被调度。
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."
在你拥有一个或多个 PriorityClasses 后,你可以创建在规范中指定这些 PriorityClass 名称之一的 Pod。优先级准入控制器会使用 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 为 P。抢占逻辑会尝试找到一个节点,在该节点上移除一个或多个优先级低于 P 的 Pod 将使 P 能够被调度到该节点上。如果找到了这样的节点,一个或多个低优先级 Pod 会从节点上被驱逐。在 Pod 消失后,P 就可以被调度到该节点上。
当 Pod P 抢占节点 N 上的一个或多个 Pod 时,Pod P 状态的 nominatedNodeName 字段被设置为节点 N 的名称。该字段有助于调度程序跟踪为 Pod P 保留的资源,并向用户提供有关其集群中抢占的信息。
请注意,Pod P 不一定会被调度到“预留节点(nominated Node)”。调度程序在遍历任何其他节点之前,总是会尝试“预留节点”。在受害者 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 (PDB) 允许应用程序所有者限制由于自愿中断而同时宕机的副本应用程序的 Pod 数量。Kubernetes 在抢占 Pod 时支持 PDB,但对 PDB 的遵守是“尽力而为”的。调度程序会尝试寻找抢占后不会违反 PDB 的受害者,但如果找不到此类受害者,抢占仍将发生,低优先级 Pod 即使违反 PDB 也将被移除。
仅当以下问题的回答为“是”时,才会考虑将节点用于抢占:“如果从节点中移除了所有优先级低于挂起 Pod 的 Pod,该挂起的 Pod 是否能被调度到该节点上?”
如果挂起的 Pod 对节点上的一个或多个低优先级 Pod 具有 Pod 间亲和性,那么在这些低优先级 Pod 不存在的情况下,Pod 间亲和性规则将无法满足。在这种情况下,调度程序不会抢占该节点上的任何 Pod。相反,它会寻找另一个节点。调度程序可能会也可能不会找到合适的节点。无法保证挂起的 Pod 一定能被调度。
我们针对此问题的推荐解决方案是仅创建指向同等或更高优先级 Pod 的 Pod 间亲和性。
假设正在考虑对节点 N 进行抢占,以便挂起的 Pod P 可以被调度到 N 上。P 可能只有在另一个节点上的 Pod 被抢占后才能在 N 上变得可行。这是一个示例:
topologyKey: topology.kubernetes.io/zone)。如果从其节点中移除了 Pod Q,则 Pod 反亲和性违规将消失,Pod P 可能就可以被调度到节点 N 上。
如果未来版本有足够的需求,且我们找到了一种性能合理的算法,我们可能会考虑添加跨节点抢占。
Pod 优先级和抢占可能会产生意想不到的副作用。以下是一些潜在问题及应对方法的示例。
抢占会在资源压力下从集群中移除现有 Pod,以便为更高优先级的挂起 Pod 腾出空间。如果你错误地给了某些 Pod 高优先级,这些无意中获得高优先级的 Pod 可能会导致集群中发生抢占。Pod 优先级通过在 Pod 规范中设置 priorityClassName 字段来指定。优先级的整数值随后会被解析并填充到 podSpec 的 priority 字段中。
要解决此问题,你可以将这些 Pod 的 priorityClassName 更改为较低的优先级类,或者将该字段留空。空的 priorityClassName 默认会被解析为零。
当 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 具有 PodDisruptionBudget,且如果在抢占时会遭到破坏,则调度程序可能会选择另一个带有更高优先级 Pod 的节点。
当存在多个可用于抢占的节点且上述场景都不适用时,调度程序会选择优先级最低的节点。
Pod 优先级和 QoS 类 是两个正交的特性,它们之间几乎没有相互作用,并且没有基于 QoS 类设置 Pod 优先级的默认限制。调度程序的抢占逻辑在选择抢占目标时不考虑 QoS。抢占考虑的是 Pod 优先级,并尝试选择一组优先级最低的目标。仅当移除最低优先级 Pod 不足以使调度程序调度抢占者 Pod,或者最低优先级 Pod 受到 PodDisruptionBudget 保护时,才会考虑抢占优先级更高的 Pod。
kubelet 使用优先级来确定节点压力驱逐的 Pod 顺序。你可以使用 QoS 类来估算 Pod 最有可能被驱逐的顺序。kubelet 根据以下因素对 Pod 进行驱逐排序:
更多详情,请参阅用于 kubelet 驱逐的 Pod 选择。
kubelet 的节点压力驱逐不会在 Pod 使用量不超过请求时将其驱逐。如果优先级较低的 Pod 没有超过其请求,它就不会被驱逐。而另一个超过其请求的高优先级 Pod 可能会被驱逐。