【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.28+,NVIDIA 驱动版本 >= 535.xx,操作系统 Ubuntu 22.04 / CentOS 9。

前置条件

在开始之前,确保你的环境满足以下条件:

检查项

要求

验证命令

Kubernetes 集群

v1.26+(Device Manager GA)

kubectl version

NVIDIA 驱动

>= 535.xx(推荐 550+)

nvidia-smi 正常输出

NVIDIA Container Toolkit

已安装并配置为默认运行时

nvidia-ctk --version

容器运行时

containerd 或 docker 均支持

systemctl status containerdsystemctl status docker

节点操作系统

Linux(Ubuntu 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 couldn't 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 不装 Toolkit,Pod 虽然能成功调度并启动,但容器里跑 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

配置运行时

如果你用的是 containerd(Kubernetes 1.24+ 默认)

# 自动配置 containerd
sudo nvidia-ctk runtime configure --runtime=containerd

# 重启 containerd
sudo systemctl daemon-reload && sudo systemctl restart containerd

如果你用的是 Docker

sudo nvidia-ctk runtime configure --runtime=docker
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 外部)。它的工作流程分三步:

  1. 注册:插件启动后,在 /var/lib/kubelet/device-plugins/ 下创建一个 Unix Socket,然后通过 Kubelet 的 Registration gRPC 服务把自己“登记”上去。登记时要告诉 Kubelet 三样东西:Socket 名字、API 版本、要发布的资源名(比如 nvidia.com/gpu)。
  2. 上报:注册成功后,插件把节点上的设备列表(比如“这个节点有 4 张 A100”)发给 Kubelet。Kubelet 再把信息同步给 API Server,更新节点的状态。这时候你 kubectl describe node 就能看到 nvidia.com/gpu: 4 了。
  3. 分配:当 Pod 请求 nvidia.com/gpu: 1 时,调度器会确保 Pod 被调度到有足够 GPU 的节点上。Kubelet 在启动容器前会调用插件的 Allocate 接口,插件负责把具体的 GPU 设备挂载到容器里。

几个关键的限制(记住这 3 条)

  • 对于 GPU 这类扩展资源,必须同时设置 requests 和 limits 且相等,或者只设置 limits(Kubernetes 会自动将 requests 填充为与 limits 相同)。只设置 requests 而不设置 limits 是无效的,会导致 GPU 分配失败。
  • 扩展资源只能是整数,不能是小数。
  • 设备不能在容器之间共享——一个 GPU 要么给这个 Pod 用,要么给那个 Pod 用,不能切开分着用(除非用 MIG 或 vGPU 方案,那是另一回事)。

部署 NVIDIA Device Plugin

NVIDIA 官方提供了两种部署方式:静态 YAMLHelm 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/class"=true

版本信息:经检索确认,NVIDIA Device Plugin 在 NGC 上的最新稳定版本为 v0.19.2(2026 年 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 app=nvidia-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 或者压根没有这一行,说明插件没注册成功——往下翻到“常见问题”章节。

配置 RuntimeClass(containerd 环境必需)

很多 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: nvidia
kubectl 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 张 GPU
kubectl 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 accelerator=nvidia-a100
kubectl label nodes node2 accelerator=nvidia-v100
kubectl label nodes node3 accelerator=nvidia-a100

然后在 Pod 里指定要哪种 GPU:

apiVersion: 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:容器内无法访问 GPU(CUDA_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 app=nvidia-device-plugin

如果看到类似这样的报错:

> failed to start device plugin: failed to initialize NVML: NVML_ERROR_DRIVER_NOT_LOADED

原因:NVIDIA 驱动没装好,或者驱动与内核版本不匹配。NVML(NVIDIA Management Library)是驱动自带的库,插件要靠它来发现 GPU。

解决方法

  1. 在节点上跑 nvidia-smi 确认驱动正常
  2. 如果 nvidia-smi 报错,重装驱动
  3. 如果 nvidia-smi 正常但插件仍报错,检查 /usr/lib/x86_64-linux-gnu/libnvidia-ml.so 是否存在,以及容器内是否能挂载到宿主机的驱动库

问题 3:Pod 一直 Pending,报 0/4 nodes are available: 1 Insufficient nvidia.com/gpu

现象

> 0/4 nodes are available: 1 Insufficient nvidia.com/gpu, 3 node(s) didn't match Pod's 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"}'

问题 4:Device 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

问题 5:GPU 资源申请写法错误

错误示例(只写 requests 不写 limits):

resources:
  requests:
    nvidia.com/gpu: 1   # 只写 requests 不写 limits → 无效!Pod 无法获得 GPU

正确示例 1(只写 limits,Kubernetes 自动填充 requests):

resources:
  limits:
    nvidia.com/gpu: 1

正确示例 2(limits 和 requests 都写且相等):

resources:
  requests:
    nvidia.com/gpu: 1
  limits:
    nvidia.com/gpu: 1

MIG(Multi-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 migStrategy=single   # 或 mixed

MIG 的完整配置需要驱动版本 >= R550(推荐最新稳定版),Container Toolkit >= v1.19.x,Device Plugin >= v0.7.0。详细配置请参考 NVIDIA 官方 MIG 文档

不支持/限制说明

以下几个场景是 Device Plugin 原生不支持的,需要额外方案:

场景

原生 Device Plugin

替代方案

GPU 分片(多个 Pod 共享一张 GPU)

❌ 不支持

NVIDIA MIG、vGPU、HAMi

GPU 超卖

❌ 不支持

需要自定义调度器

动态 GPU 资源调整

❌ 不支持

需要重启 Pod

非 NVIDIA GPU(AMD、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 官方推荐,建议在测试环境先行验证后再上生产。

结尾

好,写到这里该说的基本都说了。回顾一下核心要点:

  1. Device Plugin 的本质是 gRPC 服务,通过注册→上报→分配三步让 Kubernetes 感知 GPU 资源。
  2. NVIDIA Container Toolkit 比 Device Plugin 更重要——没有它,容器里用不了 GPU。这是 90% 新手踩的第一个坑。
  3. GPU 资源申请:必须同时设置 requests 和 limits 且相等,或者只设置 limits(自动填充)。只写 requests 不写 limits 是无效的。
  4. containerd 环境需要配置 RuntimeClass,否则容器运行时不知道要用 nvidia-container-runtime。
  5. MIG 支持三种策略none(默认)、singlemixed,可根据需求配置。

如果这篇文章帮你少踩了一个坑,欢迎分享给更多正在被 GPU 调度折磨的兄弟。

最后留个开放性问题:你们生产环境的 GPU 利用率和调度策略是怎么做的?有没有遇到过“GPU 资源碎片”的问题? 欢迎在评论区交流,我最近也在研究怎么用 MIG 和 vGPU 提高利用率,有好经验一起分享。


文档版本信息

项目

内容

适用 Kubernetes 版本

v1.26+(Device Manager GA)

NVIDIA Device Plugin 版本

v0.16.x ~ v0.19.x(最新 v0.19.2,2026-05-26)

NVIDIA Container Toolkit 版本

v1.19.x(最新 v1.19.0,2026-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 配置文档 参考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

运维老郭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值