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 绑定周期 绑定后 绑定完成后的清理或通知操作
关键理解:调度周期是"决策"阶段,决定 Pod 应该调度到哪个节点;绑定周期是"执行"阶段,将决策落地到集群状态。扩展点按固定顺序执行,插件可以注册到多个扩展点。

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 调度到"软约束"节点,而不是直接返回失败。
注意:PostFilter 不是"Filter 的补充检查",而是"Filter 全部失败后的兜底处理"。如果 Filter 阶段有至少一个节点通过,PostFilter 不会被调用。抢占调度是 PostFilter 的默认实现,但自定义插件可以覆盖此行为。

3.3 扩展点调用流程与插件协作

下面的 SVG 图展示了调度框架中扩展点的完整调用流程,以及 PreFilter / Filter / PostFilter 在调度周期中的位置和协作关系。

调度框架扩展点调用流程 调度框架扩展点调用流程 调度周期(Scheduling Cycle) 绑定周期(Binding Cycle) QueueSort PreFilter Filter PostFilter PreScore Score NormalizeScore Reserve Permit PreBind Bind PostBind 扩展点说明 • 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"       # 默认绑定插件
实践建议:自定义调度器时,优先考虑实现 PreFilter 和 Filter 扩展点,因为它们直接影响调度决策的正确性。PostFilter 仅在需要自定义抢占逻辑时才需要重写。绑定周期的扩展点(如 Permit、PreBind)适合实现准入控制和资源准备逻辑。

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 三个扩展点在调度周期中的详细数据流和决策路径:

PreFilter / Filter / PostFilter 数据流与决策路径 PreFilter / Filter / PostFilter 数据流与决策路径 Pod 入队 QueueSort PreFilter 写入 CycleState Filter 有节点通过? Score 阶段 PostFilter 抢占 / 诊断 / 降级 决策逻辑 • Filter 通过 → Score • Filter 全部失败 → PostFilter • PostFilter 可触发抢占 • 抢占成功则重新调度

3.7 扩展点与网络拓扑接口的关联

在后续章节中,我们将深入探讨网络拓扑接口(Network Topology Interface, NTI)如何与调度框架的扩展点结合。例如:

  • PreFilter 扩展点:解析 Pod 的网络拓扑约束(如要求 Pod 调度到同一可用区的节点),预处理网络延迟和带宽数据。
  • Filter 扩展点:过滤掉不满足网络拓扑要求的节点,例如节点间网络延迟超过阈值,或节点所在机架不具备所需的网络带宽。
  • PostFilter 扩展点:当所有节点都不满足网络拓扑约束时,尝试降级网络要求(如从"硬约束"降级为"软约束"),或触发网络资源的动态调整。
本章小结:调度框架的扩展点是 Kubernetes 调度器可扩展性的基石。PreFilter 负责预处理和校验,Filter 负责节点过滤,PostFilter 负责兜底处理。理解这三个扩展点的职责和数据流,是掌握自定义调度器开发和网络拓扑感知调度的前提。