HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • AI 基础设施深度教程

    • AI Infra 深度教程
    • GPU容器化

      • 01-GPU 架构基础
      • NVIDIA 容器运行时
      • GPU 共享与隔离
      • GPU 监控与调试
    • Kubernetes GPU调度

      • Device Plugin 机制深度解析
      • GPU 调度器实现
      • 拓扑感知调度
      • 弹性 GPU 调度
    • AI训练平台

      • 分布式训练框架
      • 训练任务调度
      • 模型存储与管理
      • 实验管理
      • 超参数优化
    • 推理服务

      • 推理引擎原理
      • 模型服务框架
      • 动态批处理
      • 推理优化技术
      • 多模型服务
    • 异构计算

      • 05-异构计算
      • 异构计算概述
      • GPU 虚拟化技术
      • NPU 与专用 AI 芯片
      • 设备拓扑感知调度
      • 算力池化与弹性调度
    • AI工作流引擎

      • 06-AI工作流引擎
      • AI 工作流引擎概述
      • Kubeflow Pipelines 深度实践
      • 03-Argo Workflows 深度实践
      • 04-数据版本管理
      • 05-实验跟踪与模型注册
    • MLOps实践

      • 07-MLOps实践
      • 01-MLOps 成熟度模型
      • 02-数据集工程
      • 03-Feature Store 特征存储
      • 04-模型评测体系
      • 05-模型安全与治理
    • AIOps实践

      • 08-AIOps实践
      • 01-AIOps概述与架构
      • 02-异常检测算法
      • 03-根因分析与告警聚合
      • 04-智能运维决策
      • 05-AIOps平台实战
    • 面试专题

      • 09-面试专题
      • 01-AI基础设施核心面试题
      • 02-大模型面试题
      • 03-系统设计面试题
    • CUDA编程与算子开发

      • 10-CUDA 编程与算子开发
      • 01-CUDA编程模型与内存层次
      • 02-高性能 Kernel 开发实战
      • 03-Tensor Core 与矩阵运算
      • 04-算子融合与优化技术
      • 05-Triton 编程入门
    • 通信与网络底层

      • 11-通信与网络底层
      • 01-NCCL 源码深度解析
      • 02-AllReduce 算法实现
      • 03-RDMA与InfiniBand原理
      • 04-网络拓扑与通信优化
      • 05-大规模集群网络架构
    • 框架源码解析

      • 12-框架源码解析
      • 01-PyTorch分布式源码解析
      • 02-DeepSpeed源码深度解析
      • 03-Megatron-LM源码解析
      • 04-vLLM推理引擎源码解析
      • 05-HuggingFace Transformers源码解析
    • 编译优化与图优化

      • 13-编译优化与图优化
      • 01-深度学习编译器概述
      • 02-TorchDynamo与torch.compile
      • 03-XLA编译器深度解析
      • 04-算子融合与Kernel优化
      • 05-自动调度与代码生成

NVIDIA 容器运行时

深入理解 GPU 如何在容器中工作,从底层原理到生产实践

本章目标

  • 理解容器访问 GPU 的底层原理
  • 掌握 NVIDIA Container Toolkit 架构
  • 深入分析 nvidia-container-runtime 工作机制
  • 学会配置和调试 GPU 容器环境

1. 容器访问 GPU 的挑战

1.1 问题背景

在传统容器中,进程运行在隔离的 Namespace 中,无法直接访问宿主机的硬件设备。GPU 作为 PCI 设备,需要解决以下问题:

┌─────────────────────────────────────────────────────────────┐
│                    容器访问 GPU 的挑战                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 设备文件隔离                                              │
│     - /dev/nvidia* 设备不在容器 namespace 中                  │
│     - 需要将设备文件映射到容器内                                │
│                                                             │
│  2. 驱动依赖                                                 │
│     - GPU 驱动运行在宿主机内核                                 │
│     - 容器内需要匹配的用户态库                                  │
│     - 版本兼容性问题                                          │
│                                                             │
│  3. 资源隔离                                                 │
│     - GPU 显存如何隔离?                                      │
│     - 计算资源如何分配?                                       │
│     - 多容器共享 GPU 如何处理?                                │
│                                                             │
│  4. 运行时集成                                               │
│     - Docker/containerd 如何感知 GPU?                       │
│     - Kubernetes 如何调度 GPU?                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 GPU 驱动架构

理解 NVIDIA 驱动架构是理解容器 GPU 访问的基础:

