NVIDIA k8s-device-plugin

简介

一个专门为 Kubernetes 设计的组件,用于将 NVIDIA GPU 集成到 Kubernetes 集群中,使其作为可调度资源供容器化工作负载使用。它是 NVIDIA GPU 支持在 Kubernetes 中运行的关键部分,特别是在需要高性能计算(HPC)、机器学习或图形处理的场景下。


流程图

js 复制代码
启动插件
   ↓
初始化 NVML → 发现 GPU
   ↓
启动 gRPC 服务器 → 注册到 kubelet
   ↓
ListAndWatch 上报设备列表 ← 健康检查(循环)
   ↓
Pod 请求 GPU → Allocate 配置环境变量
   ↓
持续运行并更新状态

工作流程概述

  1. 插件启动: 插件以 DaemonSet 的形式运行在每个有 NVIDIA GPU 的节点上。
  2. 设备发现: 通过 NVIDIA Management Library (NVML) 检测节点上的 GPU。
  3. 注册到 kubelet: 向节点的 kubelet 注册插件,声明其管理的资源(例如 nvidia.com/gpu)。
  4. 设备上报: 通过 gRPC 接口向 kubelet 上报可用 GPU 的列表及其状态。
  5. 健康检查: 持续监控 GPU 的健康状态,并更新上报信息。
  6. 资源分配: 当 Pod 请求 GPU 时,插件为容器配置访问 GPU 的环境。
  7. 持续运行: 插件保持运行,定期更新设备状态并响应 kubelet 的请求。

1. 插件启动

  • 运行方式: NVIDIA Device Plugin 通常以 DaemonSet 部署在 Kubernetes 集群中,确保每个有 GPU 的节点上都有一个插件实例。
  • 初始化: 插件启动时会加载配置(如资源名称 nvidia.com/gpu),并初始化 NVML 以访问 GPU 硬件。
  • 代码入口: 在 main.go 中
golang 复制代码
func main() {
    plugin, err := NewNvidiaDevicePlugin(/* 配置参数 */)
    if err != nil {
        log.Fatal(err)
    }
    plugin.Serve()
}

  1. 设备发现
  • 目的: 识别节点上所有可用的 NVIDIA GPU。
  • 实现: 使用 NVML 的 API 获取 GPU 数量和详细信息。
  • 关键代码: 在 nvml.go 中:
golang 复制代码
func (n *nvmlDeviceLister) GetDevices() ([]*pluginapi.Device, error) {
    count, ret := nvml.DeviceGetCount()
    if ret != nvml.SUCCESS {
        return nil, fmt.Errorf("nvml.DeviceGetCount() failed: %v", ret)
    }
    var devs []*pluginapi.Device
    for i := 0; i < count; i++ {
        dev, ret := nvml.DeviceGetHandleByIndex(i)
        if ret != nvml.SUCCESS {
            continue
        }
        uuid, ret := nvml.DeviceGetUUID(dev)
        if ret != nvml.SUCCESS {
            continue
        }
        devs = append(devs, &pluginapi.Device{
            ID:     uuid,
            Health: pluginapi.Healthy,
        })
    }
    return devs, nil
}

输出: 插件生成一个 pluginapi.Device 列表,每个设备包含唯一的 UUID 和初始健康状态。


  1. 注册到 kubelet
  • 目的: 通知 kubelet 插件的存在及其管理的资源。
  • 实现: 插件启动一个 gRPC 服务器,并通过 kubelet 的设备插件注册接口进行注册。
  • 关键代码: 在 server.go 中:
golang 复制代码
func (m *NvidiaDevicePlugin) Serve() error {
    m.server = grpc.NewServer()
    pluginapi.RegisterDevicePluginServer(m.server, m)
    lis, err := net.Listen("unix", m.socket) // e.g., /var/lib/kubelet/device-plugins/nvidia.sock
    if err != nil {
        return err
    }
    go m.server.Serve(lis)
    return m.registerWithKubelet()
}

func (m *NvidiaDevicePlugin) registerWithKubelet() error {
    conn, err := grpc.Dial(kubeletSocket, grpc.WithInsecure(), grpc.WithDialer(...))
    if err != nil {
        return err
    }
    defer conn.Close()
    client := pluginapi.NewRegistrationClient(conn)
    req := &pluginapi.RegisterRequest{
        Version:      pluginapi.Version,
        Endpoint:     path.Base(m.socket),
        ResourceName: m.resourceName, // "nvidia.com/gpu"
    }
    _, err = client.Register(context.Background(), req)
    return err
}

结果: kubelet 知道插件管理 nvidia.com/gpu 资源,并通过 gRPC 端点与其通信


  1. 设备上报
  • 目的: 将 GPU 的可用性和状态实时上报给 kubelet。
  • 实现: 插件实现 Kubernetes Device Plugin API 的 ListAndWatch 方法,通过 gRPC 流定期发送设备列表。
  • 关键代码: 在 server.go 中:
