Kubernetes 调度器实现原理( 五 )

对于调度框架插件的启用或者禁用,我们可以使用安装集群时的 KubeSchedulerConfiguration 资源对象来进行配置 。下面的例子中的配置启用了一个实现了 reserve 和 preBind 扩展点的插件,并且禁用了另外一个插件,同时为插件 foo 提供了一些配置信息:
 
apiVersion: kubescheduler.config.k8s.io/v1kind: KubeSchedulerConfiguration---plugins:reserve:enabled:- name: foo- name: bardisabled:- name: bazpreBind:enabled:- name: foodisabled:- name: bazpluginConfig:- name: fooargs: >foo插件可以解析的任意内容 
扩展的调用顺序如下:

  • 如果某个扩展点没有配置对应的扩展,调度框架将使用默认插件中的扩展
  • 如果为某个扩展点配置且激活了扩展,则调度框架将先调用默认插件的扩展,再调用配置中的扩展
  • 默认插件的扩展始终被最先调用,然后按照 KubeSchedulerConfiguration 中扩展的激活 enabled 顺序逐个调用扩展点的扩展
  • 可以先禁用默认插件的扩展,然后在 enabled 列表中的某个位置激活默认插件的扩展,这种做法可以改变默认插件的扩展被调用时的顺序
假设默认插件 foo 实现了 reserve 扩展点,此时我们要添加一个插件 bar,想要在 foo 之前被调用,则应该先禁用 foo 再按照 bar foo 的顺序激活 。示例配置如下所示:
 
apiVersion: kubescheduler.config.k8s.io/v1kind: KubeSchedulerConfiguration---profiles:- plugins:reserve:enabled:- name: bar- name: foodisabled:- name: foo 
在源码目录 pkg/scheduler/framework/plugins/examples 中有几个示范插件,我们可以参照其实现方式 。
示例其实要实现一个调度框架的插件,并不难,我们只要实现对应的扩展点,然后将插件注册到调度器中即可,下面是默认调度器在初始化的时候注册的插件:
 // pkg/scheduler/algorithmprovider/registry.gofunc NewRegistry() Registry { return Registry{// FactoryMap:// New plugins are registered here.// example:// {//stateful_plugin.Name: stateful.NewStatefulMultipointExample,//fooplugin.Name: fooplugin.New,// } }}但是可以看到默认并没有注册一些插件,所以要想让调度器能够识别我们的插件代码,就需要自己来实现一个调度器了,当然这个调度器我们完全没必要完全自己实现,直接调用默认的调度器,然后在上面的 NewRegistry() 函数中将我们的插件注册进去即可 。在 kube-scheduler 的源码文件 kubernetes/cmd/kube-scheduler/app/server.go 中有一个 NewSchedulerCommand 入口函数,其中的参数是一个类型为 Option 的列表,而这个 Option 恰好就是一个插件配置的定义:
// Option configures a framework.Registry.type Option func(framework.Registry) error// NewSchedulerCommand creates a *cobra.Command object with default parameters and registryOptionsfunc NewSchedulerCommand(registryOptions ...Option) *cobra.Command {......}所以我们完全就可以直接调用这个函数来作为我们的函数入口,并且传入我们自己实现的插件作为参数即可,而且该文件下面还有一个名为 WithPlugin 的函数可以来创建一个 Option 实例:
【Kubernetes 调度器实现原理】func WithPlugin(name string, factory runtime.PluginFactory) Option { return func(registry runtime.Registry) error {return registry.Register(name, factory) }}所以最终我们的入口函数如下所示:
 package mainimport ( "k8s.io/component-base/cli" "k8s.io/kubernetes/cmd/kube-scheduler/app" "math/rand" "os" // Ensure scheme package is initialized. _ "simple-scheduler/pkg/scheduler/apis/config/schema" "simple-scheduler/pkg/scheduler/framework/plugins" "time")func main() { rand.Seed(time.Now().UTC().UnixNano()) command := app.NewSchedulerCommand(app.WithPlugin(plugins.Name, plugins.New)) code := cli.Run(command) os.Exit(code)}其中 app.WithPlugin(sample.Name, sample.New) 就是我们接下来要实现的插件,从 WithPlugin 函数的参数也可以看出我们这里的 sample.New 必须是一个 framework.PluginFactory 类型的值,而 PluginFactory 的定义就是一个函数:
type PluginFactory = func(configuration runtime.Object, f framework.Handle) (framework.Plugin, error) 
所以 sample.New 实际上就是上面的这个函数,在这个函数中我们可以获取到插件中的一些数据然后进行逻辑处理即可,插件实现如下所示,我们这里只是简单获取下数据打印日志,如果你有实际需求的可以根据获取的数据就行处理即可,我们这里只是实现了 PreFilter、Filter、PreBind 三个扩展点,其他的可以用同样的方式来扩展即可:


推荐阅读