3. Scheduler Framework 核心概念:Framework 接口、CycleState、Plugin 接口、ScorePlugin 与 FilterPlugin 详解

Kubernetes 调度器从传统的硬编码调度器演进为可扩展的 Scheduler Framework,其核心设计思想是将调度流程拆解为多个可插拔的扩展点。本章将深入剖析 Framework 的核心接口、状态管理机制以及最常用的两种插件类型:FilterPlugin 和 ScorePlugin。理解这些概念是开发网络拓扑感知调度插件的基础。

3.1 Framework 接口:调度流程的编排者

Framework 接口是调度框架的顶层抽象,它定义了调度器运行一个 Pod 调度周期的完整流程。它负责管理注册的插件、维护调度上下文、并按照预定义的顺序调用各个扩展点。

方法 说明 关键参数
RunFilterPlugins 运行所有注册的 Filter 插件,对节点进行预选过滤 ctx, state, pod, nodeInfo
RunScorePlugins 运行所有注册的 Score 插件,对通过过滤的节点进行打分 ctx, state, pod, nodes
RunPreFilterPlugins 在 Filter 之前运行,用于预处理 Pod 信息或初始化 CycleState ctx, state, pod
RunPostFilterPlugins 在 Filter 之后运行,用于处理过滤结果或执行抢占逻辑 ctx, state, pod, filteredNodesStatuses
RunPreScorePlugins 在 Score 之前运行,用于计算打分所需的共享数据 ctx, state, pod, nodes
RunNormalizeScorePlugins 对 Score 插件的原始打分结果进行归一化处理 ctx, state, pod, scores
RunReservePlugins 在选定节点后,预留资源,确保调度决策的原子性 ctx, state, pod, nodeName
RunPermitPlugins 在 Reserve 之后,用于批准或延迟调度决策 ctx, state, pod, nodeName
开发提示: 在开发网络拓扑感知插件时,最常实现的是 PreFilterPlugin(用于读取 Pod 的网络拓扑约束注解)、FilterPlugin(用于过滤不满足拓扑要求的节点)和 ScorePlugin(用于对满足要求的节点进行优先级排序)。

3.2 CycleState:调度周期的上下文缓存

CycleState 是一个贯穿单个 Pod 调度全周期的上下文对象。它允许插件在调度流程的不同阶段(如 PreFilter、Filter、Score)之间安全地共享和传递数据,避免了重复计算和外部存储依赖。

  • 数据存储: 使用 state.Write(key, data) 和 state.Read(key) 方法读写数据。
  • 生命周期: 每个 Pod 的调度周期开始时创建,调度周期结束时自动销毁。
  • 并发安全: 调度器为每个 Pod 独立创建 CycleState,不同 Pod 的 CycleState 互不干扰。
// 在 PreFilter 插件中写入数据
func (pl *NetworkTopologyPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
    // 解析 Pod 的拓扑标签
    topologyKey := pod.Labels["topology.kubernetes.io/zone"]
    // 将解析结果存入 CycleState
    state.Write("topologyKey", topologyKey)
    return nil, framework.NewStatus(framework.Success, "")
}

// 在 Filter 插件中读取数据
func (pl *NetworkTopologyPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    // 从 CycleState 读取 PreFilter 阶段写入的数据
    data, err := state.Read("topologyKey")
    if err != nil {
        return framework.NewStatus(framework.Error, "failed to read topologyKey from CycleState")
    }
    topologyKey := data.(string)
    // 执行过滤逻辑...
    return framework.NewStatus(framework.Success, "")
}
避坑指南: CycleState 中的数据是 只读 的(除了写入它的插件)。在 Filter 或 Score 阶段尝试修改其他插件写入的数据会导致数据竞争和不可预期的行为。建议在 PreFilter 阶段完成所有数据预处理和写入工作。

3.3 Plugin 接口:所有插件的基石

所有调度插件都必须实现 Plugin 接口。这是最基础的接口,只包含两个方法:Name() 和 New()。

// Plugin 接口定义
type Plugin interface {
    Name() string
}

// 插件注册函数签名
type PluginFactory = func(configuration runtime.Object, f FrameworkHandle) (Plugin, error)
  • Name():返回插件的唯一名称,用于在调度器配置中标识该插件。
  • PluginFactory:用于创建插件实例的工厂函数,接收插件配置和 FrameworkHandle 作为参数。

除了基础接口,Scheduler Framework 还定义了一系列扩展点接口,插件通过实现这些接口来参与到调度流程的不同阶段。下图展示了插件接口的继承与组合关系。

Scheduler Framework 插件接口体系 Plugin 接口 PreFilterPlugin FilterPlugin ScorePlugin PreScorePlugin ReservePlugin PermitPlugin NormalizeScorePlugin PostFilterPlugin 说明 插件通过实现一个或多个 扩展点接口参与调度

3.4 FilterPlugin:节点预选过滤器

