3. Scheduler 调度基础:Kubernetes Scheduler 工作原理、调度周期与绑定流程

Kubernetes Scheduler 是控制平面的核心组件之一,负责将待调度的 Pod 分配到最合适的 Node 上。理解其内部工作原理、调度周期与绑定流程,是进行调度优化与 GitOps 同步窗口准入配置的理论基础。本章将深入剖析 Scheduler 的架构、调度框架(Scheduling Framework)以及 Pod 从创建到绑定的完整生命周期。

3.1 Scheduler 核心职责与架构

Scheduler 并非直接“创建”或“运行”Pod,而是通过监听 API Server,为未调度的 Pod(spec.nodeName 为空)选择一个最优 Node,并将绑定结果写回 API Server。其核心架构遵循“控制循环”模式,主要包含以下组件:

组件 职责 说明
Informer 监听 Pod、Node、PV/PVC 等资源变化 通过 List-Watch 机制维护本地缓存,减少 API Server 压力
Scheduling Queue 管理待调度的 Pod 队列 包含 ActiveQ、BackoffQ、UnschedulableQ,支持优先级与重试
Scheduling Framework 插件化调度周期与绑定周期 通过扩展点(Extension Points)实现 Filter、Score、Reserve 等逻辑
Cache 缓存 Node 状态与已调度 Pod 信息 用于快速计算资源可用性,避免频繁访问 API Server
Binder 将 Pod 与 Node 的绑定关系写入 API Server 通过创建 Binding 对象或直接更新 Pod 的 spec.nodeName
关键认知:Scheduler 的决策基于“快照”(Snapshot)而非实时数据。每次调度周期开始时,Scheduler 会从 Cache 中获取一份 Node 快照,确保调度过程中数据一致性,避免因并发修改导致决策冲突。

3.2 调度周期与绑定周期:两阶段模型

Kubernetes Scheduler 将 Pod 的调度过程拆分为两个独立的阶段:调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)。这种设计允许调度周期串行执行(保证一致性),而绑定周期可以并行执行(提升吞吐量)。

下图展示了从 Pod 入队到绑定完成的完整两阶段流程:

Scheduler 调度周期与绑定周期流程 阶段一:调度周期(串行) PreFilter Filter PostFilter PreScore / Score 阶段二:绑定周期(并行) Reserve Permit PreBind Bind Normal Scheduling Queue PostBind Unreserve Error 关键说明: 1. 调度周期串行执行:同一时间只调度一个 Pod,保证决策一致性。 2. 绑定周期并行执行:多个 Pod 的绑定操作可以同时进行,提升吞吐量。 3. 异常回滚:如果绑定周期中任何步骤失败,会调用 Unreserve 释放预留资源。

3.3 调度周期详解:从入队到选 Node

调度周期是 Scheduler 为 Pod 选择 Node 的核心阶段,整个过程串行执行。Kubernetes 通过 Scheduling Framework 将调度周期拆分为多个扩展点,每个扩展点可以插入自定义插件。

3.3.1 调度队列与 Pod 出队

  • ActiveQ:存放等待调度的 Pod,按优先级排序。Scheduler 每次从 ActiveQ 头部取出一个 Pod 开始调度。
  • BackoffQ:存放调度失败但可以重试的 Pod,退避时间递增。
  • UnschedulableQ:存放因资源不足或节点不可用而无法调度的 Pod,等待集群状态变化后重新入队。

3.3.2 扩展点执行顺序

扩展点 作用 典型插件示例
PreFilter 预处理 Pod 信息,校验 Pod 的调度约束 NodeResourcesFit、PodTopologySpread
Filter 过滤不满足条件的 Node,输出候选 Node 列表 NodeUnschedulable、NodeAffinity、TaintToleration
PostFilter 当所有 Node 都被过滤时,执行干预逻辑(如抢占) DefaultPreemption
PreScore 为打分阶段准备数据,计算全局评分上下文 NodeResourcesBalancedAllocation
Score 对候选 Node 进行打分(0-100),选择最高分 Node NodeResourcesFit、ImageLocality
NormalizeScore 对多个插件的打分结果进行归一化处理 内置归一化逻辑
优化关注点:在 GitOps 同步窗口场景中,Filter 阶段的 NodeSelector 和 NodeAffinity 插件可用于将工作负载绑定到特定节点池,而 Score 阶段的 NodeResourcesFit 插件可以通过调整资源权重来影响调度决策,实现资源利用率最大化。

