作者:王庆璨 张凯

前言

Kubernetes曾经成为目前事实标准上的容器集群治理平台。它为容器化利用提供了自动化部署、运维、资源调度等全生命周期治理性能。通过3年多的疾速倒退,Kubernetes在稳定性、扩展性和规模化方面都有了长足进步。 尤其是Kubernetes管制立体的外围组件日臻成熟。而作为决定容器是否在集群中运行的调度器Kube-scheduler,更是因为长久以来体现稳固,且已能满足大部分Pod调度场景,逐步不被开发人员特地关注。

随同着Kubernetes在私有云以及企业外部IT零碎中广泛应用,越来越多的开发人员尝试应用Kubernetes运行和治理Web利用和微服务以外的工作负载。典型场景包含机器学习和深度学习训练任务,高性能计算作业,基因计算工作流,甚至是传统的大数据处理工作。此外,Kubernetes集群所治理的资源类型也更加丰盛,不仅有GPU,TPU和FPGA,RDMA高性能网络,还有针对畛域工作的各种定制加速器,比方各种AI芯片,NPU,视频编解码器等。开发人员心愿在Kubernetes集群中能像应用CPU和内存那样简略地申明式应用各种异构设施。

总的来说,围绕Kubernetes构建一个容器服务平台,对立治理各种新算力资源,弹性运行多种类型利用,最终把服务按需交付到各种运行环境(包含公共云、数据中心、边缘节点,甚至是终端设备),未然成为云原生技术的发展趋势。

阿里云容器服务团队联合多年Kubernetes产品化与客户反对教训,对Kube-scheduler进行了大量扩大和改良,逐渐使其在多种场景下仍然能稳固、高效地调度简单工作负载类型。

《进击的Kubernetes调度零碎》系列文章将把咱们的教训、技术思考和实现细节全面地展示给Kubernetes用户和开发者,冀望帮忙大家更好地理解Kubernetes调度零碎的弱小能力和将来倒退方向。

晚期计划

首先,让咱们来理解一下Kubernetes社区都有过哪些晋升调度器扩大能力的计划。

要对立治理和调度异构资源与更多简单工作负载类型,首先面对挑战的就是Kube-scheduler。在Kubernetes社区里对于晋升调度器扩大能力的探讨始终一直。sig-scheduling给出的判断是,越多功能退出,使得调度器代码量宏大,逻辑简单,导致保护的难度越来越大,很多bug难以发现、解决。而对于应用了自定义调度的用户来说,跟上每一次调度器性能更新,都充斥挑战。

在阿里云,咱们的用户遇到了同样的挑战。Kubernetes原生调度器循环解决单个Pod容器的固定逻辑,无奈及时、简略地反对用户在不同场景的需要。所以针对特定的场景,咱们会基于原生Kube-scheduler扩大本人的调度策略。

最后对于Kube-scheduler进行扩大的形式次要有两种,一种是调度器扩大(Scheduler Extender), 另外一种是多调度器(Multiple schedulers)。接下来咱们对这两种形式别离进行介绍和比照。

Scheduler Extender

社区最后提供的计划是通过Extender的模式来扩大scheduler。Extender是内部服务,反对Filter、Preempt、Prioritize和Bind的扩大,scheduler运行到相应阶段时,通过调用Extender注册的webhook来运行扩大的逻辑,影响调度流程中各阶段的决策后果。

以Filter阶段举例,执行过程会通过2个阶段:

  1. scheduler会先执行内置的Filter策略,如果执行失败的话,会间接标识Pod调度失败。
  2. 如何内置的Filter策略执行胜利的话,scheduler通过Http调用Extender注册的webhook, 将调度所须要的Pod和Node的信息发送到到Extender,依据返回filter后果,作为最终后果。

咱们能够发现Extender存在以下问题:

  1. 调用Extender的接口是HTTP申请,受到网络环境的影响,性能远低于本地的函数调用。同时每次调用都须要将Pod和Node的信息进行marshaling和unmarshalling的操作,会进一步升高性能。
  2. 用户能够扩大的点比拟无限,地位比拟固定,无奈反对灵便的扩大,例如只能在执行完默认的Filter策略后能力调用。

基于以上介绍,Extender的形式在集群规模较小,调度效率要求不高的状况下,是一个灵便可用的扩大计划,然而在失常生产环境的大型集群中,Extender无奈反对高吞吐量,性能较差。

Multiple schedulers

Scheduler在Kubernetes集群中其实相似于一个非凡的Controller,通过监听Pod和Node的信息,给Pod筛选最佳的节点,更新Pod的spec.NodeName的信息来将调度后果同步到节点。所以对于局部有非凡的调度需要的用户,有些开发者通过自研Custom Scheduler来实现以上的流程,而后通过和default scheduler同时部署的形式,来反对本人非凡的调度需要。

