文章总结: 本文详细解析Kubernetes中GPU调度、隔离与资源分配的完整链路,从设备发现到容器注入,提供排查步骤、Pod编写示例、节点标签与污点管理策略,以及整卡独占、时间共享等隔离方案,强调逐层验证和版本兼容性,为运维人员提供可操作指南。 综合评分: 84 文章分类: 解决方案
Kubernetes 集群中的 GPU 调度、隔离与资源分配
点击关注👉 点击关注👉
马哥Linux运维
2026年7月15日 20:14 广东
在小说阅读器读本章
去阅读
Kubernetes 集群中的 GPU 调度、隔离与资源分配
GPU 节点已经加入 Kubernetes,驱动也能执行 nvidia-smi,业务 Pod 却一直 Pending;给容器申请了 1 张 GPU,程序仍然看到所有设备;多个推理实例共享一张卡后吞吐看似提高,某个实例显存暴涨却把其他实例一起拖垮;集群还有空闲 GPU,但一个需要 4 卡的训练任务就是调度不上去。这些现象分别涉及设备发现、调度、容器注入、硬件隔离和资源碎片,不能用“GPU 有空闲”一句话解释。
Kubernetes 原生调度器并不直接理解 CUDA、显存、NVLink 或 GPU 利用率。常见方案由 NVIDIA 驱动、容器运行时、Device Plugin、节点标签组件和监控组件共同完成:Device Plugin 把设备注册为 nvidia.com/gpu 等扩展资源,调度器根据 Pod 的资源请求选择节点,kubelet 再把分配到的设备注入容器。要实现时间共享、MIG 切分、队列、公平调度或拓扑感知,还需要额外配置或组件。
本文以 Linux 节点、Kubernetes 1.26 及以上、NVIDIA GPU 和 NVIDIA Device Plugin/GPU Operator 常见部署为基础。不同 GPU 型号、驱动、Device Plugin 和 GPU Operator 版本支持能力不同,实施前必须核对实际版本文档与兼容矩阵。文中的 <命名空间>、<GPU节点名>、<镜像地址> 等均为占位符,执行前需替换。按要求,文中所有 kubectl 命令都显式使用 -n <命名空间>;对于 Node 等集群级资源,namespace 参数不会改变资源作用域。
一、先理解 GPU 资源从节点到容器的完整链路
一个 GPU Pod 能正常运行,至少要经过以下环节:
- 宿主机 PCIe 层能识别 GPU;
- NVIDIA 内核驱动加载正常,宿主机可查询设备;
- 容器运行时已配置 NVIDIA Container Toolkit 或等效 CDI 支持;
- Device Plugin DaemonSet 在 GPU 节点运行,并向 kubelet 注册资源;
- Node 的
capacity和allocatable出现nvidia.com/gpu或 MIG 资源; - Pod 在资源
limits中声明需要的扩展资源; - 调度器找到标签、污点、亲和性和剩余资源都满足的节点;
- kubelet 与运行时把分配设备暴露给容器;
- 容器内 CUDA 用户态与宿主机驱动兼容,应用正确选择设备。
排查时应按链路逐层验证。直接进入 Pod 执行 nvidia-smi 只覆盖最后几层,无法解释 Pod Pending 或节点未注册资源。
二、环境盘点:先建立版本和硬件基线
1. Kubernetes 控制面视角
bash
kubectl -n <命名空间> version
kubectl -n <命名空间> get nodes -o wide
kubectl -n <命名空间> get nodes -L nvidia.com/gpu.present,nvidia.com/gpu.product,nvidia.com/mig.capable
节点标签通常由 Node Feature Discovery(NFD)和 GPU Feature Discovery(GFD)生成,具体键随组件版本和配置变化。没有这些标签不一定代表 GPU 不可用,应继续检查 status.capacity。
查看单节点资源:
bash
kubectl -n <命名空间> get node <GPU节点名> \
-o jsonpath='{.status.capacity}{"\n"}{.status.allocatable}{"\n"}'
kubectl -n <命名空间> describe node <GPU节点名>
示例输出:
text
Capacity:
nvidia.com/gpu: 8
Allocatable:
nvidia.com/gpu: 8
这是示例输出,表示 Device Plugin 向 kubelet 注册了 8 个可分配 GPU 资源。它不表示 8 张卡当前利用率为 0,也不代表每张卡显存完全空闲。
2. 宿主机视角
在 GPU 节点上执行:
bash
lspci -nn | grep -i nvidia
nvidia-smi
nvidia-smi -L
lsmod | grep '^nvidia'
示例输出:
text
GPU 0: <GPU型号> (UUID: GPU-<示例UUID>)
这是示例输出。nvidia-smi -L 能列出 GPU 或 MIG 实例,说明驱动基本可用;CUDA 应用是否兼容还取决于镜像中的用户态库、宿主机驱动版本和应用构建目标。
检查 kubelet 与容器运行时日志:
bash
sudo journalctl -u kubelet --since '<开始时间>' --no-pager \
| grep -Ei 'device.?plugin|nvidia|cdi|allocate|registration'
sudo crictl info
sudo crictl ps -a | grep -Ei 'nvidia|device-plugin'
如果集群使用 containerd,还应检查其配置和状态,但不要在未备份、未验证冗余的情况下直接重启运行时。重启 containerd 或 kubelet 可能影响本节点上的容器管理,执行前应先 cordon、评估工作负载、保存配置,并安排灰度节点。
三、Device Plugin:Kubernetes 为什么能看到 GPU
Kubernetes 通过 Device Plugin 框架管理 GPU 等专用设备。插件在每个合适节点运行,发现健康设备并向 kubelet 注册扩展资源。调度器只处理资源数量,实际设备 ID 的选择和容器注入发生在 kubelet 分配阶段。
查找 NVIDIA 相关 DaemonSet 和 Pod:
bash
kubectl -n <命名空间> get daemonsets
kubectl -n <命名空间> get pods -o wide \
-l app.kubernetes.io/name=nvidia-device-plugin
实际 Helm chart 或 Operator 使用的标签可能不同。若标签查询为空,先列出命名空间内 DaemonSet,再根据真实名称查询:
bash
kubectl -n <命名空间> get daemonset <DevicePlugin名称> -o yaml
kubectl -n <命名空间> logs daemonset/<DevicePlugin名称> --all-containers --tail=200
判断依据:
- DaemonSet desired 数与符合选择条件的 GPU 节点数一致;
- Pod Ready,日志没有驱动库加载、NVML 初始化或注册失败;
- Node
allocatable出现预期资源名和数量; - 创建最小测试 Pod 后,容器只看到被分配的设备。
Device Plugin Pod Running 但 Node 没有资源时,重点检查插件日志、宿主机驱动、插件 socket 目录和 kubelet 日志,而不是反复重建业务 Pod。
四、最基本的 GPU Pod 写法
扩展资源通常在 limits 中声明。Kubernetes 不允许扩展资源 overcommit;对 GPU 来说,请求值必须是整数,不能写 0.5。如果只设置 limit,Kubernetes 会在准入时将 request 视为相同值;为了清晰审计,可以显式写相同的 requests 与 limits,但集群版本和准入策略应先在测试环境验证。
最小示例:
yaml
apiVersion: v1
kind:Pod
metadata:
name:gpu-check
namespace:<命名空间>
spec:
restartPolicy:Never
containers:
-name:cuda
image:<包含nvidia-smi的CUDA镜像地址>
command: ["bash", "-lc"]
args:
-|
set -euo pipefail
nvidia-smi -L
nvidia-smi
resources:
requests:
cpu:"1"
memory:2Gi
nvidia.com/gpu:"1"
limits:
cpu:"2"
memory:4Gi
nvidia.com/gpu:"1"
先做服务端 dry-run,再创建:
bash
kubectl -n <命名空间> apply --server-side --dry-run=server -f <清单文件>
kubectl -n <命名空间> apply -f <清单文件>
kubectl -n <命名空间> get pod gpu-check -o wide
kubectl -n <命名空间> describe pod gpu-check
kubectl -n <命名空间> logs gpu-check
删除测试 Pod 属于资源删除操作,影响范围仅应是该测试对象。先确认名称和 namespace:
bash
kubectl -n <命名空间> get pod gpu-check
kubectl -n <命名空间> delete pod gpu-check --dry-run=server
kubectl -n <命名空间> delete pod gpu-check
删除后确认对象消失且节点资源重新可调度。业务 Pod 不应使用这种方式强制释放,必须先确认上层控制器、数据保存和优雅终止策略。
五、调度约束:不要只靠 nvidia.com/gpu
同一集群可能混有不同 GPU 型号、显存、架构和互联方式。只请求 nvidia.com/gpu: 1,调度器可能选择任意剩余 GPU 节点。需要用节点标签表达硬件池,再由 Pod 使用 node affinity。
1. 给节点打受控标签
例如建立由平台团队维护的稳定标签:
bash
kubectl -n <命名空间> label node <GPU节点名> \
accelerator.example.com/pool=<资源池名称> --overwrite --dry-run=server -o yaml
kubectl -n <命名空间> label node <GPU节点名> \
accelerator.example.com/pool=<资源池名称> --overwrite
Node 是集群级资源,命令中的 namespace 不改变其作用域。修改标签会影响新 Pod 调度,甚至触发现有使用 requiredDuringSchedulingRequiredDuringExecution 类约束的驱逐行为(常见 node affinity 是 IgnoredDuringExecution,不会驱逐)。执行前应查询依赖该标签的工作负载,并在单节点灰度。
回滚:
bash
kubectl -n <命名空间> label node <GPU节点名> \
accelerator.example.com/pool-
2. Pod 使用硬约束和软偏好
yaml
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
-matchExpressions:
-key:accelerator.example.com/pool
operator:In
values:
-<资源池名称>
preferredDuringSchedulingIgnoredDuringExecution:
-weight:50
preference:
matchExpressions:
-key:accelerator.example.com/generation
operator:In
values:
-<优先GPU代际>
required 不满足时 Pod 会 Pending;preferred 只影响打分,不保证一定命中。硬约束越多,资源碎片越严重。应把真正的兼容条件做成 required,把性能偏好做成 preferred。
3. GPU 专用节点使用污点和容忍
给节点加 NoSchedule 污点可阻止不相关 Pod 占用昂贵节点:
bash
kubectl -n <命名空间> taint node <GPU节点名> \
nvidia.com/gpu=present:NoSchedule --dry-run=server -o yaml
kubectl -n <命名空间> taint node <GPU节点名> \
nvidia.com/gpu=present:NoSchedule
Pod 增加容忍:
yaml
spec:
tolerations:
- key: nvidia.com/gpu
operator: Equal
value: present
effect: NoSchedule
容忍只表示“允许进入”,不会保证 Pod 去 GPU 节点。仍需声明 GPU 资源或 node affinity。加污点前要列出节点上现有 Pod,检查系统 DaemonSet 是否已有容忍,否则日志、网络、存储或监控组件的新实例可能无法调度。
回滚:
bash
kubectl -n <命名空间> taint node <GPU节点名> nvidia.com/gpu-
六、GPU 隔离有三个层次,不能混为一谈
1. 整卡独占:最清晰的默认方案
标准 nvidia.com/gpu: 1 通常代表分配一张完整 GPU。容器运行时只注入被分配设备,其他普通 Pod 不应看到它。优点是资源边界清楚、性能可预测、故障影响较小;缺点是小模型或低负载推理可能利用率不高。
整卡分配不等于绝对安全隔离。节点上的特权容器、挂载宿主机 /dev 的容器或权限过大的运维组件仍可能绕过设备边界。因此业务 Pod 应避免:
yaml
securityContext:
privileged: true
也不要手工挂载 /dev/nvidia*。GPU 注入应交给 Device Plugin 和容器运行时。配合 Pod Security、准入策略和最小 RBAC,限制创建特权 Pod 的主体。
2. Time-slicing:提高并发,不提供显存硬隔离
NVIDIA Device Plugin 的 time-slicing 可以把一张物理 GPU 宣告为多个可调度副本,让多个 Pod 轮流使用设备。它适合可容忍性能抖动、显存总量受控的小型推理或开发任务。
关键限制:
- 副本不是物理 GPU,也不是固定算力份额;
- 多个 Pod 通常共享同一 GPU 显存和故障域;
- 一个进程显存耗尽或触发设备错误,可能影响同卡其他工作负载;
- 调度器看到的是副本数量,不知道实时显存和利用率;
- 请求多个副本不一定获得更多独立硬件,因此可用
failRequestsGreaterThanOne阻止误用。
Device Plugin 支持的常见配置结构如下,必须以所部署版本的配置 API 为准:
yaml
version: v1
sharing:
timeSlicing:
renameByDefault: false
failRequestsGreaterThanOne: true
resources:
- name: nvidia.com/gpu
replicas: 4
replicas: 4 表示每个受配置影响的物理 GPU 对外提供 4 个可请求副本,不代表性能或显存变成四倍。若 renameByDefault: true,资源名会按插件规则改名,业务清单必须同步调整。
实施属于集群资源模型变更,影响范围是对应节点上的新调度和资源计数。执行前应:
- 导出当前 Device Plugin 配置和 DaemonSet;
- 盘点所有申请
nvidia.com/gpu的工作负载; - 选择一台无关键业务的节点 cordon、排空可迁移工作负载后灰度;
- 用测试 Pod 验证副本数、可见设备、并发和 OOM 行为;
- 建立单 Pod 显存上限的应用侧策略与监控;
- 保留整卡资源池,避免训练任务被共享策略影响。
Device Plugin 的 ConfigMap 名称、键、节点选择方式随安装方法而异,不应猜测后直接覆盖。先查询 Helm values、Operator CR 和当前 Pod 参数。回滚时恢复原配置,滚动更新插件,并确认 Node allocatable 回到原数量。已有 Pod 的设备分配不应仅靠修改资源计数来重置,必要时按业务计划重建。
3. MIG:硬件级分区,边界更明确
支持 Multi-Instance GPU(MIG)的型号可把一张 GPU 切成多个硬件实例,每个实例拥有相对独立的计算、显存和故障隔离边界。Device Plugin 可把 MIG profile 暴露为类似 nvidia.com/mig-<配置> 的扩展资源,实际资源名取决于 GPU 型号、MIG 几何和插件策略,应从 Node capacity 读取,不能照抄示例。
查看集群实际资源名:
bash
kubectl -n <命名空间> get node <GPU节点名> -o json \
| jq '.status.capacity | with_entries(select(.key | startswith("nvidia.com/")))'
Pod 请求实际存在的 MIG 资源:
yaml
resources:
requests:
nvidia.com/mig-<实际配置名>: "1"
limits:
nvidia.com/mig-<实际配置名>: "1"
MIG 适合需要显存和故障边界、但单任务不需整卡的生产推理。它仍有约束:profile 组合受硬件支持限制,重配置会影响整张物理 GPU 上的实例和工作负载,某些监控、P2P、性能计数或应用行为与整卡不同。
启用或重配 MIG 属于高风险操作。影响范围包括目标 GPU 上全部 Pod、设备资源名、调度容量和监控维度。执行前必须:
- 确认 GPU 型号、驱动、固件、插件与 Operator 的 MIG 支持;
- cordon 目标节点,并安全迁移或停止所有 GPU 工作负载;
- 记录
nvidia-smi -L、当前 MIG 布局和 Node capacity; - 使用 GPU Operator MIG Manager 或经厂商验证的节点流程,不在忙碌 GPU 上直接改配置;
- 先在单节点灰度,验证每种 profile 的容器可见性和业务性能;
- 准备恢复原 MIG profile 或关闭 MIG 后重新注册资源的回滚步骤。
在 Operator 管理环境中,直接手工运行 nvidia-smi mig 可能与控制器产生漂移。配置源应保持唯一,使用声明式配置并观察控制器状态。
七、MPS 与 time-slicing 的区别
CUDA Multi-Process Service(MPS)让多个 CUDA 进程更高效地共享 GPU 执行资源,在某些支持的场景下可改善并发和上下文切换开销。它不是通用的强隔离机制,适用 GPU 架构、资源控制和故障传播特性受 NVIDIA 实现与插件版本限制。
工程选择可以按目标判断:
- 需要最稳定、最少互扰:整卡独占;
- 需要硬件显存分区且 GPU 支持:MIG;
- 小任务并发、允许抖动:time-slicing;
- 明确验证过的 CUDA 多进程并发优化:MPS。
不要同时开启多种共享模式后再猜测最终行为。应建立独立节点池,每种池只使用一种资源模型,并通过标签、污点和准入策略约束工作负载。
八、命名空间配额:控制团队能申请多少 GPU
ResourceQuota 可以限制一个 namespace 内扩展资源请求总量。示例限制最多请求 8 个 nvidia.com/gpu:
yaml
apiVersion: v1
kind:ResourceQuota
metadata:
name:gpu-quota
namespace:<命名空间>
spec:
hard:
requests.nvidia.com/gpu:"8"
先 dry-run,再应用:
bash
kubectl -n <命名空间> apply --server-side --dry-run=server -f <配额清单文件>
kubectl -n <命名空间> apply -f <配额清单文件>
kubectl -n <命名空间> describe resourcequota gpu-quota
如果集群使用 MIG 或 time-slicing 改名资源,需要为实际扩展资源分别配置 quota。配额控制“声明的请求数量”,不代表 GPU 小时、公平份额或显存用量。长期公平调度还需要队列控制器或批处理调度系统。
修改配额会导致新建或扩容被拒绝,但不会自动驱逐已有 Pod。执行前列出当前 used/hard、控制器副本数和定时任务需求,先提高或灰度调整。回滚可恢复原 YAML:
bash
kubectl -n <命名空间> diff -f <原配额清单文件>
kubectl -n <命名空间> apply -f <原配额清单文件>
删除 ResourceQuota 会取消该 namespace 的对应上限,属于权限边界变更。删除前做 dry-run 并确认有替代策略:
bash
kubectl -n <命名空间> delete resourcequota gpu-quota --dry-run=server
九、多卡训练:数量满足不等于拓扑满足
Pod 请求 4 张 GPU 时,标准调度器要求同一节点有至少 4 个可分配资源。它不会自动把一个普通 Pod 的 4 张卡拆到两台节点上。跨节点分布式训练需要多个 Pod、训练控制器或作业框架,并配置 rendezvous、网络和存储。
单节点 4 卡请求:
yaml
resources:
requests:
nvidia.com/gpu: "4"
limits:
nvidia.com/gpu: "4"
即使节点显示总共 8 张卡,若已有多个单卡任务分散占用,剩余可用卡数不足 4,任务仍会 Pending。GPU 不能像 CPU 那样由调度器超卖或跨节点拼接。
标准 nvidia.com/gpu 资源也不表达卡间 NVLink/NVSwitch 拓扑。对通信敏感的训练任务,应按硬件拓扑建立同质节点池,结合节点标签选择整机配置,并在任务内用 NCCL 测试验证。应用看到的逻辑设备编号可能与宿主机编号不同,应使用容器内 CUDA_VISIBLE_DEVICES 和设备 UUID 判断,不要硬编码宿主机 GPU 0。
跨节点训练还要验证:
- 每个 worker 的 GPU 数和镜像一致;
- Pod 间 DNS、端口和 NetworkPolicy 放通;
- RDMA/InfiniBand 设备插件与驱动正确;
- NCCL 选择的接口、拓扑和共享内存符合预期;
- 作业失败重试不会产生重复 checkpoint 写入;
- 节点故障时控制器能以预期方式重建整个 worker 组。
不要用临时 hostNetwork: true 或特权容器绕过网络问题后直接上生产,这会扩大网络和主机访问面。
十、CPU、内存和 NUMA 也会限制 GPU 性能
只申请 GPU、不申请足够 CPU 和内存,数据加载、预处理和通信可能成为瓶颈。反过来,给出过大的 CPU/内存 request 会让调度更困难。应从应用 profiling 建立基线,而不是按固定“每卡多少 CPU”套模板。
对延迟敏感、NUMA 敏感的工作负载,可考虑 kubelet 的 CPU Manager static 策略和 Topology Manager single-numa-node 等策略,让独占 CPU、内存和设备尽量对齐同一 NUMA 节点。它们是节点级配置,修改可能需要重启 kubelet,并影响该节点新 Pod 准入。
实施前:
- 读取当前 kubelet 配置和 systemd 启动参数;
- 检查节点 CPU 拓扑、GPU PCIe 位置和 NUMA 归属;
- 选择空闲节点 cordon,并排空可迁移工作负载;
- 备份 kubelet 配置,先在一台节点验证;
- 创建 Guaranteed QoS 测试 Pod,检查 CPU 绑定和设备拓扑;
- 准备恢复原策略并重启 kubelet 的回滚窗口。
不要在整组生产 GPU 节点同时修改 kubelet 策略。节点级重启与排空应遵守 PodDisruptionBudget、训练 checkpoint 和本地存储约束。
十一、调度 Pending 的证据化排查
1. 先看 Pod 事件
bash
kubectl -n <命名空间> get pod <Pod名称> -o wide
kubectl -n <命名空间> describe pod <Pod名称>
kubectl -n <命名空间> get events --sort-by='.lastTimestamp' | tail -n 50
常见示例输出:
text
0/6 nodes are available: 2 Insufficient nvidia.com/gpu, 2 node(s) had untolerated taint, 2 node(s) didn't match Pod's node affinity/selector.
这是示例输出。它给出了三类独立证据:资源不足、污点不容忍、节点选择不匹配。应逐项修复,不要只因为第一项出现就扩容。
2. 核对 Pod 实际请求
bash
kubectl -n <命名空间> get pod <Pod名称> \
-o jsonpath='{range .spec.containers[*]}{.name}{"\t"}{.resources}{"\n"}{end}'
一个 Pod 的资源需求是各常规容器请求之和,并受 init container 资源计算规则影响。sidecar 或注入组件也可能改变 CPU、内存约束。
3. 核对节点剩余资源和已分配情况
bash
kubectl -n <命名空间> describe node <GPU节点名>
kubectl -n <命名空间> get pods --all-namespaces \
--field-selector spec.nodeName=<GPU节点名> -o wide
第二条命令即使带 -n,--all-namespaces 仍会跨 namespace 查询,使用前确保操作者有相应只读权限。describe node 的 Allocated resources 是调度请求汇总,不是实时 GPU 利用率。
4. 检查资源名拼写
标准整卡常见为 nvidia.com/gpu。MIG、共享重命名或其他厂商设备使用不同资源名。资源名必须与 Node capacity 完全一致:
bash
kubectl -n <命名空间> get node <GPU节点名> -o json \
| jq '.status.allocatable | with_entries(select(.key | contains("gpu") or contains("mig")))'
5. 检查 scheduler 和准入策略
Pod 可能被 ResourceQuota、LimitRange、ValidatingAdmissionPolicy、Webhook 或自定义调度器拒绝/修改。查看对象事件、审计日志和实际保存的 Pod YAML:
bash
kubectl -n <命名空间> get pod <Pod名称> -o yaml
kubectl -n <命名空间> get resourcequota
kubectl -n <命名空间> get limitrange
不要只检查提交到 Git 的 YAML;准入后保存到 API Server 的对象才是调度器实际看到的配置。
十二、Pod 已运行但看不到 GPU
先确认 Pod 是否真的申请了设备:
bash
kubectl -n <命名空间> get pod <Pod名称> \
-o jsonpath='{.spec.containers[*].resources.limits}{"\n"}'
进入目标容器:
bash
kubectl -n <命名空间> exec -it <Pod名称> -c <容器名称> -- nvidia-smi -L
kubectl -n <命名空间> exec <Pod名称> -c <容器名称> -- sh -c 'printf "%s\n" "${CUDA_VISIBLE_DEVICES:-<未设置>}"'
CUDA_VISIBLE_DEVICES 是否存在、内容格式如何,取决于插件的设备注入策略,可能使用环境变量、volume mounts 或 CDI。不能仅凭该变量为空就认定分配失败。还应检查容器设备、插件日志和运行时配置。
常见根因:
- Pod 没有声明 GPU limit;
- 使用了错误的 RuntimeClass 或容器运行时未配置 NVIDIA 支持;
- Device Plugin 在资源注册后异常,kubelet 分配失败;
- 镜像没有
nvidia-smi,但应用库仍可能可用; - 镜像 CUDA 用户态与宿主机驱动不兼容;
- 应用自行覆盖
CUDA_VISIBLE_DEVICES; - 共享或 MIG 配置变更后,旧 Pod 和新资源模型不一致。
查看事件和日志:
bash
kubectl -n <命名空间> describe pod <Pod名称>
kubectl -n <命名空间> logs daemonset/<DevicePlugin名称> --all-containers --tail=300
若需要重启 Device Plugin DaemonSet,应先确认其控制器名称、当前配置和影响节点。滚动重启会影响设备插件注册,通常不应直接终止已分配设备,但异常情况下可能影响后续分配。先单节点验证并保留原 DaemonSet YAML。
十三、监控:同时看调度资源和设备运行指标
Kubernetes allocatable、Pod requests/limits 与 GPU 实时利用率是两套不同数据:
- API Server 资源数据回答“调度器分配了多少”;
- DCGM Exporter 等设备监控回答“GPU 实际使用了多少”;
- 应用指标回答“请求吞吐、延迟和错误是否正常”。
NVIDIA DCGM Exporter 常见指标包括:
-
DCGM_FI_DEV_GPU_UTIL:GPU 利用率;
-
DCGM_FI_DEV_MEM_COPY_UTIL:显存带宽相关利用率;
-
DCGM_FI_DEV_FB_USED、
DCGM_FI_DEV_FB_FREE:显存使用与空闲; -
DCGM_FI_DEV_GPU_TEMP:GPU 温度;
-
DCGM_FI_DEV_POWER_USAGE:功耗;
-
DCGM_FI_DEV_XID_ERRORS:Xid 错误信息。
指标名称、label 和单位以实际 exporter 暴露的指标为准。先查询 /metrics 或 Prometheus 元数据,不要照抄告警表达式后直接上线。
以实际指标存在为前提,PromQL 示例:
promql
avg_over_time(DCGM_FI_DEV_GPU_UTIL[15m]) < 10
它可作为“持续低利用率”的候选条件,但单独使用会把等待数据、交互式开发和定时任务误报为空闲。
promql
max_over_time(DCGM_FI_DEV_FB_USED[10m])
可观察窗口内显存使用峰值,具体单位需以 exporter HELP/TYPE 信息为准。
promql
DCGM_FI_DEV_XID_ERRORS > 0
部分 DCGM Exporter 版本用该指标表示最近一次 Xid 错误码,而不是可用 increase() 计算的 counter。应先检查 HELP/TYPE、确认清零行为,再结合宿主机日志和时间窗口告警,不能把所有非零值机械解释为当前仍在发生故障。
容量看板应至少关联 namespace、workload、Pod、node、GPU UUID/MIG UUID 和资源池。单纯按节点显示平均 GPU 利用率,会掩盖同一节点上一张卡满载、另一张卡空闲的情况。
十四、资源碎片与公平调度
标准调度器按当前可用资源即时放置 Pod,不会为了未来大任务主动整理 GPU。大量单卡长任务可能散布在多个 8 卡节点上,导致没有节点能容纳 8 卡训练,即使集群总空闲数足够。
工程上可采用:
- 将单卡推理与多卡训练分到不同节点池;
- 用污点、标签和 node affinity 保护整机节点;
- 为大作业建立队列和准入控制,资源就绪后再整体启动;
- 使用支持 gang scheduling 的批处理调度系统,避免部分 worker 占着资源等待其余 worker;
- 通过优先级和抢占保障关键作业,但严格控制低优先级任务的中断成本;
- 训练任务定期写 checkpoint,使被抢占后可恢复。
抢占不是“无成本整理”。删除或驱逐 GPU Pod 可能丢失数小时训练进度、破坏未刷新的 checkpoint 或中断在线服务。实施前必须确认 PodDisruptionBudget、终止宽限期、checkpoint 一致性、队列重试语义和业务容量。
扩缩容也属于高风险操作。新增 GPU 节点需要验证驱动、固件、网络、存储、时间同步、Device Plugin 和监控;缩容前要 cordon、检查本地盘和不可迁移任务、等待 checkpoint,再 drain。回滚方案应保留节点实例和原节点池配置,直到工作负载在新布局稳定。
十五、升级 Device Plugin、驱动或共享策略的标准步骤
GPU 软件栈版本耦合明显,一次升级可能改变资源名、设备发现、CDI 行为或监控标签。建议按以下顺序:
1. 影响范围
列出目标节点池、GPU 型号、当前 Pod、MIG/time-slicing 配置、运行时版本、驱动版本和依赖的 RuntimeClass。确认升级是驱动、Toolkit、Device Plugin、Operator 还是多个组件联动。
2. 执行前检查与备份
bash
kubectl -n <命名空间> get daemonset <DevicePlugin名称> -o yaml > <本地备份文件>
kubectl -n <命名空间> get node <GPU节点名> -o yaml > <节点状态备份文件>
kubectl -n <命名空间> get pods --all-namespaces \
--field-selector spec.nodeName=<GPU节点名> -o wide
保存 Helm values、Operator 自定义资源、ConfigMap 和镜像 digest。YAML 状态备份用于比较,不建议把完整 Node YAML直接 apply 回去。
3. dry-run 与灰度
对清单执行服务端 dry-run 和 diff:
bash
kubectl -n <命名空间> diff -f <新清单文件>
kubectl -n <命名空间> apply --server-side --dry-run=server -f <新清单文件>
选择一台节点 cordon:
bash
kubectl -n <命名空间> cordon <GPU节点名>
cordon 只阻止新 Pod 调度,不会迁移已有 Pod。若需要 drain,先检查 DaemonSet、local storage、PDB 和训练 checkpoint。不要直接使用 --force、--delete-emptydir-data 跳过风险。
4. 验证
升级后依次验证:
bash
kubectl -n <命名空间> get node <GPU节点名> \
-o jsonpath='{.status.allocatable}{"\n"}'
kubectl -n <命名空间> get pods -o wide
kubectl -n <命名空间> logs daemonset/<DevicePlugin名称> --all-containers --tail=200
再运行单卡、多卡、共享或 MIG 的最小测试任务,验证设备数量、CUDA 应用、监控和作业结束后的资源释放。最后解除 cordon:
bash
kubectl -n <命名空间> uncordon <GPU节点名>
5. 回滚
回滚应恢复版本锁定的镜像 digest、原 Helm values 或 Operator CR,而不是只把 Pod 删除等待“自动恢复”。若驱动已升级,回滚还可能需要节点重启,必须使用同样的灰度和业务迁移流程。恢复后比较 Node capacity、插件日志和测试任务结果,再开放调度。
十六、日常巡检清单
平台团队可以定期检查:
bash
kubectl -n <命名空间> get nodes \
-o custom-columns='NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu'
kubectl -n <命名空间> get pods --field-selector=status.phase=Pending
kubectl -n <命名空间> get events --sort-by='.lastTimestamp' | tail -n 100
kubectl -n <命名空间> get resourcequota
结合宿主机或监控系统检查:
bash
nvidia-smi -L
nvidia-smi --query-gpu=uuid,name,temperature.gpu,power.draw,memory.used,memory.total,utilization.gpu \
--format=csv
巡检要关注变化而非单点数值:Device Plugin Ready 数下降、Node GPU capacity 突然改变、Xid 错误、显存长期接近满载、申请量高但利用率长期低、Pending 时间增长、同一资源池混入不同硬件等。
十七、常见误区
误区 1:节点 nvidia-smi 正常,Kubernetes 就一定能调度
驱动正常只证明宿主机层面可用。还要验证运行时、Device Plugin 注册和 Node allocatable。
误区 2:GPU 利用率低,就可以再调度一个 Pod
标准调度看资源请求,不看实时利用率。整卡已分配后即使利用率为 0,也不会再分配给另一个普通 Pod。要共享必须显式选择 time-slicing、MPS 或 MIG,并接受对应边界。
误区 3:time-slicing 的 4 个副本各有四分之一显存
副本是调度层抽象,不是显存硬分区。需要显存边界时优先评估 MIG 或整卡。
误区 4:容忍 GPU 污点就会获得 GPU
Toleration 只允许调度到节点,不分配设备。Pod 仍必须请求扩展资源。
误区 5:有 8 张空闲卡,8 卡任务就能运行
如果卡分散在多台节点,而任务是单 Pod 申请 8 卡,就无法调度。还要考虑亲和性、污点、CPU、内存和拓扑。
误区 6:删除 Pending Pod 可以修复资源问题
控制器会重新创建同样配置的 Pod,根因仍存在。应根据事件修复资源、约束、配额或插件问题。
结语
Kubernetes 的 GPU 管理可以分为三层:Device Plugin 负责发现和分配设备,调度器根据扩展资源与约束选择节点,GPU 硬件与运行时决定真正的隔离和执行行为。nvidia.com/gpu 只表达数量,不表达显存、实时利用率、互联拓扑或公平份额。
生产环境应先以整卡独占建立稳定基线,再按业务需要选择 MIG、time-slicing 或 MPS;用稳定节点标签、污点和 namespace 配额划分资源池;用事件、Node allocatable、插件日志和 DCGM 指标形成证据链。任何驱动、MIG、共享策略、kubelet 或节点池变更,都应经过备份、dry-run、单节点灰度、工作负载验证和明确回滚。这样才能在提高 GPU 利用率的同时,避免把调度效率建立在不可控的性能抖动和故障扩散之上。
文末阅读福利
仅目前来说,无论是运维人转型提升,还是零基础想转行IT,最好的岗位就是云计算运维&SRE岗位。
为了帮助大家早日快速入门云计算运维领域,给大家整理了一套【最新运维资料】高级运维工程师必备技能资料包(文末一键免费领取),内容有多详实丰富看下图!
1.38张最全工程师技能图谱
2.面试大礼包
3.Linux书籍
内容比较多,就不一一展示了
以上所有资料获取请扫码:
识别上方二维码
备注:2026最新运维资料
100%免费领取
(是扫码领取,不是在公众号后台回复,别看错了哦)
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:马哥Linux运维 点击关注👉 点击关注👉《Kubernetes 集群中的 GPU 调度、隔离与资源分配》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论