1. 为什么要在K8s里管理GPU?从单机到集群的跨越
如果你手头有几台带NVIDIA GPU的服务器,跑跑单机的深度学习训练或者模型推理,可能装个驱动、配个Docker环境就差不多了。但一旦你想把这些算力资源“池化”,让多个AI任务、多个团队甚至多个业务项目都能像用水用电一样按需、安全、高效地使用这些昂贵的GPU,事情就变得复杂起来了。这时候,Kubernetes(K8s)就成了那个不可或缺的“数据中心操作系统”。它能把分散的GPU服务器整合成一个统一的资源池,帮你搞定资源调度、应用部署、运维监控和弹性伸缩这一整套麻烦事。
我见过不少团队一开始图省事,直接物理机部署或者用简单的Docker Compose,结果很快就在资源争抢、环境隔离、任务排队和故障恢复上栽了跟头。一台八卡A100的服务器,可能一个任务只用了两张卡,剩下的六张就白白空着,其他任务却因为不知道有资源而等待。更头疼的是,不同任务需要的CUDA版本、深度学习框架版本还可能冲突。而K8s配合NVIDIA的生态工具,恰恰能解决这些痛点。它能精确地以“卡”为单位进行资源调度和隔离,确保每个容器任务互不干扰,还能让运维人员对整个集群的GPU健康状况和利用率一目了然。所以,把GPU交给K8s管理,不是赶时髦,而是从“手工作坊”迈向“自动化工厂”的必然一步。
2. 基础准备:搞定物理节点的NVIDIA驱动
要让K8s认识并使用GPU,第一步必须从物理机层面开始,确保每块GPU卡都能被操作系统正确识别和驱动。这一步是基石,没打好后面全白搭。
2.1 环境检查与冲突驱动清理
在上手安装驱动之前,我们得先摸清家底,并扫清障碍。很多Linux发行版,比如CentOS、Ubuntu,会默认安装一个叫做nouveau的开源显卡驱动。这个驱动和NVIDIA的官方私有驱动是水火不容的,必须提前禁用掉。
首先,用lspci命令看看机器里到底有没有NVIDIA的显卡。这个命令能列出所有的PCI设备,我们用grep过滤一下。
lspci | grep -i nvidia
如果输出类似0b:00.0 3D controller: NVIDIA Corporation Device 1eb8 (rev a1),恭喜你,硬件识别到了。记下这里的1eb8,这是设备的硬件ID,后面查具体型号有用。
接下来,检查是否已经安装了NVIDIA官方驱动。最直接的方法是运行nvidia-smi。如果这个命令能跑通,并显示出显卡的型号、温度、显存使用情况等详细信息,那说明驱动已经装好了,你可以跳过安装步骤。如果报错“command not found”,那就需要继续。
然后,检查那个恼人的nouveau驱动是否在运行:
lsmod | grep nouveau
如果有输出,说明它正在活跃。我们需要彻底禁用它。对于CentOS 7,编辑黑名单配置文件:
# 备份原始配置是个好习惯
sudo cp /usr/lib/modprobe.d/dist-blacklist.conf /usr/lib/modprobe.d/dist-blacklist.conf.bak
# 使用sed命令添加禁用配置
sudo sed -i '/blacklist viafb/a\blacklist nouveau\noptions nouveau modeset=0' /usr/lib/modprobe.d/dist-blacklist.conf
修改完成后,需要重建内核的初始内存盘(initramfs),并重启系统让禁用生效:
# 备份当前initramfs镜像
sudo mv /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r).img.bak
# 重新生成
sudo dracut /boot/initramfs-$(uname -r).img $(uname -r)
# 重启
sudo reboot
重启后,再次执行lsmod | grep nouveau,应该没有任何输出,这就表示nouveau已经被成功禁用了。
2.2 驱动下载与安装实战
现在可以安装NVIDIA驱动了。首先,我们需要根据显卡型号和操作系统,去NVIDIA官网下载对应的驱动。上面我们通过lspci拿到了设备ID(比如1eb8),可以到pci-ids.ucw.cz这个网站去查询具体的型号,比如输入1eb8可能会查到是Tesla T4。不过更直接的方法是去NVIDIA官方驱动下载页面。
访问NVIDIA驱动下载网站,在表单里依次选择:
- 产品类型:Data Center / Tesla(数据中心产品线)
- 产品系列:根据你的卡选择,例如T-Series
- 产品家族:例如Tesla T4
- 操作系统:Linux 64-bit
- CUDA Toolkit:这是一个关键选择! 这里选择的CUDA版本,决定了你下载的驱动版本。比如你选CUDA 11.4,可能会给你推荐470.199.02版本的驱动;如果选CUDA 11.6,可能就变成了510.108.03。你需要根据你后续要运行的AI框架(如PyTorch, TensorFlow)所依赖的CUDA版本来决定。通常选择一个较新且稳定的版本。点击搜索后下载对应的
.run安装文件。
将下载的驱动安装包(如NVIDIA-Linux-x86_64-470.199.02.run)上传到服务器。安装前,确保系统有编译环境:
sudo yum install -y gcc make kernel-devel kernel-headers
然后给安装文件添加执行权限并运行:
chmod +x NVIDIA-Linux-x86_64-470.199.02.run
sudo ./NVIDIA-Linux-x86_64-470.199.02.run
安装过程中,可能会提示你禁用nouveau(我们已经做了)、是否安装32位兼容库、是否配置Xorg等。对于无图形界面的服务器,通常一路选择默认或“Yes”即可。安装完成后,再次运行nvidia-smi,你应该能看到一个漂亮的显卡信息表格,这标志着物理节点层面的驱动大功告成。
3. 搭建桥梁:配置Docker容器运行时
驱动装好了,系统能直接和GPU“对话”了。但我们的目标是在容器里使用GPU,这就需要容器运行时(比如Docker)也能和GPU通信。这里有两个主流方案,我强烈推荐第二种。
3.1 不推荐的旧方案:nvidia-docker2
早期,NVIDIA提供了一个独立的工具叫nvidia-docker,后来进化到nvidia-docker2。它的原理是给Docker增加一个叫nvidia的运行时(runtime)。安装后,你需要修改Docker的配置文件/etc/docker/daemon.json,添加这个运行时,并且通常会把默认运行时设置为nvidia。这样,所有容器默认都能访问GPU。
但为什么我不推荐了呢?因为nvidia-docker2项目已经被官方标记为废弃,它主要适配Docker 19.03及更早的版本。在更高的Docker版本和新的容器运行时标准(如containerd)下,它可能无法正常工作或存在兼容性问题。新项目应该直接使用更现代、更标准的方案。
3.2 推荐的新方案:NVIDIA Container Toolkit
这是NVIDIA目前官方主推的方案,它是一组工具和库,遵循了OCI(开放容器倡议)标准,能与Docker、containerd、Podman等多种容器运行时无缝集成。它的安装和配置更加清晰。
首先,配置NVIDIA的容器工具库并安装:
# 添加NVIDIA容器工具库
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
# 安装NVIDIA Container Toolkit
sudo yum install -y nvidia-container-toolkit
安装完成后,我们需要告诉Docker如何使用NVIDIA容器运行时。使用工具包里的nvidia-ctk命令可以自动完成这个配置:
sudo nvidia-ctk runtime configure --runtime=docker
这个命令会智能地修改/etc/docker/daemon.json文件,在里面添加一个名为nvidia的运行时配置。让我们看一下修改后的文件内容:
{
"runtimes": {
"nvidia": {
"args": [],
"path": "nvidia-container-runtime"
}
}
}
为了让所有容器默认都能使用GPU(避免每次运行容器都要加--runtime=nvidia参数),我们通常还会设置默认运行时。手动编辑/etc/docker/daemon.json,在顶层添加"default-runtime": "nvidia"。同时,如果你之前有其他的配置(比如镜像加速),记得合并进去。一个完整的配置示例如下:
{
"registry-mirrors": ["https://your.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"],
"default-runtime": "nvidia",
"runtimes": {
"nvidia": {
"args": [],
"path": "nvidia-container-runtime"
}
}
}
保存文件后,重启Docker服务使配置生效:
sudo systemctl restart docker
现在,进行一个简单的测试,运行一个带有nvidia-smi命令的容器:
docker run --rm --gpus all nvidia/cuda:11.8.0-base nvidia-smi
或者,因为我们已经设置了默认运行时,甚至可以不指定--gpus参数(取决于Docker版本):
docker run --rm nvidia/cuda:11.8.0-base nvidia-smi
如果能在容器内看到和宿主机上一样的GPU信息输出,那么恭喜你,容器运行时这座桥梁已经稳稳地架设成功了。这意味着任何基于NVIDIA CUDA镜像的容器,现在都能直接调用宿主机的GPU了。
4. 让K8s感知GPU:部署NVIDIA Device Plugin
Docker配置好了,单个容器能用GPU了,但K8s调度器还不知道你的节点有GPU这种特殊资源。在K8s的世界里,Node的资源(CPU、内存)是通过kubelet上报给API Server的。对于GPU、FPGA这类扩展资源,需要一种机制来“广告”它们的存在。这就是Device Plugin的作用。
NVIDIA Device Plugin是一个DaemonSet,它会运行在集群中每一个有GPU的节点上。它的工作就是向kubelet注册该节点上的GPU数量(比如nvidia.com/gpu: 2),并负责在容器创建/销毁时,执行GPU设备的挂载和清理工作。没有它,kubectl describe node命令里就看不到GPU资源。
部署起来非常简单。你可以直接从NVIDIA的GitHub仓库获取部署清单。建议选择与你的K8s版本兼容的稳定版本。
# 例如,下载v0.14.1版本的部署文件
wget https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml
在部署之前,我建议你先检查一下这个YAML文件。里面通常包含一个hostPath卷,挂载了/var/lib/kubelet/device-plugins目录,这是kubelet与Device Plugin通信的套接字所在位置。确保路径在你的环境中是正确的。确认无误后,直接应用:
kubectl apply -f nvidia-device-plugin.yml
运行以下命令,检查DaemonSet的Pod是否在每个GPU节点上都成功启动:
kubectl get pods -n kube-system -l name=nvidia-device-plugin-ds
当Pod状态变为Running后,再次描述你的GPU节点,就能看到宝贵的GPU资源被列出来了:
kubectl describe node <your-gpu-node-name>
在输出的Capacity和Allocatable部分,你应该能看到类似这样的条目:
Capacity:
cpu: 64
memory: 256Gi
nvidia.com/gpu: 4
Allocatable:
cpu: 63500m
memory: 252Gi
nvidia.com/gpu: 4
看到nvidia.com/gpu: 4,就说明Device Plugin工作正常,K8s调度器现在知道这个节点有4块GPU可供调度了。这一步是承上启下的关键,没有它,后续的Pod调度就无法使用GPU资源。
5. 实战:在Pod中申请和使用GPU资源
资源注册好了,终于到了最激动人心的环节——在Pod里真正用上GPU。K8s中GPU资源的请求和限制有些特殊规则,需要特别注意。
5.1 GPU资源请求的硬性规则
在Pod的spec.containers[].resources字段里,GPU是通过nvidia.com/gpu这个资源名来指定的。它有以下几个强制性的限制,和CPU、内存很不一样:
- 只能设置
limits:你不能像CPU那样只设置requests。你必须设置limits。 requests等于limits:如果你同时设置了requests和limits,那么这两个值必须相等。如果你只设置了limits,K8s会自动将requests设置为与limits相同的值。这意味着GPU资源是“不可压缩”的,无法超售。- 必须为整数:你不能请求0.5个GPU,必须请求1个、2个等整数个。请求一个小数(如
0.5)会导致Pod创建失败,报错“must be an integer”。 - 容器间不共享:一个Pod内的多个容器,即使它们都在同一个节点上,其声明的GPU资源也是隔离的,不能共享。
这些限制源于GPU的物理特性,它不像CPU可以分时间片,也不像内存可以交换。一块GPU卡在某个时刻只能被一个容器独占使用。
5.2 编写一个GPU Pod的YAML
假设我们有一个GPU节点,已经打上了标签gpu-node=true。我们来部署一个简单的测试Deployment。
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-test-deployment
spec:
replicas: 1
selector:
matchLabels:
app: gpu-test
template:
metadata:
labels:
app: gpu-test
spec:
containers:
- name: cuda-container
image: nvidia/cuda:11.8.0-runtime-ubuntu22.04 # 选择一个合适的CUDA基础镜像
command: ["/bin/bash"]
args: ["-c", "while true; do nvidia-smi; sleep 30; done"] # 每隔30秒输出一次GPU状态
resources:
limits:
nvidia.com/gpu: 1 # 申请1块GPU
nodeSelector:
gpu-node: "true" # 调度到有GPU的节点
将这个YAML保存为gpu-pod.yaml并部署:
kubectl apply -f gpu-pod.yaml
Pod运行后,我们可以执行命令进入容器,验证GPU是否可用:
# 获取Pod名称
kubectl get pods -l app=gpu-test
# 执行nvidia-smi
kubectl exec -it <pod-name> -- nvidia-smi
如果一切顺利,你会在容器内看到熟悉的nvidia-smi输出,显示分配给该容器的那一块GPU的详细信息。这证明从物理驱动到容器运行时,再到K8s调度和资源分配,整个链路已经完全打通。
6. 进阶:GPU虚拟化与更细粒度的共享
上面我们提到,K8s默认的GPU分配单位是“整卡”。但在很多实际场景,尤其是模型推理服务中,一个任务可能根本用不满一整张卡(比如一张A100 80GB,跑一个小模型可能只用5GB显存)。按整卡分配会造成巨大的资源浪费。这时候,我们就需要GPU虚拟化或细粒度共享技术。
6.1 社区方案:GPU Share Device Plugin
NVIDIA官方提供了**MIG(Multi-Instance GPU)**技术,可以将一块物理GPU(如A100)分割成多个具备独立显存和计算单元的实例。但这需要特定的硬件支持(Ampere架构及以后),并且分割是静态的。
在开源社区,有一些项目实现了基于显存切分的虚拟化。例如,阿里云开源的GPU Sharing Device Plugin就是一个代表性方案。它的核心思想是:以显存(Memory)作为GPU资源的度量单位,而不是整卡。
它的工作原理是:
- 部署一个自定义的Device Plugin(例如
gpushare-device-plugin),它向K8s报告的资源不再是nvidia.com/gpu: N,而是类似aliyun.com/gpu-mem: 81920(表示总显存容量,单位MiB)。 - 同时,它还会部署一个Scheduler Extender,扩展K8s默认的调度器。当Pod请求
aliyun.com/gpu-mem: 5120(5GB)时,调度器会通过Extender找到一块拥有足够剩余显存的GPU卡,并将Pod调度上去。 - 多个Pod可以共享同一块物理GPU,但它们的显存使用是受限制和隔离的(通过CUDA API拦截等技术实现)。
部署这套方案相对复杂,需要修改kube-scheduler的配置,挂载策略文件,并部署额外的组件。但它带来的好处是显著的,可以将GPU的利用率提升数倍,特别适合高并发、低算力需求的推理场景。
6.2 使用注意事项与挑战
虽然GPU虚拟化方案很诱人,但在生产环境引入前,有几点必须慎重考虑:
- 性能隔离:共享同一块GPU的多个任务,虽然显存被隔离,但计算核心(SM)、内存带宽等资源仍然是共享的,可能会相互干扰,导致性能抖动。这对于延迟敏感的服务可能是不可接受的。
- 兼容性:非官方的共享方案可能对某些CUDA库、深度学习框架有兼容性要求或存在限制,需要进行充分的测试。
- 运维复杂度:引入了额外的组件(Device Plugin, Scheduler Extender),增加了集群的运维复杂度和故障排查难度。
- 监控:你需要新的监控手段来查看每块GPU上各个容器的显存使用情况,而不是简单的整卡占用。
因此,我的建议是:对于训练任务和计算密集型任务,优先使用整卡分配,保证性能稳定。对于轻量级、高并发的推理服务,在充分测试和评估性能隔离性的前提下,可以考虑引入GPU虚拟化方案来提升资源利用率。在做技术选型时,一定要用真实的业务负载进行压力测试,用数据来做出决策。我自己在部署推理集群时,就曾通过GPU共享方案,用相同数量的GPU卡支撑了接近3倍的业务流量,效果非常明显,但前期也花了大量时间进行稳定性和性能测试。
1699



被折叠的 条评论
为什么被折叠?



