LOADING

加载过慢请开启缓存 浏览器默认开启

Kubernetes二次开发——使用Scheduler Framework扩展调度器

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

完整代码