1. 概述
Kubernetes 调度 Pod 时 kube-scheduler 基于 Kubernetes 管理的资源进行调度,这些资源包括 CPU、内存、节点运行 Pod 数量等。
但是,某些场景中用户需要根据某些特定资源进行上下文调度,kube-scheduler 无法获取这些信息,此时我们需要根据需要对 Kubernetes Scheduler 进行扩展。
当前 Kubernetes 提供了以下几种不同的方式扩展调度器:
- Multi Scheduling Profiles
- Scheduler Extender
- Scheduling Framework
- 修订 Kube Scheduler 编译二进制文件
上述扩展方式中 Scheduling Framework 是 Kubernetes 提供一种新的扩展架构,它在现有的 kube-scheduler 代码中添加了一组扩展点(Extension points),用户可以在不改动 Kubernetes 核心代码的情况下添加自己的 Plugin。
通过 Scheduling Framework 方式进行扩展时,需要重新编译 kube-scheduler。严格说来这对 Kubernetes 产生了一点侵入性,但考虑到性能、灵活性、便利性的优势,目前官方倡导用户使用 Scheduling Framework 扩展调度器功能。
2. Scheduling Framework
Kubernetes Scheduler 调度一个 Pod 到某个 Node 过程分为 Scheduling Cycle 和 Binding Cycle 两个阶段,我们通常将它们统称为 Scheduling Context(调度上下文)。
其中,Scheduling Cycle 为 Pod 选择一个节点,Binding Cycle 将该 Pod 和节点绑定(更新 ETCD)。在同一个调度器中,只有一个 Pod 处于 Scheduling Cycle 阶段,而多个 Binding Cycle 可以并行运行。
当 Scheduling Cycle 或 Binding Cycle 不可调度或者过程中发生内部错误时,调度流程将被终止,被调度的 Pod 将会被放回调度队列,等待下回重试。
Scheduling Framework 框架中,预留了非常多的扩展点(Extension points),下图中所有类型扩展点用户都可以在 pkg/scheduler/framework/interface.go 文件中找到对应的接口定义。
本文不再一一介绍每个扩展点的具体功能,有兴趣的读者可以自行阅读官方文档。

