暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Kubernetes 调度系统之 Scheduling Framework

原创 cherry 2022-02-12
1651


29.jpeg


镜像下载、域名解析、时间同步请点击 阿里巴巴开源镜像站
  

image.png

Multiple schedulers

Scheduler在Kubernetes集群中其实类似于一个特殊的Controller,通过监听Pod和Node的信息,给Pod挑选最佳的节点,更新Pod的spec.NodeName的信息来将调度结果同步到节点。所以对于部分有特殊的调度需求的用户,有些开发者通过自研Custom Scheduler来完成以上的流程,然后通过和default scheduler同时部署的方式,来支持自己特殊的调度需求。

image.png


Custom Scheduler会存在一下问题:

  1. 如果与default scheduler同时部署,因为每个调度器所看到的资源视图都是全局的,所以在调度决策中可能会在同一时刻在同一个节点资源上调度不同的Pod,导致节点资源冲突的问题。
  2. 有些用户将调度器所能调度的资源通过Label划分不同的池子,可以避免资源冲突的现象出现。但是这样又会导致整体集群资源利用率的下降。
  3. 有些用户选择通过完全自研的方式来替换default scheduler,这种会带来比较高的研发成本,以及Kubernetes版本升级后可能存在的兼容性问题。

Scheduler Extender的性能较差可是维护成本较小,Custom Scheduler的研发和维护的成本特别高但是性能较好,这种情况是开发者面临这种两难处境。这时候Kubernetes Scheduling Framework V2横空出世,给我们带来鱼和熊掌可以兼得的方案。

image.png

三、Scheduling Framework 解析

社区也逐渐的发现开发者所面临的困境,为了解决如上问题,使Kube-scheduler扩展性更好、代码更简洁,社区从Kubernetes 1.16版本开始, 构建了一种新的调度框架Kubernetes Scheduling Framework的机制。
Scheduling Framework在原有的调度流程中, 定义了丰富扩展点接口,开发者可以通过实现扩展点所定义的接口来实现插件,将插件注册到扩展点。Scheduling Framework在执行调度流程时,运行到相应的扩展点时,会调用用户注册的插件,影响调度决策的结果。通过这种方式来将用户的调度逻辑集成到Scheduling Framework中。

image.png


Framework的调度流程是分为两个阶段scheduling cycle和binding cycle. scheduling cycle是同步执行的,同一个时间只有一个scheduling cycle,是线程安全的。binding cycle是异步执行的,同一个时间中可能会有多个binding cycle在运行,是线程不安全的。

1. scheduling cycle

scheduling cycle是调度的核心流程,主要的工作是进行调度决策,挑选出唯一的节点。

Queue sort

// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
    Plugin
    // Less are used to sort pods in the scheduling queue.
    Less(*PodInfo, *PodInfo) bool
}

Scheduler中的优先级队列是通过heap实现的,我们可以在QueueSortPlugin中定义heap的比较函数来决定的排序结构。但是需要注意的是heap的比较函数在同一时刻只有一个,所以QueueSort插件只能Enable一个,如果用户Enable了2个则调度器启动时会报错退出。下面是默认的比较函数,可供参考。

// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodQueueInfo.timestamp.
func (pl *PrioritySort) Less(pInfo1, pInfo2 *framework.QueuedPodInfo) bool {
    p1 := pod.GetPodPriority(pInfo1.Pod)
    p2 := pod.GetPodPriority(pInfo2.Pod)
    return (p1 > p2) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))
}

开定义的,未来会将UnReserve与Reserve统一到一起,即要求开发者在实现Reserve同时需要定义UnReserve,保证数据能够有效的清理,避免留下脏数据。

四、实现自己的调度插件

scheduler-plugins

Kubernetes负责Kube-scheduler的小组sig-scheduling为了更好的管理调度相关的Plugin,新建了项目scheduler-plugins 来方便用户管理不同的插件,用户可以直接基于这个项目来定义自己的插件。接下来我们以其中的Qos的插件来为例,演示是如何开发自己的插件。


QoS的插件主要基于Pod的 QoS(Quality of Service) class 来实现的,目的是为了实现调度过程中如果Pod的优先级相同时,根据Pod的Qos来决定调度顺序,调度顺序是: 1. Guaranteed (requests == limits) 2. Burstable (requests < limits) 3. BestEffort (requests and limits not set)

插件构造

首先插件要定义插件的对象和构造函数

// QoSSort is a plugin that implements QoS class based sorting.
type Sort struct{}
// New initializes a new plugin and returns it.
func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {
    return &Sort{}, nil
}

然后,根据我们插件要对应的extention point来实现对应的接口,Qos是作用于QueueSort的部分,所以我们要实现QueueSort接口的函数。如下所示,QueueSortPlugin接口只定义了一个函数Less,所以我们实现这个函数即可。

// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
    Plugin
    // Less are used to sort pods in the scheduling queue.
    Less(*PodInfo, *PodInfo) bool
}

实现的函数如下。默认的default QueueSort在比较的时候,首先比较优先级,然后再比较pod的timestamp。我们重新定义了Less函数,在优先级相同的情况下,通过比较Qos来决定优先级。

// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodInfo.timestamp.
func (*Sort) Less(pInfo1, pInfo2 *framework.PodInfo) bool {
    p1 := pod.GetPodPriority(pInfo1.Pod)
    p2 := pod.GetPodPriority(pInfo2.Pod)
    return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod))
}
func compQOS(p1, p2 *v1.Pod) bool {
    p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)
    if p1QOS == v1.PodQOSGuaranteed {
        return true
    } else if p1QOS == v1.PodQOSBurstable {
        return p2QOS != v1.PodQOSGuaranteed
    } else {
        return p2QOS == v1.PodQOSBestEffort
    }
}

插件注册

我们在启动的main函数中注册自己定义的插件和相应的构造函数

// cmd/main.go
func main() {
    rand.Seed(time.Now().UnixNano())
    command := app.NewSchedulerCommand(
        app.WithPlugin(qos.Name, qos.New),
    )
    if err := command.Execute(); err != nil {
        os.Exit(1)
    }
}

代码编译

$ make

Scheduler启动

kube-scheduler启动时,配置./manifests/qos/scheduler-config.yaml中kubeconfig的路径,启动时传入集群的kubeconfig文件以及插件的配置文件即可。

$ bin/kube-scheduler --kubeconfig=scheduler.conf --config=./manifests/qos/scheduler-config.yaml

至此,相信大家已经通过我们的介绍和示例了解了Kubernetes Scheduling Framework的架构和开发方法。


本文转自:Kubernetes 调度系统之 Scheduling Framework-阿里云开发者社区


「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论