kubernetes集成GPU原理

这里以Nvidia GPU设备如何在Kubernetes中管理调度为例研究, 工作流程分为以下两个方面:

  • 如何在容器中使用GPU
  • Kubernetes 如何调度GPU

容器中使用GPU

想要在容器中的应用可以操作GPU, 需要实两个目标:

  • 容器中可以查看GPU设备
  • 容器中运行的应用,可以通过Nvidia驱动操作GPU显卡

在应用程序中使用 GPU,由于需要安装 nvidia driver, Docker 引擎并没有原生支持。因此也就无法直接在容器中访问 GPU 资源。

为了解决容器中无法访问 GPU 资源的问题,有以下方案:

1、无nvidia-docker
在早期的时候,没有nvidia-docker,可以通过在容器内再部署一遍nvidia GPU驱动解决。同理,其他设备如果想在容器里使用,也可以采用在容器里重新安装一遍驱动解决。
2、nvidia-docker1.0
nvidia-docker是英伟达公司专门用来为docker容器使用nvidia GPU而设计的,设计方案就是把宿主机的GPU驱动文件映射到容器内部使用,可以通过tensorflow生成GPU驱动文件夹。
3、nvidia-docker2.0
nvidia-docker2.0对nvidia-docker1.0进行了很大的优化,不用再映射宿主机GPU驱动了,直接把宿主机的GPU运行时映射到容器即可。启动方式示例:

nvidia-docker run -d -e NVIDIA_VISIBLE_DEVICES=all --name nvidia_docker_test  nvidia/cuda:10.0-base /bin/sh -c "while true; do echo hello world; sleep 1; done" 

4、安装docker19.03及以上版本,已经内置了nvidia-docker,无需再单独部署nvidia-docker了。安装方式如下:

安装docker: yum install -y yum-utils yum-config-manager     --add-repo     https://download.docker.com/linux/centos/docker-ce.repo yum-config-manager --enable docker-ce-nightly yum-config-manager --enable docker-ce-test yum install docker-ce docker-ce-cli containerd.io systemctl start docker  安装nvidia-container-toolkit distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | sudo tee /etc/yum.repos.d/nvidia-docker.repo sudo yum install -y nvidia-container-toolkit sudo systemctl restart docker  启动容器: docker run --gpus all nvidia/cuda:10.0-base /bin/sh -c "while true; do echo hello world; sleep 1; done" 进入容器并输入nvidia-smi验证。 

在容器中重新安装 nvidia driver,容器启动的时候将 nvidia gpu 作为符号设备传递进来。这种方案的问题在于宿主机和容器内安装的 nvidia driver 版本可能不一致,因此 docker image 无法在多机间共享。这就丧失了 docker 的主要优势。

因此,为了在保持 docker image 迁移性的同时可以方便的使用 gpu,nvidia 提出了 nvidia docker 的方案。

参考文献:https://developer.nvidia.com/blog/gpu-containers-runtime/

kubernetes集成GPU原理

容器启动流程大致为:docker --> dockerd --> docker-containerd-shim --> nvidia-container-runtime-hook --> libnvidia-container--> nvidia-driver。

docker客户端将创建容器的请求发送给dockerd, 当dockerd收到请求任务之后将请求发送给docker-containerd-shim,nvidia-container-runtime创建容器时,先执行nvidia-container-runtime-hook这个hook去检查容器是否需要使用GPU(通过环境变量NVIDIA_VISIBLE_DEVICES来判断)。如果需要则调用libnvidia-container来暴露GPU给容器使用。否则则走默认的runc逻辑。

Nvidia-docker

项目地址:https://github.com/NVIDIA/nvidia-docker

Nvidia提供Nvidia-docker项目,它是通过修改Docker的Runtime为nvidia runtime工作,当我们执行 nvidia-docker create 或者 nvidia-docker run 时,它会默认加上 --runtime=nvidia 参数。将runtime指定为nvidia。

nvidia-docker是在docker的基础上做了一层封装,通过 nvidia-docker-plugin把硬件设备在docker的启动命令上添加必要的参数。

gpu-containers-runtime

