上周有个做算法平台的朋友甩过来一张kubectl describe node的截图问我为什么节点上明明插着八张卡Allocatable里却连一个nvidia.com/gpu都看不见。这种场景我见得太多了——机器装好了、驱动也装了、nvidia-smi在宿主机上跑得挺欢可一进 K8s 就仿佛这些卡根本不存在。k8s集群纳管GPU节点这件事看着只是装个插件的活儿实际上横跨了宿主机内核、驱动版本、容器运行时、kubelet 设备插件框架、调度器资源模型五个层面任何一层没对齐表现出来都是同一个症状Pod 卡在 Pending,控制器报Insufficient nvidia.com/gpu。这篇东西面向的是手上真的有一批带卡机器、准备接进集群或者已经接进来但用得不顺的人。我会把整个链路从设计选型讲到实操落地再讲到线上真正会咬人的那些坑。假设你至少能熟练敲kubectl对 Pod、Deployment、DaemonSet 这些概念不陌生但没深挖过 device plugin 的内部机制——这个前提刚好够用。文中涉及的具体版本号、参数值,都是我在实际环境里反复验证过的组合,你抄的时候记得按自己的驱动版本微调。1. 先想清楚为什么 GPU 纳管不能照抄 CPU 那套1.1 GPU 在 K8s 眼里到底是个什么东西CPU 和内存是 kubelet 自己就能算清楚的资源内核暴露多少、cgroup 能约束多少都是现成的。GPU 完全不是这个路数它是一块挂在 PCIe 总线上的外设内核里没有剩余多少张卡这种抽象只有/dev/nvidia0到/dev/nvidiaN这一堆字符设备。K8s 的控制面天生不认识这些设备文件,所以它设计了一套扩展机制——Device Plugin让第三方厂商自己写个小程序通过 gRPC 向 kubelet 上报我这台机器上有多少份某种资源。理解这一点非常关键因为它决定了后面所有的行为边界。Device Plugin 只做三件事上报资源数量、在 Pod 调度到本节点后告诉 kubelet这个容器该挂哪些设备文件、以及处理设备的健康状态。它不负责显存隔离、不负责算力隔离、不负责监控。换句话说K8s 眼里的nvidia.com/gpu: 1本质上是这个 Pod 独占一张卡的设备节点访问权仅此而已。你在容器里能用到多少显存取决于这张卡上还有没有别人在用K8s 完全不拦。我在早期项目里吃过这个亏当时天真地以为申请 1 张卡就等于独占了 80G 显存结果同一张卡被时间切片切成了四份四个训练任务互相抢显存最后集体 OOM。这个认知差是所有后续设计的地基先把地基夯实。还有一个容易忽略的点GPU 资源是不可压缩的整数资源。CPU 你可以申请 0.5 核内存可以申请 1Gi但 GPU 在默认模型下只能是整数写nvidia.com/gpu: 0.5会直接被 API Server 拒绝。这个约束直接引出了后面第 4 章要讲的切分方案。1.2 三条接入路线各自的代价不一样给集群接 GPU市面上主流有三条路别一上来就抄别人的 YAML先搞清楚自己在哪个场景。第一条是手工装三件套宿主机装驱动容器运行时接上 GPU 支持然后单独部署一个 Device Plugin 的 DaemonSet。这条路的优点是链路透明出问题你能顺着每一环往下查缺点是要维护的东西多尤其是节点扩容或者驱动升级的时候得靠脚本或者配置管理工具统一操作很容易出现新扩的三台机器版本和老机器不一致的情况。第二条是用 GPU Operator。它把驱动安装、容器运行时配置、Device Plugin、监控组件、甚至 MIG 管理全部打包成一组带编排逻辑的 Operator你丢一个 CR 进去它自己去收敛状态。好处是节点扩容几乎零人工坏处是它内部封装层次很深一旦某个环节卡住日志分散在七八个 Pod 里排查成本高。我一般建议规模超过二十个 GPU 节点的团队直接上 Operator小规模反而手工更省心。第三条是云托管节点池。云厂商在节点镜像层就把驱动和运行时都配好了你只管打标签、装插件。这条路最省事但也最被动——驱动版本、CUDA 版本基本被镜像锁死想跑一个依赖特定 CUDA 版本的老模型只能自己想办法。下面这张表是我自己选型时常用的对照参数维度按实际运维中影响最大的几项排对比维度手工三件套GPU Operator云托管节点池首次部署耗时约 30 到 60 分钟/节点15 分钟左右含镜像拉取5 分钟以内驱动升级方式手动或脚本批量改 CR 字段滚动更新换节点镜像通常要重装故障定位难度低链路清晰中需看多组件日志高底层不可见版本可控性完全可控可控但受 Operator 支持矩阵限制基本不可控适合的团队规模5 到 20 个 GPU 节点20 个节点以上业务优先、运维人力紧张选型的时候还有一个隐藏变量你是否需要 MIG 或时间切片。手工方案下你得自己改 Device Plugin 的 ConfigMap 并重启Operator 方案下改 CR 就行。如果你的场景里一个团队七八个人抢四张卡是常态那切分能力就是刚需选型时权重要给足。1.3 一个经常被跳过的设计决策GPU 节点要不要独立成池这个决策很多人是边做边想的结果做出一锅粥。我的建议是从一开始就把 GPU 节点单独打成一个节点池用标签和污点圈起来理由有三个。第一是隔离爆炸半径。GPU 节点上的驱动问题、Xid 错误、ECC 报错都可能让整台机器不可用混在通用池里会影响普通业务的调度。第二是资源不被浪费如果不打污点一个只需要 100m CPU 的日志采集 Pod 完全可能被调度到 GPU 节点上占个坑虽然占用不大但会让节点的可分配资源计算变得复杂。第三是便于做成本核算GPU 节点的单位成本比普通节点高一个数量级你总得知道钱花在哪个团队身上。具体做法是给节点打上node-role.kubernetes.io/gputrue之类的标签同时加nvidia.com/gpupresent:NoSchedule的污点。这里有个细节Device Plugin 自己的 DaemonSet 必须显式容忍这个污点否则插件部署不上去节点永远长不出 GPU 资源。新手在这一步卡住的概率极高因为 DaemonSet 的 Pod 会一直 Pending但不看事件的话很难联想到是污点问题。2. 宿主机层版本咬合比任何配置都重要2.1 上机前先跑一遍的体检清单拿到新机器别急着装东西先把下面这些命令跑一遍把输出存档。这一步花五分钟能省掉后面几小时的瞎猜。# 1. 确认卡被系统识别以及驱动版本、CUDA 版本 nvidia-smi nvidia-smi --query-gpuindex,name,memory.total,driver_version --formatcsv # 2. 确认内核版本与已加载模块 uname -r lsmod | grep -E nvidia|nvidia_uvm|nvidia_drm # 3. 确认设备节点是否存在 ls -l /dev/nvidia* # 4. 确认容器运行时版本 containerd --version # 或 docker --version # 5. 确认内核启动参数里没有把 IOMMU 之类的东西配错 cat /proc/cmdline重点看两处。一是lsmod里必须有nvidia_uvm这个模块是 CUDA 应用在容器里正常工作的前提缺了它你会看到容器内nvidia-smi正常但一跑训练就报CUDA error: unknown error非常迷惑人。二是/dev/nvidia-uvm和/dev/nvidia-uvm-tools这两个设备节点要存在如果lsmod有模块但设备节点没有通常是 udev 规则没生效重启一次或者手动nvidia-modprobe -u一般能解决。注意如果你用的是定制内核或者自己编译过内核模块务必确认nvidia.ko、nvidia-uvm.ko、nvidia-modeset.ko三个模块的版本和用户态驱动库版本完全一致。版本不一致时nvidia-smi会直接报Driver/library version mismatch这种情况只能rmmod后重装没有别的巧办法。2.2 驱动、CUDA、运行时三者的版本对齐逻辑这是最容易翻车的地方。很多人以为装最新的就行实际上这三个东西是三角咬合关系任何一角超前或滞后都会出问题。先说驱动和 CUDA。CUDA 程序依赖驱动里的用户态库libcuda.so而驱动能支持的最高 CUDA 版本是固定的——比如某个驱动分支最高支持到 CUDA 12.x你硬要跑一个编译时用了更高版本 CUDA Toolkit 的镜像运行时会报CUDA driver version is insufficient for CUDA runtime version。反过来驱动比 CUDA 新通常是兼容的向后兼容性做得还不错。所以原则是驱动版本按你镜像里最高的那个 CUDA 版本来选而不是反过来。再说运行时。容器里能看见 GPU靠的是运行时在创建容器时把宿主机的驱动库和设备文件注入进去。老版本的 Docker 需要装nvidia-docker2新版走的是nvidia-container-toolkit。containerd 用户则是在config.toml里配一个nvidia的 runtime handler 并设为默认。这里的关键是toolkit 的版本要跟运行时版本匹配版本错配的典型症状是容器起不来报failed to create task: OCI runtime create failed日志里能看到nvidia-container-cli: initialization error。我整理过一个简单的对照思路供你排查时参考现象大概率原因验证方式宿主机nvidia-smi正常容器内报 command not found镜像里没装 CUDA 基础库只有应用换基础镜像打一层测试容器内nvidia-smi报驱动版本不匹配运行时没走 nvidia handler看到的是镜像内的旧库cat /proc/1/environ看注入变量容器内能跑nvidia-smi训练报 CUDA unknown error缺少nvidia-uvm模块或设备节点lsmod | grep uvm、ls /dev/nvidia-uvmPod 起不来runtime create failedtoolkit 与运行时版本错配journalctl -u containerd查详细报错卡数量对不上只看到一张NVIDIA_VISIBLE_DEVICES被覆盖成all之外的值检查镜像 ENV 和 Pod env2.3 容器运行时的改造点逐行说明白以 containerd 为例改config.toml这件事网上抄来抄去但很少有人讲清楚每一行在干什么。我把关键段落拆开说。[plugins.io.containerd.grpc.v1.cri.containerd] default_runtime_name nvidia [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.nvidia] privileged_without_host_devices false runtime_type io.containerd.runc.v2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.nvidia.options] BinaryName /usr/bin/nvidia-container-runtimedefault_runtime_name nvidia这一行决定了所有容器的默认行为。设成 nvidia 之后每个容器创建时都会经过 toolkit 处理它会自动检测镜像里是否有 CUDA 相关环境变量有就注入驱动库没有就安静地跳过。这一步的副作用是所有容器都会多一次检测开销量大的集群里建议不要设默认而是在需要 GPU 的 Pod 上通过 RuntimeClass 显式指定。privileged_without_host_devices false这一行的含义是即使 Pod 是特权模式也不再自动把宿主机所有设备都塞进去。这是个安全收敛保持默认即可。如果你走的是 RuntimeClass 路线还要额外建一个对象apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: nvidia handler: nvidia然后在 Pod 的spec.runtimeClassName里引用它。这条路更干净但代价是每个 GPU 工作负载都得记得写这一行漏写的话容器能起来但看不见卡又是一个隐蔽的坑。改完配置记得systemctl restart containerd重启前先crictl ps确认没有正在跑的关键业务因为重启运行时会让所有容器网络短暂抖动。生产环境里这个操作一定要排进窗口期。3. 从零到 Pod 跑起来节点纳管的完整实操3.1 部署 Device Plugin以及为什么它老是起不来驱动和运行时都通了之后真正让 K8s 看见显卡的是 Device Plugin。官方提供的是k8s-device-plugin以 DaemonSet 形式部署只跑在带 GPU 标签的节点上。下面这份清单是我在生产里用的版本字段做了精简注释标出了几个必须改的地方apiVersion: apps/v1 kind: DaemonSet metadata: name: nvidia-device-plugin namespace: kube-system spec: selector: matchLabels: name: nvidia-device-plugin template: metadata: labels: name: nvidia-device-plugin spec: nodeSelector: node-role.kubernetes.io/gpu: true # 只调度到 GPU 节点 tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule # 必须容忍 GPU 污点 priorityClassName: system-node-critical containers: - name: nvidia-device-plugin image: nvcr.io/nvidia/k8s-device-plugin:v0.14.5 securityContext: allowPrivilegeEscalation: false capabilities: drop: [ALL] volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins/var/lib/kubelet/device-plugins这个挂载点是命门。Device Plugin 通过这个目录下的 Unix socket 向 kubelet 注册自己如果这个路径和 kubelet 实际的--root-dir下的路径对不上插件会一直打印failed to dial或者干脆静默不注册。我见过一次是因为集群部署时把 kubelet 的根目录改到了/data/kubelet结果插件按默认路径挂载两边永远握不上手查了整整一个下午。部署完之后用下面几条命令确认状态# 插件 Pod 是否 Running kubectl -n kube-system get pod -l namenvidia-device-plugin -o wide # 插件日志里应该能看到 Starting GRPC server 和注册成功的记录 kubectl -n kube-system logs -l namenvidia-device-plugin --tail50 # 节点上应该出现 nvidia.com/gpu 资源 kubectl get node node-name -o jsonpath{.status.capacity} kubectl describe node node-name | grep -A5 Allocatable正常情况下Capacity和Allocatable里都会出现nvidia.com/gpu: 8这样的条目。如果Capacity有而Allocatable没有通常是节点上有其他资源挤占了配额或者插件上报后又被健康检查置为不健康去看插件日志里的health相关行。提示如果你的节点同时开启了 MIG插件会上报nvidia.com/mig-1g.5gb这类带规格的资源名此时nvidia.com/gpu可能变成 0。这不是 bug是设计如此——MIG 模式下按实例粒度上报。遇到这种情况先确认nvidia-smi -L的输出里有没有MIG 1g.5gb Device字样别急着改插件配置。3.2 节点标签体系让调度器知道该把活儿派给谁资源上报成功只是能用要做到好用还得把节点的差异描述清楚。K8s 官方和 NVIDIA 生态里有一套约定俗成的标签插件会自动打上一部分我们也可以手动补。标签名含义是否自动nvidia.com/gpu.product卡的具体型号如 A100-SXM4-80GB是nvidia.com/gpu.count卡的数量是nvidia.com/gpu.memory单卡显存大小单位 MiB是nvidia.com/gpu.family架构代际如 ampere、hopper是nvidia.com/cuda.driver.major驱动主版本号是node-role.kubernetes.io/gpu我们自定义的节点角色标签否手动自动标签的好处是不用自己维护卡换了标签跟着变坏处是标签名比较长写 nodeSelector 的时候容易拼错。我的做法是在上面再封装一层业务标签比如gpu-tier: high表示大显存卡、gpu-tier: standard表示普通卡业务方只需要认业务标签底层的硬件差异由平台侧维护。这样做的另一个好处是换卡不用改业务 YAML。某次我们一个机房的卡从上一代换到新一代显存和算力都变了但因为有业务标签这层抽象所有训练任务的 YAML 一行没动只是把标签的映射关系改了一下。3.3 一个完整的端到端验证 Pod节点纳管完之后我习惯跑一个最小验证比翻文档靠谱得多。这个 Pod 做三件事确认能看见指定数量的卡、确认 CUDA 能实际跑起来、确认显存读取正常。apiVersion: v1 kind: Pod metadata: name: gpu-smoke-test spec: restartPolicy: Never nodeSelector: node-role.kubernetes.io/gpu: true tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - name: cuda image: nvidia/cuda:12.2.0-base-ubuntu22.04 command: [bash, -c] args: - | nvidia-smi nvidia-smi --query-gpuindex,name,memory.total,memory.used --formatcsv echo VISIBLE: $NVIDIA_VISIBLE_DEVICES python3 -c print(cuda check placeholder) || true resources: limits: nvidia.com/gpu: 1跑起来之后kubectl logs应该能看到被分配的那张卡的详细信息并且NVIDIA_VISIBLE_DEVICES的值是一个具体索引而不是all。如果是all说明运行时没把 Device Plugin 分配的设备 ID 传下去问题往往出在 RuntimeClass 没生效或者 containerd 配置里缺少 nvidia handler。这里补一个实操细节申请 GPU 时只需要写limits不要写requests。K8s 对扩展资源的规则是如果只写 limitsrequests 会自动等于 limits如果两个都写了但值不同API Server 会直接拒绝。这个规则和 CPU 内存不同很多人第一次写会踩。4. 资源切分与调度真正的难点在这里4.1 默认整卡独占模型下的资源账怎么算默认模型下一张卡就是一个不可分割的单位。这意味着如果你有 8 张卡最多同时跑 8 个申请 1 卡的 Pod第 9 个必须等。对于推理这种轻负载场景一张 80G 的卡跑一个小模型可能只用了 12G 显存剩下 68G 全浪费成本效率非常难看。这里要做一道很实在的算术题。假设一台 8 卡机器月成本是 C那么单卡单月的成本就是 C/8。如果业务方一个推理服务实际只用 15% 的显存和 20% 的算力整卡独占模式下的有效利用率就是 15%等于你为了这 15% 付了 100% 的钱。规模上去之后这个数字非常吓人所以只要业务形态允许切分是必须做的。但切分不是无脑开。要先回答一个问题你的负载是显存瓶颈还是算力瓶颈。纯显存瓶颈比如小 batch 的在线推理适合时间切片多开几份提高显存利用率算力瓶颈比如大 batch 训练切了也没意义因为卡本来就在满负荷跑切分只会让多个任务互相拖慢。4.2 时间切片与 MIG两条路的取舍时间切片time-slicing是 Device Plugin 层面的能力配置很简单改一个 ConfigMapversion: v1 flags: migStrategy: none sharing: timeSlicing: resources: - name: nvidia.com/gpu replicas: 4这段配置的意思是把每张物理卡虚拟成 4 份逻辑资源。改完之后节点上的nvidia.com/gpu会变成原来的 4 倍比如 8 张卡变成 32 个可分配单位。要注意的是这 32 个单位之间没有任何隔离——四个 Pod 共享同一张卡的显存和算力谁抢得多谁就快显存超了大家一起 OOM。它本质上是超卖适合那些对延迟不敏感、能容忍互相干扰的离线任务或者低频推理。MIG 是硬件级切分把一张卡在物理上切成若干独立实例每个实例有自己独立的显存和计算单元隔离性接近真独占。代价是切分组合受硬件限制比如 80G 卡常见的组合是 7 个 1g.10gb 或者 3 个 2g.20gb而且切完之后不能动态改要改得先停掉卡上所有任务重新配置。MIG 适合多租户场景尤其是不同团队之间需要强隔离的时候。我的实际取舍是这样的内部研发环境用时间切片图的是灵活、随时能改对外提供服务的生产环境用 MIG图的是稳定和隔离。两者混用的集群也有靠不同的节点池隔开就行。对比项时间切片MIG隔离级别无共享显存与算力硬件级显存算力独立切分比例任意整数份受硬件规格限制是否可动态调整改配置重启插件即可需清空任务重新配置资源上报方式nvidia.com/gpu数量翻倍上报nvidia.com/mig-*规格典型场景研发环境、离线批处理多租户生产、在线服务故障影响面一损俱损一个 Pod OOM 可能拖垮同卡其他 Pod基本互不影响4.3 资源碎片为什么明明有卡却调度不上去这个坑我在两个项目里都遇到过。场景是这样集群里有 8 卡节点也有 4 卡节点业务里既有申请 1 卡的推理 Pod也有申请 8 卡的训练 Pod。跑一段时间之后8 卡节点被一堆 1 卡的 Pod 各占一张每台都剩零散几张这时候来了个要 8 卡的训练任务虽然集群总空闲卡数够但没有任何一台机器满足任务永远 Pending。解法有三种从轻到重。最轻的是给大任务用节点亲和性绑定到 8 卡节点同时给 8 卡节点加污点只允许大任务进入——本质上是把节点池按粒度分层小任务去 4 卡池大任务去 8 卡池。中等的是用 Pod 的优先级和抢占让大任务能挤掉小任务但抢占会中断业务要谨慎。最重的是引入拓扑感知的调度插件按 NUMA 和 NVLink 拓扑做装箱这个方案效果最好但运维复杂度也最高一般只有在跑大规模分布式训练时才值得。还有一个更隐蔽的碎片来源同一台机器上多卡任务和单卡任务混部。比如一台 8 卡机器上跑了一个 2 卡任务剩 6 卡这时候来个 8 卡任务就进不来来 6 个 1 卡任务反而能塞满。这种情况下用topologySpreadConstraints把同类任务打散到不同节点能明显缓解。5. 上线之后监控、隔离与故障排查5.1 需要长期盯住的几类指标GPU 纳管不是部署完就完事真正的工作量在运维阶段。我一般要求至少覆盖四类观测。第一类是资源水位包括每张卡的显存占用率、算力利用率、温度、功耗。这些靠 DCGM Exporter 采集暴露成 Prometheus 格式再配 Grafana 面板。显存占用率持续接近 100% 但算力利用率只有 10% 的组合通常意味着模型有内存泄漏或者 batch 设置不合理得推动业务侧优化。第二类是硬件健康重点是 Xid 错误码和 ECC 错误计数。Xid 是 NVIDIA 驱动上报的错误码不同的码含义完全不同比如 13 表示图形引擎异常、48 表示双比特 ECC 错误、79 表示 GPU 掉卡。我在告警规则里对 Xid 48 和 79 设了最高级别因为这两种基本意味着卡要下线维修了。ECC 的可纠正错误计数如果持续增长也要提前安排更换别等到不可纠正错误爆发。第三类是调度侧指标包括 Pending 的 GPU Pod 数量、平均等待时长、节点 GPU 分配率。这几个指标能直接反映资源是否紧张是扩容决策的依据。第四类是运行时异常比如容器启动失败率、runtime 相关错误日志量。这类指标平时不起眼但一旦驱动或运行时升级出问题它会第一时间飙起来。5.2 常见问题速查与排查顺序下面这张表是我这些年攒下来的按从最常见的开始排序。遇到问题时从上往下走基本能覆盖八成情况。现象优先排查方向具体动作Pod Pending事件报 Insufficient nvidia.com/gpu节点资源是否上报、污点是否容忍describe node看 Allocatable检查 Pod 的 tolerations节点 Allocatable 里没有 nvidia.com/gpu插件是否在该节点运行、socket 路径是否匹配看 DaemonSet Pod 状态和日志核对 kubelet 根目录插件 Running 但日志报注册失败宿主机 device-plugins 目录权限或已被占用检查目录权限确认没有残留的旧 socket 文件容器内 nvidia-smi 报驱动版本不匹配运行时未走 nvidia handler检查 RuntimeClass 或 containerd 默认运行时配置容器内 nvidia-smi 正常训练报 CUDA unknown error缺少 uvm 模块或设备节点lsmod | grep uvm检查/dev/nvidia-uvm显存 OOM 但监控显示卡上只有自己镜像内设置了过大的显存预分配检查TF_FORCE_GPU_ALLOW_GROWTH等环境变量卡突然从节点消失硬件掉卡或 Xid 错误dmesg -T | grep -i xidnvidia-smi -q查详细状态时间切片生效但性能严重下降多 Pod 争抢算力降低 replicas 或改用 MIG排查的时候有个通用心法永远从宿主机往下查不要从 Pod 往上猜。先在宿主机上确认nvidia-smi正常、设备节点齐全、模块加载完整再确认容器运行时能创建带 GPU 的容器可以用crictl run或者简单的docker run --gpus all验证最后才看 K8s 层。这个顺序能帮你快速定位问题在哪一层避免在错误的层面浪费大量时间。注意驱动升级是 GPU 集群里风险最高的操作之一。升级前务必确认新驱动支持的所有 CUDA 版本并且逐节点滚动升级而非批量重启。升级完成后那个节点上的所有 GPU Pod 都需要重建才能用上新驱动因为运行中的容器挂载的是旧版本的驱动库。这一点经常被忽略导致升级后出现部分 Pod 性能异常的诡异现象。6. 一些踩过坑之后才想明白的事最后聊几个不成体系但很值钱的体会。关于驱动版本我现在坚持一个原则集群内所有 GPU 节点的驱动版本必须完全一致。哪怕差一个小版本号都不行。原因是一次教训——两个批次的机器驱动差了 0.1 个版本某个自定义算子在新驱动上编译出来的二进制在老驱动上跑会偶发段错误排查了三天才定位到版本差异。现在我们的节点镜像统一构建驱动版本写死在镜像里扩容时不可能出现偏差。关于资源申请我建议在准入层面加一道校验禁止申请 GPU 但没设limits的 Pod 上线也禁止写requests和limits不一致的情况。这些错误在 YAML review 阶段很容易漏等到线上才发现就是 Pending 事故。用 Kyverno 或者 OPA 写个简单策略几分钟的事长期收益很大。关于时间切片的 replicas 值别贪心。很多人一看能切就想切 8 份、16 份实际上卡就那么点显存和算力切得越碎争抢越激烈性能抖动越大最后业务方会来找你投诉为什么我的推理延迟从 20ms 变成了 200ms。我的经验值是 4 份封顶再多就得考虑换更大显存的卡或者上 MIG 了。还有一个容易被忽略的运维细节GPU 节点的重启策略。普通节点重启影响不大GPU 节点重启后如果驱动模块加载顺序有问题可能出现nvidia-smi报Unable to determine the device handle。这种情况通常是nvidia-persistenced服务没起来或者systemd的模块加载顺序不对。我一般会在节点上配一个开机自检脚本重启后自动跑一次nvidia-smi和一次最小 CUDA 测试失败就自动打上gpu-unhealthy标签把节点从调度池里摘出去避免有问题的节点接活。关于成本我还想补一句。GPU 纳管做得再漂亮如果利用率上不去钱就是白花。建议每隔一段时间拉一次节点的显存和算力利用曲线把长期低于 30% 的负载找出来要么合并、要么改成切片模式、要么直接下掉。这件事比研究怎么装插件重要得多只不过后者更技术前者更无聊所以经常被跳过。如果你的集群还没到需要切分和多租户的规模那就老老实实用默认的整卡模型把基础链路做扎实别一上来就上 Operator 加 MIG 加时间切片的全家桶——复杂度是成本而且很多时候是负收益。等你真的遇到资源碎片和利用率问题再按上面的顺序一层层加路会好走很多。