最近几个月一直在研究 kubernetes 的 scheduling-framework 调度框架,发现还是十分有意思的,我自己也实现了一个基于 scheduling-framework 调度框架的自定义调度器,希望感兴趣的同学一起学习:https://github.com/NJUPT-ISL/Yoda-Scheduler
Scheduling-framework 调度框架
Kubernetes 的 scheduling-framework 调度框架(以下简称调度框架)是针对当前 kubernetes 调度器的增强,它不同于之前的 scheduler-extender,用户可以编写多个插件,这些插件可以在调度的不同阶段作为原有调度器的扩展,并且这些插件会和 kubernetes 原有的调度器源代码会一起编译到调度程序中。
调度框架设计目标
增强 kubernetes 原有调度器的可扩展性。
通过将调度程序的某些功能移至插件,可以简化调度程序核心。
调度框架中可设置多个扩展点。
调度框架通过插件机制来接收插件结果,并根据接收到的结果继续或中止。
提出一种处理错误并将其与插件进行通信的机制。
Proposal
调度框架在 kubernetes 调度器中定义了很多 Go 的接口和 Go API,用于用户设计插件使用。这些用户设计的插件将会被添加到调度程序中,并在编译时包含在内。可以通过配置调度程序的 ComponentConfig
将允许启用、禁用和重新排序插件。自定义调度程序可以“ 在树外 ” 编写其插件并编译包含其自己的插件的调度程序二进制文件。
调度周期和绑定周期
调度器调度一个 Pod 的过程分为两个阶段:调度周期和绑定周期。
在调度周期中,调度器会为 Pod 选择一个最合适它运行的节点,然后调度过程将进入绑定周期。
在绑定周期中,调度器会检测调度周期中选中的那个“最合适的节点”是不是真的可以让这个 Pod 稳定的运行(比如检测 PV、检测是否有端口冲突等),或者需不需要做一些初始化操作(比如设置这个节点上的 FPGA 板子的状态、设置 GPU 显卡的驱动版本、CUDA 的版本等)。
扩展点
kubernetes 调度框架在调度周期和绑定周期都为我们提供了丰富的扩展点,这些扩展点可以“插上”我们自己设计的调度插件,一个插件可以在多个扩展点注册以执行更复杂或有状态的任务,实现我们想要的调度功能:
下面阐述下各个扩展点可以实现的功能。
Sort 排序
排序扩展点,由于调度器是按照 FIFO
的顺序调度 Pod 的,因此当队列里出现多个等待调度的 Pod 时,可以对这些 Pod 的先后顺序进行排序,把我们想要的 Pod(可能优先级比较高)往出队方向移动,让它可以更快地被调度。
目前的 Sort 扩展点只能启用一个,不可以启用多个 Sort 扩展插件。
我们可以看下 Sort 的接口,代码位于 kubernetes 项目的 /pkg/scheduler/framework/interface.go
中:
type QueueSortPlugin interface {
Plugin
// Less are used to sort pods in the scheduling queue.
Less(*PodInfo, *PodInfo) bool
}
也就是只需要实现 Less 方法即可,比如如下的实现:
func Less(podInfo1, podInfo2 *framework.PodInfo) bool {
return GetPodPriority(podInfo1) > GetPodPriority(podInfo2)
}
Pre-filter 预过滤
该扩展点用于预处理有关 Pod 的信息,或检查集群或 Pod 必须满足的某些条件。预过滤器插件应实现 PreFilter 函数,如果 PreFilter 返回错误,则调度周期将中止。注意,在每个调度周期中,只会调用一次 PreFilter。
Pre-filter 插件可以选择实现 PreFilterExtensions
接口,这个接口定义了 AddPod 和 RemovePod 方法以增量方式修改其预处理信息。
type PreFilterPlugin interface {
Plugin
PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) *Status
PreFilterExtensions() PreFilterExtensions
}
这里的 CycleState ,表示调度的上下文,其实是一个 map 的封装,结构体内部通过读写锁实现了并发安全,开发者可以通过 CycleState 来实现多个调度插件直接的数据传递,也就是多个插件可以共享状态或通过此机制进行通信。
type CycleState struct {
mx sync.RWMutex
storage map[StateKey]StateData
recordFrameworkMetrics bool// 该值为 true, 则调度框架会记录此次调度周期的数据
}
这里的 StateKey 是 string 类型,StateData 是一个接口类型:
type StateData interface {
// Clone is an interface to make a copy of StateData. For performance reasons,
// clone should make shallow copies for members (e.g., slices or maps) that are not
// impacted by PreFilter's optional AddPod/RemovePod methods.
Clone() StateData
}
我们可以做一个简单的接口实现,来实现 StateData: