# k8s Pod 优先级和抢占
k8s 1.8 及其以后的版本中可以指定 Pod 的优先级。优先级表明了一个 Pod 相对于其它 Pod 的重要性。当 Pod 无法被调度时,scheduler 会尝试抢占(驱逐)低优先级的 Pod,使得这些挂起的 pod 可以被调度。在 k8s 未来的发布版本中,优先级也会影响节点上资源回收的排序。
注: 抢占不遵循 PodDisruptionBudget;更多详细的信息,请查看 限制部分。
# 怎么样使用优先级和抢占
想要在 k8s 1.8 版本中使用优先级和抢占,请参考如下步骤:
- 启用功能。
- 增加一个或者多个 PriorityClass。
- 创建拥有字段 PriorityClassName 的 Pod,该字段的值选取上面增加的 PriorityClass。当然,您没有必要直接创建 pod,通常您可以把 PriorityClassName 增加到类似 Deployment 这样的集合对象的 Pod 模板中。
以下章节提供了有关这些步骤的详细信息。
# 启用优先级和抢占
k8s 1.8 版本默认没有开启 Pod 优先级和抢占。为了启用该功能,需要在 API server 和 scheduler 的启动参数中设置:
--feature-gates=PodPriority=true
在 API server 中还需要设置如下启动参数:
--runtime-config=scheduling.k8s.io/v1alpha1=true
功能启用后,您能创建 PriorityClass,也能创建使用 PriorityClassName 集的 Pod。
如果在尝试该功能后想要关闭它,那么您可以把 PodPriority 这个命令行标识从启动参数中移除,或者将它的值设置为 false,然后再重启 API server 和 scheduler。功能关闭后,原来的 Pod 会保留它们的优先级字段,但是优先级字段的内容会被忽略,抢占不会生效,在新的 pod 创建时,您也不能设置 PriorityClassName。
# PriorityClass
PriorityClass 是一个不受命名空间约束的对象,它定义了优先级类名跟优先级整数值的映射。它的名称通过 PriorityClass 对象 metadata 中的 name 字段指定。值在必选的 value 字段中指定。值越大,优先级越高。
PriorityClass 对象的值可以是小于或者等于 10 亿的 32 位任意整数值。更大的数值被保留给那些通常不应该取代或者驱逐的关键的系统级 Pod 使用。集群管理员应该为它们想要的每个此类映射创建一个 PriorityClass 对象。
PriorityClass 还有两个可选的字段:globalDefault 和 description。globalDefault 表示 PriorityClass 的值应该给那些没有设置 PriorityClassName 的 Pod 使用。整个系统只能存在一个 globalDefault 设置为 true 的 PriorityClass。如果没有任何 globalDefault 为 true 的 PriorityClass 存在,那么,那些没有设置 PriorityClassName 的 Pod 的优先级将为 0。
description 字段的值可以是任意的字符串。它向所有集群用户描述应该在什么时候使用这个 PriorityClass。
注 1:如果您升级已经存在的集群环境,并且启用了该功能,那么,那些已经存在系统里面的 Pod 的优先级将会设置为 0。
注 2:此外,将一个 PriorityClass 的 globalDefault 设置为 true,不会改变系统中已经存在的 Pod 的优先级。也就是说,PriorityClass 的值只能用于在 PriorityClass 添加之后创建的那些 Pod 当中。
注 3:如果您删除一个 PriorityClass,那些使用了该 PriorityClass 的 Pod 将会保持不变,但是,该 PriorityClass 的名称不能在新创建的 Pod 里面使用。
# PriorityClass 示例
apiVersion: v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
# Pod priority
有了一个或者多个 PriorityClass 之后,您在创建 Pod 时就能在模板文件中指定需要使用的 PriorityClass 的名称。优先级准入控制器通过 priorityClassName 字段查找优先级数值并且填入 Pod 中。如果没有找到相应的 PriorityClass,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 生成后,会进入一个队列等待调度。scheduler 从队列中选择一个 Pod,然后尝试将其调度到某个节点上。如果没有任何节点能够满足 Pod 指定的所有要求,对于这个挂起的 Pod,抢占逻辑就会被触发。当前假设我们把挂起的 Pod 称之为 P。抢占逻辑会尝试查找一个节点,在该节点上移除一个或多个比 P 优先级低的 Pod 后,P 能够调度到这个节点上。如果节点找到了,部分优先级低的 Pod 就会从该节点删除。Pod 消失后,P 就能被调度到这个节点上了。
# 限制抢占(alpha 版本)
# 饥饿式抢占
Pod 被抢占时,受害者(被抢占的 Pod)会有 优雅终止期。他们有大量的时间完成工作并退出。如果他们不这么做,就会被强行杀死。这个优雅中止期在调度抢占 Pod 以及挂起的 Pod(P)能够被调度到节点(N)之间形成了一个时间间隔。在此期间,调度会继续对其它挂起的 Pod 进行调度。当受害者退出或者终止的时候,scheduler 尝试调度挂起队列中的 Pod,在 scheduler 正式把 Pod P 调度到节点 N 之前,会继续考虑把其它 Pod 调度到节点 N 上。这种情况下,当所有受害者退出时,很有可能 Pod P 已经不再适合于节点 N。因此,scheduler 将不得不抢占节点 N 上的其它 Pod,或者抢占其它节点,以便 P 能被调度。这种情况可能会在第二轮和随后的抢占回合中再次重复,而 P 可能在一段时间内得不到调度。这种场景可能会导致各种集群中的问题,特别是在具有高 Pod 创建率的集群中。
我们将在 Pod 抢占的 beta 版本解决这个问题。计划的解决方案可以在 这里 找到。
# PodDisruptionBudget is not supported
Pod 破坏预算(Pod Disruption Budget,PDB) 允许应用程序所有者在自愿中断应用的同时限制应用副本的数量。然而,抢占的 alpha 版本在选择抢占受害者时,并没有遵循 PDB。我们计划在 beta 版本增加对 PDB 遵循的支持,但即使是在 beta 版本,也只能做到尽力支持。scheduler 将会试图查找那些不会违反 PDB 的受害者,如果这样的受害者没有找到,抢占依然会发生,即便违反了 PDB,这些低优先级的 Pod 仍将被删除。
# 低优先级 Pod 之间的亲和性
在版本 1.8 中,只有当这个问题的答案是肯定的时候才考虑一个节点使用抢占:“如果优先级低于挂起的 Pod 的所有 Pod 从节点中移除后,挂起的 Pod 是否能够被调度到该节点上?”
注: 抢占没有必要移除所有低优先级的 Pod。如果在不移除所有低优先级的 Pod 的情况下,挂起的 Pod 就能调度到节点上,那么就只需要移除部分低优先级的 Pod。即便如此,上述问题的答案还需要是肯定的。如果答案是否定的,抢占功能不会考虑该节点。
如果挂起的 Pod 对节点上的一个或多个较低优先级的 Pod 具有亲和性,那么在没有那些较低优先级的 Pod 的情况下,无法满足 Pod 关联规则。这种情况下,scheduler 不抢占节点上的任何 Pod。它会去查找另外的节点。scheduler 有可能会找到合适的节点,也有可能无法找到,因此挂起的 Pod 并不能保证都能被调度。
我们可能会在未来的版本中解决这个问题,但目前还没有一个明确的计划。我们也不会因为它而对 beta 或者 GA 版本的推进有所阻滞。部分原因是,要查找满足 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 之间有反亲和性。
- 在这个区域内没有跟 Pod P 具备反亲和性的其它 Pod。
- 为了将 Pod P 调度到节点 N 上,Pod Q 需要被抢占掉,但是 scheduler 不能执行跨节点的抢占。因此,节点 N 将被视为不可调度节点。
如果将 Pod Q 从它的节点移除,反亲和性随之消失,那么 Pod P 就有可能被调度到节点 N 上。
如果找到一个性能合理的算法,我们可以考虑在将来的版本中增加跨节点抢占。在这一点上,我们不能承诺任何东西,beta 或者 GA 版本也不会因为跨节点抢占功能而有所阻滞。