核心铁律:/dev/nvidia0= 物理 GPU 第 1 块,/dev/nvidia1= 物理 GPU 第 2 块…… 一个编号文件就对应一块实实在在的 GPU 硬件,程序认这个文件就等于认这块 GPU
以 ** 服务器插了 4 块物理 GPU(对应/dev/nvidia0~nvidia3)** 为例,结合官方文档与实践规范修正细节,完整保留全流程逻辑,新增一站式测试脚本与避坑指南,形成 “原理 + 操作 + 验证 + 排错” 的完整闭环。
1. 宿主机底层准备:先让系统 “看见” GPU 文件
这一步是所有操作的基础,没做好后面全白搭,需严格遵循版本兼容要求:
- 装 GPU 驱动:
- 要求:安装与 GPU 型号匹配的 NVIDIA 官方驱动,版本≥418.81.07(支持 CUDA 的最低驱动版本, newer 驱动向下兼容旧 CUDA 版本);需先禁用系统自带的 nouveau 内核模块(否则驱动无法加载)。
- 禁用 nouveau 步骤(必做):
bash
sudo tee /etc/modprobe.d/blacklist-nouveau.conf <<< "blacklist nouveau" sudo tee -a /etc/modprobe.d/blacklist-nouveau.conf <<< "options nouveau modeset=0" sudo update-initramfs -u && sudo reboot - 验证禁用:重启后执行
lsmod | grep nouveau,无输出则禁用成功。 - 驱动安装后效果:系统自动在
/dev目录生成两类文件:- 核心设备文件:
/dev/nvidia0-nvidia3(4 块 GPU 对应 4 个文件,少一个说明识别失败); - 辅助控制文件:
/dev/nvidiactl(总控制器)、/dev/nvidia-modeset(模式设置)、/dev/nvidia-uvm(统一内存管理)—— 必须与核心文件同时存在。
- 核心设备文件:
- 验证驱动:执行
nvidia-smi,能看到 4 块 GPU 的型号、驱动版本、CUDA 版本,说明驱动安装成功。
- 装 “桥梁工具”:nvidia-container-toolkit:
- 作用:让容器运行时(containerd/CRI-O)能识别并挂载 GPU 设备文件,版本需≥1.7.0。
- 配置 containerd(关键修正:需注册 NVIDIA 运行时):
bash
# 安装工具后配置运行时 nvidia-ctk runtime configure --runtime=containerd --set-as-default # 验证配置:/etc/containerd/config.toml 中会新增 nvidia 运行时配置 cat /etc/containerd/config.toml | grep -A 5 "nvidia" # 重启containerd生效 systemctl restart containerd
2. 给 GPU 节点设 “门禁”:标签 + 污点
GPU 很贵,需通过 “标签 + 污点” 确保资源不被滥用,新增 K8s 默认识别标签:
- 第一步:打标签:除自定义标签外,需保留 K8s 默认 GPU 标签(设备插件依赖此标签调度):
bash
# 自定义标签:标记GPU数量和型号 kubectl label nodes <你的GPU节点名> gpu-count=4 gpu-model=Tesla-T4 # 默认标签:K8s识别GPU节点的核心标签(必加) kubectl label nodes <你的GPU节点名> feature.node.kubernetes.io/pci-10de.present=true - 第二步:加污点:设 “门禁”,仅允许带容忍的 Pod 调度:
bash
kubectl taint nodes <你的GPU节点名> hardware=gpu:NoSchedule- 注意:若 GPU 节点已存在其他 NoSchedule 污点,需在 Pod 中添加对应容忍,否则设备插件 Pod 无法部署。
3. 部署 GPU 设备插件:让 K8s “认识” GPU 资源
K8s 本身不识别 GPU,需部署 NVIDIA Device Plugin(“翻译官”),必须用 DaemonSet 方式:
- 核心要求:
- K8s 集群版本≥1.10,插件与 K8s 版本需兼容(推荐 v0.14.0 及以上);
- 插件通过 NVML 库扫描 GPU,依赖宿主机驱动和运行时配置正确。
- 部署命令(官方稳定版本):
bash
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.0/nvidia-device-plugin.yml - 关键验证(新增):
- 查看插件 Pod 状态:
kubectl get pods -n kube-system -l app=nvidia-device-plugin,需为 Running; - 查看插件日志(排查故障核心):
kubectl logs -n kube-system <插件Pod名>,无 “CUDA error”“connection refused” 等错误; - 验证 K8s 资源注册:
kubectl describe node <你的GPU节点名> | grep "nvidia.com/gpu",能看到capacity: nvidia.com/gpu: 4,说明资源已上报。
- 查看插件 Pod 状态:
- 插件的 3 个核心工作:
- 扫描节点
/dev/nvidia0~3及 NVML 库,确认 GPU 数量和健康状态; - 向 K8s APIServer 注册资源
nvidia.com/gpu: 4,纳入集群资源管理; - 实时监控 GPU 状态,若某块 GPU 故障(如驱动崩溃),立即上报 K8s 标记为不可用。
- 扫描节点
4. 写 Pod 配置:申请 GPU = 要对应文件
Pod 配置需严格遵循 K8s GPU 调度规范,修正环境变量用法与资源声明规则:
4.1 基础版 Pod YAML(随机分配 2 块 GPU)
yaml
apiVersion: v1 kind: Pod metadata: name: gpu-2-pod spec: containers: - name: cuda-app image: nvidia/cuda:12.2.0-runtime command: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 2 # 必选:GPU只能在limits中声明,requests默认等于limits requests: nvidia.com/gpu: 2 # 可选:若指定,必须与limits值相等(K8s强制要求) env: - name: NVIDIA_DRIVER_CAPABILITIES # 新增:指定容器所需驱动能力(nvidia-smi依赖utility) value: "compute,utility" nodeSelector: gpu-count: "4" gpu-model: Tesla-T4 feature.node.kubernetes.io/pci-10de.present: "true" # 匹配默认GPU标签 tolerations: - key: "hardware" operator: "Equal" value: "gpu" effect: "NoSchedule"4.2 进阶版 Pod YAML(指定 GPU 卡号 / UUID)
NVIDIA_VISIBLE_DEVICES支持卡号、UUID 两种指定方式(修正原单一用法):
yaml
apiVersion: v1 kind: Pod metadata: name: gpu-specify-pod spec: containers: - name: cuda-app image: nvidia/cuda:12.2.0-runtime command: ["nvidia-smi"] env: - name: NVIDIA_VISIBLE_DEVICES # 方式1:指定卡号(0和2) value: "0,2" # 方式2:指定UUID(需先通过nvidia-smi -L获取) # value: "GPU-5da6e67e-fd5a-88fb-7a0e-109c3284f7bf,GPU-242b3020-8e5c-813a-42d9-475766d52f9d" - name: NVIDIA_DRIVER_CAPABILITIES value: "compute,utility" resources: limits: nvidia.com/gpu: 2 nodeSelector: feature.node.kubernetes.io/pci-10de.present: "true" tolerations: - key: "hardware" value: "gpu" effect: "NoSchedule"- 关键说明:
NVIDIA_VISIBLE_DEVICES还支持all(所有 GPU)、none(禁用 GPU)等值,需配合NVIDIA_DRIVER_CAPABILITIES使用才能生效。
5. K8s 调度 + 挂载:把 GPU 文件 “搬” 到容器里
提交 Pod 配置后,K8s 自动完成 5 个关键步骤,补充核心细节:
- 调度器筛选节点:同时满足 3 个条件 ——① 有
feature.node.kubernetes.io/pci-10de.present=true标签;② 容忍hardware=gpu污点;③nvidia.com/gpu空闲数≥2; - 分配 GPU 资源:K8s 通过设备插件获取空闲 GPU 列表,随机选择 2 个(如
nvidia1、nvidia3),并锁定资源防止重复分配; - 设备文件挂载:
nvidia-container-toolkit不仅挂载核心文件(nvidia1、nvidia3)和辅助文件,还会挂载驱动依赖的动态链接库(如/usr/local/nvidia/lib); - 资源隔离:通过 cgroup 限制容器仅能访问分配的 GPU,同时通过环境变量
LD_LIBRARY_PATH自动配置库路径,确保程序能调用 GPU; - 容器启动:
nvidia-smi依赖NVIDIA_DRIVER_CAPABILITIES=utility才能运行,否则会提示 “command not found” 或 “no devices found”。
6. 一站式测试脚本:部署→验证→删除
编写gpu-test-all.sh脚本,补充错误处理与关键验证步骤,避免手动操作失误:
6.1 脚本内容
bash
#!/bin/bash # K8s GPU测试一键脚本(含错误处理) # 使用前替换 <你的GPU节点名> 为实际节点名称 set -e # 遇到错误立即退出,避免资源残留 # 定义变量 POD_NAME="gpu-2-pod" NODE_NAME="<你的GPU节点名>" YAML_FILE="gpu-test-pod.yaml" # 前置检查:确认kubectl能连接集群 if ! kubectl cluster-info &> /dev/null; then echo "ERROR: 无法连接K8s集群,请检查kubeconfig配置" exit 1 fi # 前置检查:确认节点存在 if ! kubectl get node ${NODE_NAME} &> /dev/null; then echo "ERROR: 节点 ${NODE_NAME} 不存在,请检查节点名" exit 1 fi # 步骤1:生成Pod YAML文件 echo "===== 1. 生成GPU测试Pod YAML文件 =====" cat > ${YAML_FILE} << EOF apiVersion: v1 kind: Pod metadata: name: ${POD_NAME} spec: containers: - name: cuda-app image: nvidia/cuda:12.2.0-runtime command: ["nvidia-smi"] env: - name: NVIDIA_DRIVER_CAPABILITIES value: "compute,utility" resources: limits: nvidia.com/gpu: 2 requests: nvidia.com/gpu: 2 nodeSelector: gpu-count: "4" gpu-model: Tesla-T4 feature.node.kubernetes.io/pci-10de.present: "true" tolerations: - key: "hardware" operator: "Equal" value: "gpu" effect: "NoSchedule" EOF # 步骤2:部署Pod echo -e "\n===== 2. 部署GPU测试Pod =====" kubectl apply -f ${YAML_FILE} # 步骤3:等待Pod执行完成(nvidia-smi执行后Pod会变为Completed状态) echo -e "\n===== 3. 等待Pod执行完毕(最长等待30秒) =====" if ! kubectl wait pod/${POD_NAME} --for=condition=Completed --timeout=30s; then echo -e "\nERROR: Pod执行超时或失败,查看日志:kubectl logs ${POD_NAME}" kubectl delete pod ${POD_NAME} --force rm -f ${YAML_FILE} exit 1 fi # 步骤4:验证1 - 查看容器内的GPU设备文件 echo -e "\n===== 4. 容器内的GPU设备文件 =====" kubectl exec ${POD_NAME} -- ls /dev/nvidia* # 步骤5:验证2 - 查看GPU信息日志(核心验证) echo -e "\n===== 5. GPU信息日志 =====" kubectl logs ${POD_NAME} | grep -E "GPU|Driver Version|CUDA Version" # 步骤6:删除Pod,释放GPU资源 echo -e "\n===== 6. 删除Pod,释放GPU资源 =====" kubectl delete -f ${YAML_FILE} rm -f ${YAML_FILE} echo -e "\n===== 测试完成!所有GPU资源已释放 ====="6.2 脚本使用方法
- 替换脚本中的
<你的GPU节点名>为实际节点名称; - 赋予执行权限:
chmod +x gpu-test-all.sh; - 运行脚本:
./gpu-test-all.sh。
6.3 预期输出
- 设备文件:2 个核心 GPU 文件(如
nvidia1、nvidia3)+ 3 个辅助文件; - GPU 日志:显示 2 块 GPU 的型号、驱动版本、CUDA 版本,无报错;
- 最终状态:Pod 被成功删除,
kubectl describe node <节点名>中nvidia.com/gpu空闲数恢复为 4。
7. 手动验证 + 回收
若不想用脚本,可手动执行以下操作,新增故障排查命令:
- 部署 Pod:
kubectl apply -f gpu-test-pod.yaml - 查看 Pod 状态:
kubectl get pods -w(等待状态变为 Completed) - 验证 1:容器内 GPU 文件:
kubectl exec -it ${POD_NAME} -- ls /dev/nvidia* - 验证 2:GPU 信息:
kubectl logs ${POD_NAME} - 排错命令(新增):
- 若 PodPending:
kubectl describe pod ${POD_NAME},查看 “Events” 栏(常见原因:资源不足、污点未容忍、节点标签不匹配); - 若 PodCrashLoopBackOff:
kubectl logs ${POD_NAME} --previous,查看启动错误(常见原因:驱动不兼容、运行时未配置);
- 若 PodPending:
- 手动回收:
kubectl delete pod ${POD_NAME}
8. 核心细节 + 避坑指南
8.1 关键规范
- GPU 资源声明规则:只能在
limits中指定,requests若指定必须与limits相等,K8s 不支持 GPU 资源的弹性调度(因 GPU 是独占设备); - 环境变量必填项:
NVIDIA_DRIVER_CAPABILITIES必须指定(至少包含compute,运行nvidia-smi需加utility),否则容器无法调用 GPU 或工具; - 插件部署要求:必须用 DaemonSet,且 Pod 需能访问 kubelet 的设备插件套接字(
/var/lib/kubelet/device-plugins/nvidia.sock),否则无法注册资源。
8.2 常见坑与解决方案
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 插件 Pod 启动失败,日志显示 “CUDA error: no CUDA-capable device” | 驱动未安装或 nouveau 未禁用 | 重新安装驱动,执行禁用 nouveau 步骤并重启 |
| PodPending,提示 “Insufficient nvidia.com/gpu” | 节点未上报 GPU 资源,或资源已被占用 | 1. 查看插件日志;2. 验证kubectl describe node是否有nvidia.com/gpu;3. 检查节点是否有未删除的 GPU Pod |
容器内执行nvidia-smi提示 “command not found” | 未配置NVIDIA_DRIVER_CAPABILITIES=utility | 在 Pod YAML 中添加该环境变量 |
| 运行时配置后仍无法挂载 GPU | containerd 未重启或未设为默认运行时 |