• kubernetes-nvidia-plugin设计解读


    场景

            最近在工作中经常使用到gpu资源,传统环境下我们一般在宿主机上部署算法程序,但是这样无法形成快速交付,并且维护成本会非常高。所以nvidia其实也考虑到这一点 通过docker调用宿主机的gpu资源。一般情况下我们需要部署gpu-container-runtime或者nvidia-docker去调用宿主机的gpu资源。那么既然docker可以使用gpu资源,我们是不是可以在kubernetes环境去使用gpu资源呢?答案是肯定的,接下来我这边会分享在kubernetes如何使用device plugin实现调度gpu资源。

    设计

            其实在kubernetes1.11版本之前社区给出的解决方案是加入了alpha.kubernetes.io/nvidia-gpu去使用gpu资源但是这个特性在1.11版本之后就已经下线了。取而代之的是通过Extended Resource+ Device Plugin两个Kubernetes的内置模块,外加由设备提供商实现的相应Device Plugin, 完成从设备的集群级别调度至工作节点,到设备与容器的实际绑定。

    首先思考的第一个问题是为什么进入alpha.kubernetes.io/nvidia-gpu主干一年之久的GPU功能彻底移除?

    1. Kubernetes有一个核心理念:OutOfTree是一个很好的理念,简单来讲就是专注于自身核心和通用能力 将像GPU,InfiniBand,FPGA和公共云能力的工作完全交给社区和领域专家。这样一方面可以降低软件自身使用的复杂度,减小稳定性风险,另外OutOfTree分开迭代也能够更灵活实现的功能升级。

    Device Plugin的设计:

            实际上Device plugins实际上是简单的grpc server,需要实现以下两个方法 ListAndWatchAllocate,并监听在/var/lib/kubelet/device-plugins/目录下的Unix Socket,比如/var/lib/kubelet/device-plugins/nvidia.sock。

    1. type DevicePluginServer interface {
    2. // GetDevicePluginOptions returns options to be communicated with Device
    3. // Manager
    4. GetDevicePluginOptions(context.Context, *Empty) (*DevicePluginOptions, error)
    5. // ListAndWatch returns a stream of List of Devices
    6. // Whenever a Device state change or a Device disapears, ListAndWatch
    7. // returns the new list
    8. ListAndWatch(*Empty, DevicePlugin_ListAndWatchServer) error
    9. // Allocate is called during container creation so that the Device
    10. // Plugin can run device specific operations and instruct Kubelet
    11. // of the steps to make the Device available in the container
    12. Allocate(context.Context, *AllocateRequest) (*AllocateResponse, error)
    13. // PreStartContainer is called, if indicated by Device Plugin during
    14. // registeration phase,
    15. // before each container start. Device plugin can run device specific
    16. // operations
    17. // such as reseting the device before making devices available to the container
    18. PreStartContainer(context.Context, *PreStartContainerRequest) (*PreStartContainerResponse, error)
    19. }

    其中:

    • ListAndWatch: Kubelet会调用该API做设备发现和状态更新(比如设备变得不健康)
    • Allocate: 当Kubelet创建要使用该设备的容器时, Kubelet会调用该API执行设备相应的操作并且通知Kubelet初始化容器所需的device,volume和环境变量的配置。

    ListAndWatch函数(register 时候会调用一次,而且仅仅就这一次,因为 ListAndWatch 是个 GRPC 长连接,DP 可以通过这个长连接不停的反馈。):

    1. func (m *NvidiaDevicePlugin) ListAndWatch(e *pluginapi.Empty, s pluginapi.DevicePlugin_ListAndWatchServer) error {
    2. s.Send(&pluginapi.ListAndWatchResponse{Devices: m.devs})
    3. for {
    4. select {
    5. case <-m.stop:
    6. return nil
    7. case d := <-m.health:
    8. // FIXME: there is no way to recover from the Unhealthy state.
    9. d.Health = pluginapi.Unhealthy
    10. s.Send(&pluginapi.ListAndWatchResponse{Devices: m.devs})
    11. }
    12. }
    13. }

    简阅Allocate函数主要的作用是分配相应的device资源:

    1. // Allocate which return list of devices.
    2. func (m *NvidiaDevicePlugin) Allocate(ctx context.Context,
    3. reqs *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) {
    4. responses := pluginapi.AllocateResponse{}
    5. log.Infoln("----Allocating GPU for gpu mem is started----")
    6. var (
    7. podReqGPU uint
    8. found bool
    9. assumePod *v1.Pod
    10. )
    11. // podReqGPU = uint(0)
    12. for _, req := range reqs.ContainerRequests {
    13. podReqGPU += uint(len(req.DevicesIDs))
    14. }
    15. log.Infof("RequestPodGPUs: %d", podReqGPU)
    16. ........

    类似阿里云的gpu share基本在这里加入了很多自定义逻辑实现了多卡gpu调度

    大致的逻辑就是通过Annotations 标签选择合适的gpu device。这里就不详细说明了 感兴趣的同学可以去看下源码。

    之后我们来看下device plugin 工作流程

    shareGPUManager ---run()---> NvidiaDevicePlugin ----Serve()----> Start GRPC server and Register

    那么整个Kubernetes调度GPU的过程如下:

    1. GPU Device plugin 部署到GPU节点上,通过 ListAndWatch 接口,上报注册节点的GPU信息和对应的DeviceID。

    2. 当有声明 nvidia.com/gpu 的GPU Pod创建出现,调度器会综合考虑GPU设备的空闲情况,将Pod调度到有充足GPU设备的节点上。

    3.  节点上的kubelet 启动Pod时,根据request中的声明调用各个Device plugin 的 allocate接口, 由于容器声明了GPU。 kubelet 根据之前 ListAndWatch 接口收到的Device信息,选取合适的设备,DeviceID 作为参数,调用GPU DevicePlugin的 Allocate 接口,GPU DevicePlugin ,接收到调用,将DeviceID 转换为 NVIDIA_VISIBLE_DEVICES 环境变量,返回kubelet

    4. kubelet将环境变量注入到Pod, 启动容器容器启动时, gpu-container-runtime 调用 gpu-containers-runtime-hookgpu-containers-runtime-hook 根据容器的 NVIDIA_VISIBLE_DEVICES 环境变量,转换为 --devices 参数,调用 nvidia-container-cli prestart,nvidia-container-cli 根据 --devices ,将GPU设备映射到容器中。 并且将宿主机的Nvidia Driver Lib 的so文件也映射到容器中。 此时容器可以通过这些so文件,调用宿主机的Nvidia Driver。

    整体架构图:

    总结

             Kubernetes的生态地位已经确立,可扩展性将是其发力的主战场。异构计算作为非常重要的新战场,Kubernetes非常重视。而异构计算需要强大的计算力和高性能网络,需要提供一种统一的方式与GPU、FPGA、NIC、InfiniBand等高性能硬件集成。这也就是为什么淘汰了alpha.kubernetes.io/nvidia-gpu改用了Extended Resource+ Device Plugin的原因。

            当然在我司也修改了部分逻辑,我们没有沿用gpu plugin默认device规则,我们在device的基础上新增了逻辑device的概念在逻辑层限制了每个应用可以申请的gpu显存。当然没办法硬性隔离,这部分目前需要依赖device底层实现。

  • 相关阅读:
    Spark上使用pandas API快速入门
    Python利用torch.max函数计算一批特征图中每个通道的最大值
    C++代码编程学习:inline函数学习(Essential C++ 第二章)
    Cesium:实现卷帘效果
    (数据科学学习手札146)geopandas中拓扑非法问题的发现、诊断与修复
    Springboot集成JDBC
    Kotlin Files readAllBytes readAllLines readString
    正则的使用,限定符,或运算符,字符类,元字符简单实例
    如何快速实现一个颜色选择器
    写好技术书籍
  • 原文地址:https://blog.csdn.net/qq_22543991/article/details/126859553