TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

GPU Operator 栈剖析与故障排查

状态:草稿(2026-10-07 更新),反映了部分实测结果。 内容基于官方文档确认的叙述,其中 **第 5 节中 containerd 通过 SIGHUP 重启的路径(在一次性虚拟机中直接确认)** 以及读取节点的 containerd 版本和 Unit 配置是仅有的直接确认值。其余内容均以文档为准。 请先阅读文末的“确认的版本与日期”以及“未确认的事项”。 编写规则:实测数据为基础,未能测量的内容标记为“未实测”。

目录

摘要

GPU Operator 是一个能够将以往需要手动在每个 GPU 节点上安装的驱动、容器运行时配置、设备插件、指标收集器声明为一个叫做 ClusterPolicy 对象的算子。本文按顺序梳理了组件的角色与失败形态、Pod 获取 GPU 所经的两个关卡、按症状的排查顺序以及容量规划的陷阱。

读完本文后能够在面试中解释的内容

  1. 算子所启动的各个 DaemonSet 的角色与顺序依赖,以及失败时暴露的位置
  2. 调度器关注的扩展资源与运行时关注的处理程序(Handler)、CDI 这两个关卡,以及当前变更为以 CDI 默认值的路径
  3. Insufficient nvidia.com/gpu 产生于不同原因却显示相同语句的原因以及区分它们的顺序

1. 组件:谁在做什么

算子通过节点标签决定在哪个节点上启动什么。当 NFD(Node Feature Discovery)附加了 PCI 厂商标签(feature.node.kubernetes.io/pci-10de.present 等)后,算子会将其判断为 GPU 节点并附上 nvidia.com/gpu.present 和 nvidia.com/gpu.deploy.<이름>=true ,各个 DaemonSet 通过 nodeSelector 读取该标签(已确认算子源码)。顺序依赖由 Init 容器负责。Toolkit 必须先通过 driver-validation,设备插件必须先通过 toolkit-validation 才能启动。

组件 职责 失败形态
NFD, GFD 将硬件事实转化为标签。GFD 为 gpu.product·gpu.count 等 无标签或无法容忍污点时,DaemonSet DESIRED 为 0 且无报错
驱动 内核模块。容器或主机预安装 nvidia-smi 失败,其他 Pod 在 Init 中等待(包含nouveau 冲突)
Container Toolkit nvidia 在 containerd 配置中注册运行时处理器 no runtime for "nvidia" is configured
Device Plugin 注册到 kubelet,上报设备列表, Allocate,XID 监控 nvidia.com/gpu 不存在或减少
DCGM Exporter 暴露指标 无法连接到分离的 DCGM 时 CrashLoopBackOff
MIG Manager 应用 MIG 配置文件 在不支持 MIG 的 GPU 上无意义
Validator 按 driver、toolkit、cuda、plugin 的顺序进行验证 在 NVSwitch 节点上,如果没有 Fabric Manager,则 system not yet initialized

Fabric Manager 是在 NVSwitch 系统(如 HGX 等)中配置 GPU 间内存 Fabric 的独立服务。算子的驱动容器检测到 NVSwitch 时会安装它,如果使用主机驱动则需要手动安装。

2. Pod 如何获取 GPU

flowchart TD
  P["디바이스 플러그인<br/>kubelet 소켓에 등록, 장치 목록 전달"] --> N["kubelet<br/>노드 status 의 capacity·allocatable 갱신"]
  N --> S["kube-scheduler (NodeResourcesFit)<br/>요청 ≤ allocatable − 이미 배정 ?"]
  S --> A["kubelet: Allocate 호출<br/>응답: 장치 노드·환경변수·마운트·CDI 이름"]
  A --> R["containerd<br/>CDI 스펙 적용 또는 nvidia 런타임이 OCI 스펙 수정"]
  R --> C["runc 가 컨테이너 시작"]

关卡 1 是数字。 调度器查看的不是设备,而是节点 status 中的数字。GPU 只能用于 limits (如果使用requests ,则值必须相同)并且是整数。如果与插件的连接中断,kubelet 会将设备置为 unhealthy,将 allocatable 降为 0,5 分钟的宽限期过后会删除资源名称(已确认 kubelet 源码)。即使健康监控导致单个设备变为 unhealthy,capacity 保持不变,只有 allocatable 会减少(官方文档)。

