Kubernetes GPU allocatable=0 排查:宿主机 nvidia-smi 正常但 device plugin NVML Unknown Error
这类告警最容易误判的地方是:宿主机能看到 GPU,不代表 Kubernetes 能调度 GPU。Kubernetes 的 nvidia.com/gpu 不是从 nvidia-smi 直接读取,而是由 NVIDIA device plugin 通过 kubelet 的 Device Plugin API 注册。
本文用一个脱敏案例说明:宿主机有 8 张 GPU,但节点 capacity/allocatable 为 0,最终通过清理初始化失败的旧 device plugin 容器恢复到 8/8。


环境与问题现象
本文案例来自 ACK GPU 节点重启后的真实故障,节点规格为 8 卡。关键组件如下,版本仅用于说明问题发生时的组合,不代表所有相同版本都会复现:
| 组件 | 故障节点 |
|---|---|
| Kubernetes / kubelet | 1.31.1 ACK 定制版 |
| containerd | 1.6.34 |
| runc | 1.1.13 |
| NVIDIA 驱动 | 535.161.07 |
| NVIDIA Container Toolkit | 1.16.2 |
| NVIDIA device plugin | ACK 定制 v0.9.1-* |
| device plugin 部署方式 | static Pod |
节点重启后,宿主机最终能够识别 8 张 GPU,但 Kubernetes 中的 nvidia.com/gpu 为 0,并触发 GPU 不可调度告警。
一、从服务器启动到告警触发的完整流程
| 步骤 | 组件 | 作用 | 正常产物 | 本次状态 |
|---|---|---|---|---|
| 1 | Linux / PCI | 发现物理 GPU | 操作系统识别全部 GPU | 正常 |
| 2 | NVIDIA 内核驱动 | 驱动 GPU,提供内核访问能力 | nvidia-smi 能访问 GPU |
最终正常 |
| 3 | udev / NVIDIA 驱动 | 创建设备入口 | /dev/nvidia* 等字符设备 |
当前正常 |
| 4 | nvidia-persistenced / NVML |
维持驱动状态,并向上层提供 GPU 查询接口 | NVML 可稳定枚举 GPU | 启动瞬间尚未稳定 |
| 5 | containerd / NVIDIA Toolkit | 为容器提供 NVIDIA 库和设备 | GPU 容器可以调用 NVML/CUDA | 当前正常 |
| 6 | kubelet | 管理 static Pod,并接收设备插件注册 | device plugin 被启动 | 正常启动 |
| 7 | NVIDIA device plugin | 调用 NVML,发现 GPU 数量和健康状态 | 发现 8 张 GPU | NVML: Unknown Error |
| 8 | device plugin -> kubelet | 通过 Device Plugin API 注册 nvidia.com/gpu |
kubelet 收到 8 张 GPU | 没有完成注册 |
| 9 | kubelet | 更新 Node 的 capacity/allocatable | 8 / 8 |
当时为 0 / 0 |
| 10 | kube-state-metrics | 把 Node 状态转换成监控指标 | 指标值为 8 | 如实输出 0 |
| 11 | Prometheus / VictoriaMetrics | 采集并保存指标 | PromQL 查询到 8 | 当时查询到 0 |
| 12 | N9E | 根据 PromQL 触发或恢复告警 | 指标正常时不告警 | 触发 GPU 不可调度告警 |
这条链路里,Kubernetes 不会自己扫描 NVIDIA GPU。真正把 GPU 告诉 kubelet 的是 NVIDIA device plugin;监控系统只是读取 kubelet 最终写入的节点状态。
二、本次问题具体出现在哪里
故障节点的关键启动时间线如下:
节点启动
↓
11:18:16 nvidia-persistenced 启动
↓ 仅约 1 秒
11:18:17 ACK static device plugin 启动
↓
device plugin 调用 NVML
↓
【故障点】Failed to initialize NVML: Unknown Error
↓
没有发现 8 张 GPU
↓
没有完成 nvidia.com/gpu 注册
↓
kubelet 中 capacity/allocatable 变成 0
直接故障已经确认:device plugin 初始化 NVML 失败,随后没有完成 GPU 注册。结合插件仅比 NVIDIA 服务晚约 1 秒启动,启动时序竞态是高概率触发因素。
但需要保留证据边界:故障瞬间没有保存完整的 cgroup、设备权限和 /dev/char 链接快照,因此不能继续断言一定是哪一个底层设备入口尚未准备好,也不能仅凭一次事件直接定性为阿里云平台 Bug。
插件还配置了 fail-on-init-error=false。在本次 ACK 定制版本的实际表现中,初始化失败后容器没有明确退出,而是保持 Running,形成了假正常状态:
device plugin Pod = Running
NVML 初始化 = 失败
GPU 注册 = 失败
Node allocatable = 0
三、先确认 Kubernetes 看到的是什么
kubectl get node <NODE> -o jsonpath='{.status.capacity}{"\n"}{.status.allocatable}{"\n"}'
如果返回 nvidia.com/gpu: 0,调度器就会认为该节点没有 GPU。先不要把它解释成“GPU 被占满”,还要看业务 Pod 的 GPU requests 和 device plugin 状态。
四、为什么 nvidia-smi 正常但 allocatable 仍为 0
链路是:
GPU/驱动 -> NVML -> device plugin -> /var/lib/kubelet/device-plugins -> kubelet -> Node.status.allocatable
nvidia-smi 只证明宿主机上的驱动和 NVML 可用。device plugin 需要在容器运行时中再次初始化 NVML,并创建 nvidia-gpu.sock,再向 kubelet 注册资源。
只要 device plugin 初始化失败,或者旧容器继续存活但没有完成注册,kubelet 就不会把 GPU 写入节点资源。因此会出现:
宿主机 nvidia-smi = 8
device plugin = Running
Node allocatable = 0
Running 只是进程状态,不等于资源注册成功。必须在日志中看到 Registered device plugin for 'nvidia.com/gpu' with Kubelet。
五、用日志区分驱动问题和注册问题
kubectl -n kube-system logs <DEVICE_PLUGIN_POD> --tail=100
重点看:
Failed to initialize NVML: nvml: Unknown Error
Starting gRPC server for 'nvidia.com/gpu'
Registered device plugin for 'nvidia.com/gpu' with Kubelet
如果宿主机 nvidia-smi 失败,优先查驱动、Xid 和硬件。如果宿主机正常但日志停在 NVML Unknown Error,应检查 containerd 的 NVIDIA runtime、设备与库挂载,以及旧容器是否被真正重建。
六、一个容易踩坑的地方:静态 Pod 只删 API 对象不够
节点上的 device plugin 往往是 /etc/kubernetes/manifests/ 下的静态 Pod。删除 mirror Pod 可能只改变 API 对象,底层容器仍被 kubelet 复用。排查时同时看容器 ID 和创建时间:
crictl ps -a --name nvidia-device-plugin
crictl inspect <CONTAINER_ID> | grep -Ei 'runtime|nvidia|mount'
如果容器 ID 没有变化,就没有真正重新初始化。
七、实际恢复操作:清理旧容器,让 kubelet 自动重建
确认节点没有业务 Pod、并完成变更前记录后,现场只处理了初始化失败的目标 device plugin 容器:
crictl stop <OLD_CONTAINER_ID>
crictl rm <OLD_CONTAINER_ID>
这两条命令不是修复 GPU 硬件,而是强制结束旧的失败实例。kubelet 随后按 static Pod 清单重新创建 device plugin,新容器重新执行第 7、8 步:
停止并删除旧 device plugin 容器
↓
kubelet 按 static Pod 清单创建新容器
↓
新容器重新调用 NVML
↓
此时驱动和设备已经完全就绪
↓
成功发现 8 张 GPU
↓
创建 nvidia-gpu.sock
↓
向 kubelet 注册 nvidia.com/gpu
↓
capacity=8 / allocatable=8
现场在 21:32:03 观察到新插件完成注册并生成 socket。也就是说,真正的恢复点是 device plugin 第二次初始化 NVML 成功,并重新向 kubelet 注册 8 张 GPU。
然后执行以下命令验证:
kubectl -n kube-system get pod <DEVICE_PLUGIN_POD> -o wide
kubectl get node <NODE> -o jsonpath='{.status.capacity.nvidia\.com/gpu}{" / "}{.status.allocatable.nvidia\.com/gpu}{"\n"}'
kubectl -n kube-system logs <DEVICE_PLUGIN_POD> --tail=30
成功标准:Pod 为 Running/Ready,日志出现注册成功,节点资源为 8 / 8(实际卡数按节点规格替换)。
八、为什么不建议先改 PromQL 或反复重启节点
- PromQL 只是读取 Node.status,改查询不会恢复调度能力。
- 重启节点能修复部分驱动初始化问题,但不能保证旧 device plugin 容器被重新创建。
- 反复删除 Pod 对静态 Pod 可能只重建 mirror 对象,无法清理底层容器。
- 没有业务 Pod 的节点更适合执行 runtime/device plugin 维护,但仍应保留变更前后证据。
节点重启会把驱动、containerd、kubelet 和 static device plugin 的启动过程全部重新跑一遍。如果插件仍然紧贴 NVIDIA 驱动启动,它仍可能再次命中相同的初始化窗口。相比之下,在节点已经稳定后只重建 device plugin,动作范围更小,也更直接地重做了失败的“NVML 初始化与 GPU 注册”阶段。
九、为什么腾讯云同类节点重启没有出现
只读对比中,腾讯云样本的 device plugin 比 nvidia-persistenced 晚约 14 秒启动,而阿里云故障节点只有约 1 秒:
阿里云 ACK:NVIDIA 服务启动 -> 约 1 秒 -> device plugin 启动 -> NVML Unknown Error
腾讯云 TKE:NVIDIA 服务启动 -> 约 14 秒 -> device plugin 启动 -> 注册成功
两边还有版本和部署方式差异:ACK 使用较老的云厂商定制插件和 static Pod,TKE 使用较新的插件和 DaemonSet。因此更准确的结论是:ACK 插件版本、部署方式与启动时序共同暴露了这个问题;腾讯云本次没有命中竞态窗口,不代表它在任何版本和配置下都不会出现。
十、Kubernetes 和 NVIDIA 官方如何解释这类问题
Kubernetes 官方的 Device Plugin 文档 明确了注册流程:device plugin 必须先启动 gRPC 服务,再通过 /var/lib/kubelet/device-plugins/kubelet.sock 向 kubelet 注册。kubelet 重启时会删除已有 socket,插件必须检测变化并重新注册。
因此,Kubernetes 负责的是设备注册协议,并不负责初始化 NVIDIA NVML。NVML: Unknown Error 的直接故障层仍在 NVIDIA device plugin 的容器运行时视图中。
NVIDIA 上游有两组高度相关的已知问题:
- NVIDIA Container Toolkit #251:
systemctl daemon-reload重新应用 systemd 管理的 device cgroup 后,容器可能丢失 NVIDIA 设备访问权限,随后出现Failed to initialize NVML: Unknown Error。维护者在 2025 年 11 月确认这是已知问题,并表示 Toolkitv1.18.0已覆盖许多场景,但没有承诺覆盖全部组合。 - NVIDIA k8s-device-plugin #426:
daemon-reload和 kubelet 重启后,GPU 被标记为 unhealthy,插件 Pod 仍然 Running;删除并重建插件 Pod 后恢复。维护者将其归为旧版本处理 kubelet socket 重建的已知问题,建议升级到v0.16.x或更新版本。
这与本次“插件仍 Running、GPU 未注册、重建插件后恢复”的表现高度接近,也说明当前 ACK 定制 v0.9.1-* 插件存在明显的版本风险。但是,本次失败发生在插件启动时初始化 NVML 的阶段,没有保留故障瞬间的 device cgroup 和 /dev/char 快照,也没有证据证明当时明确执行过 daemon-reload。所以目前能确认的是同类已知问题,不能把某一个 GitHub issue 直接写成本次已经完全证实的根因。
下次再次出现时,应在恢复前先保留下面这组只读现场:
# 1. 宿主机与插件容器是否看到同样的 GPU
nvidia-smi -L
crictl exec <DEVICE_PLUGIN_CONTAINER_ID> nvidia-smi -L
# 2. NVIDIA 字符设备与 /dev/char 链接
ls -l /dev/nvidia* /dev/char/*
# 3. 精确版本和 cgroup 模式
containerd --version
runc --version
nvidia-ctk --version
stat -fc %T /sys/fs/cgroup
# 4. 插件实例、socket 和本次启动时间
crictl ps -a --name nvidia-device-plugin
ls -l /var/lib/kubelet/device-plugins
uptime -s
同时在本次启动窗口内核对 systemd、kubelet 和插件日志中的 daemon-reload、kubelet.sock、Unknown Error、unhealthy、Registered device plugin。只有看到 cgroup/设备访问确实丢失,才能确认是 Toolkit 的 systemd/cgroup 问题;只有看到 kubelet socket 重建后插件没有重新注册,才能确认是旧版 device-plugin 的 socket 恢复问题。
对于 ACK 定制组件,升级前必须先向阿里云确认对应的补丁和支持路径,不能直接用 NVIDIA 上游镜像覆盖厂商版本。
十一、GPU 恢复与告警恢复要分开验证
GPU 注册恢复后,应先确认 Kubernetes 和权威指标源都已经回到 8:
Node capacity=8
Node allocatable=8
kube_node_status_allocatable=8
如果这些数据已经恢复,但 N9E 中旧事件仍未关闭,应单独检查告警规则绑定的数据源、历史规则和事件收敛接口。这属于告警链路问题,不能继续归因给 NVIDIA device plugin。
快速参考
# 宿主机层
nvidia-smi -L
ls -l /dev/nvidia*
# Kubernetes 层
kubectl get node <NODE> -o jsonpath='{.status.capacity}{"\n"}{.status.allocatable}{"\n"}'
kubectl -n kube-system logs <DEVICE_PLUGIN_POD> --tail=100
# 容器运行时层
crictl ps -a --name nvidia-device-plugin
crictl inspect <CONTAINER_ID>
# 确认真正恢复
kubectl get node <NODE> -o jsonpath='{.status.capacity.nvidia\.com/gpu}{" / "}{.status.allocatable.nvidia\.com/gpu}{"\n"}'
kubectl -n kube-system logs <DEVICE_PLUGIN_POD> --tail=30
最重要的判断只有一句话:宿主机 GPU 正常,只代表硬件和驱动正常;只有 device plugin 成功向 kubelet 注册,GPU 才真正可调度。

浙公网安备 33010602011771号