如何通过Scheduler Extender在K8s中实现自定义调度逻辑?

摘要:本文主要分享如何通过 Scheduler Extender 扩展调度器从而实现自定义调度策略。 1. 为什么需要自定义调度逻辑 什么是所谓的调度? 所谓调度就是指给 Pod 对象的 spec.nodeName 赋值 待调度对象则是所有 sp
本文主要分享如何通过 Scheduler Extender 扩展调度器从而实现自定义调度策略。 1. 为什么需要自定义调度逻辑 什么是所谓的调度? 所谓调度就是指给 Pod 对象的 spec.nodeName 赋值 待调度对象则是所有 spec.nodeName 为空的 Pod 调度过程则是从集群现有的 Node 中为当前 Pod 选择一个最合适的 实际上 Pod 上还有一个平时比较少关注的属性: spec.schedulerName,用于指定该 Pod 要交给哪个调度器进行调度。 那么问题来了,平时用的时候也没给 spec.schedulerName 赋值过,怎么也能调度呢? 因为默认的 kube-scheduler 可以兼容 spec.schedulerName 为空或者为 default 的 Pod。 为什么需要自定义调度逻辑 自定义调度逻辑可以解决特定应用场景和需求,使集群资源使用更高效,适应特殊的调度策略。 比如: 不同的工作负载可能有特定的资源需求,比如 GPU 或 NPU,需要确保 Pod 只能调度到满足这些资源条件的节点上。 某些集群可能需要均衡资源消耗,避免将多个负载集中到某些节点上。 为了降低延迟,可能需要将Pod调度到特定地理位置的节点上。自定义调度器可以根据节点的地理位置标签进行调度决策。 某些应用需要与其他应用隔离运行,以避免资源争抢。通过自定义调度器,可以将特定类型的任务或工作负载隔离到专用的节点上。 ... 总之就是业务上有各种特殊的调度需求,因此我们需要通过实现自定义调度器来满足这些需求。 通过实现自定义调度器,可以根据具体的业务需求和集群环境,实现更灵活、更高效的资源管理和调度策略。 2.如何增加自定义调度逻辑 自定义调度器的几种方法 要增加自定义调度逻辑也并不复杂,K8s 整个调度流程都已经插件化了,我们并不需要重头开始实现一个调度器,而只需要实现一个调度插件,通过在调度过程中各个阶段加入我们的自定义逻辑,来控制最终的调度结果。 总体来说可以分为以下几个方向: 1)新增一个调度器 直接修改 kube-scheduler 源码,编译替换 使用 调度框架(Scheduling Framework),我们可以使用 scheduler-plugins 作为模板,简化自定义调度器的开发流程。 Kubernetes v1.15 版本中引入了可插拔架构的调度框架,使得定制调度器这个任务变得更加的容易。调库框架向现有的调度器中添加了一组插件化的 API,该 API 在保持调度程序“核心”简单且易于维护的同时,使得大部分的调度功能以插件的形式存在。 2)扩展原有调度器 通过 Scheduler Extender 可以实现对已有调度器进行扩展。单独创建一个 HTTP 服务并实现对应接口,后续就可以将该服务作为外置调度器使用。通过配置 KubeSchedulerConfiguration原 Scheduler 会以 HTTP 调用方式和外置调度器交互,实现在不改动原有调度器基础上增加自定义逻辑。 3)其他非主流方案 自定义 Webhook 直接修改未调度 Pod 的 spec.nodeName 字段,有点离谱但理论可行哈哈 二者都有自己的优缺点 优点 缺点 新增调度器 性能好:由于不依赖外部插件或 HTTP 调用,调度流程的延迟相对较低,适合对性能要求较高的场景复用性高:可复用现有的调度插件,如 scheduler-plugins,大大降低了开发难度,提升了开发效率。 多个调度器可能会冲突:比如多个调度器同时调度了一个 Pod 到节点上,先启动的 Pod 把资源占用了,后续的 Pod 无法启动。 扩展调度器 实现简单:无需重新编译调度器,通过配置 KubeSchedulerConfiguration 创建一个外部 HTTP 服务来实现自定义逻辑。零侵入性:不需要修改或重构调度器的核心代码,可快速上线新的调度逻辑。灵活性较高:原有调度器和自定义逻辑相对独立,方便维护与测试。 性能差:调度请求需要经过 HTTP 调用,增加了调用延迟,对性能可能有影响。 一般在我们要改动的逻辑不多时,直接使用 Scheduler Extender 是比较简单的。 Scheduler 配置 调度器的配置有一个单独的对象:KubeSchedulerConfiguration,不过并没有以 CRD 形式存在,而是存放到 Configmap 中的。
阅读全文