Kubernetes集群中的GPU调度、隔离与资源分配

admin 2026-07-19 05:18:02 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细解析Kubernetes中GPU调度、隔离与资源分配的完整链路,从设备发现到容器注入,提供排查步骤、Pod编写示例、节点标签与污点管理策略,以及整卡独占、时间共享等隔离方案,强调逐层验证和版本兼容性,为运维人员提供可操作指南。 综合评分: 84 文章分类: 解决方案


cover_image

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 能正常运行,至少要经过以下环节:

  1. 宿主机 PCIe 层能识别 GPU;
  2. NVIDIA 内核驱动加载正常,宿主机可查询设备;
  3. 容器运行时已配置 NVIDIA Container Toolkit 或等效 CDI 支持;
  4. Device Plugin DaemonSet 在 GPU 节点运行,并向 kubelet 注册资源;
  5. Node 的 capacity 和 allocatable 出现 nvidia.com/gpu 或 MIG 资源;
  6. Pod 在资源 limits 中声明需要的扩展资源;
  7. 调度器找到标签、污点、亲和性和剩余资源都满足的节点;
  8. kubelet 与运行时把分配设备暴露给容器;
  9. 容器内 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节点名> \
&nbsp; -o jsonpath='{.status.capacity}{"\n"}{.status.allocatable}{"\n"}'

kubectl -n <命名空间> describe node <GPU节点名>

示例输出:

text

Capacity:
&nbsp; nvidia.com/gpu: &nbsp;8
Allocatable:
&nbsp; nvidia.com/gpu: &nbsp;8

这是示例输出,表示 Device Plugin 向 kubelet 注册了 8 个可分配 GPU 资源。它不表示 8 张卡当前利用率为 0,也不代表每张卡显存完全空闲。

2. 宿主机视角

在 GPU 节点上执行:

bash

lspci -nn | grep -i nvidia
nvidia-smi
nvidia-smi -L
lsmod | grep&nbsp;'^nvidia'

示例输出:

text

GPU 0: <GPU型号> (UUID: GPU-<示例UUID>)

这是示例输出。nvidia-smi -L 能列出 GPU 或 MIG 实例,说明驱动基本可用;CUDA 应用是否兼容还取决于镜像中的用户态库、宿主机驱动版本和应用构建目标。

检查 kubelet 与容器运行时日志:

bash

sudo&nbsp;journalctl -u kubelet --since&nbsp;'<开始时间>'&nbsp;--no-pager \
&nbsp; | grep -Ei&nbsp;'device.?plugin|nvidia|cdi|allocate|registration'

sudo&nbsp;crictl info
sudo&nbsp;crictl ps -a | grep -Ei&nbsp;'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 \
&nbsp; -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:&nbsp;v1
kind:Pod
metadata:
name:gpu-check
namespace:<命名空间>
spec:
restartPolicy:Never
containers:
&nbsp; &nbsp;&nbsp;-name:cuda
&nbsp; &nbsp; &nbsp;&nbsp;image:<包含nvidia-smi的CUDA镜像地址>
&nbsp; &nbsp; &nbsp;&nbsp;command:&nbsp;["bash",&nbsp;"-lc"]
&nbsp; &nbsp; &nbsp;&nbsp;args:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-|
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; set -euo pipefail
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nvidia-smi -L
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nvidia-smi
&nbsp; &nbsp; &nbsp;&nbsp;resources:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;requests:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;cpu:"1"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memory:2Gi
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;nvidia.com/gpu:"1"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limits:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;cpu:"2"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memory:4Gi
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;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节点名> \
&nbsp; accelerator.example.com/pool=<资源池名称> --overwrite --dry-run=server -o yaml

kubectl -n <命名空间> label node <GPU节点名> \
&nbsp; accelerator.example.com/pool=<资源池名称> --overwrite

Node 是集群级资源,命令中的 namespace 不改变其作用域。修改标签会影响新 Pod 调度,甚至触发现有使用 requiredDuringSchedulingRequiredDuringExecution 类约束的驱逐行为(常见 node affinity 是 IgnoredDuringExecution,不会驱逐)。执行前应查询依赖该标签的工作负载,并在单节点灰度。

回滚:

bash

kubectl -n <命名空间> label node <GPU节点名> \
&nbsp; accelerator.example.com/pool-

2. Pod 使用硬约束和软偏好

yaml

