状态:草稿(2026-10-07 更新),反映了部分实测结果。 内容基于官方文档确认的叙述,其中 **第 5 节中 containerd 通过 SIGHUP 重启的路径(在一次性虚拟机中直接确认)** 以及读取节点的 containerd 版本和 Unit 配置是仅有的直接确认值。其余内容均以文档为准。 请先阅读文末的“确认的版本与日期”以及“未确认的事项”。 编写规则:实测数据为基础,未能测量的内容标记为“未实测”。
目录
- 摘要
- 1. 组件:谁在做什么
- 2. Pod 如何获取 GPU
- 3. 安装方式选择
- 4. 按症状排查顺序
- 5. 常见陷阱:Drop-in 覆盖了整个 CRI 配置
- 6. 容量规划
- 7. 动手尝试(只读)
- 8. 面试常见问题
- 9. 常见误解
- 10. 参考资料与确认的版本
摘要
GPU Operator 是一个能够将以往需要手动在每个 GPU 节点上安装的驱动、容器运行时配置、设备插件、指标收集器声明为一个叫做 ClusterPolicy 对象的算子。本文按顺序梳理了组件的角色与失败形态、Pod 获取 GPU 所经的两个关卡、按症状的排查顺序以及容量规划的陷阱。
读完本文后能够在面试中解释的内容
- 算子所启动的各个 DaemonSet 的角色与顺序依赖,以及失败时暴露的位置
- 调度器关注的扩展资源与运行时关注的处理程序(Handler)、CDI 这两个关卡,以及当前变更为以 CDI 默认值的路径
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 在长达一周的时间里没有被上报。总结如下:
- 由于通过 HTTP 访问的私有仓库,在
conf.d中添加了一个仓库配置 Drop-in。内容仅有 CRI 插件的registry.config_path三行。 - 最后修改相同 CRI 插件的文件会接管整个配置。对 containerd 1.7.27 的实测显示:
99-nvidia.toml单独 6 个,仓库 Drop-in 单独 0 个,两者同时存在则为 0 个。 cat 99-nvidia.toml正常,并且config dump中也显示了runc和看似合理的默认值,导致两次误诊。逐个移除 Drop-in 并重新转储(dump)后,原因才浮出水面。- 记录显示,当对四个节点同时应用时间切片(Time-slicing)时,Toolkit 重新运行并使配置生效。此后,流程变更为逐个节点应用。
外部证据确认的内容如下。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. 容量规划
- capacity 与 allocatable:不健康的设备仅从 allocatable 中剔除。容量按 allocatable 计算。
- allocatable 与实际使用:调度器查看分配情况,DCGM 查看使用情况。在作者 3 小时的实测中,一张 24GB 级消费级 GPU 的使用率为 0,但占用了 13.7 GiB 的帧缓冲(Framebuffer),功耗为 21 W。这是一张处于闲置但并非完全空闲的显卡。
- 时间切片(Time-slicing):
replicas: N是将同一设备注册 N 次,内存和错误无法隔离。例如,如果将 24 GB GPU 宣告为 5 个,则每个槽位平均 4.8 GB 仅为说明性质的算术,保证值为 0。failRequestsGreaterThanOne: true拒绝大于等于 2 的请求。算子不会监视配置 ConfigMap 的更改,且在分时复用期间 DCGM 无法将容器归属(官方文档)。 - MIG:A100 40GB 具有 8 个 5 GB 内存切片和 7 个 SM 切片,因此
1g.5gb最多为 7 个,剩余 5 GB(根据文档结构计算)。由于支持列表中没有消费级 GPU,因此无法在作者的实验环境中使用。
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. 面试常见问题
- GPU Pod 处于 Pending 状态。从哪里开始看? 对比事件语句与节点 status 的 capacity、allocatable 和分配总和。相同的
Insufficient会在插件未注册、设备不健康以及名额耗尽时均出现。 - 请说明 Pod 获取 GPU 的路径。 顺序为:插件注册、节点 status 广播、调度器数值判定、
Allocate, CDI 或 NVIDIA 运行时注入、runc。前面是调度器的关卡,后面是运行时的关卡。 - 将驱动放在容器中与放在宿主机上有何区别? 容器方式具有自动化升级和操作系统一致性限制,宿主机方式则具有自由度和直接管理的负担。共同的风险在于内核变更时驱动必须跟进。
- 分时复用(Time-slicing)和 MIG 的容量是如何计算的? 分时复用只是广播数量变为 N 倍,内存保障为 0。MIG 是硬件切片,因此总和可能小于物理容量。
- 从 containerd 兜底/分发配置(drop-in)事故中学到了什么? 必须了解合并规则,即修改相同插件的最后一个文件会接管整个配置。判定依据不是文件,而是
containerd config dump,并且更改应逐个节点应用。
9. 常见误解
- 「
nvidia-smi「在宿主机上能行的东西在 Pod 里也行」:运行时处理程序和广播是两码事。 - 「
requests「在资源中写 0.5 就是半张卡」:GPU 是整数扩展资源。 - 「
replicas: 5「分时复用将内存平分成 5 份」:它们共享同一块 VRAM。 - 「只要部署了 Operator,containerd 配置就是安全的」:工具包会写入宿主机文件。
本文的组件表按 Pod 单位进行了解析,详见第 23 篇(GPU Operator Pod 组件与角色,是否可以不需要宿主机驱动)。
10. 参考资料与确认的版本
全部于 2026-10-05 直接访问。
- https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/ 的 release-notes、platform-support、getting-started、troubleshooting、cdi、gpu-driver-upgrades、gpu-sharing、gpu-operator-mig 页面
- https://github.com/NVIDIA/gpu-operator (controllers/object_controls.go, state_manager.go, assets/)
- https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/ (install-guide, arch-overview, cdi-support)
- https://github.com/NVIDIA/nvidia-container-toolkit (cmd/nvidia-ctk-installer, pkg/config/engine/containerd)
- https://github.com/NVIDIA/k8s-device-plugin (README.md)
- https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
- https://github.com/kubernetes/kubernetes (noderesources/fit.go, pkg/kubelet/cm/devicemanager, master)
- https://github.com/containerd/containerd (config.go 1.7, main_unix.go, docs/man/containerd-config.toml.5.md)
- https://docs.nvidia.com/datacenter/tesla/mig-user-guide/ 的 supported-gpus、concepts 页面
确认的版本: 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 状态记录。