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 在三个队列之间的流转遵循严格的状态机,下图展示了完整的生命周期:

调度队列三状态流转图 ActiveQ 堆结构 · 优先级排序 BackoffQ 堆结构 · 时间排序 UnschedulableQ Map结构 · 无序存储 调度器主循环 Pop() 取出 调度失败(可重试) 调度失败(不可重试) 回退到期 资源变更/周期性重试 回退超限 流转规则: ① ActiveQ → 调度器:Pop() 取出最高优先级 Pod 进行调度;② 调度器 → BackoffQ:调度失败且可重试(如临时资源竞争);③ 调度器 → UnschedulableQ:调度失败且不可重试(如资源不足);④ BackoffQ → ActiveQ:回退时间到期;⑤ UnschedulableQ → ActiveQ:资源变更事件或周期性重试触发。

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 从创建到调度成功的完整队列流转时序示例:

  1. Pod 创建: API Server 写入 Pod 对象,Informer 触发 Add 事件,调度器将 Pod 加入 ActiveQ。
  2. 第一次调度尝试: 调度器从 ActiveQ Pop() 出 Pod,执行预选和优选。假设资源不足,调度失败。
  3. 失败分类:
    • 如果错误为“临时资源竞争”(如端口冲突),Pod 进入 BackoffQ,回退时间设为 100ms。
    • 如果错误为“CPU 不足”(所有 Node 均不满足),Pod 进入 UnschedulableQ。
  4. 回退到期: 100ms 后,BackoffQ 中的 Pod 到期,移回 ActiveQ。
  5. 第二次调度尝试: 调度器再次 Pop() 出 Pod,此时资源已释放,调度成功,Pod 绑定到 Node。
  6. 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 事件是否正常传递。