1.1 集群与GPU准备:让Kubernetes认得你的显卡


文档摘要

1.1 集群与 GPU 准备:让 Kubernetes 认得你的显卡 在动手写第一行部署 YAML 之前,请先停下来确认一件事:你的 Kubernetes 集群,到底知不知道节点上有 GPU? 这不是一句废话。我见过不止一个团队,集群搭了三个月,直到要跑大模型才发现 GPU 根本没被注册成可调度资源-- 里干干净净,没有 这一行。结果所有推理 Pod 永远 Pending,卡在"Insufficient nvidia.com/gpu"。 这一节我们就把 GPU 在 K8s 里"从无到有"的完整链路讲清楚:驱动怎么装、插件怎么选、资源怎么暴露、多卡节点拓扑怎么算、以及哪些坑会让你部署当天怀疑人生。

1.1 集群与 GPU 准备:让 Kubernetes 认得你的显卡

在动手写第一行部署 YAML 之前,请先停下来确认一件事:你的 Kubernetes 集群,到底知不知道节点上有 GPU? 这不是一句废话。我见过不止一个团队,集群搭了三个月,直到要跑大模型才发现 GPU 根本没被注册成可调度资源--kubectl describe node 里干干净净,没有 nvidia.com/gpu 这一行。结果所有推理 Pod 永远 Pending,卡在"Insufficient nvidia.com/gpu"。

这一节我们就把 GPU 在 K8s 里"从无到有"的完整链路讲清楚:驱动怎么装、插件怎么选、资源怎么暴露、多卡节点拓扑怎么算、以及哪些坑会让你部署当天怀疑人生。读完你至少能做到两点--第一,用三条命令确认 GPU 链路健康;第二,遇到 Pending 能快速定位是驱动、插件还是调度的问题。

一、先搞清楚:K8s 默认不认识 GPU

Kubernetes 本身是"显卡盲"。它只知道 CPU、内存、临时存储这类通用资源。GPU 是厂商私有硬件,K8s 核心代码里没有它的定义。要让 K8s 把 GPU 当成一个可计量、可调度、可隔离的资源,必须靠两样东西配合:

  1. 节点层面的驱动与运行时:GPU 驱动(如 NVIDIA Driver)、用户态库(CUDA)、以及让容器能用上 GPU 的 NVIDIA Container Toolkit。
  2. 集群层面的插件:一个 Device Plugin,负责把"节点上有几张卡、每张卡空闲与否"上报给 Kubelet 和调度器,并暴露成 nvidia.com/gpu 资源。

这两层缺任何一层,GPU 都跑不起来。下面这张图把关系画清楚:

```mermaid graph TD A[物理 GPU 卡] --> B[NVIDIA Driver + CUDA] B --> C[NVIDIA Container Toolkit] C --> D[容器内能访问 GPU] E[Device Plugin / GPU Operator] --> F[向 Kubelet 上报 nvidia.com/gpu] F --> G[Kubernetes 调度器可见 GPU 资源] G --> H[Pod 请求 nvidia.com/gpu 被调度到对应节点] D --> H ```

记住这张图:上面的链路(驱动+运行时)解决"容器里能不能用 GPU",下面的链路(Device Plugin)解决"K8s 知不知道有 GPU、能不能调度"。排查问题时,先分清是上面断了还是下面断了,能省掉一半时间。

二、驱动与 CUDA:版本矩阵的坑

驱动版本和 CUDA 版本的对应关系,是新手第一个雷区。简单结论先给出来:NVIDIA 驱动向后兼容 CUDA,但向前不兼容。也就是说,驱动版本决定了它能支持的最高 CUDA 版本。装了老驱动去跑需要新 CUDA 的框架,会直接报 CUDA driver version is insufficient

举个例子(具体版本号请以 NVIDIA 官方发布说明为准):如果你的节点装的是较老的 R470 系列驱动,它最高只能支持到某个 CUDA 大版本;而 vLLM 较新版本编译时依赖的 CUDA 可能超出这个范围,就会在容器启动时挂掉。反过来,新驱动跑老 CUDA 通常没问题。

我的建议很明确:

  • 新集群一律用当前稳定版驱动,别为了"稳定"去选三年前的 LTS 驱动,那往往反而最不稳(因为生态都往前走了)。
  • 框架镜像里通常自带 CUDA 运行时(如 vllm/vllm-openai 镜像基于带 CUDA 的基础镜像),所以你不需要在宿主机装完整 CUDA Toolkit,装好驱动 + Container Toolkit 即可。
  • 用这条命令确认驱动健康:nvidia-smi。如果它打不出 GPU 列表,问题在宿主机,还没到 K8s 的事。

还有一个容易忽略的点:驱动版本会限制你能用的框架版本上限。很多团队只盯着"模型支不支持框架",却忘了"框架编译依赖的 CUDA 是否被驱动覆盖"。这是一条隐藏的依赖链:物理卡 → 驱动 → CUDA 能力 → 框架镜像 → 模型权重。任何一环断了,最上面的模型就跑不起来。排障时建议从下往上逐环验证,而不是一上来就怀疑模型。

