
k8s Scheduler framework
最近又申到了GLCC的一个项目:kuscia
这是一个基于k8s Scheduler framework写的调度器,实现了一个plugin叫kusciascheduling
在写之前需要复习一下调度器框架的写法。原生调度器和volcano的逻辑还是有很大区别的
参考:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/scheduling-framework/
1、框架
原生调度器框架也是通过预留扩展点的方式,在特定位置执行特定的扩展点。
总体调度扩展点:
接下来看一下具体代码:
调度器启动后,会一直执行Scheduleone,即调度完一个pod,马上开始调度下一个pod
Q:ScheduleOne一起只处理一个pod么?
A:是的,ScheduleOne 每次只处理(尝试调度)一个 pod。它会从调度队列中取出下一个待调度的 pod,执行调度流程(选择节点、绑定等),然后返回。wait.UntilWithContext(ctx, sched.ScheduleOne, 0) 会让这个过程循环进行,每次只处理一个 pod,直到队列为空或 context 被取消。
Q:go wait.UntilWithContext(ctx, sched.ScheduleOne, 0)这个0是什么
A:这里的 0 是调度循环的周期(period),单位是纳秒。传 0 表示调度循环不会有固定的间隔,而是每次 ScheduleOne 返回后立即再次调用(即尽快调度下一个 pod),即一个pod处理完马上处理下一个pod。
ScheduleOne会先执行Scheduleling,然后执行binding。
SchedulelingCycle中会逐步执行PreFilter、Filter、PostFilter、PreScore、Score、Reserve、Permit:
如果上面两步出现了问题,触发err,才会执行PostFilter:
接下来进入bindingCycle,依次执行waitingOnPermit、PreBind、Bind、PostBind:
以上就是调度周期的扩展点触发过程
对于每一个扩展点执行处,其实都会执行每个插件注册的扩展点,这里以PostBind为例子:
每一个实现了postBindPlugin的插件,都会注册到frameworkImpl里面,然后依次调用插件的PostBind方法。
这里和volcano不一样,k8s调度器的扩展点是接口形式,volcano只是函数。
k8s是事先注册插件,到对应扩展点执行处,再从注册的插件里调用对应扩展点的函数
volcano是事先注册扩展点,到扩展点执行处,直接执行函数即可。
2、扩展点
(1)PreEnqueue
这些插件在将 Pod 被添加到内部活动队列之前被调用,在此队列中 Pod 被标记为准备好进行调度。
只有当所有 PreEnqueue 插件返回 Success
时,Pod 才允许进入活动队列。 否则,它将被放置在内部无法调度的 Pod 列表中,并且不会获得 Unschedulable
状态。
(2)PreFilter
这些插件用于预处理 Pod 的相关信息,或者检查集群或 Pod 必须满足的某些条件。 如果 PreFilter 插件返回错误,则调度周期将终止。
(3)Filter
这些插件用于过滤出不能运行该 Pod 的节点。对于每个节点, 调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行, 则不会为该节点调用剩下的过滤插件。节点可以被同时进行评估。
(4)PostFilter
这些插件在 Filter 阶段后调用,但仅在该 Pod 没有可行的节点时调用。 插件按其配置的顺序调用。如果任何 PostFilter 插件标记节点为 "Schedulable", 则其余的插件不会调用。典型的 PostFilter 实现是抢占,试图通过抢占其他 Pod 的资源使该 Pod 可以调度。
(5)PreScore
这些插件用于执行“前置评分(pre-scoring)”工作,即生成一个可共享状态供 Score 插件使用。 如果 PreScore 插件返回错误,则调度周期将终止。
(6)Score
这些插件用于对通过过滤阶段的节点进行排序。调度器将为每个节点调用每个评分插件。 将有一个定义明确的整数范围,代表最小和最大分数。 在标准化评分阶段之后, 调度器将根据配置的插件权重合并所有插件的节点分数。
(7)Reserve
实现了 Reserve 接口的插件,拥有两个方法,即 Reserve
和 Unreserve
, 他们分别支持两个名为 Reserve 和 Unreserve 的信息传递性质的调度阶段。 维护运行时状态的插件(又称"有状态插件")应该使用这两个阶段, 以便在节点上的资源被保留和解除保留给特定的 Pod 时,得到调度器的通知。
Reserve 阶段发生在调度器实际将一个 Pod 绑定到其指定节点之前。 它的存在是为了防止在调度器等待绑定成功时发生竞争情况。 每个 Reserve 插件的 Reserve
方法可能成功,也可能失败; 如果一个 Reserve
方法调用失败,后面的插件就不会被执行,Reserve 阶段被认为失败。 如果所有插件的 Reserve
方法都成功了,Reserve 阶段就被认为是成功的, 剩下的调度周期和绑定周期就会被执行。
如果 Reserve 阶段或后续阶段失败了,则触发 Unreserve 阶段。 发生这种情况时,所有 Reserve 插件的 Unreserve
方法将按照 Reserve
方法调用的相反顺序执行。 这个阶段的存在是为了清理与保留的 Pod 相关的状态。
(8)Permit
Permit 插件在每个 Pod 调度周期的最后调用,用于防止或延迟 Pod 的绑定。 一个允许插件可以做以下三件事之一:
批准
一旦所有 Permit 插件批准 Pod 后,该 Pod 将被发送以进行绑定。
拒绝
如果任何 Permit 插件拒绝 Pod,则该 Pod 将被返回到调度队列。 这将触发 Reserve 插件中的 Unreserve 阶段。
等待(带有超时)
如果一个 Permit 插件返回“等待”结果,则 Pod 将保持在一个内部的“等待中” 的 Pod 列表,同时该 Pod 的绑定周期启动时即直接阻塞直到得到批准。 如果超时发生,等待变成拒绝,并且 Pod 将返回调度队列,从而触发 Reserve 插件中的 Unreserve 阶段。
说明:
尽管任何插件可以访问“waiting”状态的 Pod 列表并批准它们 。 我们期望只有Permit插件可以批准处于“waiting”状态的预留 Pod 的绑定。 一旦 Pod 被批准了,它将发送到 Prebind 阶段。
(9)PreBind
这些插件用于执行 Pod 绑定前所需的所有工作。 例如,一个 PreBind 插件可能需要制备网络卷并且在允许 Pod 运行在该节点之前将其挂载到目标节点上。
如果任何 PreBind 插件返回错误,则 Pod 将被拒绝并且退回到调度队列中。
(10)Bind
Bind 插件用于将 Pod 绑定到节点上。直到所有的 PreBind 插件都完成,Bind 插件才会被调用。 各 Bind 插件按照配置顺序被调用。Bind 插件可以选择是否处理指定的 Pod。 如果某 Bind 插件选择处理某 Pod,剩余的 Bind 插件将被跳过。
(11)PostBind
这是个信息传递性质的接口。 PostBind 插件在 Pod 成功绑定后被调用。这是绑定周期的结尾,可用于清理相关的资源。