3. 调度框架详解:PreFilter、Filter、PostFilter、PreScore、Score、NormalizeScore、Reserve、Permit、PreBind、Bind、PostBind

Kubernetes 调度器从 v1.19 开始引入调度框架(Scheduling Framework),将调度过程拆解为一系列可扩展的插件化阶段。每个阶段对应一个或多个扩展点(Extension Point),调度器按固定顺序依次执行这些扩展点。理解这 11 个核心扩展点,是掌握调度器工作原理和开发自定义调度插件的基础。

核心认知:调度框架采用“插件 + 扩展点”架构。每个扩展点负责调度流程中的一个特定环节,插件可以注册到多个扩展点。调度器通过组合不同插件,实现灵活、可扩展的调度策略。

3.1 调度框架整体流程

下图展示了调度框架中 11 个扩展点的执行顺序。整个调度周期分为调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)两个阶段。

调度框架扩展点执行流程 调度周期(Scheduling Cycle) 绑定周期(Binding Cycle) PreFilter Filter PostFilter PreScore Score NormalizeScore Reserve Permit PreBind Bind PostBind 说明: • 调度周期为每个 Pod 执行一次 • 绑定周期可能被缓存或异步执行 • 插件可注册到多个扩展点

3.2 调度周期扩展点详解

调度周期负责为 Pod 选择一个合适的节点。它包含 7 个扩展点,按顺序依次执行。

3.2.1 PreFilter(预过滤)

作用:在正式过滤节点之前,对 Pod 和集群状态进行预处理和检查。常用于解析 Pod 的调度约束、检查 Pod 是否满足基本条件。

  • 典型用途:检查 Pod 的 NodeSelector、NodeAffinity、PodAffinity 等调度约束是否合法;预处理 Pod 的资源请求。
  • 执行结果:返回 PreFilterResult 或错误。如果返回错误,调度周期提前终止。
  • 插件示例:NodeResourcesFit 插件在此阶段检查 Pod 的资源请求是否超过集群总容量。

3.2.2 Filter(过滤)

作用:对集群中所有节点进行过滤,排除不满足 Pod 调度要求的节点。这是调度器最核心的筛选阶段。

  • 典型用途:检查节点资源是否充足、节点端口是否冲突、节点是否满足 Pod 的污点容忍条件。
  • 执行方式:每个 Filter 插件对每个节点独立执行。所有 Filter 插件都通过的节点进入下一阶段。
  • 插件示例:NodeUnschedulable 插件过滤掉标记为 Unschedulable 的节点;TaintToleration 插件检查 Pod 是否能容忍节点的污点。
性能关键点:Filter 阶段是调度器性能瓶颈之一。对于大规模集群(数千节点),Filter 插件的执行效率直接影响调度吞吐量。建议 Filter 插件保持轻量,避免在过滤阶段执行耗时操作。

3.2.3 PostFilter(后过滤)

作用:当 Filter 阶段没有找到任何可用节点时,PostFilter 插件有机会执行补救措施或生成诊断信息。

  • 典型用途:尝试抢占(Preemption)低优先级 Pod 以释放资源;记录调度失败原因供后续分析。
  • 执行条件:仅在 Filter 阶段后可用节点数为 0 时触发。
  • 插件示例:DefaultPreemption 插件在此阶段尝试抢占其他 Pod。

3.2.4 PreScore(预评分)

作用:在正式评分之前,对 Filter 阶段通过的节点列表进行预处理,为 Score 阶段准备数据。

  • 典型用途:计算节点间的拓扑关系、预加载评分所需的缓存数据。
  • 执行结果:不返回分数,只更新内部状态或缓存。
  • 插件示例:InterPodAffinity 插件在此阶段计算 Pod 亲和性所需的拓扑信息。

3.2.5 Score(评分)

作用:对 Filter 阶段通过的每个节点进行评分,分数越高表示节点越适合运行该 Pod。

  • 典型用途:根据资源利用率、节点负载、亲和性规则等维度为节点打分。
  • 评分范围:每个 Score 插件的评分范围通常是 0-100,多个插件的评分会加权求和。
  • 插件示例:NodeResourcesBalancedAllocation 插件根据 CPU 和内存的均衡程度评分;ImageLocality 插件根据节点上是否已有 Pod 所需镜像评分。

3.2.6 NormalizeScore(归一化评分)

作用:对 Score 阶段产生的原始分数进行归一化处理,确保不同插件的分数在同一量级上可比。

  • 典型用途:将插件的原始分数映射到统一的 0-100 范围;应用权重调整。
  • 执行时机:在所有 Score 插件执行完毕后,对每个节点的总分进行归一化。
  • 插件示例:大多数内置插件在 Score 阶段直接返回 0-100 分数,NormalizeScore 阶段主要用于自定义插件。

3.2.7 Reserve(预留)

作用:在选定节点后,预留该节点上的资源,防止其他调度周期使用同一资源。

  • 典型用途:预留 Pod 所需的 CPU、内存、端口等资源;记录 Pod 与节点的绑定关系。
  • 执行结果:如果预留失败(如资源冲突),调度周期回滚并重新选择节点。
  • 插件示例:NodeResourcesFit 插件在此阶段更新节点的资源可用量。

3.3 绑定周期扩展点详解

绑定周期负责将 Pod 绑定到选定的节点。它包含 4 个扩展点,按顺序依次执行。

3.3.1 Permit(许可)

