0x00 前言

在容器中复用宿主机显卡有诸多好处,比如,只需要在宿主机安装驱动即可便捷传入容器,使得容器部署方便且环境干净;同时,容器部署带来了天然的显卡复用优势,使得多个实例都能够调用显卡。当然,这种方式也有一定的缺陷,会在一定程度上使宿主机环境变重,增加了 Idle 负担,精神洁癖用户可酌情使用。

在虚拟机尝试进行显卡直通失败后,我选择了容器部署的方式。

0x01 参考资料

  1. Enabling GPU passthrough post launch? --discuss.linuxcontainers.org
  2. NVIDIA CUDA Installation Guide for Linux --docs.nvidia.com
  3. Installing the NVIDIA Container Toolkit --docs.nvidia.com

0x02 驱动安装

由于 Nvidia 驱动的特殊性,存在破坏桌面环境的可能,请做好备份工作与可能的救援准备。

我的系统环境是:

  • Debian GNU/Linux 12 (bookworm)
  • Incus 6.0.1 (从 APT 的 bookworm-backports 源安装)
  • GPU: Tesla P4

如前所述,我们需要在宿主机安装显卡驱动与 CUDA 工具包。当前 (2024/08/20) 它们的最新版分别是:

  • NVIDIA Driver Version: 560.28.03
  • CUDA Version: 12.6

参考资料 2 中给出了驱动的下载地址:CUDA Toolkit 12.6 Downloads,下载页面中也给出了安装方式,摘录如下。其中,第 4 行的 add-apt-repository 操作依赖 software-properties-common 包,其实这一步我们自己编辑 /etc/apt/sources.list 就可以,确保其中包括 contrib 源即可。

1
2
3
4
5
6
wget https://developer.download.nvidia.com/compute/cuda/12.6.0/local_installers/cuda-repo-debian12-12-6-local_12.6.0-560.28.03-1_amd64.deb
sudo apt install ./cuda-repo-debian12-12-6-local_12.6.0-560.28.03-1_amd64.deb
sudo cp /var/cuda-repo-debian12-12-6-local/cuda-*-keyring.gpg /usr/share/keyrings/
sudo add-apt-repository contrib
sudo apt update
sudo apt install cuda-toolkit-12-6

以上仅安装了 cuda-toolkit,我们还需要安装驱动。下载页面同样给出了安装方式,包含两个选项,分别是新的开源内核模块 nvidia-open 和旧的闭源模块 cuda-drivers,按显卡支持情况安装其中之一。由于 Tesla P4 属于较老的 Pascal 架构,新模块尚未支持,因此这里选择后者。

1
sudo apt install cuda-drivers

安装程序会帮助我们禁用默认的 nouveau 驱动。安装完成后,可能需要重启才能够驱动显卡。之后,通过 nvidia-smi 查看显卡驱动状态。

另外,可以依照参考资料 2 中的推荐操作部分,尝试编译运行一些调用 CUDA 的实例,以确保 CUDA 能够正常运行。

最后,我们需要安装 nvidia-container-toolkit。粗略地说,这个工具可以控制容器使用的显卡功能 (关于 NVIDIA_DRIVER_CAPABILITIES 变量,详见 Driver Capabilities ),并将必要的设备信息、工具传入容器中,使得我们可以在容器中不额外安装驱动便可以调用显卡,并使用 nvidia-smi 等工具。

参考资料 3 中给出了其下载方式与链接,但我在网络良好的情况下仍会出现 Handshake Error,可能是官方源存在一些问题,因此采用 USTC 的镜像

1
2
3
4
5
6
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://mirrors.ustc.edu.cn/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://nvidia.github.io#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://mirrors.ustc.edu.cn#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update
sudo apt install nvidia-container-toolkit

至此,所有驱动与工具均安装完毕,可以开始进行容器的部署。

0x03 容器部署

首先,我们需要获取显卡的 PCI 插槽。以 P4 为例:

1
incus info --resources | grep -in -A5 -B5 -- p4

查询得到的插槽标识是 0000:04:00.0,这个写法是 Incus 要求的标准格式,不接受其他写法。

接着,创建一个容器。其中,nvidia.driver.capabilities 配置项就对应上文提到的 NVIDIA_DRIVER_CAPABILITIES 变量,Incus 会将我们设置的值透传给 nvidia-container-toolkit,以配置我们的容器。由于将利用该显卡进行转码,所以 video 值是必要的,注意 Incus 的默认设置utility,compute,并不包含 video,因此默认配置会导致我们无法进行转码。为了方便,可以选择全都要,配置为 all

1
2
incus create images:debian/12 jelly -c nvidia.runtime=true -c nvidia.driver.capabilities=all
incus config device add jelly tesla-p4 gpu pci="0000:04:00.0"

启动该容器,应当可以在容器内执行 nvidia-smi 并获取显卡驱动状态。

1
watch -n 0.5 nvidia-smi

我通过容器内部署 Jellyfin 并进行视频转码来验证显卡可用性,不再赘述。读者可以自行验证,以 ffmpeg 为例:

1
2
3
4
# Example ffmpeg command
ffmpeg -init_hw_device cuda=cu:0 -filter_hw_device cu \
-hwaccel cuda -hwaccel_output_format cuda \
-i "sample.mp4" -c:v hevc_nvenc -f mp4 "out.mp4"

0x04 记录/小插曲

在初次部署后,使用 ffmpeg 转码会失败,提示 Unknown Error,伴随 Error caused by external library 字样。我尝试在容器内安装 nvidia-container-toolkit 后问题消失了,而在容器内卸载这个包之后问题也没有再出现。之后,新建容器也没能复现这个问题,不清楚原因,暂且写在这里。


Update: 在之后的尝试中发现,这个问题往往发生在宿主机重启之后。事实上在长时间运行的宿主机上,这个问题并不会中途发生,因此原因大概率是启动时 LXC 未能成功挂载设备 (比如 /dev/nvidia_uvm)。这可能是不完美的启动顺序导致的,但我没有深入试验。目前来看,完全关闭并重新启动 Incus daemon 是能够作为临时措施解决的。

1
incus admin shutdown && incus start --all