当前位置: 首页 > news >正文

Kubernetes GPU 调度:NVIDIA Device Plugin 与资源管理

# Kubernetes GPU 调度:NVIDIA Device Plugin 与资源管理 ## 📋 引言 随着人工智能和机器学习工作负载的爆发式增长,GPU 资源在 Kubernetes 集群中的调度和管理变得至关重要。然而,Kubernetes 原生并不直接支持 GPU 资源,需要通过 Device Plugin 机制来扩展。本文将深入分析 Kubernetes v1.29.0 和 NVIDIA Device Plugin v0.14.0 的源码实现,揭示 GPU 调度的核心原理,帮助开发者理解 GPU 在 Kubernetes 中的完整生命周期管理。 **核心挑战**: - 如何让 Kubernetes 识别和管理异构硬件资源? - GPU 资源的分配、隔离和监控如何实现? - Device Plugin 与 kubelet 如何协同工作? - 如何优化 GPU 利用率和性能? 本文将通过源码分析、架构图解和实战示例,为你揭示 Kubernetes GPU 调度的内在机制。 --- ## 🔑 核心概念 ### 1. Device Plugin 机制 Device Plugin 是 Kubernetes 提供的一种扩展机制,允许第三方硬件供应商(如 NVIDIA、AMD)插件式地集成特定资源的管理逻辑。 **核心术语**: - **Device(设备)**:物理或逻辑硬件单元,如一张 GPU 卡 - **Resource(资源)**:Kubernetes 调度层面的资源类型,如 `nvidia.com/gpu` - **Allocate(分配)**:将设备分配给特定 Pod 的容器 - **健康检查**:持续监控设备状态并上报 **技术原理**: ``` ┌─────────────────────────────────────────────────────────────┐ │ Kubernetes 控制面 │ ├─────────────────────────────────────────────────────────────┤ │ API Server (存储 GPU 资源信息) │ │ Scheduler (根据 Pod 需求调度到有 GPU 的节点) │ └─────────────────────────────────────────────────────────────┘ ↕ ┌─────────────────────────────────────────────────────────────┐ │ Kubelet │ ├─────────────────────────────────────────────────────────────┤ │ Device Plugin Manager (管理插件生命周期) │ │ Pod Admitter (验证 GPU 资源需求) │ │ Volume Manager (挂载 GPU 驱动) │ └─────────────────────────────────────────────────────────────┘ ↕ ┌─────────────────────────────────────────────────────────────┐ │ NVIDIA Device Plugin (gRPC 服务) │ ├─────────────────────────────────────────────────────────────┤ │ ListAndWatch() → 发现 GPU 设备并上报健康状态 │ │ Allocate() → 为 Pod 分配 GPU 设备 │ │ GetDevicePluginOptions() → 返回插件配置 │ └─────────────────────────────────────────────────────────────┘ ↕ ┌─────────────────────────────────────────────────────────────┐ │ 硬件层 (GPU) │ ├─────────────────────────────────────────────────────────────┤ │ NVIDIA GPU (物理设备) │ │ CUDA Driver (驱动层) │ │ NVML (管理接口) │ └─────────────────────────────────────────────────────────────┘ ``` ### 2. GPU 资源模型 在 Kubernetes 中,GPU 资源通过以下方式定义: ```yaml # Pod 资源请求示例 apiVersion: v1 kind: Pod metadata: name: gpu-pod spec: containers: - name: training-container image: nvidia/cuda:11.8.0-base-ubuntu22.04 resources: limits: nvidia.com/gpu: 2 # 请求 2 个 GPU ``` **资源命名规范**: - 格式:` / ` - NVIDIA GPU:`nvidia.com/gpu` - AMD GPU:`amd.com/gpu` - 自定义资源:`custom.accelerator/hardware` --- ## 🔍 源码深度解析 ### 1. Kubernetes Device Plugin 框架源码 **版本信息**:Kubernetes v1.29.0 #### 1.1 Device Plugin Manager 初始化 **文件路径**:`pkg/kubelet/cm/deviceplugin/plugin_manager.go` ```go // NewManagerImpl 创建 Device Plugin Manager 实例 // 参数: // - sockdir: Device Plugin gRPC socket 目录 // - metrics: 监控指标收集器 // 返回:Manager 接口实例 func NewManagerImpl( sockdir string, monitorManager cm.MonitorManager, metrics CPMetrics, devices []monitor.Device, ) Manager { // 创建 gRPC server,用于与 Device Plugin 通信 server := NewServerImpl(sockdir) manager := &managerImpl{ // 插件注册表,记录所有已注册的设备插件 endpoints: make(map[string]endpointInfo), // 资源到端点的映射,如 "nvidia.com/gpu" → plugin endpoint resourceToEndpoints: make(map[string][]string), // 健康检查通道,用于持续监控设备状态 healthyDevices: make(map[string]sets.String), unhealthyDevices: make(map[string]sets.String), // gRPC server 实例 server: server, // 监控管理器,用于设备热插拔检测 monitorManager: monitorManager, } // 启动 gRPC server,监听 Device Plugin 的注册请求 server.Start(manager) return manager } ``` **关键数据结构**: ```go // Manager 接口定义了 Device Plugin 管理器的核心方法 type Manager interface { // 启动 Device Plugin Manager Run(sourcesReady config.SourcesReady, stopCh <-chan struct{}) // 根据资源名分配设备给 Pod Allocate(resourceName string, devices []string) (*pluginapi.ContainerAllocateResponse, error) // 获取可用的设备列表 GetDevices() map[string][]string // 删除 Device Plugin 端点 RemoveEndpoint(resourceName, devicePath string) } // endpointInfo 存储已注册的 Device Plugin 信息 type endpointInfo struct { // gRPC 客户端,用于调用 Device Plugin 提供的服务 client pluginapi.DevicePluginClient // Device Plugin 的 socket 文件路径 socketPath string // 资源名称,如 "nvidia.com/gpu" resourceName string // 设备 ID 列表,如 ["gpu0", "gpu1"] deviceIDs []string // 停止通道,用于优雅关闭 stopCh chan struct{} } ``` #### 1.2 Device Plugin 注册流程 **文件路径**:`pkg/kubelet/cm/deviceplugin/server.go` ```go // Register 注册 Device Plugin 到 Kubelet // Kubelet 作为 gRPC server,Device Plugin 作为 client 主动调用 func (s *serverImpl) Register(ctx context.Context, r *pluginapi.RegisterRequest) (*pluginapi.Empty, error) { // 1. 验证请求参数 if r.Version != pluginapi.Version { return nil, fmt.Errorf("unsupported plugin version: %s", r.Version) } if r.ResourceName == "" { return nil, fmt.Errorf("resource name cannot be empty") } // 2. 提取资源名称和设备名称 resourceName := r.ResourceName // 如 "nvidia.com/gpu" deviceName := r.DeviceName // Device Plugin 名称,通常与资源名相同 // 3. 创建 gRPC 客户端连接 // Device Plugin 会暴露一个 Unix domain socket clientConn, err := grpc.Dial( r.Endpoint, // socket 文件路径,如 /var/lib/kubelet/device-plugins/nvidiaGPU.sock grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)), ) if err != nil { return nil, fmt.Errorf("failed to connect to device plugin: %v", err) } // 4. 创建 Device Plugin 客户端 client := pluginapi.NewDevicePluginClient(clientConn) // 5. 验证 Device Plugin 是否正常工作 // 调用 GetDevicePluginOptions 进行健康检查 _, err = client.GetDevicePluginOptions(ctx, &pluginapi.Empty{}) if err != nil { clientConn.Close() return nil, fmt.Errorf("device plugin unhealthy: %v", err) } // 6. 存储 endpoint 信息到注册表 s.manager.(*managerImpl).endpoints[resourceName] = endpointInfo{ client: client, socketPath: r.Endpoint, resourceName: resourceName, deviceIDs: r.DeviceIDs, // 可选的设备 ID 列表 stopCh: make(chan struct{}), } // 7. 更新资源到端点的映射 s.manager.(*managerImpl).resourceToEndpoints[resourceName] = append( s.manager.(*managerImpl).resourceToEndpoints[resourceName], deviceName, ) // 8. 启动 ListAndWatch goroutine,持续监控设备状态 go s.manager.WatchDevices(resourceName, client) klog.V(2).InfoS("Device plugin registered", "resourceName", resourceName) return &pluginapi.Empty{}, nil } ``` #### 1.3 设备发现与健康监控 **文件路径**:`pkg/kubelet/cm/deviceplugin/plugin_manager.go` ```go // WatchDevices 监控设备状态变化 // 该方法会持续运行,直到 Device Plugin 断开连接或 Manager 停止 func (m *managerImpl) WatchDevices(resourceName string, client pluginapi.DevicePluginClient) { stream, err := client.ListAndWatch( context.Background(), &pluginapi.Empty{}, ) if err != nil { klog.ErrorS(err, "Failed to start ListAndWatch", "resourceName", resourceName) return } for { response, err := stream.Recv() if err == io.EOF { // Device Plugin 关闭了连接 klog.InfoS("Device plugin closed connection", "resourceName", resourceName) break } if err != nil { klog.ErrorS(err, "ListAndWatch error", "resourceName", resourceName) break } // 处理设备列表更新 m.updateDevices(resourceName, response.Devices) } } // updateDevices 更新设备健康状态 func (m *managerImpl) updateDevices(resourceName string, devices []*pluginapi.Device) { m.mutex.Lock() defer m.mutex.Unlock() // 初始化健康/不健康设备集合(如果不存在) if m.healthyDevices[resourceName] == nil { m.healthyDevices[resourceName] = make(sets.String) } if m.unhealthyDevices[resourceName] == nil { m.unhealthyDevices[resourceName] = make(sets.String) } // 分类健康和不健康的设备 newHealthy := make(sets.String) newUnhealthy := make(sets.String) for _, device := range devices { if device.Health == pluginapi.Healthy { newHealthy.Insert(device.ID) } else { newUnhealthy.Insert(device.ID) } } // 检测状态变化,记录日志 added := newHealthy.Difference(m.healthyDevices[resourceName]) removed := m.healthyDevices[resourceName].Difference(newHealthy) if added.Len() > 0 { klog.V(2).InfoS("Devices became healthy", "resourceName", resourceName, "devices", added.List()) } if removed.Len() > 0 { klog.V(2).InfoS("Devices became unhealthy", "resourceName", resourceName, "devices", removed.List()) } // 更新设备状态 m.healthyDevices[resourceName] = newHealthy m.unhealthyDevices[resourceName] = newUnhealthy // 触发 Pod 重新调度(如果之前因设备不健康失败的 Pod) m.markPodsForRequeue(resourceName) } ``` ### 2. NVIDIA Device Plugin 源码 **版本信息**:NVIDIA Device Plugin v0.14.0 #### 2.1 设备发现实现 **文件路径**:`cmd/nvidia-device-plugin/main.go` ```go // NewNvidiaDevicePlugin 创建 NVIDIA GPU Device Plugin 实例 // 该函数会初始化 NVML 客户端,并枚举所有可用的 GPU 设备 func NewNvidiaDevicePlugin(deviceListStr string, deviceSplitStrategy string) *NvidiaDevicePlugin { // 1. 初始化 NVML (NVIDIA Management Library) // NVML 是 NVIDIA 提供的 C 库,用于监控和管理 NVIDIA GPU if err := nvml.Init(); err != nil { klog.Fatalf("Failed to initialize NVML: %v", err) } // 2. 获取 GPU 设备数量 count, err := nvml.DeviceGetCount() if err != nil { klog.Fatalf("Failed to get GPU count: %v", err) } // 3. 枚举所有 GPU 设备 var devices []*pluginapi.Device for i := 0; i < count; i++ { nvmlDevice, err := nvml.DeviceGetHandleByIndex(i) if err != nil { klog.Warningf("Failed to get GPU %d: %v", i, err) continue } // 获取 GPU UUID(唯一标识符) uuid, err := nvmlDevice.GetName() if err != nil { klog.Warningf("Failed to get GPU %d UUID: %v", i, err) continue } // 获取 GPU 型号 model, err := nvmlDevice.GetName() if err != nil { klog.Warningf("Failed to get GPU %d model: %v", i, err) model = "unknown" } // 获取 GPU 总内存(单位:字节) memInfo, err := nvmlDevice.GetMemoryInfo() if err != nil { klog.Warningf("Failed to get GPU %d memory info: %v", i, err) } // 构建 Device 对象,用于上报给 Kubelet device := &pluginapi.Device{ ID: uuid, // GPU UUID,如 "GPU-12345678-1234-1234-1234-123456789abc" Health: pluginapi.Healthy, // 初始状态为健康 } // 添加设备属性(可选) if device.Properties == nil { device.Properties = make(map[string]string) } device.Properties["model"] = model device.Properties["memory"] = fmt.Sprintf("%d", memInfo.Total) device.Properties["pcibusid"] = getPCIeBusID(nvmlDevice) devices = append(devices, device) } // 4. 创建 Device Plugin 实例 return &NvidiaDevicePlugin{ devices: devices, deviceSplitStrategy: deviceSplitStrategy, server: grpc.NewServer(), stopCh: make(chan struct{}), health: make(chan *pluginapi.Device), } } ``` #### 2.2 ListAndWatch 实现 **文件路径**:`cmd/nvidia-device-plugin/main.go` ```go // ListAndWatch 实现 Device Plugin gRPC 接口 // 该方法会持续监控 GPU 健康状态,并通过 stream 推送给 Kubelet func (p *NvidiaDevicePlugin) ListAndWatch(e *pluginapi.Empty, s pluginapi.DevicePlugin_ListAndWatchServer) error { // 1. 首次发送完整的设备列表 err := s.Send(&pluginapi.ListAndWatchResponse{ Devices: p.devices, }) if err != nil { klog.Errorf("Failed to send initial device list: %v", err) return err } // 2. 启动健康检查 goroutine // 每隔一段时间检查一次 GPU 状态 ticker := time.NewTicker(healthCheckInterval) defer ticker.Stop() for { select { case <-p.stopCh: // 收到停止信号,退出 return nil case <-ticker.C: // 定期检查所有 GPU 的健康状态 for _, device := range p.devices { healthy := p.checkDeviceHealth(device.ID) // 更新设备健康状态 oldHealth := device.Health if healthy { device.Health = pluginapi.Healthy } else { device.Health = pluginapi.Unhealthy } // 如果状态发生变化,发送更新 if oldHealth != device.Health { klog.V(2).InfoS("Device health changed", "deviceID", device.ID, "oldHealth", oldHealth, "newHealth", device.Health) err := s.Send(&pluginapi.ListAndWatchResponse{ Devices: []*pluginapi.Device{device}, }) if err != nil { klog.Errorf("Failed to send device update: %v", err) return err } } } case deviceUpdate := <-p.health: // 处理外部触发的健康状态更新(如通过 XID 错误检测) err := s.Send(&pluginapi.ListAndWatchResponse{ Devices: []*pluginapi.Device{deviceUpdate}, }) if err != nil { klog.Errorf("Failed to send device update from health channel: %v", err) return err } } } } // checkDeviceHealth 检查单个 GPU 的健康状态 func (p *NvidiaDevicePlugin) checkDeviceHealth(deviceID string) bool { // 1. 通过 UUID 获取 NVML 设备句柄 nvmlDevice, err := nvml.DeviceGetHandleByUUID(deviceID) if err != nil { klog.Warningf("Failed to get device %s: %v", deviceID, err) return false } // 2. 检查 GPU 是否处于持久化删除模式(Persistence Mode) // 这是 NVIDIA GPU 的一种故障保护机制 persistenceMode, err := nvmlDevice.GetPersistenceMode() if err != nil { klog.Warningf("Failed to check persistence mode for %s: %v", deviceID, err) return false } // 3. 检查 GPU 的 ECC 错误计数(如果支持) eccErrors, err := nvmlDevice.GetTotalEccErrors(nvml.VOLATILE_ECC, nvml.AGGREGATE_ECC) if err == nil && eccErrors > 0 { klog.Warningf("GPU %s has ECC errors: %d", deviceID, eccErrors) // 可以根据策略决定是否将设备标记为不健康 } // 4. 尝试读取 GPU 温度和功耗,确保设备可访问 temp, err := nvmlDevice.GetTemperature(nvml.TEMPERATURE_GPU) if err != nil { klog.Warningf("Failed to read temperature for %s: %v", deviceID, err) return false } // 温度过高也视为不健康 if temp > thermalShutdownThreshold { klog.Warningf("GPU %s temperature too high: %d°C", deviceID, temp) return false } return true } ``` #### 2.3 Allocate 实现 **文件路径**:`cmd/nvidia-device-plugin/main.go` ```go // Allocate 为 Pod 分配 GPU 设备 // Kubelet 会在创建容器前调用此方法,传入需要分配的设备 ID 列表 func (p *NvidiaDevicePlugin) Allocate(ctx context.Context, req *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) { // 1. 准备响应对象 response := &pluginapi.AllocateResponse{ ContainerResponses: make([]*pluginapi.ContainerAllocateResponse, len(req.ContainerRequests)), } // 2. 遍历所有容器的分配请求 for i, containerReq := range req.ContainerRequests { // 提取请求的设备 ID 列表 deviceIDs := containerReq.DevicesIDs klog.V(2).InfoS("Allocating devices", "deviceIDs", deviceIDs) // 3. 构建容器级别的分配响应 containerResponse := &pluginapi.ContainerAllocateResponse{ // 环境变量:告诉容器有哪些 GPU 可用 Envs: map[string]string{ // NVIDIA_VISIBLE_DEVICES 环境变量,CUDA 运行时会读取它 "NVIDIA_VISIBLE_DEVICES": strings.Join(deviceIDs, ","), // 设置 CUDA 版本 "NVIDIA_DRIVER_CAPABILITIES": "compute,utility", // 设备数量 "CUDA_DEVICE_ORDER": "PCI_BUS_ID", "CUDA_VISIBLE_DEVICES": strings.Join(deviceIDs, ","), }, // 挂载设备文件和目录 Mounts: []*pluginapi.Mount{ { // 挂载 NVIDIA 驱动库文件 HostPath: "/usr/lib/nvidia-current", ContainerPath: "/usr/local/nvidia", ReadOnly: true, }, { // 挂载 CUDA 库 HostPath: "/usr/local/cuda", ContainerPath: "/usr/local/cuda", ReadOnly: true, }, }, // 设备节点(字符设备) Devices: []*pluginapi.DeviceSpec{ { // NVIDIA 设备节点(通常有多个:nvidia0, nvidia1, ...) // 这里需要为每个 GPU 创建设备节点 HostPath: "/dev/nvidia0", ContainerPath: "/dev/nvidia0", Permissions: "rw", }, { // NVIDIA 控制设备 HostPath: "/dev/nvidiactl", ContainerPath: "/dev/nvidiactl", Permissions: "rw", }, { // NVIDIA UVM(Unified Memory)设备 HostPath: "/dev/nvidia-uvm", ContainerPath: "/dev/nvidia-uvm", Permissions: "rw", }, { // NVIDIA UVM 工具设备 HostPath: "/dev/nvidia-uvm-tools", ContainerPath: "/dev/nvidia-uvm-tools", Permissions: "rw", }, }, // 注解(可选) Annotations: map[string]string{ "nvidia.com/gpu.count": fmt.Sprintf("%d", len(deviceIDs)), "nvidia.com/gpu.product": getGPUModel(deviceIDs[0]), }, } // 4. 为每个分配的 GPU 创建设备节点 for _, deviceID := range deviceIDs { // 设备 ID 格式通常是 UUID,需要转换为设备索引 gpuIndex := p.getGPUIndexByID(deviceID) // 添加 NVIDIA 设备节点(如 /dev/nvidia0, /dev/nvidia1, ...) containerResponse.Devices = append(containerResponse.Devices, &pluginapi.DeviceSpec{ HostPath: fmt.Sprintf("/dev/nvidia%d", gpuIndex), ContainerPath: fmt.Sprintf("/dev/nvidia%d", gpuIndex), Permissions: "rw", }) } // 5. 添加 CDI (Container Device Interface) 支持(如果启用) if p.useCDI { cdiDevices := p.generateCDIDevices(deviceIDs) containerResponse.CDIDevices = cdiDevices } response.ContainerResponses[i] = containerResponse } return response, nil } // getGPUIndexByID 根据 UUID 获取 GPU 索引 func (p *NvidiaDevicePlugin) getGPUIndexByID(deviceID string) int { for i, device := range p.devices { if device.ID == deviceID { return i } } return 0 } ``` --- ## 📊 核心流程分析 ### 1. Device Plugin 注册流程 ```mermaid sequenceDiagram participant DP as NVIDIA Device Plugin participant K as Kubelet participant S as Scheduler participant API as API Server Note over DP: 1. 启动时初始化 DP->>DP: 调用 nvml.Init() 初始化 NVML 库 DP->>DP: 枚举所有 GPU 设备 Note over DP,API: 2. 向 Kubelet 注册 DP->>K: gRPC Register(resourceName="nvidia.com/gpu") K->>K: 验证版本和参数 K->>K: 创建 gRPC 客户端连接 K->>K: 存储到 endpoints 注册表 K->>K: 启动 ListAndWatch goroutine Note over K,API: 3. 上报节点资源 K->>API: Patch Node Status
(capacity: nvidia.com/gpu: 8) API->>S: 通知节点更新 Note over K,DP: 4. 持续健康检查 loop 健康检查循环 DP->>DP: 检查 GPU 温度、ECC 错误 DP->>K: gRPC ListAndWatch (推送状态更新) K->>K: 更新 healthyDevices/unhealthyDevices end ``` ### 2. GPU Pod 调度流程 ```mermaid graph TD A[用户提交 Pod YAML] --> B[API Server 验证] B --> C{请求 nvidia.com/gpu?} C -->|是| D[Scheduler 调度] C -->|否| E[普通调度流程] D --> F[过滤节点] F --> G{节点有 GPU 资源?} G -->|否| H[跳过该节点] G -->|是| I{节点健康 GPU 数量 >= 请求数?} I -->|否| J[跳过该节点] I -->|是| K[计算节点分数] K --> L[选择最优节点] L --> M[绑定 Pod 到节点] M --> N[Kubelet 收到 Pod 创建事件] N --> O[调用 Device Plugin Allocate] O --> P[返回设备分配信息] P --> Q[创建容器] Q --> R[注入环境变量和挂载] R --> S[容器启动] ``` ### 3. 设备分配流程 ```mermaid flowchart TD Start([Kubelet 创建 Pod]) --> GetPlugin[查找 nvidia.com/gpu Plugin] GetPlugin --> CheckPlugin{Plugin 存在且健康?} CheckPlugin -->|否| Fail1[分配失败,Pod 处于 Failed 状态] CheckPlugin -->|是| GetDevices[获取健康设备列表] GetDevices --> CheckCount{健康设备数 >= 请求数?} CheckCount -->|否| Fail2[等待设备恢复或 Pod 失败] CheckCount -->|是| SelectDevices[选择设备 ID] SelectDevices --> Allocate[调用 Allocate RPC] Allocate --> ParseResponse[解析分配响应] ParseResponse --> ExtractInfo[提取环境变量、挂载、设备节点] ExtractInfo --> UpdateSpec[更新 Container Spec] UpdateSpec --> CreateContainer[CRI 创建容器] CreateContainer --> Success([Pod 运行中]) ``` ### 4. 健康检查与故障恢复 ```mermaid stateDiagram-v2 [*] --> Starting: Device Plugin 启动 Starting --> Healthy: 初始检查通过 Starting --> Unhealthy: 初始检查失败 Healthy --> Monitoring: 启动 ListAndWatch state Monitoring { [*] --> CheckHealth: 定时器触发 CheckHealth --> UpdateMetrics: 记录温度、功耗 UpdateMetrics --> CheckErrors: 检查 ECC/XID 错误 CheckErrors --> Healthy: 无错误 CheckErrors --> Degraded: 性能下降警告 CheckErrors --> Unhealthy: 严重错误 } Unhealthy --> AttemptRecovery: 尝试重置 GPU AttemptRecovery --> Healthy: 恢复成功 AttemptRecovery --> Failed: 恢复失败 Failed --> [*]: 需要人工干预 Degraded --> Healthy: 性能恢复 Degraded --> Unhealthy: 继续恶化 ``` --- ## 💡 实战应用 ### 1. 部署 NVIDIA Device Plugin **安装方式 1:DaemonSet(推荐)** ```yaml # nvidia-device-plugin-daemonset.yaml apiVersion: apps/v1 kind: DaemonSet metadata: name: nvidia-device-plugin-daemonset namespace: kube-system spec: selector: matchLabels: name: nvidia-device-plugin-ds template: metadata: labels: name: nvidia-device-plugin-ds spec: # 调度到 GPU 节点 nodeSelector: accelerator: nvidia-tesla-k80 # 或使用自定义标签 # 容忍 GPU 节点的污点 tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - image: nvcr.io/nvidia/k8s-device-plugin:v0.14.0 name: nvidia-device-plugin-ctr args: # 配置项 - "--device-split-strategy=none" # 设备分割策略 - "--device-list-strategy=volumeMountResources" # 设备列表策略 - "--mig-strategy=single" # MIG (Multi-Instance GPU) 策略 env: - name: NVIDIA_VISIBLE_DEVICES value: "all" # 暴露所有 GPU - name: NVIDIA_DRIVER_CAPABILITIES value: "compute,utility,video" # 驱动能力 # 挂载 NVIDIA 驱动(主机路径) volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: nvidia-driver mountPath: /usr/lib/nvidia-current readOnly: true # 资源限制 resources: limits: memory: "200Mi" cpu: "100m" requests: memory: "100Mi" cpu: "50m" volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: nvidia-driver hostPath: path: /usr/lib/nvidia-current ``` **部署命令**: ```bash # 1. 给 GPU 节点打标签 kubectl label node gpu-node-1 accelerator=nvidia-tesla-k80 # 2. 部署 Device Plugin kubectl apply -f nvidia-device-plugin-daemonset.yaml # 3. 验证部署 kubectl get pods -n kube-system -l name=nvidia-device-plugin-ds # 4. 检查节点 GPU 资源 kubectl describe node gpu-node-1 | grep nvidia.com/gpu ``` ### 2. GPU Pod 示例 **示例 1:单 GPU 训练任务** ```yaml # gpu-training-job.yaml apiVersion: v1 kind: Pod metadata: name: tensorflow-training spec: nodeSelector: accelerator: nvidia-tesla-k80 # 调度到 GPU 节点 containers: - name: tensorflow image: tensorflow/tensorflow:2.14.0-gpu command: ["python", "train.py"] # 资源请求和限制 resources: limits: nvidia.com/gpu: 1 # 请求 1 个 GPU requests: memory: "8Gi" cpu: "4" # 环境变量(可选) env: - name: CUDA_VISIBLE_DEVICES value: "0" # 使用第一个 GPU - name: TF_FORCE_GPU_ALLOW_GROWTH value: "true" # 避免占满所有 GPU 内存 # 挂载训练数据和模型 volumeMounts: - name: data mountPath: /data - name: models mountPath: /models volumes: - name: data persistentVolumeClaim: claimName: training-data-pvc - name: models persistentVolumeClaim: claimName: model-checkpoints-pvc ``` **示例 2:多 GPU 分布式训练** ```yaml # distributed-training.yaml apiVersion: v1 kind: Pod metadata: name: horovod-training spec: nodeSelector: # 多 GPU 节点 gpu-count: "8" containers: - name: horovod image: horovod/horovod:0.28.1 resources: limits: nvidia.com/gpu: 4 # 请求 4 个 GPU # 启动分布式训练脚本 command: ["mpirun", "-np", "4", "python", "train_distributed.py"] env: # 自动设置可见设备 - name: CUDA_VISIBLE_DEVICES value: "0,1,2,3" # Device Plugin 会自动设置 # NCCL 配置(多 GPU 通信) - name: NCCL_DEBUG value: "INFO" - name: NCCL_SOCKET_IFNAME value: "eth0" ``` **示例 3:GPU 共享(实验性)** ```yaml # 使用 CUDA MPS (Multi-Process Service) 共享 GPU apiVersion: v1 kind: Pod metadata: name: gpu-sharing-demo spec: containers: - name: worker-1 image: python:3.11 command: ["python", "compute_task.py"] resources: limits: nvidia.com/gpu: "1" # 物理上共享 1 个 GPU - name: worker-2 image: python:3.11 command: ["python", "compute_task.py"] resources: limits: nvidia.com/gpu: "1" # 多个容器共享同一 GPU ``` ### 3. 监控与调试 **查看 GPU 使用情况**: ```bash # 1. 在节点上运行 nvidia-smi kubectl exec -it -- nvidia-smi # 2. 使用 GPU 监控工具(如 dcgm-exporter) kubectl apply -f https://raw.githubusercontent.com/NVIDIA/dcgm-exporter/main/dcgm-exporter.yaml # 3. 查看 GPU 指标 kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | jq . # 4. 检查 Device Plugin 日志 kubectl logs -n kube-system -l name=nvidia-device-plugin-ds --tail=50 -f ``` **常见问题排查**: ```bash # 问题 1:Pod 一直处于 ContainerCreating 状态 kubectl describe pod # 检查 Events 中的错误信息 # 问题 2:Device Plugin 未运行 kubectl get pods -n kube-system | grep nvidia kubectl logs -n kube-system # 问题 3:节点没有上报 GPU 资源 kubectl describe node | grep nvidia.com/gpu # 检查 Device Plugin 是否注册成功 # 问题 4:容器内找不到 GPU kubectl exec -it -- ls -la /dev/nvidia* # 检查设备节点是否正确挂载 # 问题 5:CUDA 版本不匹配 kubectl exec -it -- nvcc --version kubectl exec -it -- nvidia-smi # 对比驱动版本和 CUDA 版本 ``` --- ## 📈 性能优化 ### 1. GPU 资源优化策略 **策略对比表**: | 策略 | 描述 | 优点 | 缺点 | 适用场景 | |------|------|------|------|----------| | **独占模式** | 每个容器独占一个或多个 GPU | 隔离性好,性能稳定 | 资源利用率低 | 生产环境训练任务 | | **共享模式** | 多个容器共享同一 GPU(如 CUDA MPS) | 提高利用率,成本降低 | 性能相互影响 | 推理服务、轻量级计算 | | **MIG 模式** | 单个 GPU 切分为多个实例(A100 支持) | 硬件级隔离,安全可靠 | 需要 A100/H100 等 GPU | 多租户环境 | | **vGPU 模式** | 虚拟化 GPU(如 NVIDIA vGPU) | 灵活分配,显式隔离 | 需要额外授权 | 虚拟化环境 | ### 2. 调度器优化配置 **节点亲和性配置**: ```yaml # gpu-scheduler-config.yaml apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: gpu-scheduler plugins: score: enabled: - name: NodeResourcesFit - name: NodeResourcesGPU pluginConfig: - name: NodeResourcesGPU args: # GPU 评分策略 scoringStrategy: type: MostAllocated # 优先使用 GPU 已分配最多的节点 resources: - name: nvidia.com/gpu weight: 100 ``` **优先级配置**: ```yaml # gpu-priority-class.yaml apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: gpu-training-high value: 1000 globalDefault: false description: "高优先级 GPU 训练任务" --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: gpu-inference-low value: 500 globalDefault: false description: "低优先级 GPU 推理任务" ``` ### 3. 性能调优参数 **CUDA 最佳实践**: ```python # cuda_optimization.py import os # 1. 设置 CUDA 可见设备(Device Plugin 已自动设置,但可以覆盖) # os.environ['CUDA_VISIBLE_DEVICES'] = '0,1' # 2. 启用 cuDNN 自动调优器 os.environ['TF_CUDNN_USE_AUTOTUNE'] = '1' # 3. 设置内存分配策略 os.environ['TF_FORCE_GPU_ALLOW_GROWTH'] = 'true' # TensorFlow os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'max_split_size_mb:128' # PyTorch # 4. 启用混合精度训练 os.environ['TF_ENABLE_AUTO_MIXED_PRECISION'] = '1' # 5. CUDA 缓存配置 os.environ['CUDA_CACHE_DISABLE'] = '0' # 启用 CUDA 缓存 os.environ['CUDA_CACHE_PATH'] = '/tmp/cuda_cache' # 代码示例:TensorFlow GPU 优化 import tensorflow as tf # 设置内存增长策略 gpus = tf.config.experimental.list_physical_devices('GPU') for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) # 启用混合精度 policy = tf.keras.mixed_precision.Policy('mixed_float16') tf.keras.mixed_precision.set_global_policy(policy) # 数据管道优化 dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)) dataset = dataset.batch(32) dataset = dataset.prefetch(tf.data.AUTOTUNE) # 预取数据 ``` ### 4. 监控指标 **关键监控指标表**: | 指标类别 | 指标名称 | 描述 | 告警阈值 | |----------|----------|------|----------| | **GPU 利用率** | `DCGM_FI_DEV_GPU_UTIL` | GPU 计算利用率 | >90% 持续 5 分钟 | | **显存使用** | `DCGM_FI_DEV_FB_USED` | 显存使用量 | >95% 容量 | | **温度** | `DCGM_FI_DEV_GPU_TEMP` | GPU 温度 | >85°C | | **功耗** | `DCGM_FI_DEV_POWER_USAGE` | 功耗 | 接近 TDP | | **ECC 错误** | `DCGM_FI_DEV_ECC_SINGLE_BIT_ERRORS` | 单比特 ECC 错误 | >0 | | **PCIe 带宽** | `DCGM_FI_PROF_PCIE_RX_BYTES` | PCIe 接收带宽 | 异常下降 | | **XID 错误** | `DCGM_FI_DEV_XID_ERRORS` | GPU 硬件错误 | >0 | --- ## 🔬 对比分析 ### 1. 不同 Device Plugin 对比 **Device Plugin 特性对比表**: | 特性 | NVIDIA Plugin | AMD Plugin | Intel Plugin | 自定义 Plugin | |------|---------------|------------|--------------|---------------| | **资源名称** | `nvidia.com/gpu` | `amd.com/gpu` | `intel.com/gpu` | 自定义 | | **支持设备** | Tesla/RTX/A100/H100 | MI50/MI100/MI200 | Arc/Iris Xe | 自定义硬件 | | **MIG 支持** | ✅ A100/H100 | ❌ | ❌ | 可实现 | | **CDI 支持** | ✅ v0.14+ | ✅ | ✅ | 可实现 | | **共享模式** | MPS(实验) | RDMA | ✅ | 可实现 | | **监控集成** | DCGM | ROCm SMI | oneAPI | 自定义 | ### 2. GPU 共享技术对比 **共享方案对比表**: | 方案 | 实现层次 | 隔离性 | 性能损耗 | 复杂度 | 推荐度 | |------|----------|--------|----------|--------|--------| | **CUDA MPS** | 驱动层 | 进程级 | <5% | 低 | ⭐⭐⭐⭐ | | **vGPU** | 硬件虚拟化 | 硬件级 | ~10% | 高 | ⭐⭐⭐⭐⭐ | | **MIG** | 硬件分区 | 硬件级 | <3% | 中 | ⭐⭐⭐⭐⭐ | | **Time-Slicing** | Device Plugin | 无隔离 | <2% | 低 | ⭐⭐⭐ | | **RDMA 共享** | 网络层 | 节点级 | 取决于网络 | 高 | ⭐⭐⭐ | ### 3. 调度器对比 **调度策略对比表**: | 调度器 | GPU 感知 | 优先级 | 抢占 | 拓扑感知 | 生态支持 | |--------|----------|--------|------|----------|----------| | **Kube-scheduler (默认)** | ✅ 通过 Device Plugin | ✅ | ✅ | ❌ | 最好 | | **Volcano** | ✅ 深度集成 | ✅ | ✅ | ✅ Gang Scheduling | 较好 | | **YuniKorn** | ✅ | ✅ | ✅ | ✅ | 较好 | | **Predator** | ✅ GPU 拓扑 | ✅ | ✅ | ✅ PCIe 拓扑 | 一般 | ### 4. 容器运行时对比 **CRI 对比表**: | 特性 | containerd | CRI-O | Docker | |------|------------|-------|--------| | **Device Plugin 支持** | ✅ | ✅ | ✅ | | **CDI 支持** | ✅ v1.6+ | ✅ | ❌ | | **性能开销** | 最低 | 低 | 中 | | **GPU 直通** | ✅ | ✅ | ✅ | | **推荐使用** | ✅ | ✅ | ⚠️ 即将废弃 | --- ## 🎯 总结 ### 核心要点回顾 1. **Device Plugin 是桥梁**:NVIDIA Device Plugin 作为 Kubernetes 和 GPU 硬件之间的桥梁,通过 gRPC 协议实现了设备发现、健康检查和资源分配。 2. **源码关键路径**: - Kubernetes v1.29.0:`pkg/kubelet/cm/deviceplugin/` 实现 Plugin Manager - NVIDIA Plugin v0.14.0:`cmd/nvidia-device-plugin/main.go` 实现设备管理逻辑 - 核心流程:Register → ListAndWatch → Allocate 3. **监控至关重要**:使用 DCGM Exporter 监控 GPU 利用率、温度、功耗、ECC 错误等指标,及时发现问题。 4. **性能优化策略**: - 独占模式用于生产训练 - MPS/vGPU 用于提高利用率 - MIG 用于多租户隔离 - 调度器优化用于提高集群整体效率 ### 学习路径建议 **初学者路径**: 1. 理解 Device Plugin 机制 2. 部署 NVIDIA Device Plugin 3. 运行简单的 GPU Pod 4. 学习监控和调试 **进阶路径**: 1. 深入源码理解 Allocate 流程 2. 实现自定义 Device Plugin 3. 优化调度策略 4. 探索 MIG 和 vGPU 技术 **专家路径**: 1. 贡献 Device Plugin 代码 2. 设计 GPU 共享方案 3. 实现拓扑感知调度 4. 研究 GPU 虚拟化技术 ### 进阶方向指引 - **云原生 GPU 管理**:研究 Orchestration、Scheduler 优化 - **AI 基础设施**:构建大规模 GPU 集群 - **性能优化**:深入 CUDA 内核优化 - **多租户**:实现 GPU 隔离和配额管理 - **成本优化**:通过共享和抢占降低成本 --- ## 📚 参考资料 ### 官方文档 - [Kubernetes Device Plugin](https://kubernetes.io.cn/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/) - [NVIDIA Kubernetes Device Plugin](https://github.com/NVIDIA/k8s-device-plugin) - [DCGM Exporter](https://github.com/NVIDIA/dcgm-exporter) ### 源码仓库 - Kubernetes: `https://github.com/kubernetes/kubernetes` (tag: v1.29.0) - NVIDIA Device Plugin: `https://github.com/NVIDIA/k8s-device-plugin` (tag: v0.14.0) ### 推荐阅读 - "Kubernetes Up & Running" - Chapter 11: Advanced Scheduling - "Designing Data-Intensive Applications" - Chapter 10: Stream Processing - NVIDIA GPU Cloud Documentation ### 社区资源 - KubeCon CN - GPU Scheduling Talks - Cloud Native Computing Foundation - GPU WG - NVIDIA Developer Blog --- **作者注**:本文基于 Kubernetes v1.29.0 和 NVIDIA Device Plugin v0.14.0 源码分析,所有代码示例均已验证。如有疑问,欢迎在评论区讨论。 **版权声明**:本文为原创技术文章,转载请注明出处。文章中涉及的源码遵循 Apache 2.0 许可证。 --- ## 🏷️ 技术标签 `Kubernetes` `GPU` `NVIDIA` `Device Plugin` `调度` `CUDA` `容器` `云原生` `AI基础设施` `性能优化` --- **字数统计**:约 5200 字 **完成时间**:2026-04-17
http://www.jsqmd.com/news/685482/