┌─────────────────────────────────────────────────────────────┐
│                   NVIDIA 驱动架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户空间 (User Space)                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  应用程序                                             │   │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐             │   │
│  │  │ PyTorch │  │TensorFlow│ │  CUDA   │             │   │
│  │  └────┬────┘  └────┬────┘  └────┬────┘             │   │
│  │       └───────────┬───────────┘                    │   │
│  │              ┌────▼────┐                           │   │
│  │              │libcuda.so│  CUDA Driver API          │   │
│  │              └────┬────┘                           │   │
│  │              ┌────▼────┐                           │   │
│  │              │libnvidia-│  辅助库                   │   │
│  │              │ ml.so   │  (监控、诊断等)            │   │
│  │              └────┬────┘                           │   │
│  └───────────────────┼─────────────────────────────────┘   │
│                      │ ioctl()                             │
│  ────────────────────┼─────────────────────────────────    │
│                      ▼                                     │
│  内核空间 (Kernel Space)                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              nvidia.ko                              │   │
│  │         (NVIDIA 内核驱动模块)                         │   │
│  │                                                     │   │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐    │   │
│  │  │nvidia-uvm  │  │nvidia-modeset│ │nvidia-drm │    │   │
│  │  │ (统一内存)  │  │  (显示)      │  │ (DRM)     │    │   │
│  │  └────────────┘  └────────────┘  └────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                      │                                     │
│  ────────────────────┼─────────────────────────────────    │
│                      ▼                                     │
│  硬件层 (Hardware)                                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                 NVIDIA GPU                          │   │
│  │          (通过 PCIe 连接到主机)                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.3 关键设备文件

# GPU 相关的设备文件
$ ls -la /dev/nvidia*
crw-rw-rw- 1 root root 195,   0 Dec  1 00:00 /dev/nvidia0      # GPU 0
crw-rw-rw- 1 root root 195,   1 Dec  1 00:00 /dev/nvidia1      # GPU 1
crw-rw-rw- 1 root root 195, 255 Dec  1 00:00 /dev/nvidiactl    # 控制设备
crw-rw-rw- 1 root root 195, 254 Dec  1 00:00 /dev/nvidia-modeset
crw-rw-rw- 1 root root 510,   0 Dec  1 00:00 /dev/nvidia-uvm   # 统一内存
crw-rw-rw- 1 root root 510,   1 Dec  1 00:00 /dev/nvidia-uvm-tools

# 设备文件的作用
# /dev/nvidia0-N    : 每个 GPU 一个设备文件,用于与特定 GPU 通信
# /dev/nvidiactl    : 控制设备,用于驱动初始化和管理
# /dev/nvidia-uvm   : 统一虚拟内存(Unified Virtual Memory)
# /dev/nvidia-modeset: 显示相关(容器中通常不需要)

2. NVIDIA Container Toolkit 架构

2.1 组件概览

┌─────────────────────────────────────────────────────────────────────┐
│                NVIDIA Container Toolkit 组件架构                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │                      容器运行时层                            │  │
│   │  ┌───────────┐  ┌───────────┐  ┌───────────┐               │  │
│   │  │  Docker   │  │containerd │  │  CRI-O    │               │  │
│   │  └─────┬─────┘  └─────┬─────┘  └─────┬─────┘               │  │
│   │        └───────────────┼───────────────┘                    │  │
│   └────────────────────────┼────────────────────────────────────┘  │
│                            ▼                                        │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │              nvidia-container-runtime                        │  │
│   │                                                              │  │
│   │  ┌──────────────────────────────────────────────────────┐   │  │
│   │  │  nvidia-container-runtime-hook                        │   │  │
│   │  │  (OCI prestart hook)                                  │   │  │
│   │  │                                                       │   │  │
│   │  │  1. 解析环境变量 NVIDIA_VISIBLE_DEVICES               │   │  │
│   │  │  2. 调用 libnvidia-container                          │   │  │
│   │  │  3. 注入 GPU 设备和驱动库                              │   │  │
│   │  └──────────────────────────────────────────────────────┘   │  │
│   └────────────────────────────────────────────────────────────┘   │
│                            │                                        │
│                            ▼                                        │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │                 libnvidia-container                          │  │
│   │                                                              │  │
│   │  核心功能:                                                   │  │
│   │  - 发现宿主机 GPU 和驱动                                     │  │
│   │  - 创建设备节点                                              │  │
│   │  - 挂载驱动库到容器                                          │  │
│   │  - 配置 cgroup 设备访问权限                                  │  │
│   │                                                              │  │
│   │  ┌────────────────────────────────────────────────────────┐ │  │
│   │  │ nvidia-container-cli                                   │ │  │
│   │  │ (命令行工具,供调试和手动操作)                           │ │  │
│   │  └────────────────────────────────────────────────────────┘ │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                            │                                        │
│                            ▼                                        │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │                   宿主机驱动和设备                            │  │
│   │  ┌─────────────┐  ┌─────────────┐  ┌──────────────────┐    │  │
│   │  │ /dev/nvidia*│  │ libcuda.so  │  │ nvidia.ko (内核) │    │  │
│   │  └─────────────┘  └─────────────┘  └──────────────────┘    │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2.2 各组件详解

nvidia-container-runtime

// nvidia-container-runtime 是 runc 的封装
// 位置: github.com/NVIDIA/nvidia-container-runtime

// 主要功能:
// 1. 拦截 OCI 规范的容器配置
// 2. 注入 prestart hook
// 3. 调用底层 runc 执行容器

// 工作流程伪代码
func main() {
    // 1. 解析容器 OCI 配置
    spec := loadOCISpec()

    // 2. 检查是否需要 GPU
    if needsGPU(spec) {
        // 3. 注入 nvidia-container-runtime-hook
        spec.Hooks.Prestart = append(spec.Hooks.Prestart, Hook{
            Path: "/usr/bin/nvidia-container-runtime-hook",
            Args: []string{"nvidia-container-runtime-hook", "prestart"},
        })
    }

    // 4. 调用 runc 执行
    exec("runc", args...)
}

