3. 调度框架详解:扩展点(Extension Points)概念、PreFilter/Filter/PostFilter等扩展点作用
Kubernetes 调度器从 v1.19 开始引入了调度框架(Scheduling Framework),将调度流程拆解为一系列可插拔的扩展点(Extension Points)。每个扩展点对应调度周期中的一个特定阶段,调度器通过调用注册到这些扩展点的插件(Plugin)来执行具体的调度逻辑。这种架构使得调度器的行为高度可定制,无需修改核心代码即可实现复杂的调度策略。
3.1 扩展点概念与调度周期
调度框架将一次完整的 Pod 调度过程划分为两个主要阶段:调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)。扩展点分布在两个周期中,每个扩展点负责一个特定的决策或操作。
下表列出了调度框架中主要的扩展点及其在调度周期中的位置和作用:
| 扩展点 | 所属阶段 | 调用时机 | 核心作用 |
|---|---|---|---|
| QueueSort | 调度周期 | 调度队列排序时 | 对等待调度的 Pod 进行排序,决定下一个调度的 Pod |
| PreFilter | 调度周期 | Filter 之前 | 预处理 Pod 信息,校验 Pod 的调度约束,为 Filter 阶段准备数据 |
| Filter | 调度周期 | 节点预选时 | 过滤掉不满足 Pod 运行条件的节点(节点预选) |
| PostFilter | 调度周期 | Filter 之后,若无可调度节点 | 处理 Filter 阶段无可用节点的情况,可尝试抢占或给出诊断信息 |
| PreScore | 调度周期 | Score 之前 | 为 Score 阶段准备评分数据,运行前置计算 |
| Score | 调度周期 | 节点优选时 | 对通过 Filter 的节点进行打分,选出最优节点 |
| NormalizeScore | 调度周期 | Score 之后 | 对 Score 阶段的评分结果进行归一化或修正 |
| Reserve | 绑定周期 | 绑定前 | 在选定节点上预留 Pod 资源,防止并发调度冲突 |
| Permit | 绑定周期 | Reserve 之后,PreBind 之前 | 批准或延迟 Pod 的绑定,实现准入控制或等待条件 |
| PreBind | 绑定周期 | 绑定前 | 执行绑定前的准备工作,如挂载存储卷、配置网络 |
| Bind | 绑定周期 | 最终绑定 | 将 Pod 绑定到选定节点,更新 API Server 中的 Pod 与 Node 关联 |
| PostBind | 绑定周期 | 绑定后 | 绑定完成后的清理或通知操作 |
3.2 PreFilter / Filter / PostFilter 扩展点详解
在调度周期的节点预选阶段,PreFilter、Filter 和 PostFilter 三个扩展点构成了一个完整的"预处理-过滤-后处理"链路。它们是调度器决定"哪些节点可以运行 Pod"的核心环节。
3.2.1 PreFilter 扩展点
PreFilter 在 Filter 阶段之前被调用,主要用于:
- 校验 Pod 的调度约束:检查 Pod 的 Spec 是否满足基本的调度前提,例如 Pod 是否指定了不存在的 NodeName,或者 Pod 的 Affinity 规则是否自相矛盾。
- 预处理和缓存数据:计算 Pod 所需的资源总量(CPU、内存、GPU 等),解析 Pod 的拓扑分布约束(Pod Topology Spread Constraints),为后续的 Filter 和 Score 阶段准备高效的数据结构。
- 快速失败:如果 Pod 本身存在无法调度的硬性缺陷(如请求的资源超过集群总容量),PreFilter 可以直接返回失败,避免后续无意义的节点遍历。
// PreFilter 插件接口示例(Go 伪代码)
type PreFilterPlugin interface {
// PreFilter 在 Filter 之前调用,返回 PreFilterResult 和状态
PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) (*PreFilterResult, *Status)
}
// PreFilterResult 包含预处理后的数据,例如 Pod 请求的资源总量
type PreFilterResult struct {
// 节点必须满足的硬性资源需求
RequestedResources map[v1.ResourceName]int64
}
3.2.2 Filter 扩展点
Filter 是节点预选的核心扩展点,对集群中的每个节点执行过滤检查。只有所有注册的 Filter 插件都返回"通过"的节点,才会进入后续的 Score 阶段。
Filter 插件的典型检查项包括:
- 资源充足性:节点剩余 CPU、内存、存储是否满足 Pod 请求。
- 节点选择器(NodeSelector):Pod 的 nodeSelector 标签是否匹配节点标签。
- 节点亲和性(NodeAffinity):Pod 的 required 规则是否满足。
- Pod 亲和性/反亲和性(PodAffinity / PodAntiAffinity):节点上已有 Pod 的分布是否符合约束。
- Taints 与 Tolerations:Pod 是否容忍节点的污点。
- 端口冲突:Pod 请求的宿主机端口是否已被占用。
// Filter 插件接口示例
type FilterPlugin interface {
// Filter 对单个节点进行检查,返回 Success 或 Unschedulable
Filter(ctx context.Context, state *CycleState, p *v1.Pod, nodeInfo *NodeInfo) *Status
}
// 内置 Filter 插件示例:NodeResourcesFit
// 检查节点剩余资源是否 >= Pod 请求资源
func (pl *NodeResourcesFit) Filter(ctx context.Context, state *CycleState, pod *v1.Pod, nodeInfo *NodeInfo) *Status {
allocatable := nodeInfo.AllocatableResources
requested := state.Read().(PreFilterResult).RequestedResources
for resource, quantity := range requested {
if allocatable[resource] < quantity {
return NewStatus(Unschedulable, "节点资源不足")
}
}
return NewStatus(Success, "")
}
3.2.3 PostFilter 扩展点
PostFilter 在 Filter 阶段结束后调用,但仅在所有节点都被 Filter 过滤掉(即无可调度节点)时才会执行。它的主要职责是:
- 抢占调度(Preemption):尝试驱逐节点上优先级较低的 Pod,为当前 Pod 腾出空间。这是 PostFilter 最核心的用途。
- 诊断与日志:输出详细的不可调度原因,帮助运维人员排查问题。
- 替代方案:在某些自定义调度器中,PostFilter 可以尝试将 Pod 调度到"软约束"节点,而不是直接返回失败。
3.3 扩展点调用流程与插件协作
下面的 SVG 图展示了调度框架中扩展点的完整调用流程,以及 PreFilter / Filter / PostFilter 在调度周期中的位置和协作关系。
3.4 扩展点执行顺序与插件注册
调度框架保证扩展点按固定顺序执行,但多个插件可以注册到同一个扩展点。框架通过插件注册表(Plugin Registry)管理所有插件,并在每个扩展点按配置顺序依次调用。
以下是一个典型的调度器配置示例,展示了如何注册插件到不同的扩展点:
// 调度器配置(KubeSchedulerConfiguration 示例)
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
# 调度周期扩展点
preFilter:
enabled:
- name: "NodeResourcesFit" # 资源预计算
- name: "PodTopologySpread" # 拓扑分布预处理
filter:
enabled:
- name: "NodeUnschedulable" # 过滤不可调度节点
- name: "NodeResourcesFit" # 过滤资源不足节点
- name: "NodeName" # 过滤不匹配 NodeName 的节点
- name: "TaintToleration" # 污点容忍检查
postFilter:
enabled:
- name: "DefaultPreemption" # 默认抢占插件
# 绑定周期扩展点
bind:
enabled:
- name: "DefaultBinder" # 默认绑定插件
3.5 扩展点之间的数据传递:CycleState
调度框架通过 CycleState 对象在同一个调度周期的不同扩展点之间传递数据。PreFilter 插件可以将计算结果写入 CycleState,后续的 Filter、Score 等插件可以直接读取,避免重复计算。
// CycleState 数据传递示例
// PreFilter 插件写入数据
func (pl *MyPreFilterPlugin) PreFilter(ctx context.Context, state *CycleState, pod *v1.Pod) (*PreFilterResult, *Status) {
// 计算 Pod 的 GPU 需求
gpuRequest := calculateGPURequest(pod)
state.Write("gpuRequest", gpuRequest)
return &PreFilterResult{}, NewStatus(Success, "")
}
// Filter 插件读取数据
func (pl *MyFilterPlugin) Filter(ctx context.Context, state *CycleState, pod *v1.Pod, nodeInfo *NodeInfo) *Status {
// 从 CycleState 读取 PreFilter 写入的 GPU 需求
gpuRequest, ok := state.Read("gpuRequest").(int64)
if !ok {
return NewStatus(Error, "无法读取 GPU 需求数据")
}
// 检查节点 GPU 是否充足
if nodeInfo.GPUCount < gpuRequest {
return NewStatus(Unschedulable, "节点 GPU 不足")
}
return NewStatus(Success, "")
}
3.6 扩展点设计的最佳实践
- PreFilter:适合做全局校验、数据预处理、缓存计算。避免在 PreFilter 中执行耗时的节点遍历操作。
- Filter:适合做节点级别的硬性约束检查。每个 Filter 插件应保持幂等性,即对同一节点多次调用返回相同结果。
- PostFilter:仅在无节点可用时触发,适合实现抢占、降级、诊断。注意 PostFilter 的执行时间不应过长,以免阻塞调度队列。
- Score:适合做节点优选评分,返回 0-100 的分数。多个 Score 插件的分数会按权重合并。
- Permit:适合实现"等待条件"的调度策略,例如等待某个资源就绪后再绑定。
下面的 SVG 图展示了 PreFilter / Filter / PostFilter 三个扩展点在调度周期中的详细数据流和决策路径:
3.7 扩展点与网络拓扑接口的关联
在后续章节中,我们将深入探讨网络拓扑接口(Network Topology Interface, NTI)如何与调度框架的扩展点结合。例如:
- PreFilter 扩展点:解析 Pod 的网络拓扑约束(如要求 Pod 调度到同一可用区的节点),预处理网络延迟和带宽数据。
- Filter 扩展点:过滤掉不满足网络拓扑要求的节点,例如节点间网络延迟超过阈值,或节点所在机架不具备所需的网络带宽。
- PostFilter 扩展点:当所有节点都不满足网络拓扑约束时,尝试降级网络要求(如从"硬约束"降级为"软约束"),或触发网络资源的动态调整。