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, "")
}
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 还定义了一系列扩展点接口,插件通过实现这些接口来参与到调度流程的不同阶段。下图展示了插件接口的继承与组合关系。
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 方法的执行逻辑:
- 从
CycleState中读取 PreFilter 阶段预处理的数据(如 Pod 的拓扑约束)。 - 从
nodeInfo中获取节点的网络拓扑属性(如节点所在的可用区、机架、交换机等)。 - 判断节点是否满足 Pod 的网络拓扑要求。
- 返回
framework.NewStatus(framework.Success, "")表示节点通过过滤;返回framework.NewStatus(framework.Unschedulable, "reason")表示节点被过滤掉。
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 在完整调度流程中的位置与协作关系。