// 判断是否需要 GPU 的逻辑
func needsGPU(spec *OCI.Spec) bool {
    for _, env := range spec.Process.Env {
        if strings.HasPrefix(env, "NVIDIA_VISIBLE_DEVICES=") {
            value := strings.TrimPrefix(env, "NVIDIA_VISIBLE_DEVICES=")
            return value != "" && value != "void"
        }
    }
    return false
}

libnvidia-container

这是核心库,负责实际的 GPU 设备注入:

// libnvidia-container 核心数据结构
// 位置: github.com/NVIDIA/libnvidia-container

struct nvc_context {
    char *root;              // 容器 rootfs 路径
    uid_t uid;               // 容器用户 ID
    gid_t gid;               // 容器组 ID
    struct nvc_device *gpus; // GPU 设备列表
    struct nvc_driver_info *driver; // 驱动信息
};

struct nvc_device {
    char *path;              // 设备路径 /dev/nvidia0
    char *uuid;              // GPU UUID
    unsigned int minor;      // 次设备号
    bool mig_enabled;        // 是否启用 MIG
};

// 主要操作流程
int nvc_container_configure(struct nvc_context *ctx,
                           struct nvc_container *cnt,
                           const struct nvc_container_config *config) {
    // 1. 创建设备节点
    for each gpu in config->gpus {
        // 在容器的 /dev 下创建设备节点
        mknod(cnt->rootfs + gpu->path, S_IFCHR, makedev(195, gpu->minor));
    }

    // 2. 挂载驱动库
    // 将宿主机的 NVIDIA 库 bind mount 到容器
    mount("/usr/lib/x86_64-linux-gnu/libcuda.so.XXX",
          cnt->rootfs + "/usr/lib/x86_64-linux-gnu/libcuda.so.XXX",
          NULL, MS_BIND, NULL);

    // 3. 配置 cgroup 设备权限
    // 允许容器访问指定的 GPU 设备
    write(cnt->cgroup_path + "/devices.allow",
          "c 195:minor rwm");  // nvidia 设备

    // 4. 设置 LD_LIBRARY_PATH
    // 确保容器能找到 NVIDIA 库

    return 0;
}

2.3 运行时工作流程

┌─────────────────────────────────────────────────────────────────────────┐
│              GPU 容器启动完整流程                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 用户执行命令                                                         │
│     docker run --gpus all nvidia/cuda:12.0-base nvidia-smi              │
│                                                                         │
│  2. Docker 解析 --gpus 参数                                              │
│     ┌──────────────────────────────────────────────────────────────┐    │
│     │ 设置环境变量:                                                 │    │
│     │   NVIDIA_VISIBLE_DEVICES=all                                 │    │
│     │   NVIDIA_DRIVER_CAPABILITIES=compute,utility                 │    │
│     └──────────────────────────────────────────────────────────────┘    │
│                           │                                             │
│                           ▼                                             │
│  3. Docker 调用 containerd                                              │
│     ┌──────────────────────────────────────────────────────────────┐    │
│     │ containerd 调用 nvidia-container-runtime 代替 runc           │    │
│     └──────────────────────────────────────────────────────────────┘    │
│                           │                                             │
│                           ▼                                             │
│  4. nvidia-container-runtime 处理                                       │
│     ┌──────────────────────────────────────────────────────────────┐    │
│     │ a. 读取 OCI spec (config.json)                               │    │
│     │ b. 检测 NVIDIA_VISIBLE_DEVICES 环境变量                       │    │
│     │ c. 注入 prestart hook                                        │    │
│     │ d. 调用 runc create                                          │    │
│     └──────────────────────────────────────────────────────────────┘    │
│                           │                                             │
│                           ▼                                             │
│  5. runc 创建容器 (进程暂停在 prestart)                                   │
│     ┌──────────────────────────────────────────────────────────────┐    │
│     │ a. 创建 namespaces                                           │    │
│     │ b. 设置 cgroups                                              │    │
│     │ c. 准备 rootfs                                               │    │
│     │ d. 容器进程创建但未执行                                        │    │
│     └──────────────────────────────────────────────────────────────┘    │
│                           │                                             │
│                           ▼                                             │
│  6. 执行 prestart hook (nvidia-container-runtime-hook)                  │
│     ┌──────────────────────────────────────────────────────────────┐    │
│     │ a. 读取容器 PID 和配置                                        │    │
│     │ b. 解析 NVIDIA_VISIBLE_DEVICES (all → GPU 0,1,2...)          │    │
│     │ c. 调用 libnvidia-container:                                 │    │
│     │    - 在容器 mount namespace 中创建 /dev/nvidia* 设备         │    │
│     │    - Bind mount NVIDIA 驱动库                                │    │
│     │    - 配置 cgroup 设备白名单                                   │    │
│     └──────────────────────────────────────────────────────────────┘    │
│                           │                                             │
│                           ▼                                             │
│  7. 容器进程开始执行                                                     │
│     ┌──────────────────────────────────────────────────────────────┐    │
│     │ nvidia-smi 正常运行,可以访问 GPU                             │    │
│     └──────────────────────────────────────────────────────────────┘    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

