在 LXC/Incus 容器中复用宿主机的 Nvidia 显卡进行视频转码 —— 以 Tesla P4 为例
0x00 前言
在容器中复用宿主机显卡有诸多好处,比如,只需要在宿主机安装驱动即可便捷传入容器,使得容器部署方便且环境干净;同时,容器部署带来了天然的显卡复用优势,使得多个实例都能够调用显卡。当然,这种方式也有一定的缺陷,会在一定程度上使宿主机环境变重,增加了 Idle 负担,精神洁癖用户可酌情使用。
在虚拟机尝试进行显卡直通失败后,我选择了容器部署的方式。
0x01 参考资料
- Enabling GPU passthrough post launch? --discuss.linuxcontainers.org
- NVIDIA CUDA Installation Guide for Linux --docs.nvidia.com
- 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 | 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 |
以上仅安装了 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 | curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ |
至此,所有驱动与工具均安装完毕,可以开始进行容器的部署。
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 | incus create images:debian/12 jelly -c nvidia.runtime=true -c nvidia.driver.capabilities=all |
启动该容器,应当可以在容器内执行 nvidia-smi
并获取显卡驱动状态。
1 | watch -n 0.5 nvidia-smi |
我通过容器内部署 Jellyfin
并进行视频转码来验证显卡可用性,不再赘述。读者可以自行验证,以 ffmpeg
为例:
1 | # Example ffmpeg command |
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 |