Actions(动作)定义了调度器的工作流程和执行顺序。
目前Volcano提供了5个Actions:enqueue、allocate、backfill、preempt和reclaim。这些动作将按照定义的顺序执行。在Volcano的新版本中,可能会有增加新的动作,让我们逐一解释各个动作的作用:
1. enqueue(入队)
主要功能:将新提交的任务放入调度队列,并检查任务是否满足调度条件。
工作原理:
- 创建任务和队列的优先级队列,按照调度策略中定义的顺序处理
- 检查
PodGroup是否满足最小成员数要求 - 将符合条件的
Job状态从Pending更新为Inqueue - 更新
PodGroup的状态为PodGroupInqueue,表示已准备好被调度
示例场景:
当一个要求至少三个Pod同时运行的TensorFlow训练任务被提交时,enqueue动作会检查是否有足够的资源来运行这些Pod,如果有,则将其标记为可调度。
参数说明:
没有可配置参数。
注意事项:enqueue action 是不可省略的核心组件,原因如下:
- 调度流程的入口点:
enqueue是整个调度流程的第一步,负责将任务从"未调度"状态转移到"可调度"状态- 如果没有
enqueue,新提交的任务将无法进入调度队列,调度器将无法感知这些任务的存在
- 基础验证机制:
enqueue执行关键的前置检查,如验证PodGroup是否满足最小成员数要求- 它确保只有满足基本条件的任务才能进入调度流程,避免无效调度
- 默认配置的一部分:
- 在
Volcano的默认配置中,enqueue总是作为第一个action存在 - 即使在自定义配置中不显式指定,系统也会使用内置的
enqueue逻辑
2. allocate(分配)
主要功能:为队列中的任务分配资源,并将它们调度到合适的节点上。
工作原理:
- 根据队列权重和任务优先级对任务进行排序
- 使用插件对任务进行过滤和打分
- 过滤策略(Predicates):
- 资源匹配过滤:检查节点是否有足够的
CPU、内存、GPU等资源 - 节点亲和性过滤:根据
Pod的nodeAffinity设置过滤节点 - 污点容忍过滤:检查任务是否能容忍节点上的污点(
Taints) PodGroup约束过滤:检查是否有足够的资源同时运行PodGroup中的所有Pod- 自定义过滤器:通过
predicates插件实现的特定过滤逻辑
- 资源匹配过滤:检查节点是否有足够的
- 打分策略(Scoring):
- 节点资源打分:包括
leastrequested(选择资源使用率低的节点)、mostrequested(选择资源使用率高的节点)和balancedresource(平衡各类资源使用) - 节点亲和性打分(
nodeaffinity):根据节点亲和性规则给节点打分 Pod间亲和性打分(podaffinity):考虑Pod之间的亲和性和反亲和性- 污点容忍打分(
tainttoleration):根据Pod对节点污点的容忍度打分 - 镜像本地性打分(
imagelocality):优先选择已经有所需镜像的节点 Pod拓扑分布打分(podtopologyspread):实现Pod在拓扑域之间的均匀分布- 任务拓扑打分(
task-topology):通过注解定义任务间的亲和性和反亲和性,优化分布式任务的调度
- 节点资源打分:包括
- 过滤策略(Predicates):
- 为符合条件的任务分配资源并绑定到得分最高的节点
示例场景:
当多个队列中有多个任务时,allocate动作会首先将资源分配给高权重队列中的高优先级任务,然后再考虑低权重队列中的任务。
参数说明:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
predicateErrorCacheEnable | bool | true | 是否启用谓词错误缓存。启用后,调度器会缓存节点过滤阶段的错误信息,避免重复计算,提高调度效率。 |
参数示例:
allocate动作的参数需要在Volcano调度器的ConfigMap中配置。具体来说,这些参数应该在volcano-scheduler.conf文件的configurations部分中配置。以下是一个配置示例:
# volcano-scheduler-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: volcano-scheduler-configmap
namespace: volcano-system
data:
volcano-scheduler.conf: |
actions: "enqueue,allocate,preempt,reclaim,backfill"
tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
- name: proportion
- name: nodeorder
configurations:
- name: allocate
arguments:
predicateErrorCacheEnable: true
注意事项:
- 谓词错误缓存(
Predicate Error Cache)是一种优化机制,可以避免对已知不满足条件的节点重复执行过滤操作,从而提高调度效率。 - 在大规模集群中,启用此功能可以显著减少调度延迟,特别是当集群中有大量节点且调度频繁时。
- 在某些特殊场景下(如节点状态快速变化的环境),可能需要禁用此功能以确保调度决策基于最新状态。
最佳实践:
- 在大多数情况下,建议保持谓词错误缓存启用(默认设置)。
- 如果观察到由于缓存导致的调度异常(例如,节点状态变化后调度决策不准确),可以考虑禁用此功能。
- 在调试调度问题时,临时禁用此功能可能有助于排查问题。
注意事项:在 Volcano 调度器中,allocate action也是不可省略的核心组件,原因如下:
-
核心调度功能的实现者:
allocate是实际执行资源分配和Pod绑定的关键action- 它负责将已入队的任务分配到具体的节点上,是调度过程的核心步骤
- 如果没有
allocate,任务会停留在队列中而不会被实际调度执行
-
调度决策的执行者:
- 虽然其他
actions(如backfill、preempt)也可以执行调度,但它们都是针对特殊场景的补充 allocate处理常规的资源分配,是最基本的调度机制- 其他
actions通常在allocate无法满足需求时才会被触发
- 虽然其他
-
插件系统的主要应用点:
- 大多数调度插件(如
drf、predicates、nodeorder等)主要在allocate阶段发挥作用 - 这些插件通过过滤和打分机制帮助
allocate做出最优的调度决策
- 大多数调度插件(如
-
调度流程的核心环节:
- 在典型的调度流程中,
enqueue将任务放入队列,而allocate则负责实际分配资源 - 这两个
action构成了调度的基本闭环,缺一不可
- 在典型的调度流程中,
3. backfill(回填)
backfill(回填)是 Volcano 调度流程中非常重要的一个补充环节,其主要作用是在 allocate 完成主资源分配后,进一步挖掘和利用集群中的碎片资源。通常情况下,allocate负责为大部分高优先级或核心任务分配资源,而 backfill 主要针对那些由于资源碎片化而无法被 allocate 充分利用的节点,调度小型或低优先级任务,从而提升整体资源利用率。
backfill 在调度流程中的定位:
Volcano的调度流程并不是"一次性"分配完所有资源、所有任务就结束。调度器会不断循环执行actions,每一轮都会尝试调度新的任务和处理资源变化。enqueue负责将任务放入调度队列,allocate负责进行主要的资源分配。- 当
allocate无法满足所有任务需求,或集群中出现大量碎片资源时,backfill会被触发,专门调度适合这些碎片资源的小任务。 - 这使得调度流程形成了"主分配 + 回填补充"的高效闭环。
插件系统与 backfill 的配合:
- 大多数调度插件(如
drf、predicates、nodeorder等)虽然主要在allocate阶段发挥作用,但backfill阶段同样会复用这些插件的过滤和打分机制,确保回填任务的调度决策依然科学合理。 - 某些插件参数(如谓词错误缓存)可以显著提升
backfill的调度效率。
典型应用场景:
- 当集群中有节点仅剩少量
CPU和内存时,这些资源不足以运行大型任务,但通过backfill,可以将这些资源分配给小型批处理任务或低优先级作业,避免资源浪费。
主要功能:利用集群中的空闲资源来运行小型或低优先级的任务,提高资源利用率。
工作原理:
- 在主要的资源分配完成后执行
- 寻找集群中的碎片资源(小块未使用的资源)
- 将这些资源分配给可以快速完成的小任务
示例场景:
当集群中有一些节点只剩下少量的CPU和内存时,这些资源可能不足以运行大型任务,但backfill动作可以将这些资源分配给小型的批处理任务。
参数说明:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
predicateErrorCacheEnable | bool | true | 是否启用谓词错误缓存。启用后,调度器会缓存节点过滤阶段的错误信息,避免重复计算,提高调度效率。 |
参数示例:
backfill动作的参数需要在Volcano调度器的ConfigMap中配置。具体来说,这些参数应该在volcano-scheduler.conf文件的configurations部分中配置。以下是一个配置示例:
# volcano-scheduler-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: volcano-scheduler-configmap
namespace: volcano-system
data:
volcano-scheduler.conf: |
actions: "enqueue,allocate,preempt,reclaim,backfill"
tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
- name: proportion
- name: nodeorder
configurations:
- name: backfill
arguments:
predicateErrorCacheEnable: true
注意事项:
backfill动作与allocate动作类似,也支持谓词错误缓存功能,但它专注于利用集群的碎片资源。- 在资源紧张的集群中,启用此功能可以提高资源利用率,尤其是当有大量小型任务需要调度时。
- 如果集群中的节点状态频繁变化,禁用此功能可能会带来更准确的调度结果,但代价是调度效率的降低。
最佳实践:
- 在资源利用率需要提高的场景中,建议保持
backfill动作启用。 - 对于具有大量小型任务(如数据处理、批量任务)的工作负载,
backfill可以显著提高资源利用率。 - 对于需要精确资源预留的关键应用,可能需要谨慎使用
backfill,以避免资源碎片化影响主要工作负载的性能。
4. preempt(抢占)
主要功能:当高优先级任务无法获得足够资源时,从低优先级任务中抢占资源(仅针对同队列任务)。
工作原理:
- 识别高优先级但无法调度的任务(首先任务的
PodGroup需要处于Inqueue状态) - 查找可以被抢占的低优先级任务
- 终止被选中的低优先级任务,释放其资源
- 将释放的资源分配给高优先级任务
抢占行为主要由以下机制控制:
-
Pod的Preemptable标记:通过
volcano.sh/preemptable注解控制Pod是否可被抢占- 注意:默认值为
true(可被抢占) - 设置为
false则该Pod不会被抢占
- 注意:默认值为
-
Kubernetes PriorityClass:定义任务的优先级
- 高优先级任务可以抢占低优先级任务
- 优先级通过
priorityClassName字段指定
-
Plugin的Preemptable函数:各插件(如
priority、conformance、gang等)通过实现PreemptableFn来决定哪些任务可被抢占- 多个插件的结果取交集
- 只有所有启用的插件都认为可抢占的任务才会被抢占
-
Victim选择策略:通过
BuildVictimsPriorityQueue实现- 同一
Job内的任务:按Task优先级从低到高抢占 - 不同
Job的任务:按Job优先级从低到高抢占 - 不同
Queue的任务:按VictimQueueOrderFn排序
- 同一
注意:Volcano的抢占策略是基于优先级的确定性选择,而非随机选择。系统会优先抢占优先级最低的任务,确保高优先级任务优先保障。
示例场景:
当一个生产环境的关键任务(高优先级)需要运行,但集群资源已被开发环境的任务(低优先级)占用时,preempt动作会终止部分开发环境的任务,将资源让给生产环境的关键任务。
参数说明:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
predicateErrorCacheEnable | bool | true | 是否启用谓词错误缓存。启用后,调度器会缓存节点过滤阶段的错误信息,避免重复计算,提高调度效率。 |
enableTopologyAwarePreemption | bool | false | 是否启用拓扑感知抢占。启用后,调度器会考虑节点拓扑关系进行更智能的抢占决策。 |
topologyAwarePreemptWorkerNum | int | 16 | 拓扑感知抢占的并发工作线程数。 |
minCandidateNodesPercentage | int | 10 | 候选节点的最小百分比(用于拓扑感知抢占)。 |
minCandidateNodesAbsolute | int | 1 | 候选节点的最小绝对数量(用于拓扑感知抢占)。 |
maxCandidateNodesAbsolute | int | 100 | 候选节点的最大绝对数量(用于拓扑感知抢占)。 |
参数示例:
configurations:
- name: preempt
arguments:
predicateErrorCacheEnable: true
enableTopologyAwarePreemption: false
topologyAwarePreemptWorkerNum: 16
minCandidateNodesPercentage: 10
minCandidateNodesAbsolute: 1
maxCandidateNodesAbsolute: 100
配置示例:
preempt动作的配置主要包括启用action和配置相关plugin。以下是一个完整的配置示例:
actions: "enqueue, allocate, backfill, preempt"
tiers:
- plugins:
- name: priority
- name: conformance
- name: overcommit
- plugins:
- name: drf
- name: gang
- name: predicates
- name: proportion
- name: nodeorder
- name: binpack
工作流程:
根据源码分析,抢占的关键流程如下:
-
识别抢占者(Preemptor):调度器便利所有的任务,识别出因资源不足而无法调度的高优先级任务(通过
JobStarving方法判断)。并将任务按照队列为维度进行归并,后续按照队列维度进行遍历处理。 -
检查抢占资格:
- 检查任务的
PreemptionPolicy是否为Never - 检查任务是否有
NominatedNodeName且该节点是否仍然不可调度
- 检查任务的
-
筛选候选节点:
- 过滤掉
UnschedulableAndUnresolvable状态的节点 - 对候选节点执行
Predicate检查
- 过滤掉
-
查找可抢占任务(Victims):
- 遍历节点上的所有任务,应用
filter函数过滤 - 调用各插件的
PreemptableFn,取所有插件结果的交集 - 只有标记为
volcano.sh/preemptable: "true"(或未标记,默认为true)的Pod才可被抢占 BestEffort Pod不能抢占非BestEffort Pod
- 遍历节点上的所有任务,应用
-
选择Victim:
- 通过
BuildVictimsPriorityQueue构建优先级队列 - 同一
Job内:按Task优先级从低到高抢占 - 不同
Job:按Job优先级从低到高抢占 - 不同
Queue:按VictimQueueOrderFn排序
- 通过
-
执行抢占:
- 依次驱逐(
Evict)选中的低优先级任务 - 释放的资源累加到节点的
FutureIdle中 - 当资源足够或队列可分配时停止抢占
- 依次驱逐(
-
Pipeline调度:
- 将高优先级任务
Pipeline到目标节点 - 更新任务状态为
Pipelined
- 将高优先级任务
5. reclaim(回收)
主要功能:从超出其公平份额的队列中回收资源,并将其重新分配给其他队列(仅针对跨队列任务的资源回收,同队列不生效)。
工作原理:
- 计算每个队列的公平份额和实际使用情况
- 识别超额使用资源的队列
- 从这些队列中选择可回收的任务
- 终止这些任务以释放资源
- 只回收标记为
reclaimable: true的队列的资源
Preempt vs Reclaim 核心差别
| 维度 | Preempt Action | Reclaim Action |
|---|---|---|
| 作用范围 | 同队列内不同Job之间 | 跨队列资源回收 |
| 触发条件 | Job资源不足(JobStarving) | Job资源不足 + 队列未超额 |
| 抢占对象 | 同队列的低优先级Job | 其他队列的可回收资源 |
| 关键过滤 | job.Queue == preemptorJob.Queue | j.Queue != job.Queue |
| 队列控制 | 无需队列配置 | 需要reclaimable: true |
| 支持场景 | 1. 队列内Job优先级; 2. 同Job内Task优先级 | 队列间资源公平分配 |
示例场景:
当集群中有多个队列,每个队列都有权重设置(如生产队列权重为60%,开发队列为30%,测试队列为10%)。如果开发队列使用了超过50%的集群资源,而生产队列需要更多资源时,reclaim动作会从开发队列中回收资源。
参数说明:
没有可配置参数。
注意事项:
为什么队列设置了 Quota 还会出现超额使用的情况?
Volcano 的资源分配和回收机制采用了"宽松分配+周期性回收"的设计,主要原因有:
- 宽松分配:在实际调度时,为了提升资源利用率和调度灵活性,调度器可能允许某些队列临时超出其配额(
Quota)使用资源,尤其是在集群资源充足、其他队列没有资源需求时。 - 动态变化:集群资源和队列需求是动态变化的,某一时刻资源分配合理,但随着新任务加入或资源需求变化,某些队列可能会暂时超额。
- 周期性回收:
reclaim动作就是为了解决上述问题而设计的。它会定期检查所有队列的资源使用情况,一旦发现某队列超出其公平份额,就会触发资源回收,将多占用的资源释放出来,分配给资源不足的队列。
Volcano 的 reclaim(回收)操作本质上就是强制终止(Evict/Kill)超额队列中的部分任务的 Pod,以释放资源并将其分配给其他资源不足或高优先级的队列。被回收的任务如果有重试或重调度机制,后续可能会被重新调度到集群中。
6. shuffle(重新分配)
主要功能:选择并驱逐正在运行的任务,以实现资源重新分配或负载均衡。
工作原理:
- 收集所有正在运行的任务
- 通过插件定义的策略选择需要驱逐的任务
- 驱逐选中的任务,释放其资源
- 这些任务将在后续调度周期中被重新调度
示例场景:
当集群中的负载分布不均衡时,例如某些节点资源利用率非常高而其他节点却相对空闲,shuffle动作可以驱逐部分任务,让它们在下一个调度周期重新分配到资源利用率较低的节点上,从而实现负载均衡。
参数说明:
没有可配置参数。
注意事项:
shuffle动作会导致任务被驱逐并重新调度,这可能会对应用程序造成短暂的中断。- 需要配合相应的插件(如
victimtasks)来定义驱逐策略,决定哪些任务应该被驱逐。 - 对于状态敏感或需要长时间运行的应用,应谨慎使用
shuffle动作,或者确保这些应用有适当的状态保存和恢复机制。
最佳实践:
- 将
shuffle动作放在调度器配置的后面,确保它只在其他调度策略(如allocate、backfill)无法解决问题时才会被触发。 - 为需要保护的关键任务添加适当的标签或注解,确保它们不会被
shuffle动作驱逐。 - 在资源利用率不均衡或需要定期重新平衡集群负载的场景中,
shuffle动作可以提供显著的效益。