3. 环境配置实战

3.1 安装 NVIDIA Container Toolkit

# Ubuntu/Debian 安装

# 1. 配置软件源
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
    sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

# 2. 安装
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

# 3. 配置 Docker
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# 4. 验证安装
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi
# RHEL/CentOS 安装

# 1. 配置软件源
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.repo | \
    sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo

# 2. 安装
sudo yum install -y nvidia-container-toolkit

# 3. 配置 Docker
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

3.2 配置文件详解

# Docker daemon 配置
# /etc/docker/daemon.json
{
    "runtimes": {
        "nvidia": {
            "path": "nvidia-container-runtime",
            "runtimeArgs": []
        }
    },
    "default-runtime": "nvidia"  # 可选:设为默认运行时
}

# nvidia-container-runtime 配置
# /etc/nvidia-container-runtime/config.toml

[nvidia-container-cli]
# 调试模式
debug = "/var/log/nvidia-container-runtime.log"

# ldconfig 路径
ldconfig = "@/sbin/ldconfig"

[nvidia-container-runtime]
# 日志级别: debug, info, warn, error
debug = "/var/log/nvidia-container-runtime.log"
log-level = "info"

# 运行时模式
# "auto": 自动检测 (推荐)
# "legacy": 使用 prestart hook
# "csv": 使用 CDI (Container Device Interface)
mode = "auto"

[nvidia-container-runtime.modes.csv]
# CDI 模式配置
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"

[nvidia-container-runtime.modes.cdi]
# CDI 规范文件路径
spec-dirs = ["/etc/cdi", "/var/run/cdi"]
default-kind = "nvidia.com/gpu"

3.3 环境变量详解

# 核心环境变量

# NVIDIA_VISIBLE_DEVICES: 指定容器可见的 GPU
# 可选值:
#   - "all"          : 所有 GPU
#   - "none"         : 不使用 GPU
#   - "void"         : 不使用 GPU (与 none 相同)
#   - "0,1,2"        : 指定 GPU 索引
#   - "GPU-xxxx-..." : 指定 GPU UUID
#   - "MIG-xxxx-..." : 指定 MIG 实例

docker run --gpus '"device=0,1"' ...
# 等价于
docker run -e NVIDIA_VISIBLE_DEVICES=0,1 ...

# NVIDIA_DRIVER_CAPABILITIES: 指定需要的驱动能力
# 可选值 (逗号分隔):
#   - compute  : CUDA 和 OpenCL
#   - compat32 : 32 位兼容库
#   - graphics : OpenGL 和 Vulkan
#   - utility  : nvidia-smi 等工具
#   - video    : 视频编解码
#   - display  : 显示输出
#   - all      : 以上全部

docker run -e NVIDIA_DRIVER_CAPABILITIES=compute,utility ...

# NVIDIA_REQUIRE_*: 指定驱动/CUDA 版本要求
docker run -e NVIDIA_REQUIRE_CUDA="cuda>=12.0" ...
docker run -e NVIDIA_REQUIRE_DRIVER=">=525" ...

# CUDA_VISIBLE_DEVICES: CUDA 层面的 GPU 可见性
# 注意: 这是 CUDA 环境变量,不是 NVIDIA Container Toolkit 的
# 在容器内使用,重新编号 GPU (从 0 开始)
# 使用示例

# 使用所有 GPU
docker run --gpus all nvidia/cuda:12.0-base nvidia-smi

# 使用特定 GPU
docker run --gpus '"device=0"' nvidia/cuda:12.0-base nvidia-smi

# 使用多个指定 GPU
docker run --gpus '"device=0,2"' nvidia/cuda:12.0-base nvidia-smi

# 限制 GPU 数量
docker run --gpus 2 nvidia/cuda:12.0-base nvidia-smi  # 使用前 2 个 GPU

# 使用 GPU UUID
docker run --gpus '"device=GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a"' ...

# 完整示例:PyTorch 训练
docker run --gpus all \
    -v /data:/data \
    -e NVIDIA_DRIVER_CAPABILITIES=compute,utility \
    pytorch/pytorch:2.0-cuda12.0-runtime \
    python train.py

4. 底层原理深入分析

4.1 设备节点创建过程

// nvidia-container-cli 创建设备节点的过程
// 位置: github.com/NVIDIA/libnvidia-container/src/nvc_container.c

