查看原文
其他

K8s调度框架引入PreEnqueue设计

K8s社区 DCOS 2022-07-13



去年zouyee为大家带来《kubernetes调度系统系列文章》, 近期看到社区在调度方面的一些有趣的设计文档,分享给各位。


文|Wei Huang, Yuan Chen, Yibo Zhuang
编辑|zouyee
来源|K8s

提案阶段|评审

在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

使用场景

  • Spot instance:只有在集群有富余的可用资源或集群当前利用率较低时,Spot instance pod才会被调度。

  • 有状态的工作负载:使用PersistentVolume的pod先不入队,直到PVC已经成功绑定到PV(在PVC即时绑定模式下)。

  • 无效的secrets/configmaps:pod中指定的secrets/configmaps不存在或无效时不入队。目前,此类pod将被调度,可能抢占其他pod,但在容器启动时因此而失败。

目标提出一个扩展方式,以针对即将入队的Pod执行自定义逻辑。非目标管理未被插件PreEnqueue处理的已调度Pod。

用户画像

  • 作为一个集群容量规划者,想控制Pod的入队速度。例如,具体的逻辑可能取决于一些SLO相关的指标,如调度队列的饱和度,集群的整体利用率,或特定的业务需求。

  • 作为一个插件的开发者,想在Pod入队(进入activeQ)时得到简单的通知,这样就可以在之后的其他插件中利用自定义逻辑。


方案设计

API设计

核心逻辑是为插件开发者提供一个无状态且不可变的PreEnqueue钩子,它被注册在内部调度器activeQ中,在指定的Pod入队之前被调用。

实现方式 1. 添加一个新的插件EnqueuePlugin

在该设计中,引入了一个新的插件,叫做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)和代码有较大改动。

实现方式2. 在EnqueueExtensions接口中添加Admit()

在这个方式中,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




真诚推荐你关注



kuberneter调度由浅入深:框架

K8S 调度系统由浅入深系列:简介

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存