```mermaid flowchart LR Q{nvidia-smi 是否正常} -- 否 --> R[检查宿主机驱动/CUDA] Q -- 是 --> S[kubectl describe node 有无 nvidia.com/gpu] S -- 无 --> T[检查 Device Plugin / GPU Operator] S -- 有 --> U[Pod 可请求 GPU 资源] ```

三、Device Plugin 还是 GPU Operator?我的选择

把 GPU 暴露给 K8s,官方和社区主要两条路:

方案一:单独部署 NVIDIA Device Plugin。 它是一个 DaemonSet,每个 GPU 节点上跑一个 Pod,负责把 GPU 注册成 nvidia.com/gpu 资源。轻量、好理解,适合已经把驱动和 Container Toolkit 都手动管好的场景。缺点是驱动、Toolkit、插件三件套要你分别维护,版本对不齐就出怪问题。

方案二:NVIDIA GPU Operator。 这是我现在更推荐的做法,尤其对新集群。GPU Operator 用一个 Operator 把"驱动、Container Toolkit、Device Plugin、甚至 MIG 管理器、监控 exporter"全部打包自动管理。你只要给节点打上特定 label,Operator 会自己把驱动容器注入、把一切配好。对于"不想在基础设施上花太多精力、只想把模型跑起来"的团队,这是省心之选。

我的主张:除非你所在环境不允许 Operator 安装驱动(比如某些受管控的裸金属云,驱动由平台统一管),否则直接用 GPU Operator。 它把最容易出错的"驱动与插件版本对齐"这件事自动化了,新人踩坑概率直线下降。

判断你该用哪种,可以看这张决策表:

```mermaid flowchart TD A[新建集群/想省心] -->|是| B[用 GPU Operator 一键接管] C[驱动由平台统一托管 不能自装] -->|是| D[只装 Device Plugin] E[已有手动维护的驱动体系] -->|是| D B --> F[节点打 label 触发自动配置] D --> G[确认 nvidia.com/gpu 已注册] ```

四、验证 GPU 真的可用:三条命令

不管走哪条路,部署完请立刻用下面三步验证。不要等 Pod 跑不起来再回头查--这一步前置验证能挡掉 80% 的"为什么我的推理服务起不来"。

第一步,确认节点资源已注册:

kubectl describe node <gpu-node-name> | grep -A3 "nvidia.com/gpu"

你应该能看到 nvidia.com/gpu: <数量> 以及 Allocatable 里同样的数量。如果这一行根本不存在,说明 Device Plugin / Operator 没起作用,回头检查 DaemonSet 是否 Running。

第二步,跑一个一次性容器直接调 GPU:

kubectl run gpu-test --rm -it --image=nvidia/cuda:12.0.0-base-ubuntu22.04 \ --restart=Never --limits=nvidia.com/gpu=1 -- nvidia-smi

如果它能打印出 GPU 信息,说明从驱动到调度全链路通了。注意:镜像 tag 中的 CUDA 版本只是示例,请按你实际驱动支持的范围选择,具体以 NVIDIA 官方镜像仓库的 tag 列表为准。

第三步,确认没有诡异的污点(Taint)挡路。有些 GPU 节点默认带 nvidia.com/gpu:NoSchedule 这类污点,要求 Pod 有对应 toleration 才能调度。如果你用了 GPU Operator 的默认配置,通常已经自动加了 toleration;自己写 Deployment 时一定要记得加 tolerations,否则会 Pending。

五、多卡与多节点:GPU 拓扑你不能忽视

当你需要的算力超过单卡,就要面对"多卡、甚至多节点"的问题。这一步和后面的 tensor_parallel_size(张量并行度)直接相关,所以这里先把概念讲清,避免你第二章设错参数。

单节点多卡:同一台机器里多张 GPU 之间通过 NVLink / NVSwitch 互联,卡间通信带宽极高(远高于走 PCIe)。在这种拓扑下做张量并行,通信开销小,性能接近线性扩展。判断你的卡有没有 NVLink,看 nvidia-smi topo -m 的输出--如果显示 NVLink 而不是 PIX/SYS,恭喜,卡间通信是快车道。

跨节点多卡:当一张机器放不下你的并行度(比如要 8 卡但单机只有 4 卡),就要跨节点。跨节点的卡间通信走的是节点网络(通常是 RDMA 或普通以太网),带宽远低于 NVLink。此时张量并行的通信成本会显著拖慢推理吞吐。我的经验:尽量让一个模型副本的张量并行收敛在单节点内;如果必须跨节点,请确保节点间有高速 RDMA 网络,否则长上下文下的 KV 缓存同步会让你怀疑人生。

这对部署的启示很直接:给推理 Pod 设置节点亲和性(nodeAffinity),把组成一个模型副本的多张卡尽量钉在同一台机器上,而不是让调度器把它们打散到不同节点。这一点第二章写 Deployment 时会用到。

六、GPU 共享的两条路提前剧透

