Kubernetes GPU 调度:拓扑感知与多租户隔离

Kubernetes GPU 调度:拓扑感知与多租户隔离 Kubernetes GPU 调度拓扑感知与多租户隔离一、6 月 14 日集群事故凌晨生产集群的 GPU 资源开始打架。几个高优先级的 AI 训练任务同时提交老调度器只认 CPU 和内存对 GPU 内部的 NVLink、NVSwitch 连接关系一无所知。结果就是需要高带宽通信的分布式训练 Pod被调度器扔到了物理距离远、拓扑连接差的节点上。NCCL 通信延迟直接飙升训练吞吐量掉了 40% 以上。更糟的是低优先级任务的脏数据堵在高速缓存里部分 GPU 显存溢出驱动直接重置。问题很明确调度器不知道硬件长什么样多租户之间也没有隔离。二、拓扑感知怎么做的调度器需要在决策时算一个亲和性得分。思路不复杂通过 Device Plugin 拿到所有节点的 GPU 拓扑图包括连接类型和带宽新 Pod 进来时遍历可调度节点算它和现有负载之间的拓扑距离连接带宽越高、跳数越少距离权重越低多租户共享时给每个租户配独立的资源池防止一家独大数据流是这样的调度器从 API Server 拿 Pod 信息用 Informer 监听节点状态查拓扑数据库打分最后把最优节点绑定回 API Server。sequenceDiagram participant Pod as 训练任务 Pod participant Scheduler as K8s 调度器 participant TopoDB as 拓扑感知数据库 participant Node as 计算节点 GPU participant API as API Server Pod-Scheduler: 提交资源请求 (含 GPU 拓扑标签) Scheduler-TopoDB: 查询节点 GPU 拓扑状态 TopoDB--Scheduler: 返回拓扑图 (NVLink/PCIe 关系) Scheduler-Scheduler: 执行过滤与评分算法 Note over Scheduler: 计算拓扑距离与租户权重 Scheduler-Node: 选择最优节点进行绑定 Node--Scheduler: 确认资源预留 Scheduler-API: 更新 Pod 绑定状态 API--Pod: 调度成功开始初始化评分时如果两个节点资源都够优先选拓扑距离近、且当前租户占用率没超阈值的。三、Go 模拟实现用标准库写了个脚本sync、time、fmt没碰外部依赖。核心是SelectBestNode逻辑分三步资源过滤、拓扑打分、租户负载惩罚。package main import ( fmt sync time ) type GPUNode struct { ID string GPUCount int TopologyMap map[string]int TenantLoad float64 } type PodRequest struct { ID string RequiredGPU int TenantID string } type Scheduler struct { nodes map[string]*GPUNode mu sync.RWMutex } func NewScheduler() *Scheduler { return Scheduler{nodes: make(map[string]*GPUNode)} } func (s *Scheduler) AddNode(node *GPUNode) { s.mu.Lock() defer s.mu.Unlock() s.nodes[node.ID] node } func (s *Scheduler) SelectBestNode(req *PodRequest) string { s.mu.RLock() defer s.mu.RUnlock() var bestNodeID string maxScore : -1.0 for _, node : range s.nodes { if node.GPUCount req.RequiredGPU { continue } topoScore : 0.0 if node.TopologyMap ! nil { for _, weight : range node.TopologyMap { if weight 2 { topoScore 10.0 } } } loadPenalty : node.TenantLoad * 20.0 finalScore : topoScore - loadPenalty if finalScore maxScore { maxScore finalScore bestNodeID node.ID } } return bestNodeID } func main() { sched : NewScheduler() sched.AddNode(GPUNode{ID: Node-A, GPUCount: 8, TopologyMap: map[string]int{Node-B: 1}, TenantLoad: 0.2}) sched.AddNode(GPUNode{ID: Node-B, GPUCount: 8, TopologyMap: map[string]int{Node-A: 1}, TenantLoad: 0.8}) req : PodRequest{ID: Train-001, RequiredGPU: 4, TenantID: Tenant-X} selected : sched.SelectBestNode(req) fmt.Printf(为任务 %s 选择的最优节点: %s\n, req.ID, selected) }生产环境里TopologyMap来自真实的拓扑发现插件TenantLoad靠实时监控指标更新。这个模拟只是为了验证过滤和评分逻辑跑得通。四、故障恢复步骤运维团队按这个顺序处理的先查 Device Plugin。kubectl get pods -n kube-system看 NVIDIA Device Plugin 是否正常。如果插件重启频繁检查/var/lib/kubelet/device-plugins下的 socket 文件这通常是 GPU 资源上报失败的原因。调调度器权重。把TopologyAwareScore插件权重调高纯资源请求权重调低。ConfigMap 里把拓扑亲和性评分系数从默认值提到 0.8强制调度器优先选拓扑邻近节点。清理脏数据。对负载高的节点执行kubectl drain配合kubectl delete pod清理Error或OOMKilled状态的残留 Pod。节点上的/tmp和共享内存目录也手动清一遍释放被占用的 I/O 资源。验证效果。提交测试任务看kubectl describe pod的 Events 部分有没有拓扑评分。节点上用nvidia-smi topo -m确认 GPU 通信路径NCCL 环境配置也得对。加租户配额。Namespace 级别上ResourceQuota和LimitRange限制单个租户能占的 GPU 显存总量。折腾了两个小时集群恢复正常训练吞吐量回到基准线。五、几点经验这次事故暴露了两个问题调度器不知道 GPU 拓扑长什么样多租户之间缺乏隔离。解决思路也不复杂——让调度器能算拓扑距离给每个租户配资源池。Go 模拟验证了评分逻辑可行后续可以往 K8s Scheduler Framework 里集成。排障流程也梳理出来了查 Device Plugin → 调权重 → 清脏数据 → 验证 → 加配额。这套方案对大规模 AI 训练集群有参考价值但具体参数还得根据实际硬件和负载调整。