docker容器显卡直通——jellyfin硬解

使用场景描述

原先 jellyfin 部署在服务器的 k8s 环境内，没有独立显卡，光靠 CPU 难以实现服务端硬件解码，表现形式为任意平台通过浏览器观看媒体过程中会提示 "该客户端与媒体不兼容，服务器未发送兼容的媒体格式"。

将家中的台式机安装了 ubuntu 之后突发奇想将其作为服务端，完全脱离工作娱乐环境。于是在安装 docker 以及通过 docker 部署了 jellyfin 之后， 想通过 pci 硬件使得 jellyfin 获得硬解的能力。

万万没想到，我还没装显卡驱动，也没有将显卡分配给容器。这才有了此篇以记录折腾过程。

情况分析

客户端会提示” 该客户端与媒体不兼容，服务器未发送兼容的媒体格式 “，猜测原因是服务器自身条件受限，需求客户端进行硬件解码以支持视频播放，而如果浏览器没有相应视频格式的解码能力就会出现该提示。
或许可以尝试安装浏览器扩展以支持客户端解码（可行性未经验证）

推测依据个人体验，原有 k8s 环境下的 jellyfin 在 pc 的浏览器上播放任意视频均会出现上述提示。而通过如 potplayer、jellyfin 客户端、ios 端的 fileball 等 app 进行解码是可以正常播放的。该情况同样出现在 emby 上。

因此推测造成浏览器无法播放的原因。归根结底还是服务端缺少解码能力造成的。

解决方案

适用场景：linux 服务器（我用的是 ubuntu22.04）、k8s 环境（非 unraid、群晖）

显卡驱动安装 —— 以英伟达 GTX1660TI 为例

amd 方案如何安装驱动请自行谷歌、百度。方式方法相同

方式一（离线安装）：

官网下载驱动安装包，上传到服务器，通过 dpkg -i 的方式安装，需求纯命令行的方式安装，如果开启了 lightdm 桌面环境，需要手动关闭

Copy
# 注意使用root用户登录
> systemctl stop lightdm
# 进入纯命令行模式
> init 3
> dpkg -i <官网下载的nvidia驱动包，deb后缀>

# 安装结束重启可以恢复，如果没能恢复请执行下述命令后重启
> init 5
> systemctl set-default graphical.target

方式二（联网安装、推荐）：

1、驱动

Copy
# 先看电脑是否能识别到pci显卡
> lspci | grep -i nvidia
26:00.0 VGA compatible controller: NVIDIA Corporation TU116 [GeForce GTX 1660 Ti] (rev a1)
26:00.1 Audio device: NVIDIA Corporation TU116 High Definition Audio Controller (rev a1)
26:00.2 USB controller: NVIDIA Corporation TU116 USB 3.1 Host Controller (rev a1)
26:00.3 Serial bus controller: NVIDIA Corporation TU116 USB Type-C UCSI Controller (rev a1)

# 查看当前系统支持的驱动
> ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:03.1/0000:26:00.0 ==
modalias : pci:v000010DEd00002182sv000019DAsd00003539bc03sc00i00
vendor : NVIDIA Corporation
model : TU116 [GeForce GTX 1660 Ti]
driver : nvidia-driver-525 - distro non-free
driver : nvidia-driver-470-server - distro non-free
driver : nvidia-driver-535-open - distro non-free
driver : nvidia-driver-418-server - distro non-free
driver : nvidia-driver-525-open - distro non-free
driver : nvidia-driver-525-server - distro non-free
driver : nvidia-driver-450-server - distro non-free
driver : nvidia-driver-535 - distro non-free recommended
driver : nvidia-driver-470 - distro non-free
driver : xserver-xorg-video-nouveau - distro free builtin

## 安装最新的驱动支持
> apt-get install nvidia-driver-535.54.03

## 重启后执行，查看显卡信息
> nvidia-smi
Mon Jul 3 00:05:19 2023
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.54.03 Driver Version: 535.54.03 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 NVIDIA GeForce GTX 1660 Ti Off | 00000000:26:00.0 Off | N/A |
| 46% 41C P8 13W / 120W | 29MiB / 6144MiB | 0% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 3283 G /usr/lib/xorg/Xorg 25MiB |
| 0 N/A N/A 26860 G xfwm4 1MiB |
+---------------------------------------------------------------------------------------+

