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 #251systemctl daemon-reload 重新应用 systemd 管理的 device cgroup 后,容器可能丢失 NVIDIA 设备访问权限,随后出现 Failed to initialize NVML: Unknown Error。维护者在 2025 年 11 月确认这是已知问题,并表示 Toolkit v1.18.0 已覆盖许多场景,但没有承诺覆盖全部组合。
  • NVIDIA k8s-device-plugin #426daemon-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-reloadkubelet.sockUnknown ErrorunhealthyRegistered 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 才真正可调度。

posted @ 2026-09-03 12:49  Hello_worlds  阅读(28)  评论(0)    收藏  举报