Custom Scheduler会存在一下问题:

  1. 如果与default scheduler同时部署,因为每个调度器所看到的资源视图都是全局的,所以在调度决策中可能会在同一时刻在同一个节点资源上调度不同的Pod,导致节点资源抵触的问题。
  2. 有些用户将调度器所能调度的资源通过Label划分不同的池子,能够防止资源抵触的景象呈现。然而这样又会导致整体集群资源利用率的降落。
  3. 有些用户抉择通过齐全自研的形式来替换default scheduler,这种会带来比拟高的研发老本,以及Kubernetes版本升级后可能存在的兼容性问题。

Scheduler Extender的性能较差可是保护老本较小,Custom Scheduler的研发和保护的老本特地高然而性能较好,这种状况是开发者面临这种两难处境。这时候Kubernetes Scheduling Framework V2横空出世,给咱们带来鱼和熊掌能够兼得的计划。

新一代调度框架 Scheduling Framework之解析

社区也逐步的发现开发者所面临的窘境,为了解决如上问题,使Kube-scheduler扩展性更好、代码更简洁,社区从Kubernetes 1.16版本开始, 构建了一种新的调度框架Kubernetes Scheduling Framework的机制。

Scheduling Framework在原有的调度流程中, 定义了丰盛扩大点接口,开发者能够通过实现扩大点所定义的接口来实现插件,将插件注册到扩大点。Scheduling Framework在执行调度流程时,运行到相应的扩大点时,会调用用户注册的插件,影响调度决策的后果。通过这种形式来将用户的调度逻辑集成到Scheduling Framework中。

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

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))}

PreFilter

PreFilter在scheduling cycle开始时就被调用,只有当所有的PreFilter插件都返回success时,能力进入下一个阶段,否则Pod将会被回绝掉,标识此次调度流程失败。PreFilter相似于调度流程启动之前的预处理,能够对Pod的信息进行加工。同时PreFilter也能够进行一些预置条件的查看,去查看一些集群维度的条件,判断否满足pod的要求。

Filter

Filter插件是scheduler v1版本中的Predicate的逻辑,用来过滤掉不满足Pod调度要求的节点。为了晋升效率,Filter的执行程序能够被配置,这样用户就能够将能够过滤掉大量节点的Filter策略放到前边执行,从而缩小后边Filter策略执行的次数,例如咱们能够把NodeSelector的Filter放到第一个,从而过滤掉大量的节点。Node节点执行Filter策略是并发执行的,所以在同一调度周期中屡次调用过滤器。

PostFilter

新的PostFilter的接口定义在1.19的版本会公布,次要是用于解决当Pod在Filter阶段失败后的操作,例如抢占,Autoscale触发等行为。

PreScore

PreScore在之前版本称为PostFilter,当初批改为PreScore,次要用于在Score之前进行一些信息生成。此处会获取到通过Filter阶段的节点列表,咱们也能够在此处进行一些信息预处理或者生成一些日志或者监控信息。

Scoring

Scoring扩大点是scheduler v1版本中Priority的逻辑,目标是为了基于Filter过滤后的残余节点,依据Scoring扩大点定义的策略挑选出最优的节点。
Scoring扩大点分为两个阶段:

  1. 打分:打分阶段会对Filter后的节点进行打分,scheduler会调用所配置的打分策略
  2. 归一化: 对打分之后的构造在0-100之间进行归一化解决

Reserve

Reserve扩大点是scheduler v1版本的assume的操作,此处会对调度后果进行缓存,如果在后边的阶段产生了谬误或者失败的状况,会间接进入Unreserve阶段,进行数据回滚。

Permit

Permit扩大点是framework v2版本引入的新性能,当Pod在Reserve阶段实现资源预留之后,Bind操作之前,开发者能够定义本人的策略在Permit节点进行拦挡,依据条件对通过此阶段的Pod进行allow、reject和wait的3种操作。allow示意pod容许通过Permit阶段。reject示意pod被Permit阶段回绝,则Pod调度失败。wait示意将Pod处于期待状态,开发者能够设置超时工夫。

binding cycle

binding cycle须要调用apiserver的接口,耗时较长,为了进步调度的效率,须要异步执行,所以此阶段线程不平安。

Bind

Bind扩大点是scheduler v1版本中的Bind操作,会调用apiserver提供的接口,将pod绑定到对应的节点上。

PreBind 和 PostBind

开发者能够在PreBind 和 PostBind别离在Bind操作前后执行,这两个阶段能够进行一些数据信息的获取和更新。

UnReserve

UnReserve扩大点的性能是用于清理到Reserve阶段的的缓存,回滚到初始的状态。以后版本UnReserve与Reserve是离开定义的,将来会将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.gofunc 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作为调度器的新架构方向,在可扩展性和定制化方面提高很大。基于此Kubernetes能够逐渐承载更多类型的利用负载了, 一个平台一套IT架构和技术堆栈的愿景向前演进。同时为了更好的反对数据计算类型的工作迁徙到Kubernetes平台中,咱们也在致力将数据计算类型中罕用Coscheduling/Gang Scheduling、Capacity Scheduling、Dominant Resource Fairness和多队列治理等个性,通过Scheduling Framework的插件机制来融入到原生的Kube-scheduler中。
接下来,本系列文章将围绕AI、大数据处理和高规格计算资源集群等场景,介绍咱们是如何开发相应调度器插件的。敬请期待。