gpu-containers-runtime 是一个NVIDIA维护的容器 Runtime,它在runc的基础上,维护了一份 Patch, 在容器启动前,注入一个 prestart 的hook 到容器的Spec中(hook的定义可以查看 OCI规范 )。这个hook 的执行时机是在容器启动后(Namespace已创建完成),容器自定义命令(Entrypoint)启动前。

gpu-containers-runtime-hook

gpu-containers-runtime-hook 是一个简单的二进制包,定义在Nvidia container runtime的hook中执行。 目的是将当前容器中的信息收集并处理,转换为参数调用 nvidia-container-cli 。主要处理以下参数:

  • 根据环境变量 NVIDIA_VISIBLE_DEVICES 判断是否会分配GPU设备,以及挂载的设备ID。如果是未指定或者是 void ,则认为是非GPU容器,不做任何处理。 否则调用 nvidia-container-cli , GPU设备作为 --devices 参数传入
  • 环境环境变量 NVIDIA_DRIVER_CAPABILITIES 判断容器需要被映射的 Nvidia 驱动库。
  • 环境变量 NVIDIA_REQUIRE_* 判断GPU的约束条件。 例如 cuda>=9.0 等。 作为 --require= 参数传入
  • 传入容器进程的Pid

gpu-containers-runtime-hook 做的事情,就是将必要的信息整理为参数,传给 nvidia-container-cli configure 并执行。

nvidia-container-cli

项目地址:https://github.com/NVIDIA/libnvidia-container,基于c语言

nvidia-container-cli 是一个命令行工具,用于配置Linux容器对GPU 硬件的使用。支持

  • list: 打印 nvidia 驱动库及路径
  • info: 打印所有Nvidia GPU设备
  • configure: 进入给定进程的命名空间,执行必要操作保证容器内可以使用被指定的GPU以及对应能力(指定 Nvidia 驱动库)。 configure是我们使用到的主要命令,它将Nvidia 驱动库的so文件 和 GPU设备信息, 通过文件挂载的方式映射到容器中。

docker 19.03之后,默认支持NVIDIA GPU。

参考文献:https://collabnix.com/introducing-new-docker-cli-api-support-for-nvidia-gpus-under-docker-engine-19-03-0-beta-release/

kubernetes中使用GPU

参考资源:https://kubernetes.io/zh/docs/tasks/manage-gpus/scheduling-gpus/#deploying-amd-gpu-device-plugin

Kubernetes 提供了Device Plugin 的机制,用于异构设备的管理场景。原理是会为每个特殊节点上启动一个针对某个设备的DevicePlugin pod, 这个pod需要启动grpc服务, 给kubelet提供一系列接口。

整个 Device Plugin 的工作流程可以分成两个部分:

  • 一个是启动时刻的资源上报;
  • 另一个是用户使用时刻的调度和运行。

Device Plugin 的开发主要包括最关注与最核心的两个事件方法:

  • 其中 ListAndWatch 对应资源的上报,同时还提供健康检查的机制。当设备不健康的时候,可以上报给 Kubernetes 不健康设备的 ID,让 Device Plugin Framework 将这个设备从可调度设备中移除;
  • 而 Allocate 会被 Device Plugin 在部署容器时调用,传入的参数核心就是容器会使用的设备 ID,返回的参数是容器启动时,需要的设备、数据卷以及环境变量。

Nvidia GPU Device Plugin

为了能够在Kubernetes中管理和调度GPU, Nvidia提供了Nvidia GPU的Device Plugin。

项目地址:https://github.com/NVIDIA/k8s-device-plugin

主要功能如下:

  • 支持ListAndWatch 接口,上报节点上的GPU数量。
  • 支持Allocate接口, 支持分配GPU的行为。

调度流程

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

  • GPU Device plugin 部署到GPU节点上,通过 ListAndWatch 接口,上报注册节点的GPU信息和对应的DeviceID。
  • 当有声明 nvidia.com/gpu 的GPU Pod创建出现,调度器会综合考虑GPU设备的空闲情况,将Pod调度到有充足GPU设备的节点上。
  • 节点上的kubelet 启动Pod时,根据request中的声明调用各个Device plugin 的 allocate接口, 由于容器声明了GPU。 kubelet 根据之前 ListAndWatch接口收到的Device信息,选取合适的设备,DeviceID 作为参数,调用GPU DevicePlugin的 Allocate 接口。
  • GPU DevicePlugin ,接收到调用,将DeviceID 转换为 NVIDIA_VISIBLE_DEVICES 环境变量,返回kubelet。
  • kubelet将环境变量注入到Pod, 启动容器。
  • 容器启动时, gpu-container-runtime 调用 gpu-containers-runtime-hook
  • gpu-containers-runtime-hook 根据容器的 NVIDIA_VISIBLE_DEVICES 环境变量,转换为 --devices 参数,调用 nvidia-container-cli prestart
  • nvidia-container-cli 根据 --devices ,将GPU设备映射到容器中。 并且将宿主机的Nvidia Driver Lib 的so文件也映射到容器中。 此时容器可以通过这些so文件,调用宿主机的Nvidia Driver。

