k8s 自定义调度器插件之 Scheduler Framework
Scheduler Extender 与 Scheduler Framework
Scheduler Framework新增调度器
优点
- 性能好:由于不依赖外部插件或 HTTP 调用,调度流程的延迟相对较低,适合对性能要求较高的场景。
- 复用性高:可复用现有的调度插件,如 scheduler-plugins,大大降低了开发难度,提升了开发效率。
缺点
- 多个调度器可能会冲突:比如多个调度器同时调度了一个 Pod 到节点上,先启动的 Pod 把资源占用了,后续的 Pod 无法启动。
Scheduler Extender基于http服务扩展调度器
优点
- 实现简单:无需重新编译调度器,通过配置 KubeSchedulerConfiguration 创建一个外部 HTTP 服务来实现自定义逻辑。
- 零侵入性:不需要修改或重构调度器的核心代码,可快速上线新的调度逻辑。
- 灵活性较高:原有调度器和自定义逻辑相对独立,方便维护与测试。
缺点
- 性能差:调度请求需要经过 HTTP 调用,增加了调用延迟,对性能可能有影响。
通过以上对比,可以根据具体场景和需求选择合适的方法。
通过 Scheduler Framework 扩展主要步骤
- 1)克隆官方调度器代码scheduler-plugins
- 2)在pkg目录下新建插件目录及代码,通过下表扩展点扩展特殊逻辑
- 3)编辑cmd/scheduler/main.go,注册新的调度器插件
- 4)自定义调度器打包成镜像
- 5)helm部署到集群
- 6)测试验证
扩展点 | 描述 | 用途示例 |
---|---|---|
PreEnqueue | 在将 Pod 添加到内部活动队列之前被调用,用于避免不满足调度要求的 Pod 进入调度阶段。如果插件返回失败,Pod 不会进入活动队列。 | 如果 GPU 资源尚未准备好,阻止相关 Pod 进入队列。 |
PreFilter | 预处理 Pod 的相关信息或检查集群或 Pod 必须满足的某些条件。如果插件返回错误,则调度周期终止。 | 检查集群资源是否满足 Pod 的需求,如存储、网络等。 |
Filter | 用于过滤出不能运行该 Pod 的节点。对于每个节点,调度器按配置顺序调用这些插件。如果任何插件标记节点为不可行,则不会调用剩下的插件。主要作用是缩小可调度节点范围。 | 过滤掉不符合资源要求的节点,确保只评估合适的节点。 |
PostFilter | 在 Filter 阶段后调用,仅在 Pod 没有可行的节点时调用,用于补救措施(如抢占其他 Pod)。 | 实现 preemption 插件,通过抢占其他 Pod 来使当前 Pod 可访问。 |
PreScore | 执行前置评分工作,生成可共享状态供 Score 插件使用。如果插件返回错误,则调度周期终止。 | 准备评分所需的数据,如节点的负载信息。 |
Score | 对通过过滤阶段的节点打分并排序,帮助选择最适合的节点。调度器根据配置的插件权重合并所有插件的节点分数。 | 根据 CPU 或内存利用率进行评分,确保 Pod 分布均匀;按网络延迟或区域分布将 Pod 调度到与数据位置接近的节点。 |
NormalizeScore | 修改 Score 阶段的分数,将其映射到 0~100 范围内,便于对比。 | 将不同评分插件的得分归一化,确保评分结果一致。 |
Reserve | 在将 Pod 绑定到节点之前临时保留必要的资源,防止资源竞争问题。实现了 Reserve 接口的插件拥有 Reserve 和 Unreserve 方法。 | 保留资源以确保 Pod 绑定时资源可用;如果 Reserve 失败或后续阶段失败,触发 Unreserve 阶段清理状态。 |
Permit | 决定 Pod 是否可以绑定到指定节点,在调度周期最后阶段进行。Permit 插件可以批准、拒绝或等待 Pod 的绑定。 | 控制 Pod 的绑定,例如基于安全策略或资源预留情况决定是否允许绑定。 |
PreBind | 执行 Pod 绑定前所需的所有准备工作。如果插件返回错误,Pod 将被拒绝并退回到调度队列中。 | 制备网络卷并在允许 Pod 运行之前将其挂载到目标节点上。 |
Bind | 将 Pod 绑定到节点上。直到所有的 PreBind 插件完成,Bind 插件才会被调用。各 Bind 插件按配置顺序调用。真正执行调度的插件更新 Pod 的 spec.nodeName 字段。 | 更新 Pod 的 spec.nodeName 字段,使其绑定到特定节点。 |
PostBind | 在 Pod 成功绑定后被调用,用于传递信息或清理相关资源。这是绑定周期的结尾。 | 清理绑定过程中使用的临时资源或记录日志。 |
代码实现
binpack.go
- 在pkg目录下创建binpack(插件名)的文件夹
- 编写binpack.go,定义插件结构,该插件接收两个权重参数
type Binpack struct {
handle framework.Handle
weight priorityWeight
}
type priorityWeight struct {
BinPackingCPU int64
BinPackingMemory int64
}
- 实现New初始化方法,该方法可为参数设置默认值
- 实现Score方法,该方法根据节点的资源使用情况计算节点的得分,并根据权重进行加权计算。
- binpack默认得分公式为Score = CPU.weight * (request + used) / allocatable
// Score invoked at the score extension point.
func (pl *Binpack) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
logger := klog.FromContext(ctx)
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
return BinPackingScore(pod, nodeInfo, pl.weight, logger)
}
- 除此之外插件还需要实现其他两个方法,不然会报错哦:NormalizeScore实现分数归一化、ScoreExtensions
BinPackArgs
- 在上述New初始化方法中,我们通过BinPackArgs来从session接收参数值。参数结构体需要做一下开发
- apis\config\types.go、apis\config\v1\types.go中定义结构,添加+k8s:deepcopy-gen注释,后可通过脚本完善zz_generated.deepcopy.go
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// BinPackArgs holds arguments used to configure BinPack plugin.
type BinPackArgs struct {
metav1.TypeMeta `json:",inline"`
BinPackingCPU int64
BinPackingMemory int64
}
- apis\config\register.go、apis\config\v1\register.go中注册参数
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&CoschedulingArgs{},
&NodeResourcesAllocatableArgs{},
&TargetLoadPackingArgs{},
&LoadVariationRiskBalancingArgs{},
&LowRiskOverCommitmentArgs{},
&NodeResourceTopologyMatchArgs{},
&PreemptionTolerationArgs{},
&TopologicalSortArgs{},
&NetworkOverheadArgs{},
&SySchedArgs{},
&PeaksArgs{},
&BinPackArgs{},
)
return nil
}
注册插件
- cmd/scheduler/main.go中注册插件
func main() {
// Register custom plugins to the scheduler framework.
// Later they can consist of scheduler profile(s) and hence
// used by various kinds of workloads.
command := app.NewSchedulerCommand(
app.WithPlugin(capacityscheduling.Name, capacityscheduling.New),
app.WithPlugin(coscheduling.Name, coscheduling.New),
app.WithPlugin(loadvariationriskbalancing.Name, loadvariationriskbalancing.New),
app.WithPlugin(networkoverhead.Name, networkoverhead.New),
app.WithPlugin(topologicalsort.Name, topologicalsort.New),
app.WithPlugin(noderesources.AllocatableName, noderesources.NewAllocatable),
app.WithPlugin(noderesourcetopology.Name, noderesourcetopology.New),
app.WithPlugin(preemptiontoleration.Name, preemptiontoleration.New),
app.WithPlugin(targetloadpacking.Name, targetloadpacking.New),
app.WithPlugin(lowriskovercommitment.Name, lowriskovercommitment.New),
app.WithPlugin(sysched.Name, sysched.New),
app.WithPlugin(peaks.Name, peaks.New),
// Sample plugins below.
// app.WithPlugin(crossnodepreemption.Name, crossnodepreemption.New),
app.WithPlugin(podstate.Name, podstate.New),
app.WithPlugin(qos.Name, qos.New),
app.WithPlugin(binpack.Name, binpack.New),
)
code := cli.Run(command)
os.Exit(code)
}
编译部署
- 按doc\develop.md所示,若新增了
Args,可执行hack/update-codegen.sh命令完善实现接口 - code-generator说明
- 打包镜像
- kubernetes与volcano打包都用到了buildx,该插件访问私有仓库的坑见这里
REGISTRY=192.168.110.64:5000 RELEASE_VERSION=binpack PLATFORMS="linux/amd64,linux/arm64" DISTROLESS_BASE_IMAGE=busybox:1.36 make push-images
- manifest/install 目录中有部署的helm chart,修改image参数,使用helm部署。
cd manifest/install
helm upgrade --install binpack-scheduler charts/as-a-second-scheduler --namespace kube-system -f charts/as-a-second-scheduler/values.yaml
- 也可直接使用kubectl apply部署
cd manifest/install/charts/as-a-second-scheduler
kubectl apply -f deploy.yaml
确认服务正常运行
# kubectl -n kube-system get po|grep binpack-scheduler