FilterPlugin 是调度流程中第一个核心扩展点,用于对集群中的所有节点进行快速过滤,排除不满足 Pod 运行要求的节点。对于网络拓扑感知插件,FilterPlugin 用于过滤掉那些无法满足 Pod 网络延迟、带宽或拓扑亲和性要求的节点。

// FilterPlugin 接口定义
type FilterPlugin interface {
    Plugin
    Filter(ctx context.Context, state *CycleState, pod *v1.Pod, nodeInfo *NodeInfo) *Status
}

Filter 方法的执行逻辑:

  1. 从 CycleState 中读取 PreFilter 阶段预处理的数据(如 Pod 的拓扑约束)。
  2. 从 nodeInfo 中获取节点的网络拓扑属性(如节点所在的可用区、机架、交换机等)。
  3. 判断节点是否满足 Pod 的网络拓扑要求。
  4. 返回 framework.NewStatus(framework.Success, "") 表示节点通过过滤;返回 framework.NewStatus(framework.Unschedulable, "reason") 表示节点被过滤掉。
性能关键点: Filter 插件会被调度器并行调用,以加速过滤过程。因此,Filter 方法必须是 线程安全 的,并且应避免执行耗时的 I/O 操作。如果需要进行网络拓扑数据查询,建议在 PreFilter 阶段批量加载并缓存到 CycleState 中。

3.5 ScorePlugin:节点优先级打分器

ScorePlugin 在 Filter 阶段之后运行,用于对通过过滤的节点进行打分,分值越高表示节点越适合运行该 Pod。网络拓扑感知插件通过 ScorePlugin 实现“最优拓扑节点选择”,例如优先选择与 Pod 所在节点网络延迟最低的节点。

// ScorePlugin 接口定义
type ScorePlugin interface {
    Plugin
    Score(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) (int64, *Status)
    ScoreExtensions() ScoreExtensions
}

// ScoreExtensions 接口(可选实现)
type ScoreExtensions interface {
    NormalizeScore(ctx context.Context, state *CycleState, pod *v1.Pod, scores NodeScoreList) *Status
}

Score 打分机制:

  • 原始打分: Score 方法返回一个 int64 类型的原始分数,范围通常在 0 到 100 之间。
  • 分数归一化: 如果插件实现了 ScoreExtensions 接口,调度器会在所有 Score 插件打分完成后,调用 NormalizeScore 方法对分数进行归一化处理,确保不同插件的分数具有可比性。
  • 权重组合: 调度器会根据配置文件中为每个 Score 插件指定的权重,将归一化后的分数加权求和,得到节点的最终得分。
// 网络拓扑感知 Score 插件示例
func (pl *NetworkTopologyPlugin) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
    // 从 CycleState 获取目标拓扑域
    data, _ := state.Read("targetTopology")
    targetTopology := data.(string)

    // 获取当前节点的拓扑域
    nodeTopology := pl.topologyCache.GetNodeTopology(nodeName)

    // 计算拓扑距离(示例:相同拓扑域得 100 分,不同得 0 分)
    if nodeTopology == targetTopology {
        return 100, framework.NewStatus(framework.Success, "")
    }
    return 0, framework.NewStatus(framework.Success, "")
}

func (pl *NetworkTopologyPlugin) ScoreExtensions() framework.ScoreExtensions {
    return pl // 假设本插件也实现了 NormalizeScore
}

func (pl *NetworkTopologyPlugin) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
    // 归一化逻辑:将所有分数映射到 [0, 100] 区间
    // ...
    return framework.NewStatus(framework.Success, "")
}

3.6 FilterPlugin 与 ScorePlugin 对比

对比维度 FilterPlugin ScorePlugin
执行阶段 调度流程早期(预选阶段) 调度流程中期(优选阶段)
核心目标 快速排除不满足硬性要求的节点 对满足要求的节点进行优先级排序
返回值 *Status(Success / Unschedulable / Error) int64 分数 + *Status
执行方式 并行执行,所有 Filter 插件独立运行 并行执行,但 NormalizeScore 是串行的
对调度结果的影响 直接决定节点是否进入候选列表 影响最终节点的选择概率
网络拓扑场景示例 过滤掉不满足网络带宽要求的节点 对网络延迟最低的节点给予最高分

下图展示了 FilterPlugin 和 ScorePlugin 在完整调度流程中的位置与协作关系。

FilterPlugin 与 ScorePlugin 在调度流程中的位置 PreFilter Filter PreScore Score FilterPlugin 职责 • 硬性约束检查 • 快速排除不满足条件的节点 • 返回 Success / Unschedulable ScorePlugin 职责 • 软性偏好评估 • 对候选节点进行优先级打分 • 返回 0-100 的分数 CycleState:跨阶段数据共享通道
最佳实践: 在开发网络拓扑感知插件时,建议将“硬性拓扑约束”(如 Pod 必须调度到特定可用区)放在 FilterPlugin 中实现,将“软性拓扑偏好”(如优先选择网络延迟最低的节点)放在 ScorePlugin 中实现。这种分离使得调度策略更加灵活和可配置。