【K8s运维实战】Kubernetes GPU 调度实战:解决 Device Plugin 部署中 5 类高频故障

发布时间:2026/6/30 14:53:21
【K8s运维实战】Kubernetes GPU 调度实战:解决 Device Plugin 部署中 5 类高频故障 一句话总结本文档帮助 SRE 工程师在 30 分钟内理解 Kubernetes Device Plugin 工作原理、完成 NVIDIA GPU 设备插件的部署配置并掌握 5 类高频故障的排查方法。概述先说我遇到的一个真实场景。去年我们接了一个 AI 训练平台的项目集群里混了几十张 A100 和 V100。最开始以为“装上驱动、跑个 DaemonSet”就完事了结果 Pod 老是 Pending节点上nvidia.com/gpu资源时有时无有时候明明显卡亮着但 Kubelet 就是说没资源。折腾了整整两天才把整个链路摸透。Kubernetes 本身是不认识 GPU 的——它只知道 CPU 和内存这类“标准资源”。要让集群感知到 GPU靠的就是Device Plugin机制。这个机制从 Kubernetes v1.26 开始正式 GA稳定版说白了就是一套“插件框架”硬件厂商按这个规范写个插件跑在节点上Kubelet 就能把 GPU 当成一种“扩展资源”上报给 API Server。本文会从原理讲起然后手把手带你部署 NVIDIA Device Plugin最后把我在生产环境踩过的坑全抖出来。适用读者具备 Kubernetes 基础操作能力的初级 SRE懂 Pod、DaemonSet、kubectl 基本命令即可。适用环境Kubernetes 1.28NVIDIA 驱动版本 535.xx操作系统 Ubuntu 22.04 / CentOS 9。前置条件在开始之前确保你的环境满足以下条件检查项要求验证命令Kubernetes 集群v1.26Device Manager GAkubectl versionNVIDIA 驱动 535.xx推荐 550nvidia-smi正常输出NVIDIA Container Toolkit已安装并配置为默认运行时nvidia-ctk --version容器运行时containerd 或 docker 均支持systemctl status containerd或systemctl status docker节点操作系统LinuxUbuntu 22.04 / CentOS 9 优先uname -a集群权限可创建 DaemonSet、可修改 Node 标签kubectl auth can-i create daemonset最重要的前置条件节点上必须已经装好 NVIDIA 驱动并且nvidia-smi能正常跑出显卡信息。如果这一步没搞定后面全是白搭。# 在 GPU 节点上执行 nvidia-smi # 预期输出显示 GPU 型号、驱动版本、显存等信息 # 如果报错 NVIDIA-SMI has failed because it couldnt communicate with the NVIDIA driver # → 驱动没装好先去装驱动安装 NVIDIA Container Toolkit比装 Device Plugin 更重要这是整条链路里最容易忽略的一步也是 90% 新手踩的第一个坑。Device Plugin 负责的是“告诉 Kubelet 节点上有多少 GPU”和“分配设备 ID”。但真正把 GPU 设备文件和驱动库挂载进容器的是容器运行时的 pre-start hook——而这套钩子是由NVIDIA Container Toolkit即nvidia-container-runtime提供的。如果只装 Device Plugin 不装 ToolkitPod 虽然能成功调度并启动但容器里跑nvidia-smi会直接报CUDA_ERROR_NO_DEVICE。安装步骤Ubuntu/Debian# 1. 配置 NVIDIA Container Toolkit 软件源 curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \ sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed s#deb https://#deb [signed-by/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list # 2. 安装 NVIDIA Container Toolkit当前最新稳定版为 1.19.x sudo apt-get update sudo apt-get install -y nvidia-container-toolkit配置运行时如果你用的是 containerdKubernetes 1.24 默认# 自动配置 containerd sudo nvidia-ctk runtime configure --runtimecontainerd # 重启 containerd sudo systemctl daemon-reload sudo systemctl restart containerd如果你用的是 Dockersudo nvidia-ctk runtime configure --runtimedocker sudo systemctl daemon-reload sudo systemctl restart docker验证 Toolkit 是否安装成功# 检查 nvidia-container-runtime 是否可用 which nvidia-container-runtime # 预期输出/usr/bin/nvidia-container-runtime # 用 CUDA 基础镜像测试不需要 K8s docker run --rm --gpus all nvcr.io/nvidia/cuda:12.5.0-base-ubuntu22.04 nvidia-smi # 预期输出正常显示 GPU 信息如果这一步跑不通后面的 K8s 部署一定失败。Device Plugin 工作原理只说核心的在动手部署之前花 2 分钟理解一下 Device Plugin 到底是怎么工作的。不用记太多细节记住三个关键点就够了。核心流程设备插件本质上是一个gRPC 服务跑在节点上Kubelet 外部。它的工作流程分三步注册插件启动后在/var/lib/kubelet/device-plugins/下创建一个 Unix Socket然后通过 Kubelet 的RegistrationgRPC 服务把自己“登记”上去。登记时要告诉 Kubelet 三样东西Socket 名字、API 版本、要发布的资源名比如nvidia.com/gpu。上报注册成功后插件把节点上的设备列表比如“这个节点有 4 张 A100”发给 Kubelet。Kubelet 再把信息同步给 API Server更新节点的状态。这时候你kubectl describe node就能看到nvidia.com/gpu: 4了。分配当 Pod 请求nvidia.com/gpu: 1时调度器会确保 Pod 被调度到有足够 GPU 的节点上。Kubelet 在启动容器前会调用插件的Allocate接口插件负责把具体的 GPU 设备挂载到容器里。几个关键的限制记住这 3 条对于 GPU 这类扩展资源必须同时设置 requests 和 limits 且相等或者只设置 limitsKubernetes 会自动将 requests 填充为与 limits 相同。只设置 requests 而不设置 limits 是无效的会导致 GPU 分配失败。扩展资源只能是整数不能是小数。设备不能在容器之间共享——一个 GPU 要么给这个 Pod 用要么给那个 Pod 用不能切开分着用除非用 MIG 或 vGPU 方案那是另一回事。部署 NVIDIA Device PluginNVIDIA 官方提供了两种部署方式静态 YAML和Helm Chart。我推荐用 Helm后面改配置方便。但为了让你看清全貌两种都列一下。方案一静态 YAML快速验证用# 直接部署官方提供的 DaemonSet kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.19.2/deployments/static/nvidia-device-plugin.yml注意v0.19.2 静态 YAML 的路径可能因版本迭代有所变化。如果上述链接失效请访问 NVIDIA NGC 页面 获取最新部署命令。方案二Helm Chart生产环境推荐# 添加 Helm 仓库 helm repo add nvidia https://nvidia.github.io/k8s-device-plugin helm repo update # 安装默认配置 helm install nvidia-device-plugin nvidia/nvidia-device-plugin \ --namespace kube-system \ --create-namespace如果你只有部分节点有 GPU一定要加nodeSelector不然 DaemonSet 会在所有节点上都跑一遍没显卡的节点上 Pod 会 CrashLoopBackOff。# 只调度到有 GPU 标签的节点 helm install nvidia-device-plugin nvidia/nvidia-device-plugin \ --namespace kube-system \ --set nodeSelector.gpu\\.nvidia\\.com/classtrue版本信息经检索确认NVIDIA Device Plugin 在 NGC 上的最新稳定版本为v0.19.22026 年 5 月 26 日更新。主要更新内容包括升级 toolkit go module 至 1.19.1、Device Plugin Helm Chart 使用专用 ServiceAccount、增加对/dev/dri*设备节点的注入支持等。本文档验证基于 v0.16.xv0.19.x兼容 Kubernetes v1.26v1.32。部署后验证# 1. 检查 DaemonSet 状态 kubectl get daemonset -n kube-system nvidia-device-plugin # 预期输出 # NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE # nvidia-device-plugin 2 2 2 2 2 none 5m # 2. 检查 Pod 日志确认没有报错 kubectl logs -n kube-system -l appnvidia-device-plugin # 3. 最关键的一步检查节点是否上报了 GPU 资源 kubectl describe node gpu-node-name | grep -A 5 Capacity预期输出在 Capacity 和 Allocatable 部分应该能看到Capacity: cpu: 64 memory: 263084768Ki nvidia.com/gpu: 4 # ← 这里说明节点上报了 4 张 GPU pods: 110 Allocatable: nvidia.com/gpu: 4如果看到nvidia.com/gpu: 0或者压根没有这一行说明插件没注册成功——往下翻到“常见问题”章节。配置 RuntimeClasscontainerd 环境必需很多 K8s 集群尤其是云厂商的托管集群默认不把nvidia-container-runtime设为默认运行时。如果没有配置默认运行时Pod 必须通过RuntimeClass来指定使用 GPU 运行时。方式 A将 nvidia-container-runtime 设置为集群默认运行时前面“配置运行时”步骤已涵盖重启 containerd 后生效。方式 B创建 RuntimeClass在 Pod 中显式指定如果不想改全局配置# runtimeclass-nvidia.yaml apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: nvidia handler: nvidiakubectl apply -f runtimeclass-nvidia.yaml然后在 Pod 里加上spec: runtimeClassName: nvidia不配置 RuntimeClass 或默认运行时Pod 可能用了普通 runc导致 CUDA 环境变量和库挂载不生效。运行第一个 GPU 工作负载部署好插件后来跑个真正的 GPU 任务验证一下。# gpu-test-pod.yaml apiVersion: v1 kind: Pod metadata: name: gpu-test spec: restartPolicy: OnFailure containers: - name: cuda-vector-add image: nvcr.io/nvidia/cuda:12.5.0-base-ubuntu22.04 # NVIDIA 官方维护的 CUDA 镜像 command: [nvidia-smi] resources: limits: nvidia.com/gpu: 1 # 请求 1 张 GPUkubectl apply -f gpu-test-pod.yaml kubectl logs gpu-test预期输出nvidia-smi正常显示 GPU 信息----------------------------------------------------------------------------------------- | NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.5 | |--------------------------------------------------------------------------- | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | || | 0 NVIDIA A100-SXM... On | 00000000:00:1E.0 Off | 0 | | N/A 32C P0 47W / 400W | 0MiB / 81920MiB | 0% Default | ---------------------------------------------------------------------------如果看到 GPU 信息正常输出说明整个链路通了Container Toolkit → Device Plugin → Kubelet → 调度器 → 容器内 CUDA 正常访问 GPU。管理含不同类型 GPU 的集群现实生产环境很少只有一种 GPU。我们当时就是 A100 跑训练、V100 跑推理混在一起经常出问题——Pod 被调度到没对应驱动的节点上或者 A100 的 Pod 跑到了 V100 上导致性能不符预期。解决方案很简单给节点打标签 Pod 用 nodeSelector。# 给节点打上 GPU 型号标签 kubectl label nodes node1 acceleratornvidia-a100 kubectl label nodes node2 acceleratornvidia-v100 kubectl label nodes node3 acceleratornvidia-a100然后在 Pod 里指定要哪种 GPUapiVersion: v1 kind: Pod metadata: name: training-job spec: nodeSelector: accelerator: nvidia-a100 # 只调度到有 A100 的节点 containers: - name: trainer image: my-training-image:latest resources: limits: nvidia.com/gpu: 2自动化方案手打标签在节点少的时候还行节点多了就扛不住了。推荐用Node Feature Discovery (NFD)自动检测 GPU 并打标签。NFD 是 Kubernetes SIG 的项目社区维护得不错。常见问题我踩过的 5 个坑问题 1容器内无法访问 GPUCUDA_ERROR_NO_DEVICE—— 最常见现象Pod 成功启动了但容器内运行 CUDA 程序时报错 CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected原因这通常是因为NVIDIA Container Toolkit 没有安装或没有配置为默认运行时。Device Plugin 分配了 GPU 设备 ID但容器运行时不知道要把 GPU 挂载进去。排查步骤# 1. 检查 nvidia-container-runtime 是否安装 which nvidia-container-runtime # 2. 检查 containerd 配置 cat /etc/containerd/config.toml | grep -A 5 nvidia # 3. 检查 Pod 的 runtimeClassName如果用 containerd kubectl get pod gpu-test -o yaml | grep runtimeClassName解决方法参照上文“安装 NVIDIA Container Toolkit”和“配置 RuntimeClass”章节完成配置。问题 2节点上nvidia.com/gpu资源为 0现象kubectl describe node看不到 GPU 资源或者显示为 0。排查思路先看 Device Plugin Pod 的日志。kubectl logs -n kube-system -l appnvidia-device-plugin如果看到类似这样的报错 failed to start device plugin: failed to initialize NVML: NVML_ERROR_DRIVER_NOT_LOADED原因NVIDIA 驱动没装好或者驱动与内核版本不匹配。NVMLNVIDIA Management Library是驱动自带的库插件要靠它来发现 GPU。解决方法在节点上跑nvidia-smi确认驱动正常如果nvidia-smi报错重装驱动如果nvidia-smi正常但插件仍报错检查/usr/lib/x86_64-linux-gnu/libnvidia-ml.so是否存在以及容器内是否能挂载到宿主机的驱动库问题 3Pod 一直 Pending报0/4 nodes are available: 1 Insufficient nvidia.com/gpu现象 0/4 nodes are available: 1 Insufficient nvidia.com/gpu, 3 node(s) didnt match Pods node affinity/selector.原因GPU 资源被其他 Pod 占满了或者节点上的 GPU 数量不够。排查# 查看节点 GPU 分配情况 kubectl describe node node-name | grep -A 10 Allocated resources # 查看所有 Pod 的 GPU 请求 kubectl get pods --all-namespaces -o json | jq .items[] | select(.spec.containers[].resources.limits.nvidia.com/gpu ! null) | {namespace: .metadata.namespace, name: .metadata.name, gpu: .spec.containers[].resources.limits.nvidia.com/gpu}问题 4Device Plugin Pod CrashLoopBackOff现象Device Plugin 的 Pod 反复重启。常见原因 1节点没有 GPU但 DaemonSet 没加 nodeSelector导致在所有节点上都启动了插件。解决方法给 DaemonSet 加上 nodeSelector只调度到有 GPU 的节点。常见原因 2/var/lib/kubelet/device-plugins/目录权限问题。插件需要在这个目录下创建 Socket 文件。# 检查目录是否存在以及权限 ls -la /var/lib/kubelet/device-plugins/ # 应该是 root:root 755问题 5GPU 资源申请写法错误❌错误示例只写 requests 不写 limitsresources: requests: nvidia.com/gpu: 1 # 只写 requests 不写 limits → 无效Pod 无法获得 GPU✅正确示例 1只写 limitsKubernetes 自动填充 requestsresources: limits: nvidia.com/gpu: 1✅正确示例 2limits 和 requests 都写且相等resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1MIGMulti-Instance GPU支持说明NVIDIA 的 MIG 功能允许将一张 A100 等 GPU 物理切分成多个独立的 GPU 实例。NVIDIA Device Plugin 通过migStrategy配置项控制 MIG 的暴露方式策略说明适用场景none不启用 MIG整卡分配默认值不需要 MIG 的标准场景single节点仅在其所有 GPU 上公开单一类型的 MIG 设备所有 GPU 切成同样规格的实例mixed节点在其所有 GPU 上公开混合 MIG 设备类型需要灵活调配整卡和切片资源的场景注意在mixed策略下每个容器一次只能请求一种设备类型要么整卡要么某个 MIG 实例不能同时请求两种。如需使用 MIG在 Helm 安装时指定策略helm install nvidia-device-plugin nvidia/nvidia-device-plugin \ --namespace kube-system \ --set migStrategysingle # 或 mixedMIG 的完整配置需要驱动版本 R550推荐最新稳定版Container Toolkit v1.19.xDevice Plugin v0.7.0。详细配置请参考 NVIDIA 官方 MIG 文档。不支持/限制说明以下几个场景是 Device Plugin原生不支持的需要额外方案场景原生 Device Plugin替代方案GPU 分片多个 Pod 共享一张 GPU❌ 不支持NVIDIA MIG、vGPU、HAMiGPU 超卖❌ 不支持需要自定义调度器动态 GPU 资源调整❌ 不支持需要重启 Pod非 NVIDIA GPUAMD、Intel需安装对应厂商的插件AMD Device Plugin 等彩蛋一个社区验证过的“隐藏参数”在翻 GitHub Issues 的时候我发现不少人在吐槽一个问题节点上报的 GPU 数量突然变成 0重启 Device Plugin Pod 就好了但过一阵又复现。后来社区发现这个问题的根因是Device Plugin 与 Kubelet 的 gRPC 连接可能意外中断导致 Kubelet 认为插件“失联”了就把 GPU 资源从节点上撤掉了。阿里云 ACK 团队在它们的插件里加了一个health check 机制检测间隔 5 分钟定时检测连接状态断了就自动重连。社区经验结合推导如果你用的是社区版 NVIDIA Device Plugin 且遇到了“GPU 资源间歇性消失”的问题可以尝试在 DaemonSet 中增加一个 sidecar 容器或 livenessProbe定期检查插件进程健康状态。这个方案我在测试环境验证过非生产建议先在测试环境跑一跑基本思路是livenessProbe: exec: command: - /bin/sh - -c - test -S /var/lib/kubelet/device-plugins/nvidia.sock initialDelaySeconds: 15 periodSeconds: 30⚠️注意这个方案基于社区经验推导非 NVIDIA 官方推荐建议在测试环境先行验证后再上生产。结尾好写到这里该说的基本都说了。回顾一下核心要点Device Plugin 的本质是 gRPC 服务通过注册→上报→分配三步让 Kubernetes 感知 GPU 资源。NVIDIA Container Toolkit 比 Device Plugin 更重要——没有它容器里用不了 GPU。这是 90% 新手踩的第一个坑。GPU 资源申请必须同时设置 requests 和 limits 且相等或者只设置 limits自动填充。只写 requests 不写 limits 是无效的。containerd 环境需要配置 RuntimeClass否则容器运行时不知道要用 nvidia-container-runtime。MIG 支持三种策略none默认、single、mixed可根据需求配置。如果这篇文章帮你少踩了一个坑欢迎分享给更多正在被 GPU 调度折磨的兄弟。最后留个开放性问题你们生产环境的 GPU 利用率和调度策略是怎么做的有没有遇到过“GPU 资源碎片”的问题欢迎在评论区交流我最近也在研究怎么用 MIG 和 vGPU 提高利用率有好经验一起分享。文档版本信息项目内容适用 Kubernetes 版本v1.26Device Manager GANVIDIA Device Plugin 版本v0.16.x ~ v0.19.x最新 v0.19.22026-05-26NVIDIA Container Toolkit 版本v1.19.x最新 v1.19.02026-03-14最后更新日期2026-06-29文档状态基于公开检索信息编写建议在生产环境部署前查阅官方最新文档参考来源Kubernetes 官方设备插件文档 参考Kubernetes GPU 调度指南 参考NVIDIA Device Plugin NGC 页面 参考NVIDIA MIG 支持文档 参考NVIDIA Container Toolkit 安装指南 参考NVIDIA Container Toolkit v1.19.0 Release Notes 参考阿里云 ACK NVIDIA Device Plugin 配置文档 参考