关卡 2 是注入方式。 过去是 nvidia-container-runtime 包裹 runc 并在 prestart 钩子中调用 nvidia-container-cli 的方式,自算子 25.10.0 起默认使用 CDI(Container Device Interface)。普通的业务负载不需要 runtimeClassName 。但算子自身的 operand Pod 会由控制器附加 runtimeClassName: nvidia (使用 NRI 插件时除外,已确认源码),因此如果处理器消失,插件将无法启动,上报也会中断。

3. 安装方式选择

方式 优点 成本
驱动容器 由算子通过升级状态机(cordon、等待、删除 GPU Pod、可选 drain)进行管理 必须统一所有 GPU 节点的 OS 版本。内核升级时必须重新匹配驱动
主机预安装(driver.enabled=false) 允许混合 OS 算子不管理主机驱动的生命周期
预编译镜像 不在节点上构建 由于标签格式为 <브랜치>-<커널>-<OS> ,因此必须存在内核版本匹配的镜像

驱动是内核模块,更换前必须停止所有 GPU 客户端。文档(Ubuntu 22.04 脚注)建议关闭 unattended-upgrades 的自动内核升级,升级停止后查找 nvidia.com/gpu-driver-upgrade-state=upgrade-failed 标签节点。作者的实验环境使用主机驱动(595 系列)和 toolkit.enabled=true 的 v26.3.3。在 Secure Boot 节点上,DKMS 模块可能会因签名拒绝而无法加载,如果使用发行版签名的模块包,由于该模块绑定了内核版本,因此必须使内核版本与驱动相匹配。26.3.x 的支持状态为“Deprecated”,26.7.x 为“Supported”。消费级 GPU 不在文档支持的产品列表中,因此作者的环境应被视为超出支持范围。

4. 按症状排查顺序

GPU 파드가 안 된다
├─ 파드 오브젝트가 없다 ........ 어드미션: ResourceQuota, 없는 RuntimeClass (apply 때 에러)
└─ 있다
   ├─ spec.nodeName 이 비어 있다 (Pending)
   │   ├─ 이벤트에 taint/selector ... 톨러레이션, 라벨
   │   └─ Insufficient nvidia.com/gpu → 노드별로 capacity / allocatable / 배정 합을 대조
   │        ├─ 자원 이름이 없다 ........ 플러그인 미등록 (5분 유예 경과 포함)
   │        ├─ capacity > allocatable .. unhealthy 장치 (XID), 또는 플러그인 연결 끊김 직후
   │        └─ 배정 합 = allocatable ... 진짜 자리 소진
   └─ 노드에 붙었다
       ├─ no runtime for "nvidia" ..... containerd config dump 의 런타임 목록
       ├─ operand 가 Init 에서 정지 .... 선행 단계(드라이버, 툴킷) 로그
       └─ Running 인데 nvidia-smi 실패 . limits 누락, 드라이버/NVML, 장치 접근 상실
                                        (노드에서 dmesg | grep -iE 'NVRM|Xid')

如果调度器遇到 요청 > allocatable − 이미 배정 ,无论原因如何,都会写入 Insufficient <자원 이름> (已确认 kube-scheduler 源码)。因此,切勿仅凭一行事件就断定原因。

5. 常见陷阱:Drop-in 覆盖了整个 CRI 配置

在作者的实验环境中,4 张 GPU 在长达一周的时间里没有被上报。总结如下:

外部证据确认的内容如下。containerd 1.7 源码中的 mergeConfig 如注释所述“整体替换 Plugins 的值”,官方 man 页面的“map 是 append”这句话无法解释该行为。NVIDIA Toolkit 源码中有注释指出,在 1.7 等旧版本中,插件段仅按键单位合并,因此需要在 Drop-in 中复制整个 CRI 配置。在 2.x 源码中未看到此替换代码,但未确认是否会出现相同的复现。Toolkit 在退出时会删除自己的 Drop-in 文件。作者看到的一种解释写着“通过 SIGHUP 无法注册 Handler”,但由于 containerd 源码的处理信号列表(1.7、main)中没有 SIGHUP,且 Toolkit 的默认重启方式是 SIGHUP,根据我的理解,这是进程退出后由 systemd 恢复的路径(上游 Unit 为 Restart=always)。该路径已在一次性 VM 中得到直接确认。将 Ubuntu 软件包的 containerd 2.2.1 作为独立服务启动,并使用 systemctl kill -s HUP 发送三次 SIGHUP,在这三次中, 进程均终止并变为了新进程(PID 变更,状态经历 activating 变为 active)。日志中记录了 Sent signal SIGHUP to main process 之后打印了 Scheduled restart job, Started containerd.service ,Unit 为 Restart=always,重启等待时间 5 秒。将日志级别更改为 debug 的配置文件发送 SIGHUP 时,也启动了新进程并读取了新配置,因此这不是配置重载,而是 通过重启进行应用。读取作者环境的节点发现,containerd 部分为 1.7.27,其余为 2.3.5,Unit 均为 Restart=always,重启等待时间 5 秒(仅读取,未发送 SIGHUP)。局限性:测试的版本为 2.2.1,未测试 1.7 中的 SIGHUP(已确认源码中的处理信号列表无 SIGHUP)。

