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 |
3.2 调度周期与绑定周期:两阶段模型
Kubernetes Scheduler 将 Pod 的调度过程拆分为两个独立的阶段:调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)。这种设计允许调度周期串行执行(保证一致性),而绑定周期可以并行执行(提升吞吐量)。
下图展示了从 Pod 入队到绑定完成的完整两阶段流程:
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 | 对多个插件的打分结果进行归一化处理 | 内置归一化逻辑 |
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 挂起,直到窗口打开或超时。
Wait 状态会阻塞绑定周期,但不会阻塞调度周期。这意味着其他 Pod 可以继续被调度,但被挂起的 Pod 会一直占用 Reserve 阶段的资源。因此,需要合理设置 Permit 超时时间,避免资源死锁。
3.5 调度失败与重试机制
当调度周期或绑定周期失败时,Scheduler 会依据失败类型采取不同的重试策略:
- 调度周期失败(如 Filter 阶段无可用 Node):Pod 进入 UnschedulableQ,等待集群资源变化后重新入队。
- 绑定周期失败(如 Bind 阶段写入 API Server 失败):触发 Unreserve 释放资源,Pod 进入 BackoffQ,按指数退避策略重试。
- 抢占失败:如果 PostFilter 阶段的抢占逻辑无法找到可抢占的 Pod,Pod 也会进入 UnschedulableQ。
下图展示了调度失败后的重试路径:
3.6 调度框架的可扩展性
Scheduling Framework 的设计使得 Scheduler 具备极高的可扩展性。用户可以通过实现自定义插件来修改调度行为,而无需修改 Scheduler 核心代码。常见的自定义场景包括:
- 自定义 Filter 插件:实现基于 GPU 型号、NUMA 拓扑或自定义标签的节点过滤。
- 自定义 Score 插件:实现基于成本、能耗或网络延迟的打分策略。
- 自定义 Permit 插件:实现 GitOps 同步窗口准入、多租户配额检查或外部系统审批。
- 自定义 PreBind 插件:在绑定前动态配置网络策略或注入 Sidecar 容器。
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,等待重试。