2. 调度框架详解:调度框架设计、扩展点、插件机制
Kubernetes 调度器(kube-scheduler)在 v1.19 之后引入了全新的调度框架(Scheduling Framework),将原本耦合在调度算法中的逻辑拆解为一系列可插拔的扩展点。理解调度框架的设计是分析调度器性能瓶颈、制定优化策略的前提。
2.1 调度框架整体设计
调度框架采用“调度周期 + 绑定周期”的双阶段流水线架构。每个 Pod 的调度过程被抽象为一个固定的执行序列,调度器通过注册到各个扩展点的插件来注入自定义逻辑。框架本身负责维护调度队列、缓存(Scheduler Cache)以及调度结果的绑定与回滚。
核心设计原则:
- 可扩展性:通过插件机制支持自定义调度策略,无需修改调度器核心代码。
- 可观测性:每个扩展点都有明确的上下文和指标输出,便于性能追踪。
- 确定性:调度周期内各扩展点的执行顺序固定,保证结果可重现。
2.2 扩展点详解
调度框架共定义了 11 个扩展点,分布在调度周期和绑定周期中。每个扩展点都有明确的输入、输出和执行语义。
| 扩展点 | 所属周期 | 执行时机 | 典型用途 | 性能敏感度 |
|---|---|---|---|---|
| QueueSort | 调度周期 | Pod 入队时排序 | 自定义 Pod 优先级顺序 | 高(每次入队触发) |
| PreFilter | 调度周期 | Filter 之前,每个 Pod 一次 | 预处理 Pod 信息、预检查集群状态 | 中 |
| Filter | 调度周期 | 遍历所有候选节点 | 节点过滤(资源、污点、亲和性) | 极高(节点数线性放大) |
| PreScore | 调度周期 | Filter 之后,Score 之前 | 计算评分所需的共享数据 | 低 |
| Score | 调度周期 | 遍历所有通过 Filter 的节点 | 节点打分(资源均衡、亲和性权重) | 高(节点数线性放大) |
| NormalizeScore | 调度周期 | 所有 Score 插件执行后 | 分数归一化处理 | 低 |
| Reserve | 绑定周期 | 选定节点后,绑定前 | 在缓存中预留资源 | 中 |
| Permit | 绑定周期 | Reserve 之后 | 等待/批准/拒绝绑定 | 中(可能阻塞) |
| PreBind | 绑定周期 | Permit 通过后 | 绑定前准备(如挂载卷) | 中 |
| Bind | 绑定周期 | 最终绑定 | 写入 etcd 绑定记录 | 高(etcd 写入延迟) |
| PostBind | 绑定周期 | 绑定成功后 | 清理、日志、指标 | 低 |
性能优化提示: Filter 和 Score 扩展点的执行次数与集群节点数成正比。当集群规模超过 5000 节点时,这两个扩展点的耗时占总调度延迟的 70% 以上。优化方向包括:减少不必要的 Filter 插件、使用并行评分、引入节点缓存预计算。
2.3 插件机制与注册
调度框架的插件机制基于 Go 接口实现。每个插件只需实现对应扩展点的接口,并通过调度器配置注册即可生效。调度器启动时,框架会根据配置按顺序加载插件,并在每个扩展点依次调用已注册的插件。
插件注册的核心数据结构如下:
// 插件注册配置示例(KubeSchedulerConfiguration)
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
# 调度周期插件
preFilter:
enabled:
- name: "NodeResourcesFit"
- name: "NodePorts"
filter:
enabled:
- name: "NodeUnschedulable"
- name: "NodeResourcesFit"
- name: "NodeName"
- name: "PodTopologySpread"
score:
enabled:
- name: "NodeResourcesBalancedAllocation"
weight: 1
- name: "ImageLocality"
weight: 1
# 绑定周期插件
reserve:
enabled:
- name: "VolumeBinding"
bind:
enabled:
- name: "DefaultBinder"
2.4 插件执行顺序与权重
在同一个扩展点内,多个插件的执行顺序由配置中的顺序决定。对于 Score 扩展点,每个插件可以配置独立的权重(weight),最终节点的总分是所有 Score 插件加权求和的结果。
插件执行的关键规则:
- Filter 短路:任何一个 Filter 插件返回不可调度,该节点立即被排除,不再执行后续 Filter 插件。
- Score 累加:所有 Score 插件的分数乘以权重后累加,NormalizeScore 用于将分数归一化到 [0, 100] 范围。
- Permit 等待:Permit 插件可以返回 Wait 状态,将 Pod 放入等待队列,直到超时或被批准。
- Unreserve 回滚:如果绑定周期中任何步骤失败,框架会调用所有已执行 Reserve 插件的 Unreserve 方法释放资源。
2.5 插件生命周期与状态管理
调度框架为每个插件提供了完整的生命周期管理,包括初始化、运行、清理和指标上报。插件开发者只需关注业务逻辑,框架负责调度上下文(CycleState)的传递和并发安全。
插件状态管理要点:
- CycleState:每个调度周期独立的状态对象,插件可通过它共享数据,避免重复计算。
- 并行安全:多个 Pod 的调度周期可以并行执行,插件必须保证 CycleState 的读写安全。
- 指标注入:框架自动为每个插件记录执行耗时、调用次数、错误次数等指标,通过 Prometheus 暴露。
- 优雅关闭:调度器关闭时,框架会按逆序调用插件的 Cleanup 方法,确保资源释放。
2.6 调度框架的性能影响分析
调度框架的插件化设计在带来灵活性的同时,也引入了额外的性能开销。以下是需要关注的性能瓶颈点:
| 性能因素 | 影响范围 | 优化建议 |
|---|---|---|
| 插件数量过多 | Filter / Score 扩展点 | 合并功能相似的插件,减少链式调用次数 |
| CycleState 滥用 | 所有扩展点 | 避免在 CycleState 中存储大对象,及时清理临时数据 |
| Permit 阻塞 | 绑定周期 | 减少 Permit 等待时间,或使用异步批准机制 |
| Bind 串行化 | 绑定周期 | 使用批量绑定或异步绑定策略(需自定义 Bind 插件) |
| 插件指标采集 | 全局 | 关闭不必要的插件级指标,降低 Prometheus 采集开销 |
常见误区: 不要为了追求调度精度而注册大量 Score 插件。每个 Score 插件都会遍历所有通过 Filter 的节点,插件数量每增加 1 个,评分阶段的总耗时近似线性增长。在 5000 节点集群中,每增加 1 个 Score 插件,单 Pod 调度延迟增加约 2-5ms。