2. 调度框架:Scheduling Framework 与扩展点

Kubernetes 从 v1.19 开始将调度器核心重构为 Scheduling Framework,将调度流程拆解为一系列可插拔的扩展点(Extension Points)。每个扩展点对应调度周期中的一个阶段,允许开发者通过编写插件(Plugin)注入自定义逻辑,而无需修改调度器主代码。

核心思想:调度器不再是一个黑盒,而是一个由多个扩展点组成的管道(Pipeline)。每个扩展点可以注册多个插件,按顺序执行,任一插件返回失败即可终止当前 Pod 的调度。

2.1 调度周期与绑定周期

整个调度过程分为两个大阶段:

  • Scheduling Cycle(调度周期):为 Pod 选择一个合适的 Node,是串行执行的。
  • Binding Cycle(绑定周期):将调度结果持久化到 API Server,是并行执行的(多个 Pod 可以同时进行绑定)。

扩展点分布在两个周期中,形成完整的调度流水线:

调度框架扩展点流水线 Scheduling Framework 扩展点流水线 Scheduling Cycle(调度周期)— 串行 Binding Cycle(绑定周期)— 并行 QueueSort PreFilter Filter PostFilter PreScore Score NormalizeScore Reserve Permit PreBind Bind PostBind 说明 调度周期串行为 Pod 选 Node 绑定周期并行执行持久化操作

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 腾出空间。
扩展点数据流与插件交互 扩展点数据流与插件交互 Pod 进入调度 CycleState(调度周期状态) PreFilter 写入: myKey=42 Filter 写入: filteredNodes=[] Score 读取: myKey=42 PreFilter 插件 Filter 插件 Score 插件 调度决策输出 数据流方向 Pod → CycleState → 插件 插件间通过 CycleState 共享数据

2.6 实战建议:扩展点选择策略

在编写自定义调度插件时,应根据需求选择合适的扩展点:

需求场景 推荐扩展点 原因
自定义 Pod 调度顺序 QueueSort 唯一控制 Pod 出队顺序的扩展点
强制约束(如节点必须满足某标签) Filter Filter 失败直接淘汰 Node,效率最高
软约束(如尽量分散到不同节点) Score 通过打分影响选择,不强制淘汰
资源预留与冲突避免 Reserve 在调度周期内锁定资源,防止并发冲突
绑定前预处理(如创建存储卷) PreBind 绑定周期中最早执行的扩展点
调度门控/限流 Permit 可以延迟或拒绝 Pod 进入绑定周期
最佳实践:尽量使用 Filter 而非 Score 来实现硬约束,因为 Filter 在调度周期早期执行,性能开销更小。仅在需要区分“好”与“更好”时使用 Score。