
最近做了一个Kubernetes 方向的小项目GPU 资源调度与成本优化系统。项目地址https://github.com/ycx666994/gpu-k8s-scheduler然后这个项目的目标呢不是训练模型也不是写一个复杂的调度器而是围绕 Kubernetes 原生能力做一个可以演示 GPU 资源隔离、配额控制、节点亲和性、优先级调度和成本可视化的 MVP。主要是我想去验证几个问题1.多个团队共用 GPU 集群时怎么避免互相抢资源2.GPU 配额超了以后Kubernetes 是怎么处理的3.训练任务和推理任务能不能调度到不同类型的 GPU 节点4.没有真实 GPU 的本地 kind 集群能不能模拟出调度现象5.能不能把这些状态导出来用一个简单 Dashboard 展示最终做出来的效果是用 Namespace、ResourceQuota、Node Affinity、PriorityClass 搭了一套 GPU 调度实验环境并写了一个本地 Web 仪表盘展示当前集群状态。那么下面开始整个项目的讲解。一、项目整体架构项目整体分成三层1. 控制层使用 Kubernetes YAML 定义 Namespace、ResourceQuota、LimitRange、PriorityClass 和 GPU workload。2. 集群层Kubernetes 负责 admission 和 scheduling。这里能观察到两类关键现象FailedSchedulingPod 已创建但调度器找不到合适节点。FailedCreatePod 还没创建出来就被 ResourceQuota 拒绝。3. 可观测层用 PowerShell 脚本调用 kubectl 导出当前集群状态再由本地 Web Dashboard 读取 JSON/JS 文件展示。我画了个架构图如下图所示二、项目目录项目目录如下图所示其中k8s/ 放 Kubernetes 资源清单。scripts/ 放状态查看、导出和清理脚本。web/ 是本地仪表盘。docs/ 放报告、架构图和截图。三、Namespace 隔离设计项目里设计了三个 Namespace对应三类 GPU 使用场景示例 YAMLapiVersion: v1kind: Namespacemetadata:name: ai-traininglabels:gpu.platform/team: training这样做的好处是后续所有资源管理都可以按 Namespace 维度展开包括 GPU 配额、Pod 数量、CPU、内存等。四、ResourceQuota限制每个团队能用多少 GPUGPU 资源比较贵所以不能让某一个团队无限制申请。所以我给三个 Namespace 分别设置了 GPU 配额以 ai-inference 为例apiVersion: v1kind: ResourceQuotametadata:name: inference-gpu-quotanamespace: ai-inferencespec:hard:requests.cpu: 32requests.memory: 128Gilimits.cpu: 64limits.memory: 192Girequests.nvidia.com/gpu: 4limits.nvidia.com/gpu: 4pods: 40这里最关键的是requests.nvidia.com/gpu: 4它表示这个 Namespace最多只能申请 4 张 GPU。五、PriorityClass区分推理和训练优先级GPU 集群里不同任务的重要性是不一样的。在线推理一般比离线训练更敏感。如果资源紧张生产推理应该优先保证。所以项目里定义了三个 PriorityClass示例apiVersion: scheduling.k8s.io/v1kind: PriorityClassmetadata:name: gpu-critical-inferencevalue: 100000globalDefault: falsepreemptionPolicy: PreemptLowerPrioritydescription: Production GPU inference workloads that should win during contention.在 Pod 里使用时只需要指定priorityClassName: gpu-critical-inference六、Node Affinity训练和推理调度到不同节点池真实 GPU 集群里通常会有不同类型的 GPU 节点比如A100/H100 更适合训练低延迟节点更适合在线推理便宜卡可以跑实验或 batch 任务我在 kind 节点上用 label 模拟 GPU 节点池kubectl label node llm-platform-control-plane acceleratornvidia-a100 --overwritekubectl label node llm-platform-control-plane gpu.platform/poolinference --overwritekubectl label node llm-platform-control-plane topology.kubernetes.io/zonelocal-a --overwrite训练任务要求affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: gpu.platform/pooloperator: Invalues:- training推理任务要求affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: gpu.platform/pooloperator: Invalues:- inference这样就可以模拟训练池和推理池的隔离效果。七、本地 kind 没有 GPU为什么还能做实验这个项目是在本地 kind 集群上做的没有真实 GPU。开始我也考虑过这个问题没有 GPU是不是就没法做 GPU 调度实验实际做下来发现还是可以做出很多有价值的现象。原因是Kubernetes 调度链路里有多个阶段1. Namespace 是否存在2. ResourceQuota 是否允许3. Node Affinity 是否匹配4. 节点是否有足够 CPU/内存/GPU5. Pod 是否能最终调度即使没有真实 GPU也可以观察到0/1 nodes are available: 1 Insufficient nvidia.com/gpu.这说明 Pod 已经通过了前面的约束最后卡在节点没有 GPU 可分配资源。八、实验一Node Affinity 不匹配当训练 Pod 要求 gpu.platform/pooltraining但节点是 poolinference 时会看到0/1 nodes are available: 1 node(s) didnt match Pods node affinity/selector.这说明调度器首先会检查节点标签是否匹配。如果标签不匹配Pod 根本不会进入后续 GPU 资源检查。九、实验二节点标签匹配但没有 GPU当我把节点标签改成符合推理任务要求后kubectl label node llm-platform-control-plane gpu.platform/poolinference --overwrite推理 Pod 的失败原因变成0/1 nodes are available: 1 Insufficient nvidia.com/gpu.这就说明 Node Affinity 已经通过了剩下的问题是 kind 节点没有可分配的 GPU。这是一个很清晰的调度链路Node Affinity 不匹配↓修改节点标签↓Node Affinity 通过↓Insufficient nvidia.com/gpu十、实验三ResourceQuota 超限这个实验比较关键。当前 ai-inference 已经申请了 3 张 GPU配额是 4 张requests.nvidia.com/gpu: 3/4然后我再提交一个请求 2 张 GPU 的 Deploymentresources:requests:nvidia.com/gpu: 2limits:nvidia.com/gpu: 2结果不是 Pending而是直接被拒绝exceeded quota: inference-gpu-quota,requested: requests.nvidia.com/gpu2,used: requests.nvidia.com/gpu3,limited: requests.nvidia.com/gpu4这里可以看到 ResourceQuota 和调度器的区别这也是我觉得这个项目最有价值的地方之一它能把 Kubernetes 不同控制点的行为区分清楚。十一、导出集群状态为了让项目不只是命令行输出我写了一个导出脚本.\scripts\export-cluster-status.ps1它会调用 kubectl 收集NamespaceResourceQuotaPodNodeEvent然后生成web/data/cluster-status.jsonweb/data/cluster-status.js仪表盘直接读取 cluster-status.js这样不需要后端服务也不用部署数据库。导出的数据大概长这样{context: kind-llm-platform,summary: {gpuRequested: 5,gpuQuota: 12,pendingPods: 5,estimatedIdleCostPerHour: 12}}十二、本地 Dashboard项目里有一个简单的 Web Dashboardweb/index.html打开后可以看到GPU Requested / QuotaPending PodsEstimated Idle CostNamespace GPU 使用情况调度失败事件Quota 拒绝事件截图如下还有一张专门整理实验结果的证据页十三、GitHub Actions 校验项目上传 GitHub 后我又加了一个简单的 CIname: CIon:push:branches: [main]pull_request:branches: [main]它主要用来检查1.JavaScript 语法2.JSON 格式3.Kubernetes YAML 语法4.PowerShell 脚本语法我其实踩了一个小坑Windows PowerShell 默认写 UTF-8 时可能带 BOM所以GitHub Actions 里的 Python 解析 JSON 会报Unexpected UTF-8 BOM最后我改成无 BOM 写入$utf8NoBom New-Object System.Text.UTF8Encoding($false)[System.IO.File]::WriteAllText($jsonPath, $json, $utf8NoBom)这个问题不大但挺真实。很多跨平台项目都会遇到类似编码问题。十四、当前项目的进度目前这个项目算是一个比较完整的 MVP。目前大概做了Namespace 隔离ResourceQuota / LimitRangePriorityClassNode Affinity 训练池实验Node Affinity 推理池实验Quota 超限实验kubectl 状态导出本地 Dashboard项目报告和截图GitHub Actions CI我之后打算补充的接入真实 GPU 节点安装 NVIDIA device plugin接入 DCGM Exporter接入 Prometheus展示真实 GPU 利用率更精确的 GPU 成本计算模型十五、总结这个项目最大的收获是GPU 调度不一定一开始就要写复杂调度器Kubernetes 原生能力已经能做很多事情。通过 Namespace、ResourceQuota、PriorityClass 和 Node Affinity可以先搭出一套可解释、可复现的 GPU 资源管理机制。即使本地没有真实 GPU也可以用 kind 做出很多关键实验比如说1.节点亲和性不匹配2.GPU 资源不足3.Quota 超限拒绝4.Pending 和 FailedCreate 的区别总而言之对学习 Kubernetes 调度、平台工程和 AI Infra 来说这个项目比较适合作为入门级但有完整链路的实践项目。项目地址https://github.com/ycx666994/gpu-k8s-scheduler后面我会继续尝试把它接到 Prometheus 和 DCGM Exporter 上做成一个更接近真实生产环境的 GPU 资源监控和成本优化系统。欢迎大家评论留言以及欢迎大家来我仓库做开源贡献。谢谢!