4、容器化GPU环境:Docker与NVIDIA Container Toolkit、Kubernetes GPU设备插件、Singularity在HPC场景的应用
容器化GPU环境,说白了就是让容器能直接调用显卡算力。我最早接触这玩意儿是在2018年,那时候NVIDIA刚推出Container Toolkit,大家还在手动挂载设备文件。现在回头看,那会儿真是原始社会。
这一章咱们聊聊三个核心工具:Docker怎么跟GPU结合、Kubernetes怎么调度GPU、以及Singularity在超算场景下的特殊用法。嗯,都是我在生产环境里摸爬滚打过的经验。
4.1 Docker + NVIDIA Container Toolkit:让容器用上GPU
先说说最基础的东西。Docker本身不认GPU,你得装个插件。这个插件就是NVIDIA Container Toolkit。我习惯叫它nvidia-docker2,虽然现在官方已经改名叫nvidia-container-toolkit了。
核心原理:NVIDIA Container Toolkit本质上是一个OCI运行时钩子。容器启动时,它会自动把GPU设备文件、驱动库、CUDA库挂载进去。你不需要手动写--device /dev/nvidia0这种命令。
安装其实很简单,但有几个坑。我曾经在一台CentOS 7的老机器上折腾了整整一下午,最后发现是内核版本太旧,nvidia-docker2不支持。嗯,这里要注意:
# 1. 先装驱动(这个不用我教吧)
# 2. 配置仓库
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | \
sudo tee /etc/yum.repos.d/nvidia-docker.repo
# 3. 安装
sudo yum install -y nvidia-container-toolkit
sudo systemctl restart docker
# 4. 验证
docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
小技巧:我个人习惯用--gpus '"device=0,1"'来指定具体哪张卡,而不是--gpus all。生产环境里你肯定不想让一个测试容器把训练卡全占了。
你想想看,如果不用这个工具,你得手动挂载多少东西?/dev/nvidia0、/dev/nvidiactl、/dev/nvidia-uvm、还有一堆驱动库。Container Toolkit全帮你搞定了。
4.2 Kubernetes GPU设备插件:集群级别的GPU调度
单机用Docker跑GPU容器,那是小打小闹。真正的大规模训练,得上Kubernetes。但K8s默认不认识GPU,你得装个设备插件。
NVIDIA/k8s-device-plugin这个项目,说白了就是一个DaemonSet。它会在每个节点上跑一个Pod,这个Pod跟kubelet通信,告诉它:「嘿,我这台机器有8张V100,你可以调度它们。」
# 部署设备插件(最简单的方式)
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.0/nvidia-device-plugin.yml
# 验证
kubectl get pods -n kube-system | grep nvidia-device-plugin
# 提交一个需要GPU的Pod
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: cuda-container
image: nvidia/cuda:11.0-base
resources:
limits:
nvidia.com/gpu: 2 # 申请2张GPU
避坑指南:我曾经遇到过一个诡异的问题——Pod一直Pending,但节点上明明有空闲GPU。查了半天,发现是设备插件版本跟驱动版本不匹配。记住:设备插件v0.14.0需要驱动版本>=470.xx。这个对应关系一定要查清楚。
还有一个很多人忽略的点:GPU的显存隔离。K8s设备插件只做数量调度,不做显存隔离。什么意思?你申请了1张卡,但你的程序把显存吃满了,隔壁Pod也用不了。我建议配合NVIDIA MPS或者vGPU来做显存隔离,否则生产环境容易出事故。
| 调度方式 | 粒度 | 隔离性 | 适用场景 |
|---|---|---|---|
| nvidia.com/gpu (原生) | 整卡 | 无显存隔离 | 单卡训练、推理 |
| NVIDIA MPS | 进程级 | 弱隔离 | 多进程共享GPU |
| vGPU (虚拟化) | vGPU切片 | 强隔离 | 多租户场景 |
| 时间切片 (Time-Slicing) | 时间片 | 无显存隔离 | 推理服务 |
4.3 Singularity在HPC场景的应用
聊完K8s,咱们说说HPC。超算中心的环境跟云原生完全是两个世界。你想想看,超算上跑的是MPI作业,用的是Slurm调度器,用户连sudo权限都没有。Docker在这种环境里根本跑不起来——因为它需要root权限。
Singularity就是为这个场景设计的。它最大的特点:不需要daemon,不需要root权限,直接以用户身份运行容器。我最早在「天河二号」上部署Singularity时,管理员还担心安全问题,后来发现它确实比Docker更适合HPC。
# 构建Singularity镜像(从Docker镜像转换)
singularity build mycontainer.sif docker://nvidia/cuda:11.0-base
# 运行容器,挂载GPU
singularity exec --nv mycontainer.sif nvidia-smi
# 在Slurm作业中使用
#!/bin/bash
#SBATCH --gres=gpu:4
#SBATCH --nodes=2
singularity exec --nv --bind /scratch:/data \
mycontainer.sif python train.py --epochs 100
关键区别:Singularity使用--nv参数来启用GPU支持,而不是--gpus。它底层也是调用NVIDIA Container Toolkit,但实现方式更轻量。另外,Singularity默认把宿主机的文件系统挂载到容器里,不像Docker那样隔离得那么彻底——这在HPC场景反而是优点,因为你可以直接访问共享存储。
我记得有一次帮一个生物信息团队迁移工作流。他们原来用Docker,但超算中心不让用。我帮他们把镜像转成Singularity格式,改了几个路径挂载点,三天就搞定了。说白了,Singularity就是HPC世界的Docker,但更安全、更简单。
4.4 三种方案的选型建议
说了这么多,到底该用哪个?我个人的经验是这样的:
- 单机开发调试:Docker + NVIDIA Container Toolkit。简单直接,出了问题好排查。
- 生产级多机训练:Kubernetes + NVIDIA Device Plugin。配合Volcano或Kubeflow,调度效率很高。
- 超算/HPC环境:Singularity。没有第二个选择,因为超算管理员不会让你装Docker的。
一个实用建议:如果你在混合环境工作(比如既有K8s集群又有超算),我建议统一用Singularity构建镜像。因为Singularity可以运行Docker镜像,反过来不行。这样你只需要维护一套镜像,到处都能跑。
最后提醒一句:不管用哪种方案,驱动版本一定要对齐。容器里的CUDA版本可以跟宿主机不一样,但驱动版本必须兼容。我见过太多人卡在这个问题上,折腾一整天发现只是驱动太旧。
嗯,容器化GPU环境就聊到这儿。这些工具我都踩过坑,希望你能少走弯路。