Kubernetes Scheduler Scheduling Framework
简介
在Kubernetes Scheduler机制概览中就介绍过Scheduling Framework这个新的调度框架,它通过Plugins以及Extension Points
的方式对调度框架进行了重构,通过在各个预定义的扩展点,插入Plugin的方式,扩展Scheduler的功能,核心的调度算法逻辑,也都放到了Plugin中,如果默认的调度器中的Plugin不能满足需求,可以自己写插件,但是要重新编译代码。
关于Scheduling Framework的介绍以及配置,官方这两篇文档已经介绍的很详细了:
- https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/
- https://kubernetes.io/docs/reference/scheduling/config/
本篇文章主要来介绍下Scheduling Framework的插件机制的实现原理,以及如何写自己的插件。
实现原理
在Scheduling Framework中有这么几个概念:Profile, Framework, ExtensionPoints, Registry, Plugin。Framework是最核心的结构,在Framework中定义了一些扩展点,即ExtensionPoints,每一个扩展点都包含一些Plugin,在调度时,会依次执行Framework中的ExtensionPoints中的每一个Plugin,而Profile相当于是Framework的配置文件,它配置了哪些ExtensionPoint包含哪些Plugin,而Registry则是Plugin的工厂方法集合,插件的构造方法都需要向Registry中注册。他们之间的关系如下图所示:
Profile
Profile相当于是Framework的配置文件,它决定了Framework中各个ExtensionPoints包含哪些插件,通过将插件的name加入Enable/Disable列表中进行配置,相关结构如下:
1 | # kubernetes/pkg/scheduler/apis/config/types.go |
Profile可以通过scheduler的配置文件--config file
进行配置,比如官方文档给出的示例:
1 | apiVersion: kubescheduler.config.k8s.io/v1beta1 |
这个配置的意思是把score这个ExtensionPoint中默认的NodeResourcesLeastAllocated
插件给禁用掉,启用自定义的两个插件。
此外,Profile还有一个特性就是它是可以定义多个的,每个Profile会对应的创建一个Framework出来,意思是可以任意组合这些Plugin创建出多种调度策略,每一个调度策略都有一个名称,创建pod时,可以指定使用哪种调度策略,Scheduler已经内置了一个叫做”default-scheduler”的Profile,如果建pod时没有指定哪种调度策略,就使用默认的,先来看下默认的调度策略,都定义了哪些plugin:
1 | # kubernetes/pkg/scheduler/algorithmprovider/registry.go |
如果想要指定多个Profile的话,可以参考官方文档的一个示例:
1 | apiVersion: kubescheduler.config.k8s.io/v1beta1 |
这里除了”default-scheduler”之外,还定义了一个”no-scoring-scheduler”,它将score相关的plugin都给disable掉了,注意自定义的profile会跟”default-scheduler”做merge操作,相当于是除了没有score相关的plugin之外,其他都跟default-scheduler的配置一样。创建pod时,可以通过”.spec.schedulerName”来指定使用哪种Profile。
Framework
Framework会使用Profile来决定往ExtensionPoints中添加哪些插件,先来看下Framework的结构体:
1 | # kubernetes/pkg/scheduler/framework/v1alpha1/framework.go |
像preFilterPlugins
, filterPlugins
这样的成员变量,就是所谓的ExtensionPoints,在创建Framework时,如果Profile中对应的插件是Enable的,那么会调用registry中对应的插件的工厂方法实例化这个插件,然后添加到这个列表中的,而framework这个结构体又实现了Framework定义的接口:
1 | # kubernetes/pkg/scheduler/framework/v1alpha1/interface.go |
在调度时,会调用framework中定义的这些RunXXXPlugins()
方法,在这些方法中,又依次遍历ExtensionPoints中注册的插件的方法,去执行具体的调度逻辑。
再来看下这些插件的接口是如何定义的:
1 | # kubernetes/pkg/scheduler/framework/v1alpha1/interface.go |
内置的插件,都在kubernetes/pkg/scheduler/framework/plugins/
目录下,一个插件其实可以实现多个上述的接口,在不同的阶段执行不同的操作。自定义的插件,则需要实现对应的接口方法即可。
Registry
Registry是用来注册创建插件实例的工厂方法的结构体,其结构如下:
1 | # kubernetes/pkg/scheduler/framework/v1alpha1/registry.go |
Registry中包含两种插件,一种是Kubernetes内置的插件,叫做InTreeRegistry,一种是外部自定义的插件,叫做OutOfTreeRegistry。内置的和外置的插件会在创建Scheduler时,进行合并到同一个Registry中:
1 | # kubernetes/pkg/scheduler/scheduler.go |
内置的插件,都在kubernetes/pkg/scheduler/framework/plugins/
目录下,目前有如下内置的插件:
1 | # kubernetes/pkg/scheduler/framework/plugins/registry.go |
而外置的插件是怎么传递进来的呢?是在kube-scheduler的main方法中传进来的:
1 | # kubernetes/cmd/kube-scheduler/app/server.go |
但是可以看到,kubernetes中内置的main()并没有传递这个参数,因此如果想要自定义插件的话,就必须要改main()方法,将这个参数传递进去,因此就需要重新编译kube-scheduler这个组件了。而在app/server.go
中,已经为自定义插件留好了口子:
1 | # kubernetes/cmd/kube-scheduler/app/server.go |
需要在外面的一个项目中,编写自己的插件,主要是实现对应的插件定义的接口方法,以及该插件的工厂方法,然后重写kube-scheduler的main()方法,调用WithPlugin()
方法,构造一个Option,将其传给app.NewSchedulerCommand()
,然后重新编译这个kube-scheduler,再结合Profile,将自定义的插件注册到对应的ExtensionPoint中,就可以了。
社区的项目scheduler-plugins就维护了一些outoftree的插件,来看下它重写的main()方法的例子:
1 | func main() { |
总结
本文介绍了Scheduling Framework的实现原理,以及如何去实现自定义的插件。整体上看,这个框架设计的还是相当不错的,尤其是插件机制,以及多Profile的支持,是一个很好的设计典范。不过唯一有点别扭的是自定义插件竟然需要去更改main()方法,并且需要单独编译kube-scheduler,也许有更优雅的实现方式。
Kubernetes Scheduler Scheduling Framework
https://hackerain.me/2020/12/21/kubernetes/kube-scheduler-framework.html