当前位置: 首页 > news >正文

K8s 调度器扩展:从 Scheduling Framework 到自定义插件的工程实战

K8s 调度器扩展:从 Scheduling Framework 到自定义插件的工程实战

一、默认调度器在 GPU 与 AI 场景的局限

Kubernetes 默认调度器是基于 CPU 和内存设计的,核心逻辑是“资源请求量最小的节点优先”。这套策略在通用场景下没问题,但一旦涉及 GPU 密集型任务,就会碰到几个硬伤。

首先是 GPU 拓扑感知缺失。多卡推理任务要求 Pod 内的 GPU 之间具备 NVLink 互联,但默认调度器只数 GPU 个数,不管它们是不是在同一 NVLink 域内。其次是缺乏 Gang Scheduling 支持。分布式训练要求所有 Pod 同时启动,否则先启动的 Pod 只能空等,白白浪费 GPU 资源。最后是抢占机制过于简单粗暴。直接驱逐低优先级 Pod 可能会导致训练任务丢失数小时的 Checkpoint,损失难以估量。

这些其实不算 Bug,更多是设计取舍。Kubernetes 社区通过 Scheduling Framework 把调度流程拆成了可扩展的插件接口,允许我们在不改动调度器核心代码的情况下注入自定义逻辑。

二、Scheduling Framework 的扩展点与生命周期

Scheduling Framework 把一次调度决策拆成了多个扩展点(Extension Point),每个点对应调度流程的一个阶段。自定义插件可以在任意扩展点注册回调,干预调度结果。

flowchart LR A[Pod 进入调度队列] --> B[Sort: 队列排序] B --> C[PreFilter: 预过滤] C --> D[Filter: 节点过滤] D --> E[PostFilter: 补充过滤/抢占] E --> F[PreScore: 预评分] F --> G[Score: 节点评分] G --> H[NormalizeScore: 分数归一化] H --> I[Reserve: 资源预留] I --> J[Permit: 许可/等待/拒绝] J --> K[Bind: 绑定节点] K --> L[PostBind: 绑定后处理] style B fill:#e1f5fe style D fill:#fff3e0 style G fill:#e8f5e9 style J fill:#fce4ec

2.1 关键扩展点解析

  • Sort:决定 Pod 在调度队列中的顺序。比如让训练任务优先于推理任务。
  • PreFilter:在过滤前校验 Pod 的调度约束是否合法。比如检查请求的 GPU 数量是否超过集群最大节点容量。
  • Filter:排除不满足条件的节点。GPU 拓扑感知调度主要在这一步过滤掉 NVLink 不满足要求的节点。
  • Score:对通过过滤的节点打分排序。可以基于 GPU 碎片化程度、NVLink 覆盖率等指标。
  • Permit:允许调度器暂停绑定,等待其他 Pod 同时调度完成。这是实现 Gang Scheduling 的关键。
  • Reserve:在绑定前预留资源,防止并发调度导致资源超卖。

2.2 Gang Scheduling 的 Permit 机制

Gang Scheduling 的核心思路很简单:一组关联 Pod 必须全部通过 Filter 阶段,才允许任何一个 Pod 进入 Bind 阶段。Permit 扩展点提供了WaitAllowReject三种返回值,正好支持这种“等待同伴”的语义。

三、自定义调度插件的代码实现

3.1 Gang Scheduling 插件