6. 容量规划

7. 动手尝试(只读)

# 노드별 광고량
kubectl get nodes -o custom-columns=N:.metadata.name,CAP:.status.capacity.nvidia\.com/gpu,ALLOC:.status.allocatable.nvidia\.com/gpu
# operand 현황과 NFD 라벨
kubectl -n gpu-operator get ds,po -o wide
kubectl get nodes -L nvidia.com/gpu.present,nvidia.com/device-plugin.config
# 각 GPU 노드에서: 파일이 아니라 로드된 것을 본다 (0 이면 사고)
containerd config dump | grep -c runtimes.nvidia; ls /etc/containerd/conf.d
# 툴킷이 SIGHUP 을 보낸 시각에 containerd 가 어떻게 되었나
sudo journalctl -u containerd --since "-7d" | grep -E "Started|Stopped|signal" | tail

通过临时 Pod 结束确认只需一行 nvidia-smi -L (kubectl -n gpu-operator run gputest --rm -it --restart=Never ... --limits=nvidia.com/gpu=1 -- nvidia-smi -L)。该 Pod 短暂占用一个槽位后通过 --rm 清理。没有使用其他更改命令。

8. 面试常见问题

  1. GPU Pod 处于 Pending 状态。从哪里开始看? 对比事件语句与节点 status 的 capacity、allocatable 和分配总和。相同的 Insufficient 会在插件未注册、设备不健康以及名额耗尽时均出现。
  2. 请说明 Pod 获取 GPU 的路径。 顺序为:插件注册、节点 status 广播、调度器数值判定、 Allocate, CDI 或 NVIDIA 运行时注入、runc。前面是调度器的关卡,后面是运行时的关卡。
  3. 将驱动放在容器中与放在宿主机上有何区别? 容器方式具有自动化升级和操作系统一致性限制,宿主机方式则具有自由度和直接管理的负担。共同的风险在于内核变更时驱动必须跟进。
  4. 分时复用(Time-slicing)和 MIG 的容量是如何计算的? 分时复用只是广播数量变为 N 倍,内存保障为 0。MIG 是硬件切片,因此总和可能小于物理容量。
  5. 从 containerd 兜底/分发配置(drop-in)事故中学到了什么? 必须了解合并规则,即修改相同插件的最后一个文件会接管整个配置。判定依据不是文件,而是 containerd config dump ,并且更改应逐个节点应用。

9. 常见误解

本文的组件表按 Pod 单位进行了解析,详见第 23 篇(GPU Operator Pod 组件与角色,是否可以不需要宿主机驱动)。

10. 参考资料与确认的版本

全部于 2026-10-05 直接访问。

确认的版本: GPU Operator 26.7.1(2026-08-21,工具包 1.20.1,设备插件 0.20.1,DCGM Exporter 4.6.1-4.8.4),Kubernetes 稳定版 v1.37.1,containerd 2.4.1 及 1.7.36。作者的实验环境为 GPU Operator v26.3.3。

未能确认的内容: 26.7.0 的 containerd 最低版本在文档中写为「从 1.8 到 2.0」的原因(1.8 似乎是笔误)、在 containerd 1.7 中观测到的 SIGHUP(仅在 2.2.1 中测试过)、2.x 中的合并行为、安装文档中的「如果没有驱动,插件会静默报告 0 个」(插件 README 写作 FAIL_ON_INIT_ERROR 默认值为 true,因此会报错)、事故发生时的 operand Pod 状态记录。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论