Volcano Scheduler
Volcano Scheduler 负责 Pod 调度的组件(在功能上可以对比下k8s原生的调度器kube-scheduler)它由一系列action 和plugin 组成 。
action定义了调度各环节中需要执行的动作,plugin根据不同场景提供了action 中算法的具体实现细节。
Volcano Scheduler具有高度的可扩展性,可以根据需要实现自己的action和plugin。
Volcano scheduler的工作流程如下:
-
客户端提交的Job被scheduler观察到并缓存起来。
-
周期性的开启会话,一个调度周期开始。
-
将没有被调度的Job发送到会话的待调度队列中。
-
遍历所有的待调度Job,按照定义的次序依次执行enqueue、allocate、preempt、reclaim、backfill等动作,为每个Job找到一个最合适的节点。将该Job 绑定到这个节点。action中执行的具体算法逻辑取决于注册的plugin中各函数的实现。
-
关闭本次会话。
actions
https://github.com/volcano-sh/volcano/tree/master/pkg/scheduler/actions action 是触发执行 plugin 的动作。
如果参照
k8s scheduler,action相当于preFilter、Fileter、PostFilter、PreBind、Bind、PostBind等这些动作。
- enqueue
Enqueue action负责通过一系列的过滤算法筛选出符合要求的待调度任务并将它们送入待调度队列。经过这个action,任务的状态将由pending变为inqueue。
- allocate
Allocate action负责通过一系列的预选和优选算法筛选出最适合的节点。
- preempt
Preempt action负责根据优先级规则为同一队列中高优先级任务执行抢占调度。
- reclaim
Reclaim action负责当一个新的任务进入待调度队列,但集群资源已不能满足该任务所在队列的要求时,根据队列权重回收队列应得资源。
- backfill
backfill action负责将处于pending状态的任务尽可能的调度下去以保证节点资源的最大化利用。
plugins
https://github.com/volcano-sh/volcano/tree/master/pkg/scheduler/plugins 。
插件有两个关键方法OnSessionOpen和OnSessionClose, 用于在session开始和结束时执行。
- gang
gang plugin 认为未处于 ready 状态(包括 Binding、Bound、Running、Allocated、Succeed、Pipelined)的任务具有更高的优先级。它会检查假如驱逐某些任务回收队列部分应得资源后,该任务所属的 Job 中任务的运行数量是否满足 minAvailable 的要求,以决定是否执行驱逐动作。
- conformance
conformance plugin 认为命名空间 kube-system 下的任务具有更高的优先级。这些任务不能被抢占。
- DRF
DRF plugin 认为占用资源较少的任务具有更高的优先级。它会尝试计算已分配给抢占者和被抢占者的资源总量,并在抢占者资源资源份额更少时触发抢占行为。
- nodeorder
nodeorder plugin 通过一系列维度的打分算法,算出针对某个任务时所有的节点的得分情况。得分最高的节点被认为是针对该任务最合适的节点。
- predicates
predictions plugin 通过一系列维度的评估算法,决定某个任务是否适合被绑定到某个节点。
- priority
priority plugin 用于比较两个 job 或任务的优先级。它通过比较 job.spec.priorityClassName 来决定哪个job的优先级更高。对于两个任务,它会依次比较 task.priorityClassName、task.createTime、task.id in order来决定谁的优先级更高。
配置
在 volcano-scheduler.conf 中主要包括 actions 和 tiers 两部分。在 actions 中,使用逗号作为分隔符配置各需要执行的 action 。需要注意的是,action 的配置顺序就是 scheduler 的执行顺序。Volcano 本身不会对 action 顺序的合理性进行检查。tiers 中配置的 plugin 列表即为注册到 scheduler 中的 plugin。plugin 中实现的算法将会被 action 调用。
默认调度器基于上述调度策略的主要原因是,k8s自己没有真实去获取节点真实资源消耗,导致无法实现更合理的资源调度。开源Prometheus可以获取到各个节点的真实负载情况,基于volcano调度插件的能力可以实现基于应用能够基于真实负载调度,在资源满足的情况下,Pod优先被调度至真实负载低的节点,集群各节点负载趋于均衡。
-
支持基于节点使用的调度;
-
过滤使用率高于用户定义的使用阈值的节点;
-
优先处理节点使用情况的节点,并将pod调度到使用率较低的节点;
节点真实负载感知调度
当前的基于分配率的调度模式在一些场景下会带来各个节点资源使用率不均衡的现象,如部分节点高分配率、低使用率等。v1.8.2 版本中Volcano借助Prometheus采集的集群节点负载数据进行调度决策,保证各个节点使用率最大程度均衡,同时允许用户配置节点cpu,memory的上限值,防止部分节点使用率过高导致节点异常。
更多详情可参考 https://github.com/volcano-sh/volcano/issues/1777,调度策略配置样例如下:
更多的插件可参见 https://volcano.sh/zh/docs/v1-8-2/plugins/ 。
Volcano 源码
metrics_client.go 主要源码如下所示:
metrics_client_prometheus.go 源码如下所示:
SchedulerCache 函数如下所示:
GetMetricsData 函数如下所示:
// GetMetricsData 通过 Prometheus 获取 Node 节点资源使用情况
func (sc *SchedulerCache) GetMetricsData() {
metricsType := sc.metricsConf["type"]
if len(metricsType) == 0 {
klog.V(3).Infof("The metrics type is not set in the volcano scheduler configmap file. " +
"As a result, the CPU and memory load information of the node is not collected.")
return
}
// NewMetricsClient
client, err := source.NewMetricsClient(sc.restConfig, sc.metricsConf)
if err != nil {
klog.Errorf("Error creating client: %v\n", err)
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
defer cancel()
nodeMetricsMap := make(map[string]*source.NodeMetrics, len(sc.NodeList))
sc.Mutex.Lock()
for _, nodeName := range sc.NodeList {
nodeMetricsMap[nodeName] = &source.NodeMetrics{}
}
sc.Mutex.Unlock()
// 填充所有的 Node 节点数据
err = client.NodesMetricsAvg(ctx, nodeMetricsMap)
if err != nil {
klog.Errorf("Error getting node metrics: %v\n", err)
return
}
sc.setMetricsData(nodeMetricsMap)
}
Volcano 调度算法
func init() {
// Plugins for Jobs
framework.RegisterPluginBuilder(drf.PluginName, drf.New)
framework.RegisterPluginBuilder(gang.PluginName, gang.New)
framework.RegisterPluginBuilder(predicates.PluginName, predicates.New)
framework.RegisterPluginBuilder(priority.PluginName, priority.New)
framework.RegisterPluginBuilder(nodeorder.PluginName, nodeorder.New)
framework.RegisterPluginBuilder(conformance.PluginName, conformance.New)
framework.RegisterPluginBuilder(binpack.PluginName, binpack.New)
framework.RegisterPluginBuilder(tdm.PluginName, tdm.New)
framework.RegisterPluginBuilder(overcommit.PluginName, overcommit.New)
framework.RegisterPluginBuilder(sla.PluginName, sla.New)
framework.RegisterPluginBuilder(tasktopology.PluginName, tasktopology.New)
framework.RegisterPluginBuilder(numaaware.PluginName, numaaware.New)
framework.RegisterPluginBuilder(cdp.PluginName, cdp.New)
framework.RegisterPluginBuilder(rescheduling.PluginName, rescheduling.New)
framework.RegisterPluginBuilder(usage.PluginName, usage.New)
// Plugins for Queues
framework.RegisterPluginBuilder(proportion.PluginName, proportion.New)
// Plugins for Extender
framework.RegisterPluginBuilder(extender.PluginName, extender.New)
// Plugins for ResourceQuota
framework.RegisterPluginBuilder(resourcequota.PluginName, resourcequota.New)
}
评论区