概述
nvidia-docker 和 nvidia-container-runtime 是用于在 NVIDIA GPU 上运行 Docker 容器的两个相关工具。它们的作用是提供 Docker 容器与 GPU 加速硬件的集成支持,使容器中的应用程序能够充分利用 GPU 资源。
-
nvidia-docker
为了提高 Nvidia GPU 在 docker 中的易用性, Nvidia 通过对原生 docker 的封装提供了 nvidia-docker 工具
nvidia-docker 是一个 Docker 插件,用于在 Docker 容器中启用 NVIDIA GPU 支持。
该工具提供了一个命令行界面,允许在运行容器时通过简单的命令来指定容器是否应该访问主机上的 NVIDIA GPU 资源。
当在容器中运行需要 GPU 加速的应用程序时,可以使用 nvidia-docker 来确保容器能够访问 GPU。
# 示例命令 nvidia-docker run -it --rm nvidia/cuda:11.0-base nvidia-smi
上述命令使用 nvidia-docker 在容器中运行 NVIDIA 的 CUDA 基础镜像,并在容器中执行 nvidia-smi 命令以查看 GPU 信息
-
nvidia-container-runtime
nvidia-container-runtime 是 NVIDIA 的容器运行时,它与 Docker 和其他容器运行时(如 containerd)集成,以便容器可以透明地访问 NVIDIA GPU 资源。
与 nvidia-docker 不同,nvidia-container-runtime 不是 Docker 插件,而是一种更通用的容器运行时,可与多个容器管理工具集成。
nvidia-docker
和nvidia-container-runtime
都是用于使 Docker 容器能够访问 NVIDIA GPU 资源的工具。可以根据自己的需求选择其中一个来配置容器以利用 GPU 加速。需要注意的是,最新的 NVIDIA Docker 支持通常建议使用
nvidia-container-runtime
,因为它提供了更灵活和通用的 GPU 支持,而不仅仅是为 Docker 定制的解决方案。# 示例命令 docker run --runtime=nvidia -it --rm nvidia/cuda:11.0-base nvidia-smi
- 上述命令使用 Docker 运行容器,通过
--runtime=nvidia
参数指定使用nvidia-container-runtime
运行时,并在容器中执行nvidia-smi
命令。
- 上述命令使用 Docker 运行容器,通过
-
原生 docker 通过设备挂载和磁盘挂载的方式支持访问 GPU 资源
docker 本身并不原生支持 GPU,但使用 docker 的现有功能可以对 GPU 的使用进行支持
# 示例命令 docker run \ --device /dev/nvidia0:/dev/nvidia0 \ --device /dev/nvidiactl:/dev/nvidiactl \ --device /dev/nvidia-uvm:/dev/nvidia-uvm \ -v /usr/local/nvidia:/usr/local/nvidia \ -it --privileged nvidia/cuda
- 通过 --device 来指定挂载的 GPU 设备,通过 -v 来将宿主机上的 nvidia gpu 的命令行工具和相关的依赖库挂载到容器。这样,在容器中就可以看到和使用宿主机上的 GPU 设备了。
注意:这种方式对于 GPU 的可用性(哪些 GPU 是空闲的等)需要人为的判断,效率很低
nvidia-docker 详解
结构图及各组件说明
nvidia-docker 对于使用 GPU 资源的 docker 容器支持的层次关系:
Nvidia-docker的原理图以及各个部分的作用解析:
-
**libnvidia-container:**提供了一个库和简单的 CLI 工具,以实现在容器当中支持使用 GPU 设备的目标。
-
nvidia-container-toolkit:
是一个实现了 runC 的 prestart hook 接口的脚本,该脚本在 runC 创建一个容器之后,启动该容器之前调用,其主要作用就是修改与容器相关联的 config.json,注入一些在容器中使用 NVIDIA GPU 设备所需要的一些信息(比如:需要挂载哪些 GPU 设备到容器当中)。
-
nvidia-container-runtime:
主要用于将容器 runC spec 作为输入,然后将 nvidia-container-toolkit 脚本作为一个 prestart hook 加入到 runC spec 中,再用修改后的 runC spec 调 runc 的 Exec 接口运行容器。
所以在容器启动之前会调用 pre-start hook(nvidia-container-toolkit),这个 hook 会通过 nvidia-container-cli 文件,来调用libnvidia-container 库,最终映射挂载宿主机上的 GPU 设备、nvid 驱动的 so 文件、可执行文件到容器内部。
nvidia-container-runtime 才是真正的核心部分,它在原有的 docker 容器运行时 runc 的基础上增加一个 prestart hook,用于调用 libnvidia-container 库
-
RunC:
RunC 是一个轻量级的工具,它是用来运行容器的,只用来做这一件事。
可以认为它就是个命令行小工具,可以不用通过 docker 引擎,直接运行容器。也是 docker 默认的容器运行方式。
事实上,runC 是标准化的产物,它根据 OCI 标准来创建和运行容器。而 OCI(Open Container Initiative)组织,旨在围绕容器格式和运行时制定一个开放的工业化标准。
直接使用 RunC 的命令行即可以完成创建一个容器,并提供了简单的交互能力。
容器创建过程
-
创建一个正常容器(不支持 GPU)的流程:
docker --> dockerd --> containerd–> containerd-shim -->runc --> container-process
docker 客户端将创建容器的请求发送给 dockerd,当 dockerd 收到请求任务之后将请求发送给 containerd,containerd 经过查看校验启动 containerd-shim 或者自己来启动容器进程。
-
创建一个支持 GPU 的容器的流程:
docker–> dockerd --> containerd --> containerd-shim–> nvidia-container-runtime --> nvidia-container-runtime-hook --> libnvidia-container --> runc – > container-process
基本流程和不支持 GPU 的容器差不多,只是把 docker 默认的运行时替换成了 NVIDIA 封装的 nvidia-container-runtime
这样当 nvidia-container-runtime 创建容器时,先执行 nvidia-container-runtime-hook,这个 hook 去检查容器是否需要使用GPU(通过环境变 NVIDIA_VISIBLE_DEVICES 来判断)。如果需要则调用 libnvidia-container 来暴露 GPU 给容器使用。否则走默认的 runc 逻辑。
NVIDIA Docker 整体工作架构
软硬件基础
-
硬件,服务器上安装了英伟达 GPU
-
宿主机,安装了操作系统和 Cuda Driver,以及 Docker 引擎
-
容器,包含容器 OS 用户空间,Cuda Toolkit,以及用户应用程序
注意:
- 宿主机上需要安装 cuda driver,容器内需要安装 cuda toolkit。容器内无需安装 cuda driver
- NVIDIA 提供了一些官方镜像,其中已经安装好了 cuda toolkit,但还是需要在宿主机安装 cuda driver。
API 结构
NVIDIA 提供了三层 API:
-
CUDA Driver API:
GPU 设备的抽象层,通过提供一系列接口来操作 GPU 设备,性能最好,但编程难度高,一般不会使用该方式开发应用程序
-
CUDA Runtime API:
对 CUDA Driver API 进行了一定的封装,调用该类 API 可简化编程过程,降低开发难度
-
CUDA Libraries:
是对 CUDA Runtime API 更高一层的封装,通常是一些成熟的高效函数库,开发者也可以自己封装一些函数库便于使用
CUDA 结构图如下:
-
CUDA 调用关系:
应用程序可调用 CUDA Libraries 或者 CUDA Runtime API 来实现功能,当调用 CUDA Libraries 时,CUDA Libraries 会调用相应的 CUDA Runtime API,CUDA Runtime API 再调用 CUDA Driver API,CUDA Driver API 再操作 GPU 设备。
CUDA 的容器化
目标:让应用程序可以在容器内调用 CUDA API 来操作 GPU
因此需要实现:
-
在容器内应用程序可调用 CUDA Runtime API 和 CUDA Libraries
-
在容器内能使用 CUDA Driver 相关库。因为 CUDA Runtime API 其实就是 CUDA Driver API 的封装,底层还是要调用到 CUDA Driver API
-
在容器内可操作 GPU 设备
因此容器中访问 GPU 资源过程为:
-
要在容器内操作 GPU 设备,需要将 GPU 设备挂载到容器里
-
Docker 可通过 --device 挂载需要操作的设备,或者直接使用特权模式(不推荐)。
-
NVIDIA Docker 是通过注入一个 prestart 的 hook 到容器中,在容器自定义命令启动前就将GPU设备挂载到容器中。
至于要挂载哪些GPU,可通过 NVIDIA_VISIBLE_DEVICES 环境变量控制。
-
-
挂载 GPU 设备到容器后,还要在容器内可调用 CUDA API
-
CUDA Runtime API 和 CUDA Libraries 通常跟应用程序一起打包到镜像里
-
CUDA Driver API 是在宿主机里,需要将其挂载到容器里才能被使用。
NVIDIA Docker 挂载 CUDA Driver 库文件到容器的方式和挂载 GPU 设备一样,都是在 runtime hook 里实现的。
-
注意:
-
该方案也有一些问题,即容器内的 CUDA Runtime 同宿主机的 CUDA driver 可能存在版本不兼容的问题。
CUDA Libraries 和 CUDA Runtime API 是和应用程序一起打包到镜像中的,而 Driver 库是在创建容器时从宿主机挂载到容器中的,需要保证 CUDA Driver 的版本不低于 CUDA Runtime 版本。
,
NVIDIA Docker 2.0(nvidia-container-runtime)
实现机制
-
nvidia-docker2.0 是一个简单的包,它主要通过修改 docker 的配置文件 /etc/docker/daemon.json,将默认的 Runtime 修改为 nvidia-container-runtime,可实现将 GPU 设备,CUDA Driver 库挂载到容器中。
cat /etc/docker/daemon.json { "default-runtime": "nvidia", "runtimes": { "nvidia": { "path": "/usr/bin/nvidia-container-runtime", "runtimeArgs": [] } } }
Debug日志
修改 nvidia runc 的默认配置文件 /etc/nvidia-container-runtime/config.toml ,打开 hook 的 debug 日志选项,可以看到宿主机挂载 nvidia 驱动、设备到容器内部的详细过程。