默认情况下,一张物理 GPU 被一个 Pod 请求后就是整卡独占。但很多时候你并不需要一整张卡:开发联调、轻量模型、或者想在一张卡上跑多个低负载实例。这时候有两条路:

  • MIG(Multi-Instance GPU):把一张卡在硬件层面切成多个独立实例,每个实例有固定的算力和显存配额,彼此硬隔离。适合对隔离性要求高的场景,但只有特定型号(如 A100/H100 的部分卡)支持,且切分粒度固定。
  • 时间切片(Time-Slicing):多个 Pod 共享同一张卡,按时间片轮转。配置简单、利用率高,但显存不隔离,一个 Pod 吃满显存会拖垮同卡其他 Pod。

这两条路的具体配置和取舍,我们放在第三章专门讲。这里你只要记住一件事:默认整卡独占,规划资源预算时别按"一张卡能跑 N 个小模型"去算,老老实实按整卡预留,等第三章掌握了共享手段再优化成本。

七、最常见、最隐蔽的五个坑

坑一:Pod 一直 Pending,报 Insufficient nvidia.com/gpu。 90% 的情况不是 GPU 没了,而是:

  • 你请求了整数张卡,但节点上已经被别人占满;或
  • 你的节点根本没注册 GPU 资源(回到第三步验证);或
  • 你的 Pod 没加对应 toleration,被污点挡在门外。
    定位口诀:先看 kubectl describe pod 的 Events,再 kubectl get node -o wide 看资源,最后查污点。

坑二:容器起来了,但代码里 torch.cuda.is_available() 返回 False。 这通常是 Container Toolkit 没装好,或者你用的运行时不是 nvidia runtime。检查节点上 /etc/docker/daemon.json(或 containerd 的 config.toml)里是否把默认运行时设成了 nvidia。GPU Operator 会自动处理这一步,这也是我推荐它的原因之一。

坑三:多卡节点上"一卡多用"导致显存打架。 就是上一节说的整卡独占问题。如果你以为能在一张卡上自由跑多个实例,结果第二个 Pod 一起动就 OOM,那多半是忘了显存不隔离或没用共享方案。

坑四:节点 GPU 型号不一致导致"同 Pod 在不同节点表现不同"。 有些集群混插了不同代际的卡(比如 A10 和 A100 混在一个 GPU 池)。虽然都叫 nvidia.com/gpu,但算力和显存天差地别。GLM-5.2 对显存敏感,混用会让一部分副本跑得动、一部分 OOM。解决:用 nodeAffinity / nodeSelector 按卡型分组,或者给不同卡型打 label 区分。

坑五:把 GPU 资源“超卖”。 有人图省事,在 DaemonSet 或自定义配置里把 nvidia.com/gpu 的 allocatable 改大,以为能塞更多 Pod。这是灾难——GPU 不支持 CPU 那样的软超卖,硬改会导致多个 Pod 抢同一张卡,集体崩溃。GPU 资源必须如实上报,绝不超卖。

坑六:忽略了 GPU 节点的可抢占与扩缩容。 在云上,GPU 节点常被放到竞价实例或可被集群自动扩缩容回收的节点池。一旦推理副本所在的 GPU 节点被回收,Pod 会重新调度,但新节点未必立刻有可用 GPU,造成服务短暂不可用。生产环境建议给推理负载打上“不可抢占”或固定节点池的标记,并对副本数设置 PodDisruptionBudget,避免节点维护时一次性干掉所有副本。

八、本节小结:部署前你必须确认的环境项

把下面这张清单打勾,再去写推理服务的 YAML:

  • nvidia-smi 在宿主机能正常列出 GPU
  • 驱动版本覆盖框架所需 CUDA 范围(以官方矩阵为准)
  • 已部署 GPU Operator 或 Device Plugin,且 DaemonSet 全部 Running
  • kubectl describe node 能看到 nvidia.com/gpu 资源,且未超卖
  • 节点污点已确认,Deployment 里加了对应 tolerations
  • 容器运行时为 nvidia(Container Toolkit 就位)
  • 多卡副本已规划节点亲和性,尽量收敛在单节点内
  • GPU 型号一致或已按卡型分组调度
  • 你有可拉的推理框架镜像源(国内环境提前规划好镜像加速)
  • 你已确认模型权重的获取渠道(HuggingFace 或 ModelScope,见 1.3)

把这十条过一遍,你的地基就稳了。需要特别强调的是,这一节的检查不是"部署前做一次性",而是每次集群扩容、升级驱动、或者新增 GPU 节点后都要重做一遍。我见过最痛的事故:某团队升级了节点驱动,结果驱动版本回退(镜像 tag 写错),GPU Operator 没健康检查出来,推理服务悄悄跑在老驱动上,长上下文请求开始随机 OOM,排查了整整两天。所以把这条检查清单固化成 CI 或上线前卡点,比任何事后救火都管用。

下一节我们聊:GPU 链路通了之后,推理框架该选谁--这是直接影响你后面所有 YAML 写法和性能上限的关键决策。


发布者: 作者: 转发
评论区 (0)
U