最近做了一个 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。这里能观察到两类关键现象:
     FailedScheduling:Pod 已创建,但调度器找不到合适节点。
     FailedCreate:Pod 还没创建出来,就被 ResourceQuota 拒绝。

  3. 可观测层
     用 PowerShell 脚本调用 kubectl 导出当前集群状态,再由本地 Web Dashboard 读取 JSON/JS 文件展示。

我画了个架构图,如下图所示:

   

 二、项目目录

  项目目录如下图所示:
  

  其中:

  k8s/ 放 Kubernetes 资源清单。
  scripts/ 放状态查看、导出和清理脚本。
  web/ 是本地仪表盘。
  docs/ 放报告、架构图和截图。

三、Namespace 隔离设计

项目里设计了三个 Namespace,对应三类 GPU 使用场景:

示例 YAML:

  apiVersion: v1
  kind: Namespace
  metadata:
    name: ai-training
    labels:
      gpu.platform/team: training

这样做的好处是后续所有资源管理都可以按 Namespace 维度展开,包括 GPU 配额、Pod 数量、CPU、内存等。

四、ResourceQuota:限制每个团队能用多少 GPU

GPU 资源比较贵,所以不能让某一个团队无限制申请。

所以我给三个 Namespace 分别设置了 GPU 配额:

 以 ai-inference 为例:

  apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: inference-gpu-quota
    namespace: ai-inference
  spec:
    hard:
      requests.cpu: "32"
      requests.memory: 128Gi
      limits.cpu: "64"
      limits.memory: 192Gi
      requests.nvidia.com/gpu: "4"
      limits.nvidia.com/gpu: "4"
      pods: "40"

 这里最关键的是:requests.nvidia.com/gpu: "4"

 它表示这个 Namespace 最多只能申请 4 张 GPU

五、PriorityClass:区分推理和训练优先级

 GPU 集群里,不同任务的重要性是不一样的。
 在线推理一般比离线训练更敏感。如果资源紧张,生产推理应该优先保证。

 所以项目里定义了三个 PriorityClass:

 示例:

  apiVersion: scheduling.k8s.io/v1
  kind: PriorityClass
  metadata:
    name: gpu-critical-inference
  value: 100000
  globalDefault: false
  preemptionPolicy: PreemptLowerPriority
  description: "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 accelerator=nvidia-a100 --overwrite
 kubectl label node llm-platform-control-plane gpu.platform/pool=inference --overwrite
 kubectl label node llm-platform-control-plane topology.kubernetes.io/zone=local-a --overwrite

训练任务要求:

  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: gpu.platform/pool
                operator: In
                values:
                  - training

推理任务要求:

  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: gpu.platform/pool
                operator: In
                values:
                  - inference

这样就可以模拟训练池和推理池的隔离效果

七、本地 kind 没有 GPU,为什么还能做实验?

这个项目是在本地 kind 集群上做的,没有真实 GPU。开始我也考虑过这个问题:没有 GPU,是不是就没法做 GPU 调度实验?实际做下来发现,还是可以做出很多有价值的现象。

原因是 Kubernetes 调度链路里有多个阶段

  1. Namespace 是否存在
  2. ResourceQuota 是否允许
  3. Node Affinity 是否匹配
  4. 节点是否有足够 CPU/内存/GPU
  5. Pod 是否能最终调度

  即使没有真实 GPU,也可以观察到:

  0/1 nodes are available: 1 Insufficient nvidia.com/gpu.

  这说明 Pod 已经通过了前面的约束,最后卡在节点没有 GPU 可分配资源。

八、实验一:Node Affinity 不匹配

  当训练 Pod 要求 gpu.platform/pool=training,但节点是 pool=inference 时,会看到:

  0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector.

  这说明调度器首先会检查节点标签是否匹配。

  如果标签不匹配,Pod 根本不会进入后续 GPU 资源检查。

九、实验二:节点标签匹配,但没有 GPU

  当我把节点标签改成符合推理任务要求后:

  kubectl label node llm-platform-control-plane gpu.platform/pool=inference --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 的 Deployment:

  resources:
    requests:
      nvidia.com/gpu: "2"
    limits:
      nvidia.com/gpu: "2"

 结果不是 Pending,而是直接被拒绝:

  exceeded quota: inference-gpu-quota,
  requested: requests.nvidia.com/gpu=2,
  used: requests.nvidia.com/gpu=3,
  limited: requests.nvidia.com/gpu=4

 这里可以看到 ResourceQuota 和调度器的区别:

 

 这也是我觉得这个项目最有价值的地方之一:它能把 Kubernetes 不同控制点的行为区分清楚。

十一、导出集群状态

 为了让项目不只是命令行输出,我写了一个导出脚本:

  .\scripts\export-cluster-status.ps1

 它会调用 kubectl 收集:

  Namespace
  ResourceQuota
  Pod
  Node
  Event

 然后生成:

  web/data/cluster-status.json
  web/data/cluster-status.js

 仪表盘直接读取 cluster-status.js,这样不需要后端服务,也不用部署数据库。

 导出的数据大概长这样:

  {
    "context": "kind-llm-platform",
    "summary": {
      "gpuRequested": 5,
      "gpuQuota": 12,
      "pendingPods": 5,
      "estimatedIdleCostPerHour": 12
    }
  }

十二、本地 Dashboard

项目里有一个简单的 Web Dashboard:

 web/index.html

打开后可以看到:

  GPU Requested / Quota
  Pending Pods
  Estimated Idle Cost
  Namespace GPU 使用情况
  调度失败事件
  Quota 拒绝事件

  截图如下:
  

  还有一张专门整理实验结果的证据页:
  

 十三、GitHub Actions 校验

 项目上传 GitHub 后,我又加了一个简单的 CI:

  name: CI

  on:
    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 / LimitRange
  PriorityClass
  Node 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 资源监控和成本优化系统。

欢迎大家评论留言以及欢迎大家来我仓库做开源贡献。谢谢!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