作用:在绑定之前进行最终审批,可以批准、拒绝或延迟绑定操作。

  • 典型用途:实现多调度器协调、跨集群调度审批、基于外部系统的绑定决策。
  • 执行结果:返回 Allow(允许绑定)、Deny(拒绝绑定)或 Wait(等待外部条件满足)。
  • 插件示例:自定义审批插件可以在此阶段调用外部 API 确认 Pod 是否可以绑定。

3.3.2 PreBind(预绑定)

作用:在正式绑定之前执行准备工作,如创建网络资源、挂载存储卷等。

  • 典型用途:动态创建 PV、配置网络策略、设置 DNS 记录。
  • 执行结果:如果 PreBind 失败,绑定周期终止,Reserve 阶段预留的资源会被释放。
  • 插件示例:VolumeBinding 插件在此阶段处理动态卷绑定。

3.3.3 Bind(绑定)

作用:将 Pod 与选定节点正式绑定,更新 API Server 中的 Pod 对象。

  • 典型用途:调用 Kubernetes API 创建 Pod 的 NodeName 绑定关系。
  • 执行方式:通常只有一个 Bind 插件会实际执行绑定操作。如果所有 Bind 插件都跳过,调度器使用默认绑定逻辑。
  • 插件示例:DefaultBinder 插件是内置的默认绑定实现。

3.3.4 PostBind(后绑定)

作用:绑定完成后执行清理或通知操作。

  • 典型用途:记录调度日志、更新监控指标、释放临时资源。
  • 执行时机:无论 Bind 成功还是失败,PostBind 都会执行。
  • 插件示例:自定义监控插件在此阶段记录调度延迟指标。

3.4 扩展点对比总结

扩展点 所属周期 核心职责 失败影响 典型插件
PreFilter 调度 预处理 Pod 调度约束 终止调度周期 NodeResourcesFit
Filter 调度 过滤不满足条件的节点 节点被排除 TaintToleration
PostFilter 调度 无可用节点时执行补救 触发抢占或报错 DefaultPreemption
PreScore 调度 为评分阶段准备数据 评分可能不准确 InterPodAffinity
Score 调度 对节点进行评分 影响节点选择 NodeResourcesBalancedAllocation
NormalizeScore 调度 归一化评分结果 评分权重异常 自定义插件
Reserve 调度 预留节点资源 回滚并重新选择 NodeResourcesFit
Permit 绑定 最终审批绑定操作 延迟或拒绝绑定 自定义审批插件
PreBind 绑定 绑定前准备工作 释放预留资源 VolumeBinding
Bind 绑定 执行 Pod 与节点绑定 Pod 无法调度 DefaultBinder
PostBind 绑定 绑定后清理与通知 不影响调度结果 监控插件

3.5 插件注册与执行顺序

调度框架通过 调度配置(Scheduler Configuration) 管理插件的注册和执行顺序。每个扩展点可以注册多个插件,插件按配置顺序依次执行。

# 调度器配置示例(KubeSchedulerConfiguration)
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
    plugins:
      filter:
        enabled:
          - name: NodeUnschedulable
          - name: NodeResourcesFit
          - name: NodeName
          - name: TaintToleration
      score:
        enabled:
          - name: NodeResourcesBalancedAllocation
            weight: 1
          - name: ImageLocality
            weight: 1
      reserve:
        enabled:
          - name: NodeResourcesFit
      preBind:
        enabled:
          - name: VolumeBinding
      bind:
        enabled:
          - name: DefaultBinder
注意事项:
  • 插件可以同时注册到多个扩展点。例如 NodeResourcesFit 同时注册了 Filter、Score、Reserve 三个扩展点。
  • Score 插件的 weight 参数控制该插件在总分中的权重,默认值为 1。
  • 如果多个插件注册到同一个扩展点,执行顺序由配置中的顺序决定,而不是插件名称的字母顺序。

3.6 扩展点之间的数据传递

调度框架使用 CycleState 对象在扩展点之间传递数据。每个调度周期创建一个独立的 CycleState,插件可以从中读取和写入数据。

CycleState 数据传递机制 CycleState 调度周期共享数据存储 PreFilter 插件 Filter 插件 Score 插件 Reserve 插件 PreBind 插件 Bind 插件 插件通过 CycleState.Write() 写入数据,通过 CycleState.Read() 读取数据 数据在调度周期结束后自动清理,不跨周期共享

3.7 实践建议与常见误区

开发自定义插件的最佳实践:
  • 选择合适的扩展点:资源检查类逻辑放在 Filter,优化选择类逻辑放在 Score,预处理类逻辑放在 PreFilter 或 PreScore。
  • 保持幂等性:插件在同一个扩展点可能被多次调用,确保逻辑是幂等的。
  • 避免阻塞操作:调度周期是同步执行的,避免在 Filter 或 Score 中执行网络调用或磁盘 I/O。
  • 利用 CycleState 缓存:如果多个扩展点需要相同数据,在 PreFilter 中计算一次并存入 CycleState。
常见误区:
  • 误区:认为 PostFilter 一定会执行。
    纠正:PostFilter 仅在 Filter 阶段后可用节点数为 0 时触发。
  • 误区:在 Filter 中修改节点状态。
    纠正:Filter 是只读操作,不应修改任何集群状态。
  • 误区:认为 Bind 阶段可以回滚。
    纠正:Bind 操作一旦提交到 API Server 就无法回滚,PostBind 只负责清理。
  • 误区:所有插件都必须注册到所有扩展点。
    纠正:插件只注册到它关心的扩展点,未注册的扩展点不会调用该插件。