容器基础:Docker与Kubernetes基础、容器运行时、容器资源管理
在深入GPU资源分配与隔离之前,必须先厘清容器技术栈的分层结构。很多GPU相关的问题,根源并不在GPU驱动或CUDA版本,而在于对容器运行时和编排器的资源管理机制理解错位。本节将梳理Docker、Kubernetes与容器运行时(Container Runtime)之间的关系,并重点剖析容器资源管理的底层逻辑——这是后续理解GPU隔离(如NVIDIA Container Toolkit、Device Plugin)的基石。
3.1 核心分层:Docker、Kubernetes与容器运行时
许多初学者将Docker等同于容器,实际上Docker只是容器生态中的一环。现代容器体系可拆分为三个逻辑层:
- 编排层(Orchestration):Kubernetes负责调度、服务发现、扩缩容,它并不直接启动容器,而是通过CRI(Container Runtime Interface)调用下层运行时。
- 容器管理层(Container Management):Docker Engine(dockerd)承担镜像构建、存储、网络管理等开发者体验功能,并通过containerd与底层内核交互。
- 运行时层(Runtime):真正与Linux内核交互、创建cgroups和namespace的组件。典型如runc(OCI标准实现)、containerd内置的runC,以及支持GPU的nvidia-container-runtime。
/usr/bin/nvidia-container-runtime)必须被containerd或CRI-O正确识别,而非仅配置Docker。
3.2 容器资源管理:cgroups与namespace
容器资源管理的底层机制是Linux内核的cgroups(控制组)与namespace(命名空间)。namespace实现“隔离”——让容器看到独立的进程、网络、文件系统视图;cgroups实现“限制”——约束容器能使用的CPU、内存、设备等资源量。
对于GPU资源,cgroups扮演双重角色:
- 设备白名单:通过
devices控制器(cgroups v1)或device cgroup(cgroups v2),控制容器能否访问/dev/nvidia0、/dev/nvidiactl等设备节点。未加入白名单的GPU设备对容器不可见。 - 资源配额:虽然GPU显存和算力不由标准cgroups CPU/内存控制器直接管理,但NVIDIA Container Toolkit通过LD_PRELOAD注入
libnvidia-container,在用户态拦截CUDA调用,实现显存与算力限制(如--gpus '"device=0,1"')。
--cpus或--memory限制GPU算力或显存。CPU cgroups与GPU资源完全无关。GPU隔离必须依赖NVIDIA提供的运行时钩子(hook)或Kubernetes Device Plugin机制。
3.3 方法流程:从Docker到Kubernetes的GPU资源传递
理解GPU资源如何在容器栈中传递,是配置和排障的关键。以下是一个标准流程:
| 层级 | 组件 | GPU相关操作 |
|---|---|---|
| 用户请求 | kubectl / Docker CLI | 声明nvidia.com/gpu: 1(K8s)或--gpus all(Docker) |
| 调度决策 | Kubernetes Scheduler | 根据节点上的Device Plugin上报的可用GPU数量进行调度 |
| 资源上报 | NVIDIA Device Plugin (DaemonSet) | 监听/dev/nvidia*,向kubelet注册GPU资源,并监控健康状态 |
| 运行时注入 | containerd + nvidia-container-runtime | 根据环境变量NVIDIA_VISIBLE_DEVICES注入设备节点和驱动库 |
| 内核隔离 | cgroups + namespace | 设备cgroup放行指定GPU设备,Mount namespace隔离驱动路径 |
在Docker环境中,GPU透传的典型命令为:
# 暴露所有GPU
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi
# 暴露指定GPU(按UUID或索引)
docker run --rm --gpus '"device=0,2"' \
-e NVIDIA_VISIBLE_DEVICES=0,2 \
nvidia/cuda:12.0-base nvidia-smi
在Kubernetes中,GPU资源通过Device Plugin机制声明式管理。Pod的YAML示例如下:
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
3.4 案例:多容器共享GPU与显存隔离
假设一台节点拥有2块NVIDIA A100(各40GB显存)。你需要运行两个推理服务:
- 服务A:需要16GB显存,延迟敏感
- 服务B:需要8GB显存,可容忍抖动
若直接使用--gpus all启动两个容器,两者都会看到全部2块GPU,且显存分配由CUDA运行时动态竞争——可能导致服务A因显存不足而OOM。正确做法是显式指定GPU编号,并利用NVIDIA_MPS(Multi-Process Service)或CUDA的CUDA_VISIBLE_DEVICES实现物理隔离:
# 服务A:独占GPU0
docker run -d --name service-a \
--gpus '"device=0"' \
-e CUDA_VISIBLE_DEVICES=0 \
-e NVIDIA_MPS=on \
your-image:latest
# 服务B:独占GPU1
docker run -d --name service-b \
--gpus '"device=1"' \
-e CUDA_VISIBLE_DEVICES=1 \
your-image:latest
若需在同一块GPU上隔离显存(如A和B共享GPU0),则需借助NVIDIA MPS或CUDA的显存池控制(如CUDA_MAX_CONNECTIONS),但标准Docker/K8s并不提供细粒度显存隔离——这正是GPU资源管理相比CPU更复杂的核心原因。
3.5 误区与复盘:GPU资源管理的常见陷阱
在cgroups v2下,设备访问控制不再通过
devices文件,而是通过eBPF程序实现。若使用旧版NVIDIA Container Toolkit(<1.13),可能导致GPU设备无法在容器内访问。升级toolkit并重启containerd即可解决。
若节点上同时存在Docker和containerd,且两者都配置了nvidia-container-runtime,但kubelet使用containerd,则Docker的GPU配置不会生效。必须确保
/etc/containerd/config.toml中正确设置runtime为nvidia-container-runtime。
1. 宿主机执行
nvidia-smi确认驱动正常;2. 检查
docker run --gpus all是否能成功;3. 若Docker成功但K8s失败,检查Device Plugin日志与kubelet的
--node-labels;4. 最后查看容器内
ls -l /dev/nvidia*确认设备节点是否注入。