在k8s中启用GPU支持

必要条件:

  • NVIDIA 驱动程序 ~= 384.81
  • nvidia-docker 版本 > 2.0
  • docker 配置为 nvidia 作为默认运行时
  • Kubernetes 版本 >= 1.10
$ kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.11.0/nvidia-device-plugin.yml 

运行GPU作业

部署守护程序后,可以使用nvidia.com/gpu资源类型请求 NVIDIA GPU:

apiVersion: v1 kind: Pod metadata:   name: gpu-pod spec:   containers:     - name: cuda-container       image: nvcr.io/nvidia/cuda:9.0-devel       resources:         limits:           nvidia.com/gpu: 2 # requesting 2 GPUs     - name: digits-container       image: nvcr.io/nvidia/digits:20.12-tensorflow-py3       resources:         limits:           nvidia.com/gpu: 2 # requesting 2 GPUs 

AMD GPU Device Plugin

项目地址:https://github.com/RadeonOpenCompute/k8s-device-plugin

在k8s中启用GPU支持

必要条件:

  • 支持 ROCm 的机器
  • ROCm 内核安装指南)或最新的 AMD GPU Linux 驱动程序(安装指南
  • --allow-privileged=true对于 kube-apiserver 和 kubelet(仅当设备插件通过 DaemonSet 部署时才需要,因为设备插件容器需要特权安全上下文才能访问/dev/kfd设备健康检查)

部署 AMD 设备插件:

kubectl create -f https://raw.githubusercontent.com/RadeonOpenCompute/k8s-device-plugin/r1.10/k8s-ds-amdgpu-dp.yaml 

k8s 共享GPU方案

在kubernetes中运行GPU程序,通常都是将一个GPU卡分配给一个容器。这可以实现比较好的隔离性,确保使用GPU的应用不会被其他应用影响;对于深度学习模型训练的场景非常适合;

但是如果对于模型开发和模型预测的场景就会比较浪费。很多诉求是能够让更多的预测服务共享同一个GPU卡上,进而提高集群中Nvidia GPU的利用率。

阿里云开源了一个gpushare项目,实现多个pod共享同一块gpu卡。

核心模块:

  1. GPU Share Scheduler Extender: 利用Kubernetes的调度器扩展机制,负责在全局调度器Filter和Bind的时候判断节点上单个GPU卡是否能够提供足够的GPU Mem,并且在Bind的时刻将GPU的分配结果通过annotation记录到Pod Spec以供后续Filter检查分配结果。
  2. GPU Share Device Plugin: 利用Device Plugin机制,在节点上被Kubelet调用负责GPU卡的分配,依赖scheduler Extender分配结果执行。

工作流程(gpushare):
1)GPU Share Device Plugin利用nvml库查询到GPU卡的数量和每张GPU卡的显存, 通过ListAndWatch()将节点的GPU总显存(数量 *显存)作为另外Extended Resource汇报给Kubelet; Kubelet进一步汇报给Kubernetes API Server。
2)Kubernetes默认调度器在进行完所有过滤(filter)行为后会通过http方式调用GPU Share Scheduler Extender的filter方法,找出单卡满足调度条件的节点和卡。
3)当调度器找到满足条件的节点,就会委托GPU Share Scheduler Extender的bind方法进行节点和Pod的绑定。
4)当Pod和节点绑定的事件被Kubelet接收到后,Kubelet就会在节点上创建真正的Pod实体,在这个过程中, Kubelet会调用GPU Share Device Plugin的Allocate方法, Allocate方法的参数是Pod申请的gpu-mem。

发表评论

相关文章