发布时间:2026/7/17 15:02:40
Kube-Scheduler 扩展:自定义 GPU 调度插件实现业务感知
Kube-Scheduler 扩展自定义 GPU 调度插件实现业务感知一、默认调度器不知道你的 GPU 在干什么Kubernetes 默认调度器对 GPU 的理解停留在有没有卡、几块卡的层面。Node 上有nvidia.com/gpu: 8Pod 请求nvidia.com/gpu: 1调度器找到一个满足资源需求的节点就算完成。但在实际业务中有 GPU和能用 GPU之间有巨大的鸿沟。同一个节点上的 8 张 A100可能其中 3 张正在跑模型训练持续高负载2 张在跑在线推理延迟敏感剩下 3 张空闲。默认调度器只知道有 3 张可用但不知道这 3 张是 NVLink 拓扑最优的、还是已经产生了大量 GPU 内存碎片的。真实的痛点包括跨 NUMA 节点分配 GPU 导致 PCIe 带宽瓶颈推理和训练混合部署时的显存碎片化某张卡温度过高需要主动避让同一业务的多张卡需要 NVLink 直连以降低通信开销。这些问题默认调度器一个也解决不了。二、Kube-Scheduler 插件框架与 GPU 拓扑感知调度Kubernetes 1.19 引入的调度框架Scheduling Framework允许在调度的多个阶段插入自定义逻辑。GPU 调度插件主要在 Filter、Score 和 Reserve 三个阶段发挥作用。Filter 阶段不只是检查 GPU 数量。插件从 Prometheus 拉取实时指标GPU 利用率、显存使用率、温度过滤掉超温或显存碎片过高的节点。同时检查 NUMA 亲和性——如果 Pod 请求 4 张 GPU只保留 4 张卡在同一 NUMA 节点上的候选。Score 阶段不是简单的空闲越多分越高。评分策略包含多个维度显存连续性优先选碎片少的、NVLink 直连跳数优先选拓扑最近的、负载均衡优先选已有业务少的、同业务亲和同一模型的 Pod 尽量调度到同一节点利用 NVLink。Reserve 阶段预留具体的 GPU 索引如GPU-0,GPU-1,GPU-3,GPU-4而不是简单标记已占用 4 张。这避免了两个 Pod 同时调度时争抢同一张卡的问题。三、GPU 感知调度插件实现以下是基于调度框架的 GPU Filter Score 插件核心代码package gpuscheduler import ( context fmt sync time v1 k8s.io/api/core/v1 k8s.io/apimachinery/pkg/runtime k8s.io/kubernetes/pkg/scheduler/framework k8s.io/kubernetes/pkg/scheduler/framework/plugins/names ) const ( Name GPUTopology gpuKey nvidia.com/gpu // 避免过热的温度阈值 tempThreshold 80.0 // 显存碎片阈值剩余显存 / 总显存 fragThreshold 0.3 ) // GPUTopologyPlugin 拓扑感知的 GPU 调度插件 type GPUTopologyPlugin struct { handle framework.Handle gpuState *GPUStateCache cacheTTL time.Duration mu sync.RWMutex } type GPUStateCache struct { mu sync.RWMutex nodes map[string]*NodeGPUInfo lastSync time.Time } type NodeGPUInfo struct { TotalGPUs int FreeGPUs []int // 空闲 GPU 索引列表 UsedGPUs map[int]string // GPU索引 - Pod名称 Temperature map[int]float64 MemoryFree map[int]uint64 // 剩余显存MiB MemoryTotal map[int]uint64 NVLinkTopo [][]int // NVLink 连通矩阵 NUMA map[int]int // GPU索引 - NUMA节点 PodCount int } var _ framework.FilterPlugin GPUTopologyPlugin{} var _ framework.ScorePlugin GPUTopologyPlugin{} var _ framework.ReservePlugin GPUTopologyPlugin{} // Name 返回插件名称 func (p *GPUTopologyPlugin) Name() string { return Name } // Filter 过滤不满足 GPU 调度条件的节点 func (p *GPUTopologyPlugin) Filter( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo, ) *framework.Status { gpuRequested : getGPURequest(pod) if gpuRequested 0 { return nil // 非 GPU Pod放行 } node : nodeInfo.Node() if node nil { return framework.NewStatus(framework.Unschedulable, node not found) } nodeName : node.Name info, err : p.getNodeGPUInfo(nodeName) if err ! nil { return framework.NewStatus(framework.UnschedulableAndUnresolvable, fmt.Sprintf(failed to get GPU info for node %s: %v, nodeName, err)) } // 检查1温度阈值 — 有高温卡则排除当前节点 for gpuIdx, temp : range info.Temperature { if temp tempThreshold { return framework.NewStatus(framework.Unschedulable, fmt.Sprintf(GPU-%d on node %s temperature %.1f exceeds threshold %.1f, gpuIdx, nodeName, temp, tempThreshold)) } } // 检查2显存碎片 — 剩余显存过低的不算可用 freeCount : 0 for _, gpuIdx : range info.FreeGPUs { memFree : info.MemoryFree[gpuIdx] memTotal : info.MemoryTotal[gpuIdx] if memTotal 0 float64(memFree)/float64(memTotal) fragThreshold { continue // 显存碎片严重不算可用 } freeCount } if freeCount gpuRequested { return framework.NewStatus(framework.Unschedulable, fmt.Sprintf(node %s has %d usable GPUs (excluding fragmented), need %d, nodeName, freeCount, gpuRequested)) } // 检查3NUMA 亲和性 — 多卡请求需要同一 NUMA 节点 if gpuRequested 1 { if !hasNUMAAffinity(info, gpuRequested) { return framework.NewStatus(framework.Unschedulable, fmt.Sprintf(node %s cannot allocate %d GPUs in same NUMA node, nodeName, gpuRequested)) } } return nil } // Score 对通过 Filter 的节点打分 func (p *GPUTopologyPlugin) Score( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string, ) (int64, *framework.Status) { gpuRequested : getGPURequest(pod) if gpuRequested 0 { return 0, nil } info, err : p.getNodeGPUInfo(nodeName) if err ! nil { return 0, framework.NewStatus(framework.Error, err.Error()) } score : int64(0) // 维度1显存连续性可用 GPU 的显存剩余率均值 var totalFragScore float64 count : 0 for _, gpuIdx : range info.FreeGPUs { if info.MemoryTotal[gpuIdx] 0 { totalFragScore float64(info.MemoryFree[gpuIdx]) / float64(info.MemoryTotal[gpuIdx]) count } } if count 0 { score int64((totalFragScore / float64(count)) * 40) // 40 分权重 } // 维度2业务亲和性 — 同 Deployment 的 Pod 已在节点上运行时加分 if deploymentName, ok : pod.Labels[app]; ok { for _, usedBy : range info.UsedGPUs { if usedBy deploymentName { score 30 // 30 分权重 break } } } // 维度3负载均衡 — 当前 Pod 数越少分越高 maxPodFraction : 50.0 currentPods : float64(info.PodCount) if cap : float64(info.TotalGPUs); cap 0 { score int64((1.0 - currentPods/cap) * maxPodFraction) } return score, nil } // ScoreExtensions 返回评分归一化范围 func (p *GPUTopologyPlugin) ScoreExtensions() framework.ScoreExtensions { return p } func (p *GPUTopologyPlugin) NormalizeScore( _ context.Context, _ *framework.CycleState, _ *v1.Pod, scores framework.NodeScoreList, ) *framework.Status { // 不做归一化分数差异本身就反映了节点优劣 return nil } // Reserve 预留具体 GPU 索引 func (p *GPUTopologyPlugin) Reserve( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string, ) *framework.Status { gpuRequested : getGPURequest(pod) if gpuRequested 0 { return nil } info, err : p.getNodeGPUInfo(nodeName) if err ! nil { return framework.NewStatus(framework.Error, err.Error()) } // 按显存剩余量和 NVLink 跳数选择最优 GPU 组合 selected, err : selectBestGPUs(info, gpuRequested) if err ! nil { return framework.NewStatus(framework.Unschedulable, err.Error()) } // 写入 CycleState 供后续 Bind 阶段使用 state.Write(reserved-gpus, ReservedGPU{ NodeName: nodeName, GPUs: selected, }) return nil } // Unreserve 调度失败时释放预留 func (p *GPUTopologyPlugin) Unreserve( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string, ) { // 清理 Reserved 状态 state.Delete(reserved-gpus) } type ReservedGPU struct { NodeName string GPUs []int } // selectBestGPUs 从可用 GPU 中选择 NVLink 拓扑最优的组合 func selectBestGPUs(info *NodeGPUInfo, n int) ([]int, error) { if len(info.FreeGPUs) n { return nil, fmt.Errorf(only %d free GPUs, need %d, len(info.FreeGPUs), n) } // 贪心策略按显存剩余量排序取前 n 个 // 生产环境应结合 NVLink 连通矩阵做最小跳数选择 candidates : make([]int, len(info.FreeGPUs)) copy(candidates, info.FreeGPUs) // 按剩余显存降序排列 for i : 0; i len(candidates); i { for j : i 1; j len(candidates); j { if info.MemoryFree[candidates[i]] info.MemoryFree[candidates[j]] { candidates[i], candidates[j] candidates[j], candidates[i] } } } return candidates[:n], nil } func hasNUMAAffinity(info *NodeGPUInfo, required int) bool { numaCounts : make(map[int]int) for _, gpuIdx : range info.FreeGPUs { numa : info.NUMA[gpuIdx] memFree : info.MemoryFree[gpuIdx] memTotal : info.MemoryTotal[gpuIdx] if memTotal 0 float64(memFree)/float64(memTotal) fragThreshold { numaCounts[numa] } } for _, count : range numaCounts { if count required { return true } } return false } func getGPURequest(pod *v1.Pod) int { total : 0 for _, container : range pod.Spec.Containers { if qty, ok : container.Resources.Requests[gpuKey]; ok { total int(qty.Value()) } } return total } func (p *GPUTopologyPlugin) getNodeGPUInfo(nodeName string) (*NodeGPUInfo, error) { p.gpuState.mu.RLock() info, ok : p.gpuState.nodes[nodeName] p.gpuState.mu.RUnlock() if !ok || time.Since(p.gpuState.lastSync) p.cacheTTL { // 触发刷新 return p.refreshNodeGPUInfo(nodeName) } return info, nil } func (p *GPUTopologyPlugin) refreshNodeGPUInfo(nodeName string) (*NodeGPUInfo, error) { // 实际实现应从 DCGM Exporter / Prometheus 拉取数据 // 此处省略 Prometheus 查询和 NVLink 拓扑解析逻辑 return nil, fmt.Errorf(refresh not implemented) }调度器配置中的插件注册apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: gpu-topology-scheduler plugins: filter: enabled: - name: GPUTopology score: enabled: - name: GPUTopology reserve: enabled: - name: GPUTopology四、调度插件的边界与取舍Filter 开销每个 GPU 节点的 Filter 调用都需要查询外部数据源Prometheus/DCGM这会增加调度延迟。必须设置缓存 TTL建议 5-10 秒避免每次调度都发起网络请求。缓存的代价是可能基于过时数据做出决策需要在准确性和性能之间权衡。Score 策略复杂度评分维度越多越容易产生非预期的交互效应。比如负载均衡少 Pod 高分和业务亲和性同业务 Pod 高分在某些场景下会矛盾。建议针对业务特点固定 3-4 个评分维度过多维度反而降低可解释性。GPU 碎片化Reserve 阶段预留了具体 GPU 索引后如果有 Pod 被抢占或失败需要可靠的清理机制更新 GPU 状态。否则会出现幽灵占用——GPU 实际空闲但状态标记为已用。五、总结Kube-Scheduler 扩展框架为 GPU 调度提供了灵活的定制空间。Filter 阶段基于实时指标过滤不合规节点Score 阶段综合显存连续性、业务亲和性和负载均衡打分Reserve 阶段预留具体 GPU 索引避免竞态。实现过程中最大的挑战不是代码本身而是 GPU 状态数据的准确性和实时性——调度决策的质量取决于你对集群 GPU 状态的感知精度。

相关新闻

Conclave:基于CRDT和WebRTC的实时协作编辑器完整指南
2026/7/17 15:02:40

Conclave:基于CRDT和WebRTC的实时协作编辑器完整指南

Conclave:基于CRDT和WebRTC的实时协作编辑器完整指南 【免费下载链接】conclave CRDT and WebRTC based real-time, peer-to-peer, collaborative text editor 项目地址: https://gitcode.com/gh_mirrors/co/conclave Conclave是一款开源的实时协作文本编辑器…

阅读更多
Rust Rosetta Code算法解析:10个排序算法的Rust实现对比
2026/7/17 15:02:40

Rust Rosetta Code算法解析:10个排序算法的Rust实现对比

Rust Rosetta Code算法解析:10个排序算法的Rust实现对比 【免费下载链接】rust-rosetta Implementing Rosetta Code problems in Rust. 项目地址: https://gitcode.com/gh_mirrors/ru/rust-rosetta 在计算机科学领域,排序算法是基础且核心的内容。…

阅读更多
Wonka与TypeScript完美结合:类型安全的流处理最佳实践
2026/7/17 15:02:40

Wonka与TypeScript完美结合:类型安全的流处理最佳实践

Wonka与TypeScript完美结合:类型安全的流处理最佳实践 【免费下载链接】wonka 🎩 A tiny but capable push & pull stream library for TypeScript and Flow 项目地址: https://gitcode.com/gh_mirrors/wo/wonka Wonka 是一个轻量级但功能强大…

阅读更多
如何将现有Rust项目迁移到error-chain:7步完整迁移指南
2026/7/17 16:02:40

如何将现有Rust项目迁移到error-chain:7步完整迁移指南

如何将现有Rust项目迁移到error-chain:7步完整迁移指南 【免费下载链接】error-chain Error boilerplate for Rust 项目地址: https://gitcode.com/gh_mirrors/er/error-chain 想要为你的Rust项目提供一致且强大的错误处理体验吗?error-chain库正…

阅读更多
批量读取并合并数据
2026/7/17 16:02:40

批量读取并合并数据

我记录自动化办公踩坑实录:帮客户把报表耗时从48小时压到2小时前不久接了个内部提效的活儿,客户是一家做跨境电商的中大型公司,财务和运营团队每个月底都要对着十几份Excel表手动核对数据、拼接报表。说实话,他们之前全靠人工搬运…

阅读更多
深入解析VTube Studio API:打造智能虚拟主播互动系统的核心技术
2026/7/17 16:02:40

深入解析VTube Studio API:打造智能虚拟主播互动系统的核心技术

深入解析VTube Studio API:打造智能虚拟主播互动系统的核心技术 【免费下载链接】VTubeStudio VTube Studio API Development Page 项目地址: https://gitcode.com/gh_mirrors/vt/VTubeStudio 开篇引入:当虚拟形象需要与观众实时互动时 想象一下…

阅读更多
终极指南:在Windows和Linux上使用Ryujinx模拟器畅玩Switch游戏
2026/7/17 16:02:40

终极指南:在Windows和Linux上使用Ryujinx模拟器畅玩Switch游戏

终极指南:在Windows和Linux上使用Ryujinx模拟器畅玩Switch游戏 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx 想要在个人电脑上体验任天堂Switch游戏的魅力吗?…

阅读更多
实战指南:如何用MateCloud构建高效微服务架构的5大秘诀
2026/7/17 16:02:40

实战指南:如何用MateCloud构建高效微服务架构的5大秘诀

实战指南:如何用MateCloud构建高效微服务架构的5大秘诀 【免费下载链接】matecloud 🔥MateCloud是一款基于Spring Cloud Alibaba的微服务架构。目前已经整合Spring Boot 4.0.7、 SpringCloud 2025、Spring Cloud Alibaba 2025、Spring Security Oauth2、…

阅读更多
Kube-Scheduler 扩展:自定义 GPU 调度插件实现业务感知
2026/7/17 15:02:40

Kube-Scheduler 扩展:自定义 GPU 调度插件实现业务感知

Kube-Scheduler 扩展:自定义 GPU 调度插件实现业务感知 一、默认调度器不知道你的 GPU 在干什么 Kubernetes 默认调度器对 GPU 的理解停留在"有没有卡、几块卡"的层面。Node 上有 nvidia.com/gpu: 8,Pod 请求 nvidia.com/gpu: 1,调…

阅读更多
智慧树刷课插件:5分钟实现自动化学习的智能助手
2026/7/16 21:32:43

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

阅读更多
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案
2026/7/16 15:03:34

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

阅读更多
办公自动化实战:5个免费工具构建合同处理流水线
2026/7/16 22:47:50

办公自动化实战:5个免费工具构建合同处理流水线

1. 这不是“又一个工具包”,而是一套经过237次真实场景验证的效率组合拳“2026.04.28实用教程工具分享”这个标题乍看平平无奇,像极了你邮箱里被自动归入“促销/订阅”文件夹的那类通知——但如果你真把它当普通更新忽略,接下来半年里&#x…

阅读更多
2026全国外贸独立站搭建公司推荐排行,含零代码SAAS、AI编程、源码定制
2026/7/17 0:02:36

2026全国外贸独立站搭建公司推荐排行,含零代码SAAS、AI编程、源码定制

2026全国外贸独立站搭建公司推荐排行 外贸工厂、工贸企业建设官网,核心需求与普通展示型网站并不相同。除了页面整洁、多语言适配和海外访问体验,企业还要重点考虑数据能否导出、站点能否迁移、源码是否可控、后续费用是否透明,以及更换服务…

阅读更多
HarmonyOS 应用开发《掌上英语》第18篇:Grid 布局实战——从功能 5 宫格到练习 2×2 模式
2026/7/17 0:02:36

HarmonyOS 应用开发《掌上英语》第18篇:Grid 布局实战——从功能 5 宫格到练习 2×2 模式

Grid 布局实战——从功能 5 宫格到练习 22 模式引言 Grid 布局是 ArkTS 中最强大的布局容器之一,它通过行列模板系统实现灵活的多列多行布局。在本英语学习 App 中,Grid 布局贯穿了首页的多个核心模块:功能栏 5 宫格、练习模式 22 卡片、以及…

阅读更多
HarmonyOS 应用开发《掌上英语》第19篇:3D 翻转动画实现——ArkTS 动画系统全解析
2026/7/17 0:02:36

HarmonyOS 应用开发《掌上英语》第19篇:3D 翻转动画实现——ArkTS 动画系统全解析

3D 翻转动画实现——ArkTS 动画系统全解析引言 在移动应用中,卡片翻转动画是最受欢迎的交互动效之一,它能给用户带来直观的"物理世界"操作感。在我们的英语学习 App 的单词学习页面(CourseHomePage.ets)中,就…

阅读更多
基于Dify与DeepSeek构建私有知识库问答系统实战指南
2026/7/17 5:10:40

基于Dify与DeepSeek构建私有知识库问答系统实战指南

在业务中快速构建一个能理解私有文档、准确回答专业问题的智能助手,是很多开发团队面临的共同挑战。传统方案往往需要从零开始搭建复杂的 RAG(检索增强生成)系统,涉及文档解析、向量化、检索、大模型调用等多个环节,整…

阅读更多
FAE放射组学分析工具:医学影像特征探索的完整解决方案
2026/7/17 5:34:11

FAE放射组学分析工具:医学影像特征探索的完整解决方案

FAE放射组学分析工具:医学影像特征探索的完整解决方案 【免费下载链接】FAE FeAture Explorer 项目地址: https://gitcode.com/gh_mirrors/fae/FAE 你是否曾经面对海量医学影像数据感到无从下手?想要从CT、MRI等影像中提取有价值的定量特征&#…

阅读更多
DesktopNaotu:你的终极离线思维导图解决方案,告别网络依赖!
2026/7/17 7:48:38

DesktopNaotu:你的终极离线思维导图解决方案,告别网络依赖!

DesktopNaotu:你的终极离线思维导图解决方案,告别网络依赖! 【免费下载链接】DesktopNaotu 桌面版脑图 (百度脑图离线版,思维导图) 跨平台支持 Windows/Linux/Mac OS. (A cross-platform multilingual Mind Map Tool) 项目地址:…

阅读更多