优先级和抢占机制解决的是Pod调度失败如何处理的问题

正常情况下,一个Pod被调度失败,就会被搁置起来,直到Pod被更新或者集群的状态发生变化,调度器才会对这个Pod进行重新调度。

但是有的时候,我们希望优先级高的Pod在调度失败的时候能够通过挤掉优先级低的Pod来保证高优先级的Pod能够被成功调度。

要使用这个机制,需要在k8s提交一个PriorityClass的定义,例如下面的定义:

apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for high priority service pods only."

在k8s中,优先级是32bit的整数,最大值不超过10亿,值越大表示优先级越高。而超过10亿的值是用来给系统Pod使用的,保证系统Pod不会被挤掉

创建了PriorityClass之后,Pod就可以使用它了

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

这样使用了这个PriorityClass的Pod就具有了其优先级。当这个 Pod 被提交给 Kubernetes 之后,Kubernetes 的 PriorityAdmissionController 就会自动将这个 Pod 的 spec.priority 字段设置为 1000000

当一个高优先级的Pod调用失败的时候,调度器就会在当前集群中寻找一个节点,使得这个节点上的一个或者多个低优先级的Pod被删除,然后自己调度到这个节点上。当然这个过程并不是立即发生的,实际上 调度器只会spec.nominatedNodeName 字段,设置为被抢占的 Node 的名字。然后,抢占者会重新进入下一个调度周期,然后在新的调度周期里来决定是不是要运行在被抢占的节点上

k8s调度器的抢占机制有两个队列:

  • activeQ:存放下一个调度周期要调度的对象。
  • unschedulableQ:用来存放调度失败的Pod。并且当这个队列中的Pod更新之后,会自动重新加入到activeQ这个队列当中。

当一个Pod被调度失败然后加入unschedulableQ之后,调度器会先检查调度失败的原因,来确认是否可以帮助抢占者找到一个新节点(如何PodFitsHost,这种情况除非改变Node的名字,否则永远不可能在这个Node上调度成功的)。 如果确定抢占可以发生,那么调度器就会把自己缓存的所有节点信息复制一份,然后使用这个副本来模拟抢占过程。

调度器会检查缓存副本里的每一个节点,然后从该节点上最低优先级的 Pod 开始,逐一“删除”这些 Pod。而每删除一个低优先级 Pod,调度器都会检查一下抢占者是否能够运行在该 Node 上。一旦可以运行,调度器就记录下这个 Node 的名字和被删除 Pod 的列表,这就是一次抢占过程的结果了。

模拟出结果之后,就可以开始真正的抢占了。

  1. 检查牺牲者列表,清理这些 Pod 所携带的 nominatedNodeName 字段。
  2. 把抢占者的 nominatedNodeName,设置为被抢占的 Node 的名字。
  3. 开启一个 Goroutine,同步地删除牺牲者