2. 调度框架:Scheduling Framework 与扩展点
Kubernetes 从 v1.19 开始将调度器核心重构为 Scheduling Framework,将调度流程拆解为一系列可插拔的扩展点(Extension Points)。每个扩展点对应调度周期中的一个阶段,允许开发者通过编写插件(Plugin)注入自定义逻辑,而无需修改调度器主代码。
核心思想:调度器不再是一个黑盒,而是一个由多个扩展点组成的管道(Pipeline)。每个扩展点可以注册多个插件,按顺序执行,任一插件返回失败即可终止当前 Pod 的调度。
2.1 调度周期与绑定周期
整个调度过程分为两个大阶段:
- Scheduling Cycle(调度周期):为 Pod 选择一个合适的 Node,是串行执行的。
- Binding Cycle(绑定周期):将调度结果持久化到 API Server,是并行执行的(多个 Pod 可以同时进行绑定)。
扩展点分布在两个周期中,形成完整的调度流水线:
2.2 扩展点详解
每个扩展点都有明确的职责和调用时机。下表列出所有扩展点及其核心作用:
| 扩展点 | 所属周期 | 核心职责 | 典型插件示例 |
|---|---|---|---|
| QueueSort | 调度周期 | 对调度队列中的 Pod 进行排序,决定下一个调度的 Pod | PrioritySort |
| PreFilter | 调度周期 | 预处理 Pod 信息,检查 Pod 是否满足调度前提条件 | NodeResourcesFit, PodTopologySpread |
| Filter | 调度周期 | 过滤不符合条件的 Node,是调度器的核心过滤阶段 | NodeUnschedulable, NodeAffinity, TaintToleration |
| PostFilter | 调度周期 | 当 Filter 阶段无可用 Node 时执行,尝试抢占或给出替代方案 | Preemption |
| PreScore | 调度周期 | 在打分前运行,用于计算打分阶段所需的共享数据 | NodeResources, InterPodAffinity |
| Score | 调度周期 | 对通过 Filter 的 Node 进行打分,分值越高越优先 | NodeResourcesBalancedAllocation, ImageLocality |
| NormalizeScore | 调度周期 | 对 Score 阶段的分数进行归一化处理,确保分数范围一致 | DefaultPodTopologySpread |
| Reserve | 调度周期 | 在选定 Node 上预留资源,防止并发调度冲突 | NodeResources |
| Permit | 调度周期 | 批准或延迟 Pod 的调度,常用于实现调度门控 | InterPodAffinity |
| PreBind | 绑定周期 | 在正式绑定前执行预处理,如挂载网络存储 | VolumeBinding |
| Bind | 绑定周期 | 将 Pod 与 Node 的绑定关系写入 API Server | DefaultBinder |
| PostBind | 绑定周期 | 绑定完成后执行清理或通知操作 | VolumeBinding |
2.3 扩展点执行流程与插件注册
调度器在启动时加载一组默认插件,用户可以通过 KubeSchedulerConfiguration 自定义启用/禁用插件,或调整插件顺序。以下是一个典型的配置片段:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
preFilter:
enabled:
- name: "NodeResourcesFit"
- name: "PodTopologySpread"
disabled:
- name: "*" # 禁用所有其他 PreFilter 插件
filter:
enabled:
- name: "NodeUnschedulable"
- name: "NodeAffinity"
- name: "TaintToleration"
- name: "NodeResourcesFit"
score:
enabled:
- name: "NodeResourcesBalancedAllocation"
weight: 2
- name: "ImageLocality"
weight: 1
bind:
enabled:
- name: "DefaultBinder"
关键点:
- 每个扩展点可以启用多个插件,按配置顺序执行。
- Score 扩展点支持
weight参数,用于调整不同打分插件的权重。 - Filter 阶段是“与”逻辑:所有 Filter 插件都必须通过,Pod 才能进入下一阶段。
- Score 阶段是“加权求和”:最终得分 = Σ(插件得分 × 插件权重)。
2.4 扩展点之间的数据传递
插件之间通过 CycleState(调度周期状态)共享数据。每个 Pod 在调度周期开始时创建一个独立的 CycleState,插件可以在其中写入和读取数据:
// 插件写入数据示例
func (pl *MyPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
state.Write("myKey", &myData{Value: 42})
return nil, framework.NewStatus(framework.Success, "")
}
// 后续插件读取数据示例
func (pl *MyPlugin) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) {
data, err := state.Read("myKey")
if err != nil {
return 0, framework.NewStatus(framework.Error, "failed to read state")
}
myData := data.(*myData)
// 使用 myData.Value 进行打分
return myData.Value, framework.NewStatus(framework.Success, "")
}
注意:CycleState 只在同一个 Pod 的调度周期内有效,不同 Pod 之间的 CycleState 相互隔离。绑定周期使用独立的 CycleState,与调度周期不共享。
2.5 扩展点与调度性能调优
理解扩展点对性能调优至关重要:
- Filter 前置:将计算量小、过滤能力强的插件放在 Filter 前面,尽早淘汰不合格 Node,减少后续打分计算量。
- Score 权重调整:根据业务需求调整打分插件的权重,例如对 CPU 密集型应用提高
NodeResourcesBalancedAllocation的权重。 - Permit 延迟:利用 Permit 扩展点实现 Pod 调度门控,控制调度节奏,避免突发调度导致集群震荡。
- PostFilter 抢占:在资源紧张时,通过 PostFilter 的抢占机制为高优先级 Pod 腾出空间。
2.6 实战建议:扩展点选择策略
在编写自定义调度插件时,应根据需求选择合适的扩展点:
| 需求场景 | 推荐扩展点 | 原因 |
|---|---|---|
| 自定义 Pod 调度顺序 | QueueSort | 唯一控制 Pod 出队顺序的扩展点 |
| 强制约束(如节点必须满足某标签) | Filter | Filter 失败直接淘汰 Node,效率最高 |
| 软约束(如尽量分散到不同节点) | Score | 通过打分影响选择,不强制淘汰 |
| 资源预留与冲突避免 | Reserve | 在调度周期内锁定资源,防止并发冲突 |
| 绑定前预处理(如创建存储卷) | PreBind | 绑定周期中最早执行的扩展点 |
| 调度门控/限流 | Permit | 可以延迟或拒绝 Pod 进入绑定周期 |
最佳实践:尽量使用 Filter 而非 Score 来实现硬约束,因为 Filter 在调度周期早期执行,性能开销更小。仅在需要区分“好”与“更好”时使用 Score。