本文基于k8s 1.18.2

自定义调度的方法

k8s中自定义调度一共有三种方法:

  1. 直接修改源码,重新编译。
  2. k8s自身实现了很多种调度算法,自定义如何使用这些算法
  3. 实现自己的调度算法,在原调度基础上,调度器调接口执行自定义调度
  4. 脱离原调度器,完全自己实现调度
  1. 第一种方法对源码侵入性比较大,正常情况下不适用。
  2. 第二种方法在官方实现的调度算法中,通过组合方式能够实现自己调度需求的则可用,否则不可用。
  3. 第三种使用场景更加广泛,可执行用户自己实现的调度策略。
  4. 第四种更自由,但是无法使用官方的调度算法

自定义调度

修改源码+定义调度算法:

最小入侵方案:

方案1:

  1. 在 pkg/scheduler 包下建立自己的代码包,实验代码结构如下:

    1
    2
    3
    4
    5
    --outoft
    |-- outoft.go
    |-- plugins
    |-- fakeplugin
    |-- fakeplugin.go

    其中,plugins包为扩展调度包,其中包含用户自己调度逻辑。如果需要实现什么,在里边实现即可。此处给出fakePlugin代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package fakeplugin
    import (
    "context"
    "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/runtime"
    framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
    )
    const (
    Name = "FakePlugin"
    )
    type FakePlugin struct {
    handle framework.FrameworkHandle
    }
    func (f FakePlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    return nil
    }
    func (f FakePlugin) Name() string {
    return Name
    }
    func New(_ runtime.Object, handle framework.FrameworkHandle) (framework.Plugin, error) {
    return &FakePlugin{
    handle: handle,
    }, nil
    }

    注意1:plugin需要实现pkg/scheduler/framework/v1alpha1/Plugin接口,用于注册该plugin。另外,如果需要在某个调度点实现自定义功能,实现指定的方法即可:此处实现了pkg/scheduler/framework/v1alpha1/FilterPlugin接口,可通过配置,在调度filter阶段执行完默认调度策略后来调用该方法。

  2. 注册plugin的方法
    此处给出 outoft.go文件代码,重点在 Register方法,该方法将用户自定义plugin注册到registry里。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package outoft
    import (
    framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
    "k8s.io/kubernetes/pkg/scheduler/outoft/plugins/fakeplugin"
    )
    func Register(registry framework.Registry) error {

    for name, factory := range reg {
    registry.Register(name, factory)
    }
    return nil
    }
    var reg = framework.Registry{
    fakeplugin.Name: fakeplugin.New,
    }
  3. 配置调用注册plugin
    修改scheduler的启动main函数。

    1
    2
    3
    4
    5
    6
    7
    8
    package main
    // ...

    func main() {
    // ...
    command := app.NewSchedulerCommand()
    // ...
    }

    修改为

    1
    2
    3
    4
    5
    6
    7
    8
    package main
    // ...

    func main() {
    // ...
    command := app.NewSchedulerCommand(outoft.Register)
    // ...
    }

    如此,在创建调度器的过程中,会调用到注册plugin的方法,从而将用户自定义的调度plugin注册。
    调用链:command.Run() -> runCommand() -> Steup() -> option(outOfTreeRegistry)注册成功 -> scheduler.New() -> registry.Merge() 将outOfTree plugin合并到registry。

  4. 配置调度器文件
    只注册了调度器是不会被调用的,需要用户自定义调度策略。配置调度器的config.yaml文件(启动调度器时需指定 –config为此文件),其中profiles里配置了自定义的调度策略,default-scheduler是默认,my-scheduler为自定义的调度策略。修改完配置文件启动,修改测试deployment.spec.template.spec.schedulerName值为my-scheduler,新建的pod调度过程即可调用到用户自定义的调度器。

    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
    apiVersion: kubescheduler.config.k8s.io/v1beta1
    clientConnection:
    acceptContentTypes: ""
    burst: 100
    contentType: application/vnd.kubernetes.protobuf
    kubeconfig: /etc/kubernetes/scheduler.conf
    qps: 50
    disablePreemption: false
    enableContentionProfiling: true
    enableProfiling: true
    healthzBindAddress: 0.0.0.0:10251
    kind: KubeSchedulerConfiguration
    leaderElection:
    leaderElect: true
    leaseDuration: 15s
    renewDeadline: 10s
    resourceLock: endpointsleases
    resourceName: kube-scheduler
    resourceNamespace: kube-system
    retryPeriod: 2s
    metricsBindAddress: 0.0.0.0:10251
    percentageOfNodesToScore: 0
    podInitialBackoffSeconds: 1
    podMaxBackoffSeconds: 10
    profiles:
    - pluginConfig:
    - args:
    apiVersion: kubescheduler.config.k8s.io/v1beta1
    hardPodAffinityWeight: 1
    kind:
    name: InterPodAffinity
    schedulerName: default-scheduler
    - schedulerName: my-scheduler
    plugins:
    filter:
    enabled:
    - name: FakePlugin

方案2:

新建自己的项目,将kubernetes包放到vender下,或者用go mod导入,用户自定义的plugin、plugin注册代码同上,只需将kube-scheduler的启动包下的文件copy到当前用户自定义项目中,修改该启动方法,增加plugin注册参数,编译运行即可。(启动scheduler时,需指定 –config文件为上边的config.yaml文件)

在原调度基础上,扩展调度

用户可建立独立项目,通过http方式接入第三方调度方法(目前仅支持http,pkg/scheduler/framework/v1alpha1/Extender接口,目前仅有http实现,后续可能增加实现方法)

用户可实现的调度接入点:Filter、Prioritize、Bind、preempt,实现相应调度点自定义调度需要

  1. 用户提供http请求服务,处理调度请求
  2. 配置config.yaml配置文件

扩展对所有调度策略生效,如果用户在某些场景下不需要执行调度,需要用户在代码逻辑中自行判断

1
2
3
4
5
6
# 配置文件内容大体同上边的 config.yaml,在上边文件最后追加内容:
extenders:
- urlPrefix: http://10.1.1.1:8080/prefix
filterVerb: filter
prioritizeVerb: prioritize
weight: 1

注意1:请求时,会将urlPrefix与 xxxVerb 拼在一起作为请求路径,另外,如果自定义逻辑里对node信息有做缓存,那么可以通过配置请求时只传nodeName,以减少数据传输

自定义调度策略服务需要接收 /prefix/filter/prefix/prioritize的请求,并对body解析,执行相应调度策略,然后返回。API格式,可参照源码中test示例(test/integration/scheduler/extender_test.go Extender的方法:Filter\Prioritize\Bind):

1
2
Filter(arg *v1.ExtenderArgs) (*v1.ExtenderFilterResult, error)
Prioritize(arg *v1.ExtenderArgs) (*v1.HostPriorityList, error)

脱离k8s原调度器,完全实现自己的调度

  1. 给需要使用这种调度策略的Pod指定一个原调度器中没有的调度策略,这样,原调度器就不会调度这个Pod。
  2. 此时Pod会一直处于Pending状态,用户需实现自己的调度器来ListPod、ListNode,发现未调度的Pod就调度(主要是指定nodeName)。