3、调度队列(Scheduling Queue):ActiveQ、BackoffQ、UnschedulableQ 的结构与流转逻辑
kube-scheduler 的调度队列是调度器的核心状态机,负责管理所有待调度 Pod 的生命周期。调度队列由三个子队列组成:ActiveQ(活跃队列)、BackoffQ(回退队列) 和 UnschedulableQ(不可调度队列)。理解这三个队列的结构与流转逻辑,是排查调度一致性问题的关键。
3.1 三队列结构概览
三个队列在数据结构、优先级策略和存储方式上各有不同,下表总结了它们的核心差异:
| 队列名称 | 数据结构 | 优先级策略 | Pod 状态 | 出队方式 |
|---|---|---|---|---|
| ActiveQ | 堆(Heap) | 基于 Pod 优先级 + 创建时间 | 可调度或待重试 | Pop() 取出最高优先级 Pod |
| BackoffQ | 堆(Heap) | 基于回退到期时间 | 调度失败,等待回退 | 到期后移回 ActiveQ |
| UnschedulableQ | Map(无序) | 无优先级排序 | 不可调度(资源不足/污点/亲和性不满足) | 周期性或事件触发重新调度 |
关键认知: ActiveQ 和 BackoffQ 使用堆结构保证有序性,UnschedulableQ 使用 Map 结构仅做去重存储。这种设计反映了不同队列的调度目标——ActiveQ 追求优先级公平,BackoffQ 追求时间公平,UnschedulableQ 追求事件驱动的批量重试。
3.2 队列流转逻辑与状态机
Pod 在三个队列之间的流转遵循严格的状态机,下图展示了完整的生命周期:
3.3 各队列详细结构与流转规则
3.3.1 ActiveQ(活跃队列)
ActiveQ 是调度器的主工作队列,所有新创建的 Pod 和从其他队列移回的 Pod 都会先进入 ActiveQ。调度器的主循环不断从 ActiveQ 中 Pop() 出 Pod 进行调度。
- 数据结构: 最小堆(Heap),比较函数为
PodPriority > PodTimestamp。高优先级 Pod 优先出队,同优先级按创建时间 FIFO。 - 入队时机: 新 Pod 创建、BackoffQ 到期、UnschedulableQ 重试、调度失败但可重试(直接回 ActiveQ)。
- 出队时机: 调度器主循环 Pop() 取出。
- 一致性关注点: 当集群资源发生变更(如 Node 新增/删除、资源量更新)时,ActiveQ 中的 Pod 优先级顺序不应被破坏。如果堆排序被破坏,会导致低优先级 Pod 被错误地优先调度。
3.3.2 BackoffQ(回退队列)
BackoffQ 用于存放调度失败但预期短期内可重试的 Pod。调度器为每个 Pod 维护一个回退时间(backoff duration),失败次数越多,回退时间越长(指数退避)。
- 数据结构: 最小堆(Heap),比较函数为
backoffTime。回退时间最早的 Pod 位于堆顶。 - 入队时机: 调度失败且错误类型为
FitError但非永久性(如临时端口冲突、临时资源竞争)。 - 出队时机: 后台协程定期检查堆顶 Pod 的回退时间是否到期,到期则移回 ActiveQ。
- 回退策略: 默认初始回退时间 100ms,最大回退时间 10s,指数因子 2.0。每次失败后回退时间翻倍。
- 一致性关注点: 如果回退时间计算出现偏差(如时钟不同步),可能导致 Pod 过早或过晚回到 ActiveQ,影响调度公平性。
排查技巧: 当观察到 Pod 长时间停留在 BackoffQ 时,检查
kube-scheduler 日志中的 Backoff 关键字,确认回退时间是否异常增长。使用 kubectl get pods --watch 观察 Pod 状态变化频率,可以辅助判断回退队列的健康状况。
3.3.3 UnschedulableQ(不可调度队列)
UnschedulableQ 存放调度失败且当前集群中没有任何 Node 能满足其资源需求的 Pod。这些 Pod 需要等待集群资源发生变化后才能重新调度。
- 数据结构: Map(Go 中的
map[string]*Pod),以 Pod 的 Namespace/Name 为 key。无序存储,仅保证去重。 - 入队时机: 调度失败且错误类型为
FitError且为永久性(如 CPU/Memory 不足、节点污点不匹配、Pod 亲和性不满足)。 - 出队时机:
- 事件驱动: 当集群资源发生变更(Node 新增/删除、Node 资源量更新、PV/PVC 变更)时,调度器会遍历 UnschedulableQ 中的所有 Pod,重新评估其可调度性,将可调度的 Pod 移回 ActiveQ。
- 周期性重试: 默认每 5 分钟(
unschedulableQTimeInterval)触发一次全量重试。
- 一致性关注点: 事件驱动重试是全量扫描,当 UnschedulableQ 中 Pod 数量很大(如数千个)时,扫描耗时可能超过调度周期,导致调度延迟。此外,如果资源变更事件丢失(如 Informer 缓存未更新),Pod 可能一直停留在 UnschedulableQ 中,形成“调度死锁”。
3.4 队列流转的完整时序
以下是一个 Pod 从创建到调度成功的完整队列流转时序示例:
- Pod 创建: API Server 写入 Pod 对象,Informer 触发 Add 事件,调度器将 Pod 加入 ActiveQ。
- 第一次调度尝试: 调度器从 ActiveQ Pop() 出 Pod,执行预选和优选。假设资源不足,调度失败。
- 失败分类:
- 如果错误为“临时资源竞争”(如端口冲突),Pod 进入 BackoffQ,回退时间设为 100ms。
- 如果错误为“CPU 不足”(所有 Node 均不满足),Pod 进入 UnschedulableQ。
- 回退到期: 100ms 后,BackoffQ 中的 Pod 到期,移回 ActiveQ。
- 第二次调度尝试: 调度器再次 Pop() 出 Pod,此时资源已释放,调度成功,Pod 绑定到 Node。
- UnschedulableQ 重试: 如果 Pod 在 UnschedulableQ 中,等待集群新增 Node 或资源变更事件触发,移回 ActiveQ 后重新调度。
3.5 一致性复核要点
在资源变更后,需要重点复核以下队列一致性指标:
| 复核项 | 检查方法 | 异常表现 | 影响 |
|---|---|---|---|
| ActiveQ 堆排序正确性 | 导出 ActiveQ 快照,检查 Pod 优先级顺序 | 低优先级 Pod 出现在堆顶 | 高优先级 Pod 被延迟调度 |
| BackoffQ 回退时间准确性 | 检查 Pod 的 backoffCount 和 backoffTime | 回退时间未按指数增长 | Pod 频繁重试,浪费调度资源 |
| UnschedulableQ 事件响应 | 模拟资源变更,观察 Pod 移出延迟 | 资源变更后 Pod 仍滞留超过 5 分钟 | 调度延迟,Pod 长时间 Pending |
| 队列间 Pod 去重 | 检查同一 Pod 是否出现在多个队列 | Pod 同时存在于 ActiveQ 和 UnschedulableQ | 重复调度,可能导致绑定冲突 |
常见陷阱: 当集群发生大规模资源变更(如批量删除 Node)时,UnschedulableQ 中的 Pod 数量可能激增。此时调度器的全量扫描可能耗时数秒甚至数十秒,导致 ActiveQ 中的 Pod 调度被阻塞。建议在资源变更后,手动检查
scheduler_queue_incoming_pods_total 和 scheduler_queue_unschedulable_pods 指标,确认队列负载是否在正常范围内。
3.6 队列监控指标
kube-scheduler 暴露了丰富的 Prometheus 指标用于监控队列状态,以下是关键指标:
# 队列中 Pod 数量
scheduler_queue_incoming_pods_total{queue="active"} # ActiveQ 入队总数
scheduler_queue_incoming_pods_total{queue="backoff"} # BackoffQ 入队总数
scheduler_queue_incoming_pods_total{queue="unschedulable"} # UnschedulableQ 入队总数
# 当前队列深度
scheduler_queue_depth{queue="active"} # ActiveQ 当前 Pod 数
scheduler_queue_depth{queue="backoff"} # BackoffQ 当前 Pod 数
scheduler_queue_depth{queue="unschedulable"} # UnschedulableQ 当前 Pod 数
# Pod 在队列中的等待时间
scheduler_queue_pod_wait_time_bucket{queue="active"} # ActiveQ 等待时间分布
scheduler_queue_pod_wait_time_bucket{queue="backoff"} # BackoffQ 等待时间分布
# 队列流转次数
scheduler_queue_move_total{type="active_to_backoff"} # ActiveQ -> BackoffQ
scheduler_queue_move_total{type="active_to_unschedulable"} # ActiveQ -> UnschedulableQ
scheduler_queue_move_total{type="backoff_to_active"} # BackoffQ -> ActiveQ
scheduler_queue_move_total{type="unschedulable_to_active"} # UnschedulableQ -> ActiveQ
通过监控这些指标,可以快速定位队列流转异常。例如,如果 scheduler_queue_move_total{type="unschedulable_to_active"} 长时间为 0,说明 UnschedulableQ 中的 Pod 未被正确触发重试,需要检查 Informer 事件是否正常传递。