3. 开发示例
为了演示如何通过 Scheduling Framework 扩展调度器,本文将基于 PostBind 扩展点实现一个自定义调度插件。
1. 接口和业务逻辑实现
从 pkg/scheduler/framework/interface.go 文件接口定义可以知道,基于 PostBind 扩展调度器时,我们需要实现 Name() 和 PostBind(),其中 Name() 接口用于自定义插件的注册和配置,而 PostBind() 用于实现我们的业务逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// Plugin is the parent type for all the scheduling framework plugins.
type Plugin interface {
Name() string
}
// PostBindPlugin is an interface that must be implemented by "PostBind" plugins.
// These plugins are called after a pod is successfully bound to a node.
type PostBindPlugin interface {
Plugin
// PostBind is called after a pod is successfully bound. These plugins are
// informational. A common application of this extension point is for cleaning
// up. If a plugin needs to clean-up its state after a pod is scheduled and
// bound, PostBind is the extension point that it should register.
PostBind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string)
}
|
参考 Kubernetes 代码现有的目录结构,我们在 pkg/scheduler/framework/plugins/ 路径下创建新目录,用于存放自定义插件的代码,代码框架如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// pkg/scheduler/framework/plugins/spscheduler/postbind.go
package spscheduler
import (
"context"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/scheduler/framework"
)
type SPPostBind struct {
apiclient clientset.Interface
}
var _ framework.PostBindPlugin = &SPPostBind{}
// Name is the name of the plugin used in Registry and configurations.
const (
Name = "SPPostBind"
)
func (s *SPPostBind) Name() string {
return Name
}
func (s *SPPostBind) PostBind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) {
// TODO: just log information
klog.Infof("[SPPostBind] scheduler <%s> on node<%s>", p.Name, nodeName)
return
}
// New initializes a new plugin and returns it.
func New(args runtime.Object, fh framework.Handle) (framework.Plugin, error) {
return &SPPostBind{}, nil
}
|
2. 注册插件和配置文件
定义完成调度插件后,我们需要在 kube-scheduler 代码中注册 SPPostBind 插件。
为了尽量减少对 Kubernetes 源码的侵入,我们选择编辑 cmd/kube-scheduler/scheduler.go 文件注册插件(PS:当然你可以选择改其他地方)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package main
import (
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/spscheduler"
"os"
"k8s.io/component-base/cli"
_ "k8s.io/component-base/logs/json/register" // for JSON log format registration
_ "k8s.io/component-base/metrics/prometheus/clientgo"
_ "k8s.io/component-base/metrics/prometheus/version" // for version metric registration
"k8s.io/kubernetes/cmd/kube-scheduler/app"
)
func main() {
command := app.NewSchedulerCommand(app.WithPlugin(spscheduler.Name, spscheduler.New))
code := cli.Run(command)
os.Exit(code)
}
|
完成上述修订后,我们编译 kube-scheduler 并定义以下配置文件(如何编译请参考官方文档,建议配置魔法上网减轻复杂度),将 SPPostBind 加入默认调度器中。
需要注意的是 kube-scheduler 指定 –config 参数时会覆盖命令行中的 –kubeconfig 参数,因此我们需要在 KubeSchedulerConfiguration 中定义该参数。
1
2
3
4
5
6
7
8
9
10
|
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: /etc/kubernetes/kube-scheduler.conf
profiles:
- schedulerName: default-scheduler
plugins:
postBind:
enabled:
- name: SPPostBind
|
3. 自定义参数
通常一些项目中需要为自定义插件提供可配置的参数,参考 Scheduler Framework Plugins,可以实现该功能。
在以下文件参数 SPPostBindArgs 作为上述插件的自定义配置 struct(PS:需要注意 struct 的名称格式为 <插件名>+Args):
- pkg/scheduler/apis/config/types_pluginargs.go
- staging/src/k8s.io/kube-scheduler/config/v1beta3/types_pluginargs.go
1
2
3
4
5
6
7
8
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SPPostBindArgs holds arguments used to configure the
// SPPostBindPlugin plugin.
type SPPostBindArgs struct {
metav1.TypeMeta `json:",inline"`
Kubeconfig string `json:"kubeconfig,omitempty"` // kubeconfig 作为自定义配置项
}
|
定义完 SPPostBindArgs 对象后,在以下文件的 addKnownTypes() 中注册 SPPostBindArgs 对象。
- staging/src/k8s.io/kube-scheduler/config/v1beta3/register.go
- pkg/scheduler/apis/config/register.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&KubeSchedulerConfiguration{},
&DefaultPreemptionArgs{},
&InterPodAffinityArgs{},
&NodeResourcesBalancedAllocationArgs{},
&NodeResourcesFitArgs{},
&PodTopologySpreadArgs{},
&VolumeBindingArgs{},
&NodeAffinityArgs{},
&SPPostBindArgs{}, // 注册 SPPostBindArgs 对象
)
return nil
}
|
在 pkg/scheduler/apis/config/v1beta3/defaults.go 文件中可以添加 SetDefaults_SPPostBindArgs() 函数,来自动生成 SPPostBindArgs 的默认值。
1
2
3
4
5
|
func SetDefaults_SPPostBindArgs(obj *v1beta3.SPPostBindArgs) {
if obj.Kubeconfig == "" {
obj.Kubeconfig = "/etc/kubernetes/admin.conf"
}
}
|
执行以下命令重新生成代码,此时会自动更新两处 zz_generated.conversion.go 文件和一处 zz_generated.defaults.go 文件(PS: 更新完成后可以执行 go mod vendor 更行 vendor 目录下的文件):
1
2
|
./hack/update-codegen.sh
make generated_files
|
更新 postbind.go 中的 New() 和配置文件:
1
2
3
4
5
6
7
8
9
10
|
package spscheduler
// New initializes a new plugin and returns it.
func New(args runtime.Object, fh framework.Handle) (framework.Plugin, error) {
postBindArgs, isOK := args.(*config.SPPostBindArgs)
if !isOK {
return nil, fmt.Errorf("got args of type %T, values %v, want *SPPostBindArgs", args, args)
}
klog.Infof("[SPScheduler] Read user kubeconfig: %v", postBindArgs.Kubeconfig)
return &SPPostBind{}, nil
}
|
完成上述修改后重新编译 kube-scheduler,并修改 KubeSchedulerConfiguration 定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: /etc/kubernetes/kube-scheduler.conf
profiles:
- schedulerName: default-scheduler
plugins:
postBind:
enabled:
- name: SPPostBind
pluginConfig:
- name: SPPostBind
args:
kubeconfig: "/etc/kubeconfig/custom.conf"
|
4. 总结
从上述描述来看,通过 scheduling-framework 扩展调度器并不复杂,但由于需要重新编译 kubernetes 代码,导致和内部 CICD 工具集成不太方便。
参考