Kubernetes GPU 设备分配:从 Device Plugin 到 DRA 的拓扑隔离
GPU 数量可调度不等于设备、CPU 与内存拓扑正确。本文区分 Device Plugin 和 DRA 的职责,给出 API 探测、NUMA 对齐、租户隔离、验收与回滚方法。
本文要点
- 主题聚焦:AI、大模型、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
把 Pod 的 nvidia.com/gpu: 1 调度成功,只能证明节点还公布了一个扩展资源,不能证明 GPU 型号、显存切片、NUMA 节点与 CPU 核心符合推理进程的要求。跨 NUMA 访存会抬高尾延迟;同一物理设备若被两套驱动重复公布,还可能造成错误分配。容量估算应先参考 GPU 推理容量规划,本篇解决的是容量进入 Kubernetes 后如何被正确分配。
先选择一种设备所有权模型
传统 Device Plugin 由节点上的插件向 kubelet 注册 Unix socket,通过当前仍为 v1beta1 的插件协议执行 ListAndWatch 与 Allocate,再把 vendor.example/gpu 之类的整数扩展资源写入 Node 状态。它部署成熟、Pod 写法简单,适合驱动只提供扩展资源的集群;但调度器看到的主要是资源名和数量,更细的设备属性通常要靠不同资源名、节点标签或厂商插件表达。
DRA 则由驱动发布 ResourceSlice,管理员定义 DeviceClass,工作负载通过 ResourceClaim 或 ResourceClaimTemplate 请求属性匹配的设备。当前官方文档把这些核心对象放在 resource.k8s.io/v1。它适合需要按型号、拓扑或可共享能力选设备的场景,但前提是设备驱动和集群版本真正支持 DRA,不能只升级 API Server 就假定可用。
升级前先以集群发现结果为准:
kubectl api-resources --api-group=resource.k8s.io
kubectl explain deviceclass.spec --api-version=resource.k8s.io/v1
kubectl explain resourceclaimtemplate.spec --api-version=resource.k8s.io/v1
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu
如果前三条没有返回目标资源,继续使用已验证的 Device Plugin。迁移节点时必须确认同一块 GPU 只由一条分配路径负责;不要同时把它作为扩展资源和 DRA device 交给两个可调度工作负载。DRA 对象能描述什么还取决于驱动公布的 attributes,不要在通用模板中臆造厂商字段。
把数量隔离扩展为拓扑与故障域隔离
Device Plugin 扩展资源不可超卖,却不自动隔离显存。MIG、vGPU 或时间切片必须由厂商驱动公布可分配单元,不能用容器 memory limit 代替显存边界。不同 GPU 池再用受保护标签、taint 和 AI 平台租户配额隔离 约束。
DRA 还需用 ValidatingAdmissionPolicy allowlist deviceClassName、selectors、count 和 RuntimeClass;普通租户一律拒绝 adminAccess。只有平台管理员能给 namespace 设置 resource.kubernetes.io/admin-access=true,或修改 DeviceClass、ResourceSlice 与节点标签。当前 Kubernetes 1.35 的 admin access 为默认启用 beta 能力,可访问已占用设备,不能只靠 ResourceClaim 的 namespace RBAC 推断隔离成立。
低延迟服务还要验证节点侧的 Topology Manager。它与 DRA 是不同层:DRA 选择可访问设备,不会自动保证 NUMA 对齐;只有实际设备管理路径能提供 topology hint 时,策略才能协调 CPU、内存与设备。pod scope 配合 single-numa-node policy 可要求同一 Pod 的提示收敛到单一 NUMA 节点,CPU 对齐还依赖 CPU Manager 与正确的 requests/limits。先在一组同构节点灰度:
# KubeletConfiguration 片段,实际 apiVersion 以节点发行版为准
cpuManagerPolicy: static
topologyManagerScope: pod
topologyManagerPolicy: single-numa-node
single-numa-node 无法满足时,kubelet 会拒绝 Pod;这与调度阶段的 Pending 不同。控制器必须能重建失败 Pod,并把 Topology Affinity 类事件送入告警。若模型能容忍多 NUMA,先用 restricted 验证,再根据基准数据决定是否收紧,不能仅凭“GPU 利用率更高”判断效果。
用设备身份而不是 Pod Running 验收
验收至少记录四组证据:Node allocatable 与插件健康设备数一致;Pod 实际拿到的设备 UUID 或切片 ID 唯一;CPU affinity、GPU PCIe/NUMA 位置符合策略;重建后身份和性能仍满足约束。对每个 GPU 型号分别运行短推理基准,比较同 NUMA 与故意错配时的 P50/P99、主机到设备带宽和加载时长,并把结果接入 Prometheus 与 Grafana 可观测性。
故障测试要覆盖插件重启、设备被标记 unhealthy、节点 kubelet 重启、Claim 长时间未分配以及单 NUMA 资源碎片。检查路径可以固定为:
kubectl describe pod -n ai <pod>
kubectl get events -n ai --sort-by=.lastTimestamp
kubectl get resourceclaims -A
kubectl get resourceslices
只有在预期资源不足时保持未准入、设备恢复后可重新排队、运行中进程不会拿到重复设备,才算通过。应用侧的批量任务还应采用 LLM 批量推理 中的幂等检查点,避免设备故障后整批重算。
回滚以节点池为单位
拓扑策略导致拒绝率上升时,先 cordon 灰度节点并排空可迁移工作负载,恢复上一版 kubelet 配置,重启 kubelet并确认健康后再 uncordon。不要用删除 requests 或放宽租户 nodeSelector 来“修复”排队,这会把故障转成串租户和性能抖动。
DRA 回滚前先停止新 Claim,等待或终止已分配工作负载,确认驱动释放设备,再恢复原 Device Plugin 节点池。保留两类池并通过队列迁移比在同一节点原地切换更稳妥。若生产正在做 模型服务金丝雀发布,设备分配层和模型版本层应分两个变更窗口,否则性能回退无法归因。
延伸阅读
Related