去年zouyee为大家带来《kubernetes调度系统系列文章》, 近期看到社区在调度方面的一些有趣的设计文档,分享给各位。
文|Wei Huang, Yuan Chen, Yibo Zhuang
编辑|zouyee
提案阶段|评审
在Kubernetes调度器框架中提供一个PreEnqueue 钩子,使插件能够在将Pod添加到调度器的内部活动队列之前运行自定义逻辑。如果该插件返回false,则调度器不会将该Pod入队。
当前Kubernetes调度器无条件地将待调度的Pod(即spec.nodeName为空)添加到调度队列中。在Pod入队前,插件无法得知,同样也不能决定Pod是否应该入队。
PreEnqueue钩子的缺失将导致工作负载的生命周期管理的不完善,并且也会因无需调度的Pod扰动调度器的内部队列。例如,一些Pod在创建时可能还没有准备好立即被调度,控制器可能有定制的逻辑来决策Pod的Ready时机,并更新它们。因此,让 unready的Pod入队是不可取的,其浪费了宝贵的调度时间。另外,如果一个 unready的Pod被过早的调度,并在事后被抢占,这将浪费集群资源。此外,PreEnqueue钩子可以作为一种信息传输的途径,使插件能够实现更为精准的QueueSort计算,以及更高级的调度举措。
注意:这里unready Pod是指没有准备好立即被调度的Pod
使用场景
目标提出一个扩展方式,以针对即将入队的Pod执行自定义逻辑。非目标管理未被插件PreEnqueue处理的已调度Pod。
用户画像
核心逻辑是为插件开发者提供一个无状态且不可变的PreEnqueue钩子,它被注册在内部调度器activeQ中,在指定的Pod入队之前被调用。
在该设计中,引入了一个新的插件,叫做EnqueuePlugin。该插件中Admit()方法可以根据定制的配置文件,以判定一个pod准入/拒入activeQ。
注意:如非目标部分所述,如果Admin()返回错误,则需要由插件开发者来实现重新入队的逻辑。例如,更新Pod的spec/annotation,以便调度器的Pod处理程序会自动触发入队。
type EnqueuePlugin interface {
Plugin
// Admit is invoked every time a Pod is about to be added to activeQ.
// The given Pod won't be enqueued if a `false` value is returned.
Admit(*QueuedPodInfo) bool
}
优点:自定义入队逻辑通过新的扩展点被隔离出来,从而与其他插件实现隔离。对那些不关心此功能的调度器插件没有任何影响。
缺点:对API(KubeSchedulerConfiguration)和代码有较大改动。
在这个方式中,Admit()并不会与一个新的插件产生关联。相反,它拓展了现有的EnqueueExtensions接口。
type EnqueueExtensions interface {
// EventsToRegister returns a series of possible events that may cause a Pod
// failed by this plugin schedulable.
// The events will be registered when instantiating the internal scheduling queue,
// and leveraged to build event handlers dynamically.
// Note: the returned list needs to be static (not depend on configuration parameters);
// otherwise it would lead to undefined behavior.
EventsToRegister() []ClusterEvent
// Admit is invoked every time a Pod is about to be added to activeQ.
// The given Pod won't be enqueued if a `false` value is returned.
Admit(*QueuedPodInfo) bool
}
优点:API上没有变化。(EnqueueExtensions更像是一个SDK,由插件开发者使用,而不是SRE/Devops等最终用户使用)
缺点:EnqueueExtensions是一个可选的接口,它需要成为具体插件的一部分。这意味着如果只想实现Admit()方法,仍然需要实现一个(无实际意义)的插件来与之关联。当前实现EnqueueExtension接口的插件需要被修改以实现Admit(),当然可以通过植入一个 AlwaysAdmit函数以复用。
无论选择哪种方式,Admit()都可以在NewFramework()处构建SchedulerProfile后获得,最终传递给internalqueue.NewSchedulingQueue。
将引入一个新的结构体,叫做activeQ,其中包含heap.Heap和一个map,其映射关系为{profileName: admitFn}。
type activeQ struct {
heap.Heap
// Keyed with profile name, valued with AdminFunc
admitFuncMap map[string]framework.AdminFunc
}
func (aq *activeQ) Add(qInfo *framework.QueuedPodInfo) error {
if fn, ok := aq.admitFuncMap[qInfo.Pod.Spec.SchedulerName]; !ok || !fn(qInfo) {
return nil
}
return aq.Heap.Add(qInfo)
}
有了这个结构体,能够确保原先调用activeQ.XYZ()逻辑保持不变。
由于笔者时间、视野、认知有限,本文难免出现错误、疏漏等问题,期待各位读者朋友、业界专家指正交流。
参考文献
1.https://github.com/kubernetes/kubernetes/issues/93591
2.https://github.com/kubernetes/kubernetes/issues/102271