// 简化的设备创建流程
func createDeviceNodes(containerPid int, devices []Device) error {
    // 1. 进入容器的 mount namespace
    containerNsFd, _ := os.Open(fmt.Sprintf("/proc/%d/ns/mnt", containerPid))
    defer containerNsFd.Close()

    // 保存当前 namespace
    hostNsFd, _ := os.Open("/proc/self/ns/mnt")
    defer hostNsFd.Close()

    // 切换到容器 namespace
    unix.Setns(int(containerNsFd.Fd()), unix.CLONE_NEWNS)
    defer unix.Setns(int(hostNsFd.Fd()), unix.CLONE_NEWNS)

    // 2. 创建设备节点
    for _, dev := range devices {
        // /dev/nvidia0, /dev/nvidia1, ...
        path := filepath.Join("/dev", dev.Name)

        // mknod 创建字符设备
        // 主设备号: 195 (nvidia)
        // 次设备号: 根据 GPU 索引
        mode := uint32(unix.S_IFCHR | 0666)
        devNum := unix.Mkdev(195, uint32(dev.Minor))

        if err := unix.Mknod(path, mode, int(devNum)); err != nil {
            return err
        }
    }

    // 3. 创建控制设备
    // /dev/nvidiactl (minor=255)
    // /dev/nvidia-uvm (major=510, minor=0)

    return nil
}

4.2 驱动库挂载机制

# 查看容器内挂载的 NVIDIA 库
docker run --rm --gpus all nvidia/cuda:12.0-base \
    cat /proc/self/mountinfo | grep nvidia

# 输出示例 (简化):
# /usr/lib/x86_64-linux-gnu/libcuda.so.535.104.05
# /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.535.104.05
# /usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.535.104.05
# ... 更多库文件
// 驱动库挂载的实现
// 位置: github.com/NVIDIA/libnvidia-container/src/nvc_mount.c

type DriverLibrary struct {
    HostPath      string  // 宿主机路径
    ContainerPath string  // 容器内路径
    Type          string  // 库类型
}

// NVIDIA 驱动库列表
var nvidiaLibraries = []DriverLibrary{
    // CUDA Driver API
    {"/usr/lib/x86_64-linux-gnu/libcuda.so.XXX", "/usr/lib/libcuda.so.1", "cuda"},

    // NVML (监控库)
    {"/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.XXX", "/usr/lib/libnvidia-ml.so.1", "nvml"},

    // PTX JIT 编译器
    {"/usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.XXX", "/usr/lib/libnvidia-ptxjitcompiler.so.1", "ptx"},

    // 更多库...
}

func mountDriverLibraries(containerRootfs string, libs []DriverLibrary) error {
    for _, lib := range libs {
        target := filepath.Join(containerRootfs, lib.ContainerPath)

        // 确保目标目录存在
        os.MkdirAll(filepath.Dir(target), 0755)

        // 创建空文件作为挂载点
        f, _ := os.Create(target)
        f.Close()

        // Bind mount
        if err := unix.Mount(lib.HostPath, target, "", unix.MS_BIND|unix.MS_RDONLY, ""); err != nil {
            return fmt.Errorf("mount %s failed: %v", lib.HostPath, err)
        }
    }
    return nil
}

4.3 Cgroup 设备访问控制

# 查看 cgroup 设备白名单
# Cgroup v1
cat /sys/fs/cgroup/devices/docker/<container_id>/devices.list

# 示例输出:
# c 195:* rwm    # 允许所有 nvidia 设备 (主设备号 195)
# c 510:* rwm    # 允许 nvidia-uvm 设备 (主设备号 510)

# Cgroup v2 中没有专门的 devices controller
# 使用 BPF 程序来控制设备访问
// Cgroup v1 设备控制
func configureDeviceCgroup(cgroupPath string, devices []Device) error {
    // 默认拒绝所有设备
    ioutil.WriteFile(
        filepath.Join(cgroupPath, "devices.deny"),
        []byte("a"),
        0644,
    )

    // 允许基本设备
    allowBasicDevices(cgroupPath)

    // 允许指定的 GPU 设备
    for _, dev := range devices {
        // c: 字符设备, 195: nvidia 主设备号, minor: 具体 GPU
        rule := fmt.Sprintf("c 195:%d rwm", dev.Minor)
        ioutil.WriteFile(
            filepath.Join(cgroupPath, "devices.allow"),
            []byte(rule),
            0644,
        )
    }

    // 允许 nvidia 控制设备
    ioutil.WriteFile(cgroupPath+"/devices.allow", []byte("c 195:255 rwm"), 0644) // nvidiactl
    ioutil.WriteFile(cgroupPath+"/devices.allow", []byte("c 510:0 rwm"), 0644)   // nvidia-uvm
    ioutil.WriteFile(cgroupPath+"/devices.allow", []byte("c 510:1 rwm"), 0644)   // nvidia-uvm-tools

    return nil
}

4.4 OCI Hook 机制

// OCI 规范中的 hooks 定义
// config.json (容器配置)
{
    "hooks": {
        "prestart": [
            {
                "path": "/usr/bin/nvidia-container-runtime-hook",
                "args": ["nvidia-container-runtime-hook", "prestart"],
                "env": []
            }
        ],
        "poststart": [],
        "poststop": []
    }
}
// Hook 执行时机
// 位置: runc 源码 libcontainer/process_linux.go

type HookState struct {
    Version string   `json:"ociVersion"`
    ID      string   `json:"id"`
    Pid     int      `json:"pid"`
    Root    string   `json:"root"`
    Bundle  string   `json:"bundle"`
}