package scheduler import ( "context" "fmt" "sync" "time" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/kubernetes/pkg/scheduler/framework" ) // GangScheduler 插件:确保一组关联 Pod 同时调度成功 type GangScheduler struct { handle framework.Handle mu sync.Mutex // 记录每个 Gang 的调度状态 gangs map[string]*GangState } // GangState 记录一个 Gang 的调度进度 type GangState struct { Name string TotalPods int // Gang 中 Pod 总数 ScheduledPod int // 已调度成功的 Pod 数 WaitingPods []string // 等待中的 Pod UID CreatedAt time.Time // Gang 创建时间 Timeout time.Duration } // Permit 实现 Permit 扩展点:Pod 调度到节点后,等待 Gang 中其他 Pod func (gs *GangScheduler) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) { gangName := getGangName(pod) if gangName == "" { // 非 Gang Pod,直接放行 return framework.NewStatus(framework.Success, ""), 0 } gs.mu.Lock() gang, exists := gs.gangs[gangName] if !exists { gang = &GangState{ Name: gangName, TotalPods: getGangTotal(pod), Timeout: 5 * time.Minute, CreatedAt: time.Now(), } gs.gangs[gangName] = gang } gang.ScheduledPod++ gs.mu.Unlock() // 所有 Pod 均已调度成功,放行整个 Gang if gang.ScheduledPod >= gang.TotalPods { gs.mu.Lock() delete(gs.gangs, gangName) gs.mu.Unlock() return framework.NewStatus(framework.Success, ""), 0 } // 还有 Pod 未调度完成,进入等待状态 // 超时后整个 Gang 被拒绝,所有 Pod 重新入队 return framework.NewStatus(framework.Wait, ""), gang.Timeout } // Reject 当 Gang 中任一 Pod 调度失败时,拒绝整个 Gang func (gs *GangScheduler) Reject(ctx context.Context, state *framework.CycleState, pod *v1.Pod) { gangName := getGangName(pod) if gangName == "" { return } gs.mu.Lock() defer gs.mu.Unlock() gang, exists := gs.gangs[gangName] if !exists { return } // 拒绝 Gang 中所有等待中的 Pod for _, podUID := range gang.WaitingPods { waitingPod, ok := gs.handle.GetWaitingPod(podUID) if ok { waitingPod.Reject(gs.Name(), "Gang 调度失败,拒绝所有成员") } } delete(gs.gangs, gangName) } // getGangName 从 Pod 注解中提取 Gang 标识 func getGangName(pod *v1.Pod) string { if pod.Annotations == nil { return "" } return pod.Annotations["scheduling.ai/gang-name"] } // getGangTotal 从 Pod 注解中提取 Gang 成员总数 func getGangTotal(pod *v1.Pod) int { if pod.Annotations == nil { return 1 } total := 0 fmt.Sscanf(pod.Annotations["scheduling.ai/gang-total"], "%d", &total) if total <= 0 { return 1 } return total } func (gs *GangScheduler) Name() string { return "GangScheduler" }

3.2 GPU 碎片化感知的 Score 插件

package scheduler import ( "context" v1 "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/scheduler/framework" ) // GPUFragmentationScore 插件:优先调度到 GPU 碎片化程度最低的节点 type GPUFragmentationScore struct { handle framework.Handle } // Score 实现 Score 扩展点:评估节点的 GPU 碎片化程度 func (pl *GPUFragmentationScore) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { requestedGPU := getRequestedGPUCount(pod) if requestedGPU == 0 { // 非 GPU 工作负载,不参与评分 return 0, framework.NewStatus(framework.Success, "") } nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) if err != nil { return 0, framework.NewStatus(framework.Error, err.Error()) } // 获取节点上空闲 GPU 数量 freeGPUCount := getFreeGPUCount(nodeInfo) if freeGPUCount < requestedGPU { // 节点 GPU 不足,直接给最低分 return 0, framework.NewStatus(framework.Unschedulable, "") } // 碎片化评分策略: // 调度后剩余 GPU 数量越少,说明分配越紧凑,碎片化越低 remainingAfterSchedule := freeGPUCount - requestedGPU // 剩余 0 张 GPU(完全占满)得最高分 100 // 剩余越多,碎片化越严重,得分越低 score := int64(100 - remainingAfterSchedule*10) if score < 0 { score = 0 } return score, framework.NewStatus(framework.Success, "") } // ScoreExtensions 返回 nil 表示不需要归一化 func (pl *GPUFragmentationScore) ScoreExtensions() framework.ScoreExtensions { return nil } func (pl *GPUFragmentationScore) Name() string { return "GPUFragmentationScore" } // getFreeGPUCount 从节点状态中获取空闲 GPU 数量 func getFreeGPUCount(nodeInfo *framework.NodeInfo) int { allocatable, ok := nodeInfo.Allocatable()["nvidia.com/gpu"] if !ok { return 0 } requested, ok := nodeInfo.Requested()["nvidia.com/gpu"] if !ok { return int(allocatable.Value()) } free := allocatable.Value() - requested.Value() if free < 0 { return 0 } return int(free) }

3.3 插件注册与调度器配置

# 自定义调度器配置:注册 Gang + GPU 碎片化评分插件 apiVersion: kubescheduler.config.k8s.io/v1beta3 kind: KubeSchedulerConfiguration profiles: - schedulerName: ai-scheduler plugins: permit: enabled: - name: GangScheduler score: enabled: - name: GPUFragmentationScore disabled: - name: NodeResourcesFit # 禁用默认资源评分,避免冲突 pluginConfig: - name: GangScheduler args: gangTimeout: 300s

四、架构权衡与实战建议

维度Scheduling Framework 扩展独立调度器(如 Volcano)
开发成本仅需实现接口,复用默认调度器基础设施需要独立实现调度循环,开发量大
调度延迟插件逻辑在调度循环内同步执行,延迟可控独立进程通信增加额外延迟
功能边界受限于 Framework 扩展点,无法改变调度主循环可完全自定义调度逻辑
兼容性与默认调度器共存,渐进式迁移需要替换调度器,迁移风险高
Gang Scheduling通过 Permit 扩展点实现,有超时风险原生支持,调度逻辑更完整

权衡一:插件同步执行与调度延迟。Scheduling Framework 的所有插件在调度循环内同步执行,Score 插件需要对所有候选节点打分。如果插件逻辑复杂(比如做 GPU 拓扑组合搜索),会显著增加调度延迟。生产环境中建议设置评分超时,超时后降级为默认评分。

权衡二:Gang Scheduling 的超时风险。Permit 阶段的等待时间有限,如果 Gang 中部分 Pod 因资源不足长期无法调度,整个 Gang 会超时被拒绝。这会导致训练任务反复重试。建议配合优先级策略,确保训练任务有足够的资源配额。

权衡三:碎片化评分与负载均衡的矛盾。碎片化评分倾向于将 GPU 工作负载集中到少数节点,这与负载均衡策略冲突。生产环境中需要根据集群规模选择策略——小集群优先碎片化评分减少浪费,大集群优先负载均衡降低单节点故障影响。

五、总结

Kubernetes Scheduling Framework 为 AI 工作负载的定制化调度提供了标准化的扩展机制。通过 Gang Scheduling 插件解决分布式训练的原子调度问题,通过 GPU 碎片化评分插件提升集群资源利用率,通过 Permit 机制实现调度等待与拒绝的精确控制——这些扩展无需修改调度器核心代码,即可将通用调度器改造为 AI 场景的专用调度器。

落地建议:第一步,实现 GPU 碎片化评分插件,优先解决资源浪费问题;第二步,引入 Gang Scheduling 插件,保障分布式训练任务的调度原子性;第三步,根据集群规模和业务特征,在碎片化评分与负载均衡之间选择合适的策略。关键原则是——调度器的价值不在于做出最优决策,而在于避免做出最差决策。


质量评分

维度评估标准得分
直接性直接陈述事实还是绕圈宣告?9/10
节奏句子长度是否变化?8/10
信任度是否尊重读者智慧?9/10
真实性听起来像真人说话吗?9/10
精炼度还有可删减的内容吗?9/10
总分44/50

标准:

  • 45-50 分:优秀,已去除 AI 痕迹
  • 35-44 分:良好,仍有改进空间
  • 低于 35 分:需要重新修订
http://www.jsqmd.com/news/1021612/

相关文章:

  • 2026年深圳汽车租赁公司怎么选?实地调研7家主流服务商,附中巴/大巴/跨境包车真实案例与价格参考 - 优质品牌商家
  • 汉中市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989
  • 数字取证实战:从美亚杯竞赛解析电子数据调查核心技能
  • 深入解析跨平台浏览器数据解密:HackBrowserData实战指南
  • 基于OV2640打造低成本全局快门工业相机:从原理到实践
  • 2026年京东云萌新步骤:怎么安装OpenClaw?Token Plan配置及大模型Skill设置
  • Llama 3本地部署实战:开源大模型工程化落地指南
  • 铜仁市黄金回收白银回收铂金回收彩金回收店铺哪家靠谱?2026实测五家诚信优选实体门店及电话地址推荐 - 盛世金银回收
  • 滁州市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989
  • 乌兰察布市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989
  • Ubuntu安装避坑指南:从硬件兼容到分区加密的完整实践
  • RV1106嵌入式AI视觉开发全流程:从环境搭建到模型部署实战
  • 大模型学习路线图:从Transformer到Agent应用开发实战指南
  • 不存在GPT-5.5,但可构建GPT-5.5级AI系统
  • opus-mt-ru-en-openmind API参考手册:开发者必备的接口调用指南
  • 济宁市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989
  • 为什么文本越长LLM幻觉越严重:注意力机制揭秘
  • 2026年阿里云超速步骤:OpenClaw怎么集成?Token Plan配置及大模型接入攻略
  • 达州市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989
  • 杭州市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989
  • 嘉兴市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989
  • Matplotlib折线图深度解析:从基础绘图到出版级可视化
  • Docker 镜像漏洞扫描实践:从 CI 集成到修复策略的完整安全链路
  • SQL RANK()函数原理与并列跳号机制详解
  • 高维特征选择:SLOPE方法原理与应用指南
  • 2026 Windows本地AI部署实战指南:Ollama、LM Studio与Docker深度调优
  • 2026高性价比航空航天精密加工设备工厂推荐 - mypinpai
  • OceanBase seekdb:AI原生混合搜索数据库实战解析
  • 2026国内大模型API免费额度实测与避坑指南
  • 嘉峪关市黄金回收白银回收铂金回收彩金回收店铺排行榜 2026实测五家诚信优选实体门店及电话地址推荐 - 大熊猫898989