ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Volcano 分层队列设计:基于 Capacity 插件实现多层级队列的借用、回收与抢占控制

Volcano 分层队列设计:基于 Capacity 插件实现多层级队列的借用、回收与抢占控制 Volcano 分层队列设计基于 Capacity 插件实现多层级队列的借用、回收与抢占控制【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano在云原生 AI 任务调度场景中公平调度与资源利用率是两个核心诉求。Volcano 的 capacity 插件已经支持按deserved资源量实现队列间的弹性借用/归还与回收但真实业务中队列往往呈树形组织对应公司/团队层级结构。本文基于 Volcano 官方设计文档 Hierarchical Queue on Capacity Plugin 及配套源码实现完整讲解分层队列Hierarchical Queue的动机、Webhook 校验、调度器内部数据结构、关键函数改造以及ancestorReclaimLevel回收范围控制的语义与 11 个单元测试场景帮助读者掌握如何在 Volcano 中配置并运维一套父子层级化的队列体系。一、设计动机与范围Volcano 社区的 capacity 插件通过为每个队列指定deserved应得资源量实现了细粒度的队列资源借用/借贷管理空闲队列的资源可被其他队列占用而当资源原属主队列有作业进入时可按deserved量把资源收回。这一机制显著提升了资源利用率。但在实际生产中队列通常是分层的与组织内部门—团队—项目组的层级结构一一对应。为了让队列管理贴合真实使用场景并进一步提升 AI 任务调度的资源利用率Volcano 在 capacity 插件基础上引入了分层队列管理能力。设计范围明确划界In Scope增强 capacity 插件的弹性队列管理能力支持分层队列Out of Scope不扩展其他插件如 proportion 等的分层队列能力。用户故事设计文档给出了四条用户故事它们分别对应分层队列的四个关键能力Story 1管理员视角创建分层队列以有效管理不同队列间的资源分配包括父队列与子队列间的资源管理并确保所有子队列的 capacity/guarantee 资源之和不超过父队列Story 2用户视角只有叶子队列没有子队列的队列才能调度作业/PodGroup从而在不同层级间高效管理资源与任务Story 3状态同步当子队列的资源状态发生变化时其父队列的资源状态应当自底向上同步更新Story 4回收与抢占的层级感知执行 reclaim 和 preempt 动作时应优先回收/抢占与抢占者同父队列的队列中的作业然后再按层级自底向上考虑其他队列。设计文档给出的例子在如下队列结构中当作业 A 执行 reclaim 或 preempt 时系统应先考虑作业 B与 A 属于同一父队列a再考虑作业 C属于同一父队列root。二、Queue 资源与层级字段分层队列在 API 层的核心承载是 Queue CRD 的spec.parent字段定义于 Queue 类型定义Parent string json:parent,omitempty protobuf:bytes,8,opt,nameparent配合原有的spec.deserved应得、spec.capability能力上限、spec.guarantee保证字段一个典型的分层队列配置如下# 父队列整个项目组capability 为该组可使用的总量 apiVersion: scheduling.volcano.sh/v1beta1 kind: Queue metadata: name: project spec: capability: cpu: 16 memory: 64Gi deserved: cpu: 16 memory: 64Gi # 子队列叶子队列具体团队parent 指向父队列 apiVersion: scheduling.volcano.sh/v1beta1 kind: Queue metadata: name: team-a spec: parent: project capability: cpu: 8 deserved: cpu: 4注意约束子队列的capability/deserved不能突破父队列的上限见下文 Webhook 校验与调度器侧检查且只有叶子队列可以挂作业。三、Webhook 准入校验设计文档要求 Webhook 在队列的创建、删除、关闭三个环节做层级感知校验实现位于 queue 准入校验器3.1 创建/变更时的层级校验在准入处理中CREATE 操作或 UPDATE 时spec.parent发生变化都会触发validateHierarchicalQueuevalidate_queue.go#L95-L100。该校验的具体逻辑validate_queue.go#L373-L408若parent为空或为root视为顶层队列直接放行禁止自引用队列不能把自己作为父队列深度限制validateQueueDepthL500-L518沿父链向上遍历深度超过MaxQueueDepth即拒绝。webhook-manager 的默认配置为defaultMaxQueueDepth 5见 webhook 启动参数父队列正在调度检查若父队列当前尚无任何子队列且其status.allocated中已存在被分配 Pod 数则拒绝把它作为父队列对应设计文档中若父队列正在调度作业/PodGroup则不允许创建子队列的约束。3.2 资源总量约束校验当队列的parent、capability、deserved、guarantee等资源属性变更时会触发validateHierarchicalQueueResourcesL527-L567其包含三类约束与设计文档 Story 1 完全对应validateChildAgainstAncestor子队列的capability不能超过祖先队列的capability。实现上通过findNearestAncestorCapability沿父链向上找到最近的、对该资源维度定义了 capability 的祖先作为上限validateSiblingsSum同一父队列下所有兄弟队列含本队列的deserved/guarantee之和不能超过父队列对应上限validateChildrenConstraints父队列侧同样校验子队列集合的约束子队列 capability ≤ 父队列 capability 等。3.3 删除与关闭的级联语义设计文档规定删除队列时先删除其子队列再删除队列本身关闭队列时先关闭其子队列再关闭队列本身。准入层在 DELETE 操作调用validateQueueDeleting拦截不满足级联条件的删除请求保证树结构不会出现悬空子队列。3.4 Root 队列保护webhook-manager 默认开启enable-root-queue-protectionoptions.go#L105默认值为true当 UPDATE 请求试图修改名为root的队列的capability、deserved、guarantee资源属性时会被直接拒绝validate_queue.go#L102-L109。四、Root 队列整棵队列树的顶点设计文档定义root 队列是所有队列的父队列。当调度器配置启用 capacity 插件并打开enableHierarchy选项时一个 queueID 为root的根队列被自动纳入调度会话其Deserved与Capability字段每次基于ssn.TotalResource更新。从源码看这一设计落在 capacity 插件中capacity.go常量rootQueueID root与插件字段rootQueueL42-L69const ( PluginName capacity ancestorReclaimLevelKey ancestorReclaimLevel rootQueueID root ) type capacityPlugin struct { rootQueue string totalResource *api.Resource totalGuarantee *api.Resource ancestorReclaimLevel int queueOpts map[api.QueueID]*queueAttr pluginArguments framework.Arguments // ... }在会话构建阶段buildHierarchicalQueueAttrsroot 队列的capability若为空则被设置为无限资源含为每个标量资源维度设置MaxInt64因为用户允许提交超出当前集群总资源量的作业root 队列不应成为瓶颈限制应通过用户手动创建队列来表达L1325-L1342rootQueueAttr : cp.queueOpts[api.QueueID(cp.rootQueue)] if rootQueueAttr nil { klog.Warningf(Root queue %q not found in session queues, skipping hierarchy initialization, cp.rootQueue) return false } infinityResource : api.InfiniteResource() // ... if rootQueueAttr.capability.IsEmpty() { rootQueueAttr.capability infinityResource } rootQueueAttr.realCapability rootQueueAttr.capability队列父子关系的构建由updateAncestors完成L1452-L1485从叶子队列出发沿spec.parent向上回溯缺省 parent 即root同时把自身注册进父队列的children、把父链写入自身的ancestors期间还做了环检测visited集合发现回环时报cycle detected in queue hierarchy错误和悬空父队列检查父队列不在会话中报错。五、数据结构改造queueAttr 的 ancestors 与 children设计文档提出为 capacity 插件的queueAttr增加两个字段parents含 root 在内的所有父队列与children所有子队列。当前源码实现见 capacity.go#L81-L101字段命名为ancestors语义与设计文档一致type queueAttr struct { queueID api.QueueID name string share float64 ancestors []api.QueueID // 含 root 队列在内的所有祖先队列 children map[api.QueueID]*queueAttr // 该队列的全部子队列 deserved *api.Resource allocated *api.Resource request *api.Resource // elastic 作业弹性资源之和job elastic job.allocated - job.minAvailable elastic *api.Resource // inqueue inqueue 作业的请求资源 inqueue *api.Resource capability *api.Resource // realCapability 是队列资源上限不超过 capability realCapability *api.Resource guarantee *api.Resource // ... }这两个字段是整棵队列树在调度器内存中的双向索引ancestors用于自底向上的资源汇总与逐级回收检查children用于判断叶子队列isLeafQueue仅检查children是否为空L1585-L1587以及队列排序。5.1 会话打开时的层级构建与一致性检查OnSessionOpen中根据ssn.HierarchyEnabled(PluginName)决定走层级构建路径buildHierarchicalQueueAttrs还是原有buildQueueAttrsL450-L457。层级路径包含为会话内每个队列建立queueAttr并回溯父链若任一队列的updateAncestors失败悬空父队列、环整个会话判定为不可调度readyToSchedule falsereclaim 函数直接返回util.Reject——这落实了设计文档检查不合规时打印错误、相关函数返回 disallowed、用户需按错误信息调整队列直至恢复调度的要求校验作业的队列必须是叶子队列若job.Queue对应的attr.children非空直接报错The Queue x of Job ns/name is not leaf queue并使会话不可调度L1241-L1244落实 Story 2自底向上的资源汇总统计每个叶子队列的allocated/request/inqueue/elastic增量后沿attr.ancestors逐层把增量累加到各祖先队列L1301-L1319这正是设计文档updateParentQueue一节的遍历队列层级自底向上更新父队列资源信息的落地方式对 root 队列调用checkHierarchicalQueue做递归一致性检查L1487-L1556子队列deserved取max(deserved, guarantee)子队列未显式设置 CPU/Memory 或标量资源的 capability 时继承父队列对应维度值子队列 capability 超出父队列 capability 时打印 Error 日志其有效上限realCapability按父队列收敛realCapability (父 realCapability - 兄弟 guarantee 之和) ∩ min(自身 capability)子队列deserved/guarantee之和超过父队列对应值时打印错误日志提示可能影响调度中的资源分配/无法同时满足全部 guarantee。从源码注释可以看到checkHierarchicalQueue只打印告警而不中断会话never returns errors to avoid aborting the entire scheduling cycle due to configuration issues即硬错误悬空父队列、环、非叶子挂作业阻断调度软越界deserved 之和超父队列降级为告警并收敛 effective capability——这是实现上对设计文档不合规即拒绝的一种务实细化。5.2 队列排序叶子优先 逐级祖先比较AddQueueOrderFn注册的队列排序函数L1365-L1398实现了设计文档优先叶子队列、两个叶子队列再逐层比较父队列的语义先比队列Priority数值大者优先双方中叶子队列排在非叶子队列之前两个都非叶子时比较shareallocated/deserved比率两个都是叶子时调用getQueueLevel找到两队列自 root 向下开始分叉的层级然后比较分叉点的上一层即更靠近 root 的最近公共父级之后的一层父队列的share等价于设计文档所说的从 root 开始逐层比较父队列。比较函数compareShareWithDeservedL1558-L1578的 tie-break 规则是share 相等时设置了 deserved 的队列优先于 best-effort未设 deserved队列。5.3 资源分配准入叶子队列 祖先链逐级检查设计文档要求AddAllocatableFn确保只有叶子队列能调度作业/PodGroup且分配不能突破队列上限。实现为checkQueueAllocatableHierarchicallyL1679-L1695func (cp *capacityPlugin) checkQueueAllocatableHierarchically(ssn *framework.Session, queue *api.QueueInfo, candidate *api.TaskInfo) bool { // If hierarchical queue is not enabled, list will only contain the queue itself. list : append(cp.queueOpts[queue.UID].ancestors, queue.UID) // Check whether the candidate task can be allocated to the queue and all its ancestors. for i : len(list) - 1; i 0; i-- { if !cp.queueAllocatable(ssn.Queues[list[i]], candidate, ...) { // 日志级别 5 时会沿祖先链把每一级的失败明细都打印出来 return false } } return true }即从叶子队列沿ancestors链逐级校验realCapability余量allocated reserved 候选请求 ≤ realCapability其中 reserved 是与调度门控队列准入机制配合的预留缓存任何一级不通过即拒绝。同理作业入队走checkJobEnqueueableHierarchicallyL1725-L1744对队列当前 allocatedinqueue-elastic 是否已逼近 realCapability做同一条祖先链检查失败时记录 PodGroup 事件queue resource quota insufficient。5.4 受害者排序BuildVictimsPriorityQueue 与 VictimQueueOrderFn设计文档提出引入VictimTaskOrderFn/VictimJobOrderFn用于层级上下文中的受害者排序。当前源码中这一能力落在Session.BuildVictimsPriorityQueuesession_plugins.go#L1163 起与插件注册的VictimQueueOrderFn上。capacity 插件在层级模式下注册了AddVictimQueueOrderFncapacity.go#L1400-L1417ssn.AddVictimQueueOrderFn(cp.Name(), func(l, r, preemptor interface{}) int { lLevel : getQueueLevel(cp.queueOpts[lv.UID], cp.queueOpts[pv.UID]) rLevel : getQueueLevel(cp.queueOpts[rv.UID], cp.queueOpts[pv.UID]) // 与抢占者分叉层级更深的队列排前面 同父更近的公共祖先优先成为受害者 if lLevel rLevel { return -1 } return 1 })其语义与设计文档一致两个候选受害者队列相对抢占者所在队列与抢占者分叉越晚即共同祖先越深、越同族的队列越先被回收——这正是 Story 4 中先考虑与抢占者同父队列 a 的作业 B再考虑 root 下的作业 C的排序实现。同一排序类内部队列 sharemax(allocated/deserved)继续驱动回收压力向超用队列倾斜。六、回收范围控制ancestorReclaimLevel这是分层模式下最可操作、也是对运维影响最大的一项参数。当 capacity 插件开启层级模式时可用插件参数ancestorReclaimLevel控制 reclaim 能跨越多少层队列边界取值语义来自设计文档与 capacity 插件用户指南0无祖先限制保持既有行为。回收者队列可以从任何超过 deserved 的叶子队列回收即使二者不在同一分支1对跨父队列回收追加父级 deserved 检查——若受害者叶子队列超其 deserved、但其父队列未超 deserved则阻止从该队列回收2当两队列在祖父级才分叉时追加祖父级 deserved 检查N以此类推向更深层祖先逐级加检查。参数解析在parseArgumentscapacity.go#L1006-L1017默认 0负值会打印告警并回退为 0。核心判定逻辑在 reclaim 回调中L515-L560先做叶子层的即时受害者/deserved 超额判定isImmediateVictim/checkDeservedExceedance若层级开启且ancestorReclaimLevel 0再逐层调用getReclaimeeAncestorToCheckL1019-L1043取受害者侧第level层祖先该祖先不存在或为root时跳过该层检查若抢占者同层祖先与受害者同层祖先相同即两队列在该层尚未分叉跳过该层检查否则检查该祖先队列自身是否超 deserved不超则本轮回收被阻止。此外还有一个叶子层争用避免门当层级开启、level0、两队列在配置层级内共享非 root 祖先、且双方叶子队列对请求资源都没有 deserved 信号时直接跳过回收L524-L534避免在无真实争用时发生不必要抢占。6.1 层级内的交互示例兄弟队列同直接父两队列共享该父祖先祖先层检查被跳过但仍可能被叶子层争用避免门阻止双方叶子都没有相应 deserved 信号时堂表队列不同父、同祖父ancestorReclaimLevel1时回收同时要求叶子级与父级 deserved 超额2时在适用时追加祖父级检查远表队列不同祖父、共享更高层祖先更高的ancestorReclaimLevel会在允许回收前追加对应祖先层的 deserved 检查。这样运维可以在大型队列树上收紧回收边界而在无需限制时保持默认行为。6.2 用户可复制的配置示例按 capacity 插件用户指南 的配置方式开启层级模式并配置回收层级kind: ConfigMap apiVersion: v1 metadata: name: volcano-scheduler-configmap namespace: volcano-system data: volcano-scheduler.conf: | actions: enqueue, allocate, backfill, reclaim # 需要开启 reclaim action tiers: - plugins: - name: priority - name: gang enablePreemptable: false - name: conformance - plugins: - name: drf enablePreemptable: false - name: predicates - name: capacity # 开启 capacity 插件与 proportion 互斥 enableHierarchy: true arguments: # 控制 reclaim 可以跨越的队列层级边界 # 0: 无祖先限制默认行为 # 1: 跨父回收时追加父级 deserved 检查 # 2: 追加祖父级 deserved 检查更大的值依此类推 ancestorReclaimLevel: 1 - name: nodeorder - name: binpack前置条件reclaim action 已开启、capacity 插件启用且 proportion 插件已移除两者冲突不能共存。6.3 单元测试场景地图case1–case11设计文档列出表驱动测试Test_capacityPlugin_AncestorReclaimScenarios实现见 capacity_test.go#L840覆盖了 case1–case11用显式拓扑与回收结果验证上述语义。逐案摘要如下均为单节点 n1、以 a100 或 cpu/mem 维度请求case1level1 时可基于父级 deserved 执行回收负载p1运行于case1_queue2请求 a1004p2待调度于case1_queue11a1001。预期p1被驱逐p2podgrouppg2被管线到 n1——父case1_queue1超 deserved允许跨父回收。case2project2 子队列与父队列均超 deserved 时跨父回收让 project1 待调度作业管线化拓扑root 下两个项目队列project1_root: deserved a1001 / capability a1002project2_root: deserved a1003 / capability a1004各自再分 non-preemptable与父同 deserved与 preemptable未设 deserved两个子队列。负载p1..p4运行于project2_preemptable各 a1001p5待调度于project1_preemptablea1001。level1。预期低优先级的p4被驱逐p5pg5管线到 n1。case3跨父回收可先驱逐兄弟队列中的受害者拓扑同 case2负载为延续态p1..p3运行于project2_preemptablep4处于已被回收的待调度态p5已运行于project1_preemptablep6待调度于project2_non-preemptable。level1。预期p3被驱逐p6pg6管线到 n1——受害者选取仍遵循同族优先的排序语义。case4受害者子队列未超 deserved 时跨父回收被阻止负载p1运行于case4_child1a1001p3运行于兄弟队列不可抢占a1001p2待调度于case4_child2a1001。level1。预期无驱逐、无管线——受害方父队列case4_parent1未超 deserved。case5level0 允许基于叶子级 deserved 检查的回收负载p-victim运行于queue-bcpu2, mem2Gip-reclaimer待调度于queue-acpu2, mem2Gi。level0。预期p-victim被驱逐p-reclaimer管线化queue-b叶子层已超 deserved。case6level1 且父级检查通过时仍允许回收拓扑与负载同 case5level1。预期父级检查通过parent-b超 deserved驱逐/管线照常发生。case7level2 时祖父级检查失败则阻止回收拓扑与负载同 case5level2。预期祖父级grand-b门检查失败无驱逐、无管线。case8level1 且叶子 deserved 未设置、祖先门不适用时阻止兄弟间回收负载同 case5level1。预期无驱逐、无管线——双方叶子均无 deserved 信号命中争用避免门。case9level2 且叶子 deserved 未设置、队列共享祖父时阻止回收负载同 case5level2。预期无驱逐、无管线。case10不平衡的共享祖父树上level2 且叶子 deserved 未设置时阻止回收负载p-victim运行于queue-deepcpu2, mem2Gip-reclaimer待调度于queue-shallowcpu2, mem2Gi。level2。预期无驱逐、无管线。虽然grand超 deserved但受害叶子与回收叶子对所请求的 cpu/memory 均无 deserved 资源两队列在 level2 内共享grand叶子层争用避免门在祖父 deserved 检查生效之前就跳过了回收。case11深度 2 的祖先未超 deserved 时level2 阻止回收负载p-victim运行于queue-deep请求 a1002p-reclaimer待调度于queue-shallowa1001。level2。预期深度 2 的祖先deep-grand与shallow-parent所在层门检查失败无驱逐、无管线。上述修改主要针对enableHierarchy: true的场景若 capacity 插件不需要分层队列管理OnSessionOpen等函数将走原有buildQueueAttrs实现行为保持不变。七、vcctl 对分层队列的可观测性设计文档规划了面向分层结构的 vcctl 命令例如查看某队列的子队列、查看整棵分层队列结构。当前仓库中vcctl queue list的列定义已包含Parent列pkg/cli/queue/list.go#L65运维可以直接输出每行队列的父队列归属来核对树结构结合kubectl get queue -o yaml查看spec.parent与各队列status中的 allocated 计数即可验证子队列资源之和不超过父队列的实际运行状态。八、已知限制设计文档明确了三点限制使用时需要留意非叶子队列不可调度当前设计不支持把作业/PodGroup 调度到非叶子队列只有叶子队列能直接为作业分配资源调度器侧以会话不可调度的方式强制Webhook 侧以父队列已有分配 Pod 则不能再收子队列配合不支持队列迁移为避免人工运维复杂度当前设计不处理分层队列管理中的队列迁移问题即作业从一个队列树迁移到另一棵子树暂停状态的层级联动待优化引入队列暂停调度状态时父队列暂停会连带其子队列暂停恢复父队列时自动联动恢复子队列的能力尚待优化属于设计文档中明确标注的未完善点。九、小结Volcano 的分层队列能力以 capacity 插件为载体形成了三层完整的实现闭环API/Webhook 层pkg/webhooks/admission/queues/validate/validate_queue.go通过spec.parent建树校验自引用、深度上限默认 5、父队列排他性与子队列资源之和 ≤ 父队列上限并保护 root 队列调度器会话层pkg/scheduler/plugins/capacity/capacity.goqueueAttr的ancestors/children双向索引、自底向上的资源汇总、叶子队列准入、逐级 realCapability/deserved 检查以及VictimQueueOrderFn实现的同族优先受害者排序回收策略层ancestorReclaimLevel参数把回收能跨几层边界交给运维显式配置0保持默认弹性1/2/N逐级加严并由Test_capacityPlugin_AncestorReclaimScenarios的 case1–case11 全量行为映射保障语义稳定。这套设计让部门—团队—项目组式队列树在保持 capacity 插件借用/归还弹性的同时把跨边界回收的爆炸半径限制在可控范围内是当前仓库中 AI 任务队列治理的一条可落地路径。【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表