尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Volcano 队列准入 Scheduling Gates:让 Pod 在队列容量就位前对自动扩缩容“隐身“

发布时间:2026/9/17 3:37:16

资讯中心
01
ARTICLE

Volcano 队列准入 Scheduling Gates:让 Pod 在队列容量就位前对自动扩缩容“隐身“

Volcano 队列准入 Scheduling Gates:让 Pod 在队列容量就位前对自动扩缩容“隐身“
Volcano 队列准入 Scheduling Gates让 Pod 在队列容量就位前对自动扩缩容隐身【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcanoVolcano 默认的调度失败处理会把所有分配失败的 Pod 标记为Unschedulable这让 Cluster Autoscaler / Karpenter 无法区分集群真的缺资源和只是在等待队列准入从而触发不必要的节点扩容。本文围绕 Volcano 的SchedulingGatesQueueAdmission功能展开先说明问题根因再给出从启用 Feature Gate、配置 capacity 插件、Pod 侧 opt-in 注解到验证生效的完整操作步骤并结合源码剖析 webhook 注入 gate、调度器异步摘除 gate 以及 capacity 插件预留容量reserved capacity的底层实现帮助你在生产集群中安全启用这一机制并理解其边界。1. 问题背景为什么 Volcano 会让 Autoscaler 误扩容Cluster AutoscalerCA与 Karpenter 这类集群自动扩缩容组件其扩容信号本质上依赖 Pod 的条件type: PodScheduled status: False reason: Unschedulable对于默认的kube-scheduler这个条件出现通常意味着集群资源不足触发扩容是合理的。但 Volcano 的实现不同在每一个调度周期结束后调度器缓存的事件记录机制会检查所有未被分配的任务并统一将它们的PodScheduled条件置为statusFalse, reasonUnschedulable——无论失败原因是集群资源不足应当扩容还是队列容量限制不该扩容。Autoscaler 只能看到这个条件无法区分两种场景于是在 Pod 仅仅等待 Volcano 队列准入时也会错误地发起扩容。从源码结构看这条链路由pkg/scheduler/cache/中的缓存事件记录逻辑驱动任务只要在当前周期没有获得分配就会被补上Unschedulable条件。这正是 设计文档 中 Motivation 一节描述的行为也是该功能要解决的核心矛盾。2. 解决方案用 schedulingGates 延迟可见性该功能利用 Kubernetes 原生的 schedulingGates 机制Pod Scheduling ReadinessPod 只要spec.schedulingGates非空kube-scheduler 和各类 Autoscaler 的调度失败检测都看不到它。Volcano 的思路是Pod 创建时Webhook 为 opt-in 的 Pod 注入名为scheduling.volcano.sh/queue-allocation-gate的 gate使其处于 gated 状态对 Autoscaler 不可见队列有容量后调度器摘除该 gate摘除 gate 后若 Pod 能落到节点则正常调度若因缺少匹配节点而无法调度此时才被合法地标记为UnschedulableAutoscaler 才会做出正确响应。设计目标引自设计文档还包括提供基于 Pod 注解的 opt-in 机制、保持 Volcano 既有调度语义不变、以及非阻塞实现异步摘除 gate避免拖累调度器性能。非目标则是不修改 CA/Karpenter 自身逻辑、不在准入时拒绝 Pod、不引入外部控制器。前置条件Volcano v1.15并启用SchedulingGatesQueueAdmissionFeature Gate调度器配置了capacity插件——该功能中防止摘除 gate 与 Pod 分配之间出现竞态的预留资源跟踪就实现在这个插件里。Feature Gate 在 pkg/features/volcano_features.go 中注册默认为false成熟度为Alpha// SchedulingGatesQueueAdmission uses Kubernetes schedulingGates to delay // setting the Unschedulable condition on pods until the queue has enough // capacity, preventing cluster autoscalers from triggering unnecessary // scale-ups for pods that are simply waiting for queue admission. SchedulingGatesQueueAdmission featuregate.Feature SchedulingGatesQueueAdmission ... SchedulingGatesQueueAdmission: {Default: false, PreRelease: featuregate.Alpha},需要同时在scheduler和webhook-manager两个组件上开启webhook 侧决定是否注入 gatescheduler 侧决定是否异步摘除 gate 并执行预留容量逻辑。3. 启用 Feature Gate方式一Helm 安装helm install volcano volcano/volcano --namespace volcano-system --create-namespace \ --set custom.scheduler_feature_gatesSchedulingGatesQueueAdmissiontrue \ --set custom.admission_feature_gatesSchedulingGatesQueueAdmissiontrue方式二kubectl apply在volcano-scheduler与volcano-admission两个 Deployment 的容器启动参数中分别追加--feature-gatesSchedulingGatesQueueAdmissiontrue此外可以按需配置异步摘除 gate 的工作协程数量默认 5--gate-removal-worker-num10该 flag 在 cmd/scheduler/app/options/options.go 中定义默认值为 5且注释明确说明仅在 SchedulingGatesQueueAdmission 启用时生效fs.IntVar(s.GateRemovalWorkerNum, gate-removal-worker-num, 5, The number of async workers for scheduling gate removal (used when SchedulingGatesQueueAdmission is enabled).)方式三源码级的启用时机在 pkg/scheduler/scheduler.go 的Scheduler.Run()中只有当 Feature Gate 打开时才会创建并启动 gate 管理器其生命周期与调度器进程绑定// Start the gate manager (if the feature gate is enabled). if utilfeature.DefaultFeatureGate.Enabled(features.SchedulingGatesQueueAdmission) { pc.schGateManager gate.NewSchGateManager(pc.cache.Client(), options.ServerOpts.GateRemovalWorkerNum) pc.schGateManager.Start() go func() { -stopCh pc.schGateManager.Stop() }() }4. 配置 capacity 插件确保调度器配置中启用了capacity插件预留资源跟踪防止摘除 gate 与分配之间的竞态就实现于其中。示例调度器配置actions: enqueue, allocate, backfill tiers: - plugins: - name: priority - name: gang - plugins: - name: predicates - name: capacity - name: nodeorder5. Pod 侧 Opt-in注解与 Gate 命名约定该功能按 Pod 粒度 opt-in。注解键与 gate 名是同一个常量定义在 staging/src/volcano.sh/apis/pkg/apis/scheduling/v1beta1/labels.go// QueueAllocationGateKey is the annotation key to opt-in to queue capacity // gate management and the name of the scheduling gate that controls queue admission. const QueueAllocationGateKey GroupName /queue-allocation-gate即scheduling.volcano.sh/queue-allocation-gate。为需要 gate 控制的 Pod 加上注解即可apiVersion: v1 kind: Pod metadata: name: my-pod annotations: # Opt-in annotation scheduling.volcano.sh/queue-allocation-gate: true spec: schedulerName: volcano containers: - name: worker image: nginx resources: requests: cpu: 1 memory: 1GiPod 创建之后会发生什么Volcano webhook 注入scheduling.volcano.sh/queue-allocation-gate调度 gatePod 保持 gated 状态对 Autoscaler 不可见直到队列有容量队列有容量后调度器异步摘除 gate若 Pod 能落到某个节点正常完成调度若没有节点匹配例如需要特定机型、等待 Autoscaler 加节点此时才被标记Unschedulable从而正确地触发 Autoscaler。注意一个细节如果有人手工加了 Volcano gate 但没有加 opt-in 注解调度器不会自动摘除 gate并会输出告警日志见下文 allocate 逻辑Pod 将永远卡在 gated 状态。6. 验证功能生效创建 opt-in Pod 后先确认 mutation webhook 注入了 gatekubectl get pod my-pod -o jsonpath{.spec.schedulingGates}等待队列容量期间预期输出[{name:scheduling.volcano.sh/queue-allocation-gate}]队列有容量、调度器摘除 gate 后同一条命令输出为空kubectl get pod my-pod -o jsonpath{.spec.schedulingGates} # empty output此外可通过kubectl describe pod my-pod观察条件变化gated 期间不应出现Unschedulable条件gate 摘除后若节点不匹配才会出现PodScheduledFalse/Unschedulable。仓库中还包含针对该流程的 E2E 测试 test/e2e/schedulinggates/scheduling_gates.go可作为行为基准参考。7. 源码剖析Webhook 如何注入 gate注入逻辑位于 pkg/webhooks/admission/pods/mutate/mutate_pod.go 的patchSchedulingGates它挂在现有的 Pod 创建Createmutation 流程中。核心实现有三个要点1双重开关校验——Feature Gate 未开启或 Pod 没有 opt-in 注解时直接跳过func patchSchedulingGates(pod *v1.Pod) *patchOperation { // Skip if SchedulingGatesQueueAdmission feature gate is not enabled if !utilfeature.DefaultFeatureGate.Enabled(features.SchedulingGatesQueueAdmission) { return nil } // Check if opt-in annotation is present if !api.HasQueueAllocationGateAnnotation(pod) { return nil } ... }2幂等性——若 Pod 已存在同名 gate例如 mutation 重试不再追加避免重复 gate。3JSON Patch 的两种形态——Kubernetes 规定schedulingGates在 Pod 创建后只能删、不能加因此注入必须发生在创建时spec.schedulingGates为空时对整个字段做add /spec/schedulingGates值为包含该 gate 的数组已存在其他 gate 时用add /spec/schedulingGates/-追加到数组末尾避免覆盖并行 webhook 写入的其他 gate。判断辅助函数集中在 pkg/scheduler/api/helpers.go供 scheduler、capacity 插件与 gate 管理器共用保证语义一致// HasOnlyVolcanoSchedulingGate checks if a Pod has only the Volcano queue allocation gate func HasOnlyVolcanoSchedulingGate(pod *v1.Pod) bool { return len(pod.Spec.SchedulingGates) 1 pod.Spec.SchedulingGates[0].Name schedulingv1beta1.QueueAllocationGateKey } // HasQueueAllocationGateAnnotation checks if a Pod has the queue allocation gate annotation func HasQueueAllocationGateAnnotation(pod *v1.Pod) bool { return pod.Annotations ! nil pod.Annotations[schedulingv1beta1.QueueAllocationGateKey] true }8. 源码剖析调度器的容量准入检查与异步摘 gateVolcano 此前已支持 Pod Scheduling Readiness带外部scheduling gate 的 Pod 不会被 allocate/backfill/reclaim/preempt 动作分配。本功能的关键改造是让仅带 Volcano gate的 Pod 重新进入队列容量计算从而能参与准入检查。8.1 作业工作表放行 Volcano gate Pod在 pkg/scheduler/actions/allocate/allocate.go 的organizeJobWorksheet中只有带外部非 Volcanogate 的任务才被跳过for _, task : range subJob.TaskStatusIndex[api.Pending] { // Skip tasks with external (non-Volcano) scheduling gates // Allow Volcano-managed gates (theyll be handled by capacity plugin) if task.SchGated !api.HasOnlyVolcanoSchedulingGate(task.Pod) { klog.V(4).Infof(Task %v/%v has external scheduling gate, skip it., ...) continue } ... }同时JobInfo.GetSchGatedPodResources()在扣除被调度门控的资源时会排除仅带 Volcano gate 的 Pod——这让它们计入 inqueue 资源、参与队列准入判定而不会被误当作完全不可见的资源。8.2 allocate 主循环先判容量再排队摘 gate在 allocate 动作的分配主循环中allocateResourcesForTasks任务先过队列容量检查ssn.Allocatable(queue, task)会驱动 capacity 插件做容量判定。通过检查且带 opt-in 注解的 gated 任务会被送入异步摘除队列而当轮不做分配——gate 由后台 worker 摘除、informer 缓存刷新后下一个调度周期才会真正走到节点过滤与分配// If task passed allocation check and has the QueueAllocationGate, initiate async gate removal. // Gate will be removed by the background worker (best effort). if utilfeature.DefaultFeatureGate.Enabled(features.SchedulingGatesQueueAdmission) task.SchGated api.HasQueueAllocationGateAnnotation(task.Pod) { klog.V(3).Infof(Task %s/%s has the QueueAllocationGate, queue async gate removal, task.Namespace, task.Name) ssn.SchGateManager().Enqueue(task) } // Skip gated tasks. If someone added the Volcano gate without the opt-in annotation, // warn them since the gate will never be removed automatically. if task.SchGated { if api.HasOnlyVolcanoSchedulingGate(task.Pod) !api.HasQueueAllocationGateAnnotation(task.Pod) { klog.Warningf(Task %s/%s has Volcano scheduling gate but missing the opt-in annotation %q; gate will not be removed automatically, ...) } continue }这种当轮跳过、下轮分配的设计保证了容量判定与节点分配之间的一致性gated 任务不会被分配而摘 gate 动作是 best-effort 的异步操作不阻塞调度循环。8.3 SchGateManager异步摘除 gate 的后台管理器实现位于 pkg/scheduler/gate/schedulinggate.go。要点默认 5 个 workerDefaultWorkerNum 5每个 worker 的通道缓冲为 200bufferPerWorker总通道容量 workerNum × 200Enqueue是非阻塞投递通道满时打警告并放弃返回 false下个周期还会再尝试保证调度器吞吐不受摘 gate 速度影响投递前会用HasOnlyVolcanoSchedulingGate再校验一次Pod 上还挂着其他控制器的 gate 时不摘 Volcano gate见第 9 节worker 实际调用cache.RemoveVolcanoSchGate(kubeClient, namespace, name)更新 API 中的 Pod。func (m *SchGateManager) Enqueue(task *api.TaskInfo) bool { if !api.HasOnlyVolcanoSchedulingGate(task.Pod) { return false } op : gateRemovalOp{namespace: task.Namespace, name: task.Name} select { case m.opCh - op: return true default: klog.Warningf(Gate operation queue full, skipping gate removal for %s/%s, ...) return false } }Scheduler通过framework.OpenSession(...)把该 manager 传入每个调度 sessionallocate 动作经ssn.SchGateManager()访问生命周期随进程启停。9. 与其他 Scheduling Gate 的交互如果 Pod 上还带有其他控制器注入的 gate如example.com/my-gateVolcano不会在仅剩 Volcano gate之前摘除自己的 gate。这由两处保证SchGateManager.Enqueue在投递前检查HasOnlyVolcanoSchedulingGate(task.Pod)不满足直接放弃webhook 注入时不覆盖已有 gate只追加。由此保证 Volcano 不会干扰其他 gate 控制器的语义多 gate 并存时 Pod 会一直等到所有 gate 都被各自控制器移除。10. 源码剖析capacity 插件的预留容量Reserved Capacity这是理解该功能为什么需要 capacity 插件的关键。考虑一个竞态场景引自设计文档3 个 opt-in Podpod-1/2/3各请求1 CPU / 1 GiB队列 capability 为1 CPU / 1 GiBpod-2的 nodeSelector 指向尚不存在、等待 Autoscaler 扩容出来的节点机型初始三者都被 webhook 加上 gate全部 gatedNAME PHASE CONDITION GATES pod-1 Pending SchedulingGated scheduling.volcano.sh/queue-allocation-gate pod-2 Pending SchedulingGated scheduling.volcano.sh/queue-allocation-gate pod-3 Pending SchedulingGated scheduling.volcano.sh/queue-allocation-gate第 1 轮pod-1通过容量检查、gate 被摘除随后正常调度到Runningpod-1结束/删除后pod-2通过容量检查、gate 被摘除但因 nodeSelector 匹配不到节点而变为Unschedulable用来触发 Autoscaler。问题在于pod-2摘除 gate 后、尚未分配到节点之前它既不计入allocated未绑定也看不到 gate已摘除。如果没有预留机制队列在容量账本上是空的——此时新建的pod-3会通过容量检查并直接跑起来。于是 Autoscaler 为pod-2扩出的新节点永远无法被使用队列容量已被pod-3占走形成扩容了节点却调度不进去的死局。10.1 预留缓存的三段式生命周期实现位于 pkg/scheduler/plugins/capacity/capacity.go。capacity 插件新增一个按队列组织的预留缓存// queueGateReservedTasks tracks tasks that passed capacity checks but cannot be scheduled // These tasks reserve queue capacity to prevent other tasks from consuming it // Rebuilt fresh at the start of each scheduling cycle in OnSessionOpen queueGateReservedTasks map[api.QueueID]map[api.TaskID]*api.TaskInfo1会话开始时全量重建OnSessionOpen中调用buildQueueReservedTasksCache扫描所有 Pending 任务凡是没有 gate 有 opt-in 注解 Pending的任务——即已判过容量、正等待节点的任务——都计入预留for _, task : range job.TaskStatusIndex[api.Pending] { // Tasks that passed capacity have: NO gate HAS annotation Pending status if !task.SchGated api.HasQueueAllocationGateAnnotation(task.Pod) { ... cp.queueGateReservedTasks[job.Queue][task.UID] task } }2容量检查通过时增量写入插件注册的AddAllocatableFn回调中任务通过层级化容量检查且带 opt-in 注解时写入预留缓存allocatable : cp.checkQueueAllocatableHierarchically(ssn, queue, candidate) // If queue has capacity and task has the QueueAllocationGate annotation. if allocatable utilfeature.DefaultFeatureGate.Enabled(features.SchedulingGatesQueueAdmission) api.HasQueueAllocationGateAnnotation(candidate.Pod) { cp.addTaskToReservedCache(queue.UID, candidate) }3分配/回滚时维护账本任务真正分配tentative assign后其资源转入allocated统计必须从预留缓存移除以免重复计数若发生回滚如 gang 调度未能全部放置DeallocateFunc又把它恢复回预留缓存// AllocateFunc: 分配成功后 if utilfeature.DefaultFeatureGate.Enabled(features.SchedulingGatesQueueAdmission) { cp.removeTaskFromReservedCache(event.Task.UID) } // DeallocateFunc: 回滚时 if utilfeature.DefaultFeatureGate.Enabled(features.SchedulingGatesQueueAdmission) api.HasQueueAllocationGateAnnotation(event.Task.Pod) { cp.addTaskToReservedCache(job.Queue, event.Task) }10.2 容量判定中如何计入预留资源容量检查走queueAllocatable→queueAllocatableWithReserved把预留缓存中除候选任务自身之外的任务资源一并累加进未来占用// Calculate total reserved resources directly from cache reserved : api.EmptyResource() if queueGateReserved : cp.queueGateReservedTasks[queue.UID]; queueGateReserved ! nil { for _, task : range queueGateReserved { if task.UID ! candidate.UID { // Skip candidate to avoid double-counting (it will be added in futureUsed below) reserved.Add(task.Resreq) } } }由此队列容量账本同时覆盖已分配bound/binding/running资源既有行为 已摘 gate 但未落地的 Pending 资源新行为。会话结束时queueGateReservedTasks被整体清空OnSessionClose置 nil与每轮重建的设计配套避免跨周期脏数据。11. 限制与运维注意事项摘除 gate 后没有超时机制一旦 gate 被摘除Pod 会一直占用队列预留容量直到被调度或删除。若它长期 Unschedulable例如等待 Autoscaler 加节点、或节点始终匹配不上会持续占用队列容量可能阻塞其他 Pod当前版本故意不实现超时释放以避免在 Pod 即将获得节点时提前放容量造成超卖。运维上需要意识到ungated-but-unschedulable 的 Pod 可以无限期地持有队列容量。该功能仅在capacity插件启用时具备完整的预留语义未启用 capacity 插件时队列容量判定路径不同。仅带 Volcano gate 而缺少 opt-in 注解的 Podgate 不会被自动摘除调度器会打 Warning 日志Pod 会永久 gated——部署时请保证注解与 gate 成对出现。该功能为 Alpha、默认关闭且按 Pod opt-in未加注解的存量工作负载行为完全不变可以灰度采用。12. 小结SchedulingGatesQueueAdmission用一个创建时注入、准入后异步摘除的 scheduling gate把 Volcano 的队列等待与集群缺资源两种状态在 Autoscaler 视角下彻底分开前者 Pod 始终 gated、不可见后者 gate 摘除后才暴露Unschedulable条件扩容信号恢复可信。整条链路涉及四个部分webhook 的幂等 gate 注入pkg/webhooks/admission/pods/mutate/mutate_pod.go、allocate 动作的容量准入与异步排队pkg/scheduler/actions/allocate/allocate.go、后台 gate 管理器pkg/scheduler/gate/schedulinggate.go以及 capacity 插件的预留容量账本pkg/scheduler/plugins/capacity/capacity.go。理解这套机制后你可以放心在混合了 Volcano 队列调度与 Cluster Autoscaler/Karpenter 的集群中启用它同时清楚预留容量无超时释放这一运维边界。更多设计细节可参考 设计文档操作指引见 用户指南。【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。