2、CUDA
上述 CUDA Version 为可安装版本信息，不代表已经安装，安装方式如下（命令来自英伟达官方）：

Copy
> wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
> dpkg -i cuda-keyring_1.1-1_all.deb
> apt-get update
> apt-get -y install cuda

执行完成后设置变量环境，在～/.bashrc 文件末尾添加

Copy
> vim ~/.bashrc
#添加下述内容，注意版本号
export PATH=/usr/local/cuda-12.2/bin${PATH:+:${PATH}}

#确认nvidia-persistenced守护进程是否开启
> systemctl status nvidia-persistenced
● nvidia-persistenced.service - NVIDIA Persistence Daemon
Loaded: loaded (/lib/systemd/system/nvidia-persistenced.service; static)
Active: active (running) since Sun 2023-07-02 05:43:54 CST; 18h ago
Process: 1937 ExecStart=/usr/bin/nvidia-persistenced --user nvidia-persistenced --no-persistence-mode --verbose (code=exited, status=0/SUCCESS)
Main PID: 1947 (nvidia-persiste)
Tasks: 1 (limit: 47904)
Memory: 568.0K
CPU: 3ms
CGroup: /system.slice/nvidia-persistenced.service
└─1947 /usr/bin/nvidia-persistenced --user nvidia-persistenced --no-persistence-mode --verbose

不论哪种方式安装的显卡驱动，装完之后需要重启

docker 直通

1、安装 nvidia-docker2 和 nvidia-container-runtime
将显卡挂载给 docker 时会报错导致容器一直处于 create 状态

Copy
# 添加apt源
> distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& 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://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

> apt update
> apt-get install -y nvidia-docker2 nvidia-container-runtime

2、运行容器以挂载显卡驱动
以 docker-compose 为例

Copy
version: "3.7"
services:
jellyfin:
container_name: jellyfin
image: "jellyfin/jellyfin:latest"
environment:
- PUID=0
- GUID=0
- NVIDIA_DRIVER_CAPABILITIES=all
- NVIDIA_VISIBLE_DEVICES=all
ports:
- "10029:8096"
volumes:
- "/root/jellyfin/config:/config"
- "/mnt/media:/media"
restart: always
user: root
devices:
- "/dev/dri:/dev/dri"
deploy:
resources:
reservations:
devices:
- capabilities: ["gpu"]

jellyfin 硬解设置

这里就没啥好说的，有条件了就全勾上。

结语

到此为止算是完成了 jellyfin 的硬件直通，实测原本不支持播放视频的浏览器也可以进行媒体播放。
并且，当有用户在播放媒体时，能明显观察到显卡实际显存占用的变化

Copy
Mon Jul 3 00:47:21 2023
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.54.03 Driver Version: 535.54.03 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 NVIDIA GeForce GTX 1660 Ti Off | 00000000:26:00.0 Off | N/A |
| 46% 44C P2 31W / 120W | 257MiB / 6144MiB | 3% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 3725 G /usr/lib/xorg/Xorg 13MiB |
| 0 N/A N/A 6735 C /usr/lib/jellyfin-ffmpeg/ffmpeg 238MiB |
+---------------------------------------------------------------------------------------+

对比发现当用户在 web 客户端进行播放时，jellyfin 服务端正在用显卡进行解码，显卡内存占用由 29MiB 变为 257MiB，其中运行的进程为 ffmpeg

故，可以总结为本文推论相对正确。可以通过硬件解码的方式消除 jellyfin 客户端出现的 "该客户端与媒体不兼容，服务器未发送兼容的媒体格式" 的提示。

也从侧面说明了，在无显卡的情况下，jellyfin 服务端不能进行解码，视频解码的方式通过客户端本地进行，这才有了 web 无法播放但客户端 app 可以的情况。这也可以推出，此情况下，无论 jellyfin 上是否设置第三方 ffmpeg 客户端不影响客户端本地解码，会出现报错提示仅仅是因为服务端解码能力不足而已。

题外话
nvidia-docker2 是 nvidia 对 docker 容器的支持组件，如果不采用 docker 方式部署服务，可以不装。
同样的方式可以应用在 stable diffution 的 docker 项目上。实现远程炼丹🫠

参考资料

1、Pop!_OS 22.04（Ubuntu 22.04）安装 Nvidia GPU 驱动、CUDA、cuDNN 以及 Docker GPU 支持（nvidia-docker2）
2、CUDA Toolkit 12.1 Downloads