3.4 绑定周期详解:从选 Node 到绑定完成

绑定周期负责将调度周期的决策结果持久化到 API Server,并执行必要的资源预留与清理工作。与调度周期不同,绑定周期支持并行执行,即多个 Pod 可以同时进入绑定周期。

3.4.1 绑定周期扩展点

扩展点 作用 典型插件示例
Reserve 在 Cache 中预留 Pod 所需的资源,防止其他调度决策冲突 VolumeBinding
Permit 批准或延迟绑定操作,常用于实现多调度器协调或准入控制 自定义 Permit 插件
PreBind 执行绑定前的准备工作,如动态创建 PV、挂载卷 VolumeBinding
Bind 执行最终的绑定操作,将 Pod 与 Node 的关联写入 API Server DefaultBinder
PostBind 绑定成功后的清理或通知操作 无默认实现,可自定义
Unreserve 绑定失败时释放 Reserve 阶段预留的资源 VolumeBinding

3.4.2 Permit 扩展点与同步窗口准入

Permit 扩展点是实现 GitOps 同步窗口准入控制的关键位置。通过自定义 Permit 插件,可以在绑定操作执行前检查当前时间是否在允许的同步窗口内。如果不在窗口内,Permit 插件可以返回 Wait 状态,将 Pod 挂起,直到窗口打开或超时。

实践注意:Permit 插件的 Wait 状态会阻塞绑定周期,但不会阻塞调度周期。这意味着其他 Pod 可以继续被调度,但被挂起的 Pod 会一直占用 Reserve 阶段的资源。因此,需要合理设置 Permit 超时时间,避免资源死锁。

3.5 调度失败与重试机制

当调度周期或绑定周期失败时,Scheduler 会依据失败类型采取不同的重试策略:

  • 调度周期失败(如 Filter 阶段无可用 Node):Pod 进入 UnschedulableQ,等待集群资源变化后重新入队。
  • 绑定周期失败(如 Bind 阶段写入 API Server 失败):触发 Unreserve 释放资源,Pod 进入 BackoffQ,按指数退避策略重试。
  • 抢占失败:如果 PostFilter 阶段的抢占逻辑无法找到可抢占的 Pod,Pod 也会进入 UnschedulableQ。

下图展示了调度失败后的重试路径:

调度失败重试路径 调度周期开始 Filter 无可用 Node 绑定周期失败 抢占失败 UnschedulableQ BackoffQ 集群资源变化或退避超时后重新入队

3.6 调度框架的可扩展性

Scheduling Framework 的设计使得 Scheduler 具备极高的可扩展性。用户可以通过实现自定义插件来修改调度行为,而无需修改 Scheduler 核心代码。常见的自定义场景包括:

  • 自定义 Filter 插件:实现基于 GPU 型号、NUMA 拓扑或自定义标签的节点过滤。
  • 自定义 Score 插件:实现基于成本、能耗或网络延迟的打分策略。
  • 自定义 Permit 插件:实现 GitOps 同步窗口准入、多租户配额检查或外部系统审批。
  • 自定义 PreBind 插件:在绑定前动态配置网络策略或注入 Sidecar 容器。
GitOps 实践关联:在后续章节中,我们将基于 Permit 扩展点实现一个“同步窗口准入插件”,该插件会在绑定周期中检查当前时间是否在 GitOps 允许的同步窗口内。如果不在窗口内,Permit 插件会返回 Wait 状态,将 Pod 挂起,直到窗口打开或超时。这种机制可以有效防止非工作时间的自动部署引发生产事故。

3.7 本章小结

  • Scheduler 通过两阶段模型(调度周期 + 绑定周期)实现 Pod 到 Node 的分配,调度周期串行执行,绑定周期并行执行。
  • 调度周期包含 PreFilter、Filter、PostFilter、PreScore、Score、NormalizeScore 等扩展点,负责 Node 筛选与打分。
  • 绑定周期包含 Reserve、Permit、PreBind、Bind、PostBind、Unreserve 等扩展点,负责资源预留与持久化绑定。
  • Permit 扩展点是实现 GitOps 同步窗口准入控制的关键位置,通过挂起绑定操作来阻止窗口外的调度。
  • 调度失败后,Pod 会根据失败类型进入 UnschedulableQ 或 BackoffQ,等待重试。