golang 复制代码
func (m *NvidiaDevicePlugin) ListAndWatch(e *pluginapi.Empty, s pluginapi.DevicePlugin_ListAndWatchServer) error {
    prevDevices := make(map[string]*pluginapi.Device)
    for {
        currentDevices, err := m.getDevices()
        if err != nil {
            return err
        }
        if !devicesEqual(prevDevices, currentDevices) {
            resp := &pluginapi.ListAndWatchResponse{Devices: currentDevices}
            if err := s.Send(resp); err != nil {
                return err
            }
            prevDevices = make(map[string]*pluginapi.Device)
            for _, dev := range currentDevices {
                prevDevices[dev.ID] = dev
            }
        }
        time.Sleep(10 * time.Second) // 定期更新
    }
}

结果: kubelet 收到设备列表,了解当前可用的 GPU 及其健康状态(如 Healthy 或 Unhealthy)。


  1. 健康检查
  • 目的: 监控 GPU 的运行状态,检测故障或异常。

  • 实现: 插件通过 NVML 检查 GPU 的电源状态、温度、XID 错误等,并在状态变化时更新设备列表。

  • 关键代码: 在 server.go 中:

    golang 复制代码
    func (m *NvidiaDevicePlugin) healthCheck() {
        for {
            devices, _ := m.getDevices()
            for _, dev := range devices {
                healthy := m.checkHealth(dev.ID)
                if dev.Health != healthy {
                    dev.Health = healthy
                    m.updateDevices(dev)
                }
            }
            time.Sleep(5 * time.Second)
        }
    }
    
    func (m *NvidiaDevicePlugin) checkHealth(uuid string) string {
        dev, ret := nvml.DeviceGetHandleByUUID(uuid)
        if ret != nvml.SUCCESS {
            return pluginapi.Unhealthy
        }
        xidErrors := m.getXidErrors(dev)
        if len(xidErrors) > 0 {
            return pluginapi.Unhealthy
        }
        return pluginapi.Healthy
    }

结果: 不健康的 GPU 被标记为 Unhealthy,不会被分配给 Pod。


  1. 资源分配
  • 目的: 当 Pod 请求 GPU 时,插件为容器配置访问权限。

  • 实现: 实现 Allocate 方法,返回环境变量(如 NVIDIA_VISIBLE_DEVICES)给容器。

  • 关键代码: 在 server.go 中:

    golang 复制代码
    func (m *NvidiaDevicePlugin) Allocate(ctx context.Context, req *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) {
        var responses []*pluginapi.ContainerAllocateResponse
        for _, r := range req.ContainerRequests {
            resp := &pluginapi.ContainerAllocateResponse{
                Envs: map[string]string{
                    "NVIDIA_VISIBLE_DEVICES": strings.Join(r.DevicesIDs, ","),
                },
            }
            responses = append(responses, resp)
        }
        return &pluginapi.AllocateResponse{ContainerResponses: responses}, nil
    }

结果: 容器启动时通过 NVIDIA 容器运行时访问指定的 GPU。


  1. 持续运行
  • 行为: 插件保持 gRPC 服务器运行,持续执行 ListAndWatch 和健康检查。
  • 容错: 如果插件崩溃或节点重启,DaemonSet 会重新部署插件,重复上述流程。

结束

NVIDIA Kubernetes Device Plugin 是连接 NVIDIA GPU 硬件和 Kubernetes 调度系统的桥梁。它通过发现 GPU、注册资源、上报状态和分配设备,使得 GPU 在 Kubernetes 中成为一种可管理的资源类型。它的设计充分利用了 NVML 的硬件访问能力和 Kubernetes 的插件机制,为 GPU 密集型工作负载提供了高效、云原生的支持。

简单来说,它让 Kubernetes "认识"并"使用" NVIDIA GPU,就像管理 CPU 和内存一样自然。

相关推荐
leobertlan7 小时前
2025年终总结
前端·后端·程序员
面向Google编程8 小时前
从零学习Kafka:数据存储
后端·kafka
易安说AI8 小时前
Claude Opus 4.6 凌晨发布,我体验了一整晚,说说真实感受。
后端
易安说AI9 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
易安说AI9 小时前
用 Claude Code 远程分析生产日志,追踪 Claude Max 账户被封原因
后端
颜酱10 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
Coder_Boy_12 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
掘金者阿豪13 小时前
关系数据库迁移的“暗礁”:金仓数据库如何规避数据完整性与一致性风险
后端
ServBay13 小时前
一个下午,一台电脑,终结你 90% 的 Symfony 重复劳动
后端·php·symfony
sino爱学习13 小时前
高性能线程池实践:Dubbo EagerThreadPool 设计与应用
java·后端