保姆级教程:在Kubernetes 1.25集群中配置NVIDIA GPU支持(含常见问题排查)
从零到一:在Kubernetes 1.25集群中深度集成NVIDIA GPU实战指南
最近在帮一个做AI推理服务的团队搭建生产环境,他们最头疼的就是如何让Kubernetes集群稳定、高效地调用GPU资源。Kubernetes 1.25版本带来了一些底层运行时接口的变化,这让原本就有些复杂的GPU支持配置,又多了几个需要特别注意的“坑”。如果你也正在为如何在K8s里用好GPU而折腾,特别是面对较新的1.25版本,这篇文章或许能帮你省下不少排查时间。我会从一个实际部署者的视角,带你走一遍从驱动检查、容器运行时配置,到设备插件部署和问题诊断的完整流程,重点分享那些官方文档里不会写的细节和“踩坑”经验。
1. 环境准备与前置条件深度核查
在开始动手配置之前,花点时间把基础环境摸清楚,能避免后续80%的莫名其妙的问题。很多人一上来就照着教程敲命令,结果卡在某个环节,回头发现是驱动版本不匹配或者容器运行时没配好。
1.1 节点层面的硬核检查
首先,你需要确保你的GPU节点(无论是物理机还是云上的GPU实例)已经具备了最基础的运行条件。这不仅仅是安装一个驱动那么简单。
1. 验证NVIDIA驱动安装与版本 通过SSH登录到你的节点,运行最基础的检查命令:
nvidia-smi
这个命令的输出信息量很大,你需要重点关注几个地方:
- Driver Version:驱动版本。Kubernetes的NVIDIA设备插件对驱动有最低要求,通常需要>=384.81。但为了稳定性和兼容更新的GPU架构,我强烈建议使用450.x或更高版本。
- CUDA Version:这里显示的是驱动内建的CUDA兼容性版本,并非系统安装的CUDA Toolkit版本。对于K8s设备插件来说,这个信息更多是参考。
- GPU列表与状态:确认所有预期的GPU都被正确识别,并且状态是
OK。
如果nvidia-smi命令报错或找不到,那第一步就是安装官方驱动。不同Linux发行版的安装方式差异很大。以Ubuntu 22.04为例,你可以通过apt安装:
# 添加官方GPU驱动仓库
sudo add-apt-repository ppa:graphics-drivers/ppa -y
sudo apt update
# 安装推荐版本的驱动(通常会是最新的稳定版)
sudo apt install nvidia-driver-535 -y
安装完成后,务必重启节点。
2. 安装与验证NVIDIA Container Toolkit
这是连接Docker/Containerd和GPU驱动的桥梁,至关重要。它的旧称是nvidia-docker2。安装步骤同样因发行版而异。
# 以Ubuntu/Debian为例,配置仓库并安装
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | sudo tee /etc/apt/sources.list.d/libnvidia-container.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
安装完成后,关键一步是将其配置为你的容器运行时的默认底层运行时(low-level runtime)。很多人后面发现GPU在容器中不可用,问题就出在这一步没做对。
1.2 容器运行时配置:Containerd与Docker的差异
Kubernetes 1.25版本,社区对容器运行时的支持重心进一步向Containerd倾斜。你需要根据集群实际使用的运行时进行配置。
对于使用Containerd的集群(当前主流推荐):
Containerd的配置位于/etc/containerd/config.toml。你需要编辑此文件,在[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]部分添加nvidia运行时。
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[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"
然后,你还需要在[plugins."io.containerd.grpc.v1.cri"]部分,将default_runtime_name设置为"runc"(这是默认值),而对于需要GPU的Pod,K8s设备插件会通过注解等方式告知kubelet使用nvidia运行时。更常见的做法是,不设置默认运行时,而是让设备插件来管理。修改配置后,重启containerd服务:
sudo systemctl restart containerd
对于仍使用Docker作为运行时的集群:
配置Docker的daemon.json文件(通常位于/etc/docker/daemon.json):
{
"runtimes": {
"nvidia": {
"path": "/usr/bin/nvidia-container-runtime",
"runtimeArgs": []
}
}
}
注意:早期教程可能会让你设置
"default-runtime": "nvidia"。在Kubernetes环境下,这通常是不必要且可能导致问题的。因为Kubernetes希望由设备插件来决定哪些Pod需要使用nvidia运行时,将所有容器都默认用nvidia运行时可能会引发兼容性问题。除非你确定节点上所有容器都需要GPU,否则不要设置默认运行时。
验证配置是否生效:
# 对于Docker
docker info | grep -i runtime
# 应该能看到 `Runtimes: nvidia runc`
# 对于Containerd,可以通过运行一个测试容器来验证
sudo ctr image pull docker.io/nvidia/cuda:12.5.0-base
sudo ctr run --rm --gpus 0 docker.io/nvidia/cuda:12.5.0-base test nvidia-smi
如果测试容器能成功输出nvidia-smi的信息,说明容器运行时这一层的配置基本正确。
2. 部署NVIDIA设备插件(k8s-device-plugin)
当节点层面的环境就绪后,下一步就是在Kubernetes集群中部署nvidia-device-plugin。它的核心作用是将节点的GPU资源“广告”给Kubernetes API Server,让调度器知道哪些节点有GPU、有多少,并负责在Pod创建时执行GPU设备的注入和清理工作。
2.1 理解Device Plugin的工作机制
在动手部署YAML之前,花两分钟理解它的工作原理,对排查问题有奇效。Device Plugin并不是一个魔法黑盒,它其实是一个遵循K8s Device Plugin接口的gRPC服务。它作为DaemonSet运行在每个GPU节点上,主要干三件事:
- 向kubelet注册自己:告诉kubelet:“嗨,我负责管理
nvidia.com/gpu这种资源。” - 上报设备列表与健康状态:持续监控本节点的GPU,将数量、型号(可选)和健康状态汇报给kubelet。
- 分配与清理设备:当kubelet要创建一个申请了GPU的Pod时,Device Plugin会收到“分配设备”的请求,它负责执行一些挂载设备、库文件等预处理操作;Pod删除时,则执行清理。
2.2 选择合适的部署清单
NVIDIA在GitHub上提供了官方的部署YAML。但直接使用kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml可能会遇到镜像拉取问题(尤其是国内环境)。更稳妥的做法是,先查看当前可用的最新稳定版本,并考虑使用一些经过验证的配置。
下面是一个针对Kubernetes 1.25优化过的、更健壮的DaemonSet配置示例。我增加了一些在生产环境中很有用的参数:
# nvidia-device-plugin-daemonset-optimized.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
namespace: kube-system
spec:
selector:
matchLabels:
name: nvidia-device-plugin-ds
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
name: nvidia-device-plugin-ds
spec:
priorityClassName: "system-node-critical"
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
- key: "CriticalAddonsOnly"
operator: "Exists"
- operator: "Exists"
effect: "NoExecute"
- operator: "Exists"
effect: "NoSchedule"
nodeSelector:
# 假设你给GPU节点打了这个标签
hardware-type: nvidia-gpu
containers:
- image: nvcr.io/nvidia/k8s-device-plugin:v0.14.1
# 建议使用确定的版本标签,而非latest
name: nvidia-device-plugin-ctr
args:
- --fail-on-init-error=true
- --mig-strategy=single
# 启用兼容模式,对某些旧版本K8s或特定场景有帮助
- --pass-device-specs=false
- --device-list-strategy=envvar
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "300Mi"
cpu: "500m"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
- name: nvidia-driver-root
mountPath: /usr/local/nvidia
readOnly: true
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
type: DirectoryOrCreate
- name: nvidia-driver-root
hostPath:
path: /usr/local/nvidia
type: Directory
这个配置做了几处关键增强:
priorityClassName: system-node-critical:确保这个DaemonSet被当作关键插件,在资源紧张时优先被调度。- 更宽松的
tolerations:除了GPU污点,还容忍了常见的节点污点,确保插件能在各种节点上运行。 nodeSelector:通过节点选择器,只将插件部署到打了hardware-type: nvidia-gpu标签的节点上,避免在没有GPU的节点上运行无用Pod。- 明确的启动参数:如
--fail-on-init-error=true让初始化错误快速失败,便于发现问题。 - 资源限制:为容器设置了合理的资源请求与限制,避免其占用过多节点资源。
使用以下命令部署:
kubectl apply -f nvidia-device-plugin-daemonset-optimized.yaml
然后检查部署状态:
kubectl get daemonset -n kube-system -l name=nvidia-device-plugin-ds
kubectl get pods -n kube-system -l name=nvidia-device-plugin-ds -o wide
查看Pod日志,确认没有报错:
kubectl logs -n kube-system <nvidia-device-plugin-pod-name>
2.3 验证集群GPU资源发现
部署成功后,最令人兴奋的一步就是验证Kubernetes是否真的“看见”了GPU。
-
检查节点资源容量与可分配量:
kubectl describe node <your-gpu-node-name>在输出的
Capacity和Allocatable部分,你应该能看到类似这样的条目:Capacity: cpu: 64 memory: 251Gi nvidia.com/gpu: 2 ... Allocatable: cpu: 63500m memory: 250Gi nvidia.com/gpu: 2 ...这明确表示该节点有2块可分配的GPU。
-
检查节点标签: 设备插件通常会给节点打上标签,标明GPU厂商、数量等信息。
kubectl get node <your-gpu-node-name> --show-labels | grep nvidia你可能会看到
nvidia.com/gpu.count=2、nvidia.com/gpu.product=Tesla-V100-SXM2-32GB等标签。这些标签可以被用来做更精细的Pod调度。
3. 运行GPU工作负载与实战测试
资源注册成功,接下来就是真刀真枪地跑一个GPU应用了。这里我们不止步于简单的测试,而是探讨几种不同场景的部署方式。
3.1 基础测试Pod:快速验证
这是一个最简化的Pod定义,用于快速验证GPU是否能在Pod内被访问和使用。
# gpu-test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cuda-vectoradd-test
spec:
restartPolicy: OnFailure
containers:
- name: cuda-vectoradd
image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0
# 这是一个NVIDIA官方提供的极简CUDA示例,只做向量加法,运行完即退出
resources:
limits:
nvidia.com/gpu: 1 # 申请1个GPU
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
创建并查看日志:
kubectl apply -f gpu-test-pod.yaml
kubectl logs cuda-vectoradd-test
如果看到类似[Vector addition of 50000 elements]和Test PASSED的输出,恭喜你,最基本的GPU调用链路已经打通。
3.2 部署真实的AI推理服务:以TensorFlow Serving为例
让我们看一个更贴近生产的例子:部署一个TensorFlow Serving服务,它加载一个预训练的模型,并提供gRPC/REST API进行推理。
# tf-serving-gpu-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tensorflow-serving-resnet
spec:
replicas: 1 # GPU资源宝贵,通常每个Pod独占GPU,副本数受限于GPU数量
selector:
matchLabels:
app: tf-serving-gpu
template:
metadata:
labels:
app: tf-serving-gpu
spec:
nodeSelector:
# 确保调度到有GPU的节点
hardware-type: nvidia-gpu
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: serving
image: tensorflow/serving:latest-gpu
# 使用官方GPU镜像,已包含CUDA和cuDNN
args:
- "--model_name=resnet"
- "--model_base_path=/models/resnet"
- "--port=8500"
- "--rest_api_port=8501"
ports:
- containerPort: 8500
name: grpc
- containerPort: 8501
name: restapi
resources:
limits:
nvidia.com/gpu: 1
memory: "4Gi"
cpu: "2"
requests:
memory: "2Gi"
cpu: "1"
volumeMounts:
- name: model-storage
mountPath: /models/resnet
readOnly: true
livenessProbe:
httpGet:
path: /v1/models/resnet
port: restapi
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /v1/models/resnet
port: restapi
initialDelaySeconds: 30
periodSeconds: 5
volumes:
- name: model-storage
# 这里可以是PersistentVolumeClaim、HostPath或ConfigMap,根据实际情况配置
hostPath:
path: /data/models/resnet-v2
---
apiVersion: v1
kind: Service
metadata:
name: tf-serving-service
spec:
type: NodePort
selector:
app: tf-serving-gpu
ports:
- port: 8501
targetPort: 8501
name: rest
nodePort: 30085
- port: 8500
targetPort: 8500
name: grpc
这个部署清单体现了几个生产级考量:
- 资源限制与请求:明确设置了GPU、CPU和内存的限制,这对于调度和稳定性至关重要。
- 就绪和存活探针:确保服务真正准备好才接收流量,并在异常时重启。
- 节点选择器:将Pod精准调度到GPU节点。
- 服务暴露:通过Service对外提供访问入口。
3.3 多GPU与GPU共享策略
如果你的节点有多块GPU,或者想让一个GPU被多个低负载的Pod共享(通过时间片或MIG技术),配置会有所不同。
申请多个GPU:
在Pod的resources.limits中直接指定数量即可。
resources:
limits:
nvidia.com/gpu: 4
Kubernetes调度器会确保Pod被调度到至少有4块可用GPU的节点上。
使用NVIDIA MIG(Multi-Instance GPU):
对于A100、H100等支持MIG的GPU,可以将一块物理GPU划分为多个更小的、隔离的实例。设备插件通过--mig-strategy参数支持MIG。在Pod中申请资源时,资源名称会发生变化:
resources:
limits:
# 申请一个MIG实例,例如A100 40GB可以划分成7个5GB的实例
nvidia.com/mig-1g.5gb: 1 # 具体资源名称取决于MIG配置
MIG的配置和管理是一个相对高级的话题,需要在节点上预先通过nvidia-smi工具配置好MIG实例。
4. 深度问题排查与性能调优指南
即使按照步骤一步步来,也难免会遇到问题。这一章我们集中火力,解决那些最常见的“坑”。
4.1 典型问题排查清单
当GPU Pod无法启动或无法访问GPU时,可以按照以下清单自上而下排查:
| 问题现象 | 可能原因 | 排查命令与步骤 |
|---|---|---|
Pod状态为Pending | 1. 节点没有GPU资源或资源不足。 2. Pod的 tolerations不匹配节点的taints。3. nodeSelector不匹配。 | 1. kubectl describe pod <pod-name> 查看事件,通常是调度失败。2. kubectl describe node <node-name> 检查节点资源、污点和标签。 |
Pod状态为CrashLoopBackOff或Error | 1. 容器内nvidia-smi命令失败。2. CUDA库缺失或版本不匹配。 3. 镜像本身有问题。 | 1. kubectl logs <pod-name> 查看容器日志。2. kubectl exec -it <pod-name> -- nvidia-smi 尝试在容器内执行命令。3. 检查基础镜像是否包含正确的CUDA版本。 |
| Pod运行但报告找不到GPU | 1. 容器运行时(Docker/Containerd)未正确配置nvidia运行时。 2. 设备插件DaemonSet Pod未在该节点运行或运行异常。 3. kubelet与设备插件通信失败。 | 1. 在节点上运行 docker info | grep -i runtime 或检查containerd配置。2. kubectl get pods -n kube-system -o wide | grep device-plugin 确认Pod状态。3. 查看设备插件Pod日志: kubectl logs -n kube-system <device-plugin-pod>。4. 检查节点 /var/log/kubelet.log(或journalctl -u kubelet)有无相关错误。 |
nvidia.com/gpu未出现在节点资源中 | 1. 设备插件未成功向kubelet注册。 2. 节点驱动未安装或 nvidia-smi在节点上失败。3. 设备插件启动参数有误。 | 1. kubectl describe node 确认无GPU资源。2. 在节点上直接运行 nvidia-smi,确认驱动正常。3. 检查设备插件DaemonSet的YAML,特别是 args和volumeMounts路径是否正确。 |
4.2 容器运行时配置错误详解
这是最常见的问题之一。症状是:节点nvidia-smi正常,设备插件Pod运行也正常,节点也显示了GPU资源,但用户Pod就是无法使用GPU,在容器内运行nvidia-smi会报错Failed to initialize NVML: Unknown Error。
根本原因:Kubelet在创建容器时,没有使用nvidia-container-runtime。对于Docker,需要确保/etc/docker/daemon.json中正确配置了runtimes,并且Kubernetes知道如何使用它。对于Containerd,配置更为关键。
针对Containerd的深度检查:
- 确认
config.toml中[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]部分配置正确,且BinaryName路径/usr/bin/nvidia-container-runtime存在。 - 关键一步:检查kubelet的参数。kubelet需要知道使用哪个容器运行时。在1.24及以上版本,使用Containerd时,kubelet的
--container-runtime-endpoint参数通常是unix:///run/containerd/containerd.sock。确保你的kubelet配置正确。 - 一个高级技巧是检查容器创建时的底层调用。你可以查看问题Pod所在节点上,kubelet的日志,搜索
CreateContainer相关的错误。或者,手动使用crictl工具测试:
如果手动创建也失败,那问题肯定出在容器运行时配置或nvidia-container-toolkit安装上。# 在节点上,使用crictl(需安装)模拟创建一个使用nvidia运行时的容器 # 首先创建一个sandbox SANDBOX_ID=$(sudo crictl runp sandbox.json) # 然后用一个包含GPU请求的容器配置创建容器 CONTAINER_ID=$(sudo crictl create $SANDBOX_ID container-gpu.json pod-config.json)
4.3 性能监控与优化建议
配置成功只是第一步,让GPU在K8s中发挥最大效能需要持续监控和调优。
1. 监控GPU利用率
光有Prometheus和Grafana还不够,你需要集成NVIDIA DCGM Exporter或利用Kubernetes Metrics Server结合节点nvidia-smi的定期采集。
- 部署DCGM Exporter:NVIDIA提供了一个官方的DaemonSet,能暴露丰富的GPU指标(利用率、显存、温度、功耗等)。
helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts helm install dcgm-exporter gpu-helm-charts/dcgm-exporter - 简易脚本监控:对于小规模集群,可以写一个简单的DaemonSet,定期在节点上执行
nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu --format=csv -l 5,并将日志输出到集中日志系统。
2. 优化Pod调度
- 使用节点亲和性/反亲和性:将需要GPU通信(NVLink)的Pod调度到同一个节点的不同GPU上,或者避免将多个高负载GPU Pod挤到同一节点。
spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - high-load-ai topologyKey: kubernetes.io/hostname - 设置合适的资源请求:除了
nvidia.com/gpu,务必为CPU和内存设置合理的requests和limits。GPU任务通常也需要可观的CPU进行数据预处理。
3. 镜像优化
尽量使用NVIDIA官方优化过的基础镜像(如nvcr.io/nvidia/tensorflow:xx.xx-py3),它们已经集成了匹配的CUDA、cuDNN和TensorFlow版本。避免在Dockerfile里从头编译CUDA库,这能极大缩短镜像构建时间和减少镜像层数。
最后,关于集群升级,尤其是Kubernetes版本升级时,需要特别注意:先滚动更新节点,确保每个节点上的nvidia-container-toolkit、驱动和设备插件版本都兼容新版本的kubelet和容器运行时接口(CRI),再进行控制平面升级。在测试环境中充分验证升级流程,是保证生产环境GPU工作负载平稳运行的不二法门。
更多推荐
所有评论(0)