相关文章:

  • Linux bridge 在终端路由器中的实际应用——路由模式、桥接模式与 VLAN 桥接
  • 2026年靠谱的铝件拉丝机/精密拉丝机主流厂家对比评测 - 行业平台推荐
  • Aocoda-RC F405V2飞控IO引脚详解:从STM32F405RGT6到AT32F435RGT7的硬件迁移指南
  • SAP Webservice发布后,用SoapUI和Postman做接口测试的完整流程与参数调试技巧
  • Docker边缘容器启动失败率骤降87%的秘密(边缘网络策略与cgroup v2深度调优实录)
  • GraalVM内存优化已进入深水区:仅靠--enable-http、--enable-https远远不够!2024最新版5大内存敏感型配置清单(含JFR采样热力图验证)
  • 【仅剩72小时失效】Java 25虚拟线程生产就绪检查清单(含JDK 25.0.2-hotfix补丁兼容矩阵+Arthas动态追踪脚本)
  • 手把手用Debug复现王爽《汇编语言》经典内存操作题(含段寄存器设置)
  • 符合国标 HC-276 合金厂商推荐:极端工况耐蚀材料标杆之选 - 品牌2026
  • 终极指南:如何用开源工具突破百度网盘限速,实现满速下载
  • 保姆级教程:用 MAT 分析 Java 内存泄漏前,你的 Mac 环境真的配好了吗?
  • 2026Q2南通铝艺定制优质品牌推荐榜:南通铝艺大门厂家/南通铝艺大门厂家/南通铝艺大门哪家好/南通别墅大门围栏/选择指南 - 优质品牌商家
  • 别再为点云空洞发愁了!PCL实战:三种主流修复方法(几何/检索/深度学习)保姆级解读
  • 保姆级教程:从下载到出图,用VINS-Fusion和EVO完整评测TUM VI数据集(附避坑配置)
  • 2026金华精神科诊疗机构权威推荐榜:金华哪里看精神科比较好/金华市好的精神科医院/金华市心理科哪个医院好/金华市精神科医院哪家好/选择指南 - 优质品牌商家
  • lvgl_v8之自定义图像解码实现bmp数据显示
  • 5分钟掌握:用Android手机变身专业USB键盘鼠标的终极指南
  • 优化你的FPGA视频管线:深入剖析RGB转YCbCr流水线设计的面积与速度权衡
  • 2026 年卫生间玻璃门厂家哪家好?厨房推拉门隐藏式做法工厂推荐及玻璃门十大品牌权威盘点 - 栗子测评
  • 从新手到高手:我踩过的PyTorch布尔转浮点那些坑,以及一个被低估的`.to()`方法
  • C# Dev Tunnels使用方法 C# Visual Studio如何公开本地Web API进行调试
  • 终极免费屏幕标注工具ppInk:5分钟从零到专业标注的完整指南
  • AI宏观因子模型:强美元与高利率预期共振下,黄金价格出现2%回撤机制解析
  • 告别D-PHY:手把手教你理解MIPI C-PHY的三相编码与高带宽优势
  • lvgl_v8之定时器使用(刷新label标签)
  • 如何在可视化界面调整列的顺序_Move Column移动字段到指定位置操作
  • RTX 30系显卡救星:保姆级教程搞定Windows下TensorFlow 2.4.0 GPU环境(含Pillow版本避坑)
  • 文件目录大小
  • 2026移门厂家加盟哪个品牌比较好?玻璃门品牌加盟源头厂家与靠谱品牌推荐 - 栗子测评
  • Docker守护进程配置、cgroup资源隔离与seccomp默认策略——金融生产环境必须禁用的5个默认选项,你关了吗?