// Hook 的调用顺序:
// 1. createRuntime hooks   - runc create 开始时
// 2. createContainer hooks - namespace 创建后
// 3. startContainer hooks  - 容器进程执行前 (这是 nvidia 使用的)
// 4. poststart hooks       - 容器进程启动后
// 5. poststop hooks        - 容器停止后

func runHooks(hooks []configs.Hook, state *HookState) error {
    for _, h := range hooks {
        // 将状态信息通过 stdin 传递给 hook
        stateJson, _ := json.Marshal(state)

        cmd := exec.Command(h.Path, h.Args[1:]...)
        cmd.Stdin = bytes.NewReader(stateJson)
        cmd.Env = h.Env

        if err := cmd.Run(); err != nil {
            return err
        }
    }
    return nil
}

5. CDI (Container Device Interface)

5.1 CDI 概述

CDI 是 CNCF 提出的容器设备接口标准,用于统一不同硬件设备在容器运行时中的访问方式:

┌─────────────────────────────────────────────────────────────────────┐
│                    CDI 架构                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  传统方式 (Hook-based)                CDI 方式                       │
│  ┌────────────────────┐              ┌────────────────────┐        │
│  │   容器运行时        │              │   容器运行时        │        │
│  │   (Docker/containerd)│            │   (Docker/containerd)│       │
│  └─────────┬──────────┘              └─────────┬──────────┘        │
│            │                                    │                   │
│            ▼                                    ▼                   │
│  ┌────────────────────┐              ┌────────────────────┐        │
│  │ nvidia-container-  │              │   CDI Spec Files   │        │
│  │ runtime            │              │   (/etc/cdi/*.json)│        │
│  └─────────┬──────────┘              └─────────┬──────────┘        │
│            │                                    │                   │
│            ▼                                    │                   │
│  ┌────────────────────┐                        │                   │
│  │ nvidia-container-  │                        │                   │
│  │ runtime-hook       │                        │                   │
│  │ (prestart hook)    │◄───────────────────────┘                   │
│  └─────────┬──────────┘                                            │
│            │                                                        │
│            ▼                                                        │
│  ┌────────────────────┐                                            │
│  │ libnvidia-container│                                            │
│  └────────────────────┘                                            │
│                                                                     │
│  CDI 优势:                                                          │
│  1. 标准化接口,不依赖特定运行时                                      │
│  2. 声明式配置,更容易管理                                           │
│  3. 支持多种设备类型统一管理                                          │
│  4. 与 Kubernetes Device Plugin 更好集成                            │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

5.2 CDI 规范文件

# 生成 CDI 规范文件
nvidia-ctk cdi generate --output=/etc/cdi/nvidia.json

# 查看生成的规范
cat /etc/cdi/nvidia.json
// CDI 规范示例 (/etc/cdi/nvidia.json)
{
    "cdiVersion": "0.5.0",
    "kind": "nvidia.com/gpu",
    "devices": [
        {
            "name": "0",
            "containerEdits": {
                "deviceNodes": [
                    {
                        "path": "/dev/nvidia0",
                        "hostPath": "/dev/nvidia0",
                        "type": "c",
                        "major": 195,
                        "minor": 0,
                        "permissions": "rw"
                    }
                ],
                "hooks": [
                    {
                        "hookName": "createContainer",
                        "path": "/usr/bin/nvidia-ctk",
                        "args": ["nvidia-ctk", "hook", "create-symlinks", "--link", "..."]
                    }
                ],
                "mounts": [
                    {
                        "hostPath": "/usr/lib/x86_64-linux-gnu/libcuda.so.535.104.05",
                        "containerPath": "/usr/lib/x86_64-linux-gnu/libcuda.so.1",
                        "options": ["ro", "nosuid", "nodev", "bind"]
                    }
                ],
                "env": [
                    "NVIDIA_VISIBLE_DEVICES=0"
                ]
            }
        },
        {
            "name": "all",
            "containerEdits": {
                "deviceNodes": [
                    {"path": "/dev/nvidia0", ...},
                    {"path": "/dev/nvidia1", ...}
                ],
                "env": [
                    "NVIDIA_VISIBLE_DEVICES=all"
                ]
            }
        }
    ],
    "containerEdits": {
        "deviceNodes": [
            {
                "path": "/dev/nvidiactl",
                "hostPath": "/dev/nvidiactl"
            },
            {
                "path": "/dev/nvidia-uvm",
                "hostPath": "/dev/nvidia-uvm"
            }
        ]
    }
}

5.3 使用 CDI

# Docker (需要 Docker 25+)
docker run --device nvidia.com/gpu=0 nvidia/cuda:12.0-base nvidia-smi

# Podman
podman run --device nvidia.com/gpu=all nvidia/cuda:12.0-base nvidia-smi

# containerd (通过 ctr)
ctr run --device nvidia.com/gpu=0 docker.io/nvidia/cuda:12.0-base test nvidia-smi

# Kubernetes (通过 nvidia-device-plugin)
# 自动处理,无需手动指定

6. containerd 集成

6.1 containerd 配置

# /etc/containerd/config.toml

version = 2

[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    [plugins."io.containerd.grpc.v1.cri".containerd]
      default_runtime_name = "nvidia"

      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
          privileged_without_host_devices = false
          runtime_engine = ""
          runtime_root = ""
          runtime_type = "io.containerd.runc.v2"

          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
            BinaryName = "/usr/bin/nvidia-container-runtime"
            SystemdCgroup = true

6.2 CRI-O 配置

# /etc/crio/crio.conf.d/99-nvidia.conf

[crio.runtime]
default_runtime = "nvidia"

[crio.runtime.runtimes.nvidia]
runtime_path = "/usr/bin/nvidia-container-runtime"
runtime_type = "oci"
runtime_root = "/run/nvidia/runc"

7. 调试与故障排查

7.1 常用调试命令

# 1. 检查 NVIDIA 驱动状态
nvidia-smi

# 2. 检查 nvidia-container-cli
nvidia-container-cli info
nvidia-container-cli list

# 输出示例:
# NVRM version:   535.104.05
# CUDA version:   12.2
# Device Index:   0
# Device Minor:   0
# Model:          NVIDIA A100-SXM4-40GB
# GPU UUID:       GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# 3. 手动测试设备注入
nvidia-container-cli --load-kmods configure \
    --device=0 \
    --compute \
    --utility \
    $(pwd)/test-rootfs

# 4. 查看运行时日志
journalctl -u docker -f
# 或
cat /var/log/nvidia-container-runtime.log

# 5. 检查容器内的 GPU 访问
docker run --rm --gpus all nvidia/cuda:12.0-base bash -c '
    echo "=== Device Files ==="
    ls -la /dev/nvidia*

    echo -e "\n=== NVIDIA Libraries ==="
    ldconfig -p | grep nvidia

    echo -e "\n=== nvidia-smi ==="
    nvidia-smi
'

7.2 常见问题排查

# 问题 1: "could not select device driver"
# 原因: Docker 未配置 nvidia 运行时

# 解决:
nvidia-ctk runtime configure --runtime=docker
systemctl restart docker

# 验证配置
cat /etc/docker/daemon.json | grep nvidia

# 问题 2: "nvidia-container-cli: initialization error"
# 原因: NVIDIA 驱动未正确加载

# 解决:
# 检查驱动
lsmod | grep nvidia
dmesg | grep -i nvidia

# 重新加载驱动
sudo modprobe nvidia
sudo modprobe nvidia-uvm

# 问题 3: "failed to create device node"
# 原因: 权限问题或 cgroup 配置错误

# 检查 cgroup
cat /sys/fs/cgroup/devices/docker/<container_id>/devices.list

# 检查设备权限
ls -la /dev/nvidia*

# 问题 4: 容器内看不到所有 GPU
# 原因: NVIDIA_VISIBLE_DEVICES 配置

# 检查环境变量
docker inspect <container_id> | jq '.[0].Config.Env'

# 问题 5: CUDA 版本不兼容
# 原因: 驱动版本低于容器内 CUDA 要求

# 检查兼容性
nvidia-smi  # 查看 CUDA Version (最高支持)
docker run --rm --gpus all nvidia/cuda:12.0-base nvcc --version  # 查看容器 CUDA

# CUDA 版本 vs 驱动版本对应:
# CUDA 12.x → Driver 525+
# CUDA 11.8 → Driver 520+
# CUDA 11.0 → Driver 450+

7.3 性能调试

# 检查 PCIe 带宽
nvidia-smi topo -m

# GPU 到 GPU 的 P2P 访问
nvidia-smi topo -p2p r

# 检查 GPU 时钟频率
nvidia-smi -q -d CLOCK

# 在容器内运行 CUDA 示例程序测试
docker run --rm --gpus all nvidia/cuda:12.0-devel bash -c '
    cd /usr/local/cuda/samples/1_Utilities/bandwidthTest
    make
    ./bandwidthTest
'

# 使用 nvidia-smi dmon 监控
docker run -d --gpus all --name gpu-test nvidia/cuda:12.0-base sleep infinity
nvidia-smi dmon -i 0 -s u

8. 生产环境最佳实践

8.1 安全配置

# 1. 使用非 root 用户
docker run --gpus all \
    --user 1000:1000 \
    nvidia/cuda:12.0-base nvidia-smi

# 2. 只授予必要的 capabilities
docker run --gpus all \
    --cap-drop ALL \
    nvidia/cuda:12.0-base nvidia-smi

# 3. 限制可见 GPU
docker run --gpus '"device=0"' \
    nvidia/cuda:12.0-base nvidia-smi

# 4. 使用只读文件系统
docker run --gpus all \
    --read-only \
    --tmpfs /tmp \
    nvidia/cuda:12.0-base nvidia-smi

# 5. 限制驱动能力 (只计算,不允许显示)
docker run --gpus all \
    -e NVIDIA_DRIVER_CAPABILITIES=compute,utility \
    nvidia/cuda:12.0-base nvidia-smi

8.2 资源限制

# GPU 显存限制 (需要 GPU 支持 MPS 或使用 MIG)
# 注意: 原生 NVIDIA 运行时不支持显存软限制

# 方法 1: 使用 MIG (Multi-Instance GPU)
# 在支持 MIG 的 GPU (A100, H100) 上预先配置
nvidia-smi mig -cgi 9,9,9 -C  # 创建 3 个 GPU 实例

docker run --gpus '"device=MIG-xxxxx"' ...

# 方法 2: 使用 CUDA 环境变量限制
docker run --gpus all \
    -e CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps \
    -e CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-mps-log \
    nvidia/cuda:12.0-base nvidia-smi

# 方法 3: 应用层限制 (PyTorch 示例)
docker run --gpus all nvidia/cuda:12.0-base python3 -c "
import torch
torch.cuda.set_per_process_memory_fraction(0.5)  # 限制使用 50% 显存
"

8.3 镜像构建最佳实践

# 基础镜像选择
# runtime: 只有运行时库,体积最小
# devel: 包含编译工具,用于构建
# base: 最小安装,需要手动安装额外组件

# 生产环境推荐:多阶段构建
FROM nvidia/cuda:12.0-devel AS builder
WORKDIR /app
COPY . .
RUN make

FROM nvidia/cuda:12.0-runtime
COPY --from=builder /app/binary /app/binary
CMD ["/app/binary"]

# 设置正确的环境变量
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility

# 使用非 root 用户
RUN useradd -m appuser
USER appuser
# 镜像大小对比
docker images | grep cuda
# nvidia/cuda:12.0-base      ~120MB
# nvidia/cuda:12.0-runtime   ~870MB
# nvidia/cuda:12.0-devel     ~4.5GB

9. 与 Kubernetes 集成概览

GPU 在 Kubernetes 中的调度将在下一章详细介绍,这里简要说明运行时集成:

# Kubernetes 使用 GPU 的流程
# 1. Device Plugin 发现 GPU 并上报给 kubelet
# 2. Pod 请求 GPU 资源
# 3. kubelet 调用 containerd/CRI-O
# 4. containerd 调用 nvidia-container-runtime
# 5. GPU 注入到容器

# Pod 示例
apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  containers:
  - name: cuda-container
    image: nvidia/cuda:12.0-base
    command: ["nvidia-smi"]
    resources:
      limits:
        nvidia.com/gpu: 1  # 请求 1 个 GPU

10. 本章总结

10.1 核心知识点

┌─────────────────────────────────────────────────────────────────────┐
│                    NVIDIA 容器运行时知识图谱                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  组件架构                                                           │
│  ├── nvidia-container-toolkit        # 工具包总称                   │
│  ├── nvidia-container-runtime        # 运行时 (runc 封装)           │
│  ├── nvidia-container-runtime-hook   # OCI prestart hook           │
│  ├── libnvidia-container             # 核心库                       │
│  └── nvidia-container-cli            # 命令行工具                   │
│                                                                     │
│  工作原理                                                           │
│  ├── 设备注入: mknod 创建 /dev/nvidia* 设备节点                     │
│  ├── 驱动挂载: bind mount NVIDIA 库到容器                           │
│  ├── Cgroup 控制: 设备白名单 (c 195:* rwm)                          │
│  └── 环境变量: NVIDIA_VISIBLE_DEVICES 控制可见 GPU                  │
│                                                                     │
│  关键环境变量                                                        │
│  ├── NVIDIA_VISIBLE_DEVICES    # 可见 GPU 列表                      │
│  ├── NVIDIA_DRIVER_CAPABILITIES # 驱动能力 (compute/utility/...)   │
│  └── NVIDIA_REQUIRE_*          # 版本要求                          │
│                                                                     │
│  配置文件                                                           │
│  ├── /etc/docker/daemon.json            # Docker 运行时配置         │
│  ├── /etc/nvidia-container-runtime/config.toml # 运行时配置        │
│  ├── /etc/containerd/config.toml        # containerd 配置          │
│  └── /etc/cdi/nvidia.json               # CDI 规范文件             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

10.2 面试要点

  1. 容器如何访问 GPU?

    • 通过 nvidia-container-runtime 的 prestart hook
    • 在容器 namespace 中创建设备节点
    • Bind mount NVIDIA 驱动库
    • 配置 cgroup 设备白名单
  2. NVIDIA_VISIBLE_DEVICES 是如何工作的?

    • nvidia-container-runtime-hook 解析该环境变量
    • 转换为具体的 GPU 设备列表
    • 只将指定设备注入到容器
  3. CDI 相比传统 Hook 方式有什么优势?

    • 标准化接口,不依赖特定运行时
    • 声明式配置,更容易管理和审计
    • 支持多种设备类型统一管理
  4. 如何排查容器 GPU 访问问题?

    • 检查驱动状态:nvidia-smi
    • 检查工具链:nvidia-container-cli info
    • 检查运行时日志
    • 验证 cgroup 设备权限

10.3 下一章预告

下一章我们将深入探讨 GPU 共享与隔离技术:

  • MIG (Multi-Instance GPU) 原理与实践
  • vGPU 虚拟化技术
  • 时分复用方案
  • GPU 共享方案对比与选型

参考资料

  • NVIDIA Container Toolkit 官方文档
  • libnvidia-container 源码
  • nvidia-container-runtime 源码
  • CDI 规范
  • OCI Runtime 规范
Prev
01-GPU 架构基础
Next
GPU 共享与隔离