spec:
&nbsp;&nbsp;affinity:
&nbsp; &nbsp;&nbsp;nodeAffinity:
&nbsp; &nbsp; &nbsp;&nbsp;requiredDuringSchedulingIgnoredDuringExecution:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;nodeSelectorTerms:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-matchExpressions:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-key:accelerator.example.com/pool
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;operator:In
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;values:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-<资源池名称>
&nbsp; &nbsp; &nbsp;&nbsp;preferredDuringSchedulingIgnoredDuringExecution:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-weight:50
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;preference:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;matchExpressions:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-key:accelerator.example.com/generation
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;operator:In
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;values:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-<优先GPU代际>

required 不满足时 Pod 会 Pending;preferred 只影响打分,不保证一定命中。硬约束越多,资源碎片越严重。应把真正的兼容条件做成 required,把性能偏好做成 preferred。

3. GPU 专用节点使用污点和容忍

给节点加 NoSchedule 污点可阻止不相关 Pod 占用昂贵节点:

bash

kubectl -n <命名空间> taint node <GPU节点名> \
&nbsp; nvidia.com/gpu=present:NoSchedule --dry-run=server -o yaml

kubectl -n <命名空间> taint node <GPU节点名> \
&nbsp; nvidia.com/gpu=present:NoSchedule

Pod 增加容忍:

yaml

spec:
&nbsp;&nbsp;tolerations:
&nbsp; &nbsp;&nbsp;-&nbsp;key:&nbsp;nvidia.com/gpu
&nbsp; &nbsp; &nbsp;&nbsp;operator:&nbsp;Equal
&nbsp; &nbsp; &nbsp;&nbsp;value:&nbsp;present
&nbsp; &nbsp; &nbsp;&nbsp;effect:&nbsp;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:
&nbsp;&nbsp;privileged:&nbsp;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:&nbsp;v1
sharing:
&nbsp;&nbsp;timeSlicing:
&nbsp; &nbsp;&nbsp;renameByDefault:&nbsp;false
&nbsp; &nbsp;&nbsp;failRequestsGreaterThanOne:&nbsp;true
&nbsp; &nbsp;&nbsp;resources:
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;name:&nbsp;nvidia.com/gpu
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;replicas:&nbsp;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 \
&nbsp; | jq&nbsp;'.status.capacity | with_entries(select(.key | startswith("nvidia.com/")))'

Pod 请求实际存在的 MIG 资源:

yaml

resources:
&nbsp;&nbsp;requests:
&nbsp; &nbsp;&nbsp;nvidia.com/mig-<实际配置名>:&nbsp;"1"
&nbsp;&nbsp;limits:
&nbsp; &nbsp;&nbsp;nvidia.com/mig-<实际配置名>:&nbsp;"1"

MIG 适合需要显存和故障边界、但单任务不需整卡的生产推理。它仍有约束:profile 组合受硬件支持限制,重配置会影响整张物理 GPU 上的实例和工作负载,某些监控、P2P、性能计数或应用行为与整卡不同。

启用或重配 MIG 属于高风险操作。影响范围包括目标 GPU 上全部 Pod、设备资源名、调度容量和监控维度。执行前必须:

  1. 确认 GPU 型号、驱动、固件、插件与 Operator 的 MIG 支持;
  2. cordon 目标节点,并安全迁移或停止所有 GPU 工作负载;
  3. 记录 nvidia-smi -L、当前 MIG 布局和 Node capacity;
  4. 使用 GPU Operator MIG Manager 或经厂商验证的节点流程,不在忙碌 GPU 上直接改配置;
  5. 先在单节点灰度,验证每种 profile 的容器可见性和业务性能;
  6. 准备恢复原 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:&nbsp;v1
kind:ResourceQuota
metadata:
name:gpu-quota
namespace:<命名空间>
spec:
hard:
&nbsp; &nbsp;&nbsp;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:
&nbsp;&nbsp;requests:
&nbsp; &nbsp;&nbsp;nvidia.com/gpu:&nbsp;"4"
&nbsp;&nbsp;limits:
&nbsp; &nbsp;&nbsp;nvidia.com/gpu:&nbsp;"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'&nbsp;|&nbsp;tail&nbsp;-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名称> \
&nbsp; -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 \
&nbsp; --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 \
&nbsp; | jq&nbsp;'.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名称> \
&nbsp; -o jsonpath='{.spec.containers[*].resources.limits}{"\n"}'

进入目标容器:

bash

kubectl -n <命名空间>&nbsp;exec&nbsp;-it <Pod名称> -c <容器名称> -- nvidia-smi -L
kubectl -n <命名空间>&nbsp;exec&nbsp;<Pod名称> -c <容器名称> -- sh -c&nbsp;'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 \
&nbsp; --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节点名> \
&nbsp; -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 \
&nbsp; -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'&nbsp;|&nbsp;tail&nbsp;-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 \
&nbsp; --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 调度、隔离与资源分配》

评论:0   参与:  0