Tesla P4 Linux NAS使用指北

0x00: 序
最近在閑魚收了塊Tesla?P4給自己的NAS用,這里做下配置記錄。宿主機依舊是Archlinux,別的發(fā)行版請自己想辦法。

主要實現如下能力:
虛擬機GPU直通
容器能使用GPU加速(比如jellyfin的顯卡硬解)

0x01: GPU虛擬機直通
參考:https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF
首先進主板的BIOS把IOMMU功能打開,然后在內核參數中添加?iommu=pt?,intel平臺還要再加上?intel_iommu=on ,然后重啟,在dmesg中看到iommu相關就算成了。
如何配置內核啟動參數請看這:https://wiki.archlinux.org/title/Kernel_parameters

我是用virt-manger + libvirt + qemu + kvm來實現和管理虛擬機,安裝的包如下
$ yay -Qqe | grep -e virt -e qemu -e ovmf
edk2-ovmf
libvirt
qemu-emulators-full
qemu-full
virt-manager
virtio-win
以上的玩意裝好后打開virt-manger,新建虛擬機時候記得固件一定選擇UEFI。然后先正常的裝個windows,裝好virtio的驅動,開啟自帶的遠程桌面后關機。然后再添加一個PCI主機設備選擇的你的顯卡,然后進win裝好驅動即可(驅動下載,自備梯子:https://cloud.google.com/compute/docs/gpus/grid-drivers-table?hl=zh-cn#windows_drivers)。之后你就可以把顯卡選為none,刪掉顯示器,之后只從rdp來訪問虛擬機了。


0x02:GPU容器
參考:https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html
安裝如下的包:
nvidia-docker
nvidia-dkms (這個驅動貌似不裝也行?)
裝nvidia-docker的時候可能會提示你/etc/docker/daemon.json已經存在,你可以先把之前的daemon.json備份下刪掉,在裝好這個包后在覆蓋回來,然后記得執(zhí)行 sudo nvidia-ctk runtime configure --runtime=docker 在daemon.json中添加nvidia的runtime即可。然后重啟docker,執(zhí)行?docker run --rm --runtime=nvidia --gpus all nvidia/cuda:11.6.2-base-ubuntu20.04 nvidia-smi 能看到GPU信息就算成功。
$ docker run --rm --runtime=nvidia --gpus all nvidia/cuda:11.6.2-base-ubuntu20.04 nvidia-smi
Sun Apr 23 01:03:51 2023
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 530.41.03 ? ? ? ? ? ? ?Driver Version: 530.41.03 ? ?CUDA Version: 12.1 ? ? |
|-----------------------------------------+----------------------+----------------------+
| 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 ?Tesla P4 ? ? ? ? ? ? ? ? ? ? ? ?Off| 00000000:04:00.0 Off | ? ? ? ? ? ? ? ? ? ?0 |
| N/A ? 44C ? ?P8 ? ? ? ? ? ? ? ?7W / ?75W| ? ? ?0MiB / ?7680MiB | ? ? ?0% ? ? ?Default |
| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ?| ? ? ? ? ? ? ? ? ?N/A |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
| ?GPU ? GI ? CI ? ? ? ?PID ? Type ? Process name ? ? ? ? ? ? ? ? ? ? ? ? ? ?GPU Memory |
| ? ? ? ?ID ? ID ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Usage ? ? ?|
|=======================================================================================|
| ?No running processes found ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
+---------------------------------------------------------------------------------------+

0x03: Jellyfin容器硬解
以jellyfin為例來看看如何部署一個能使用GPU的容器,直接上docker-compose.yaml
version: '3.5'
services:
?jellyfin:
? # 這里選擇nyanmisaka魔改的版本,官方鏡像也是一樣的
? ?image: nyanmisaka/jellyfin:230414-amd64
? ?restart: always
? ?container_name: jellyfin
? ?user: 1000:995
? ?volumes:
? ? ?# 根據個人習慣把/config和/cache目錄掛載出去,jellyfin的配置存儲在這里
? ? ?- /home/chigusa/.jellyfin/config:/config
? ? ?- /home/chigusa/.jellyfin/cache:/cache
? ?environment: # 這倆玩意記得加上,要不找不到GPU
? ? ?- NVIDIA_DRIVER_CAPABILITIES=all
? ? ?- NVIDIA_VISIBLE_DEVICES=all
? ?labels:
? ? ?# 這個是我個人用來區(qū)分哪個容器用到GPU的,會和下文的腳本配合,不是必須
? ? ?gpu: true
? ?# runtime 和 deploy 直接照nv官方文檔抄過來
? ?runtime: nvidia
? ?deploy:
? ? ?resources:
? ? ? ?reservations:
? ? ? ? ?devices:
? ? ? ? ? ?- capabilities: [gpu]
然后進jellyfin中在 控制臺 - 播放 中硬件加速選擇Nvidia NVENC就成了




0x04: GPU容器和虛擬機和諧共處
當有容器在使用GPU的時候,開啟GPU直通的虛擬機是沒法啟動的,你需要先把容器停掉在啟動虛擬機。每次都要人肉在虛擬機開啟前關掉GPU容器、關機后再打開有點腦殘,所以這里用libvirt的hook機制來做這個事情。你需要為所有使用GPU的容器打一個?gpu=true?的label(看上邊jellyfin的例子),然后把下面這個腳本放到?/etc/libvirt/hooks/qemu?,賦予執(zhí)行權限。

#!/usr/bin/env python3
import sys
from functools import partial
import subprocess
import json
_hooks = []
def hook(fn=None, domain: str = None, action: str = None, stage: str = None):
? ?if fn is None:
? ? ? ?return partial(hook, domain=domain, action=action, stage=stage)
? ?_hooks.append((domain, action, stage, fn))
class DockerUtils:
? ?@staticmethod
? ?def is_docker_running():
? ? ? ?'''
? ? ? ?用 systemd 來判斷 docker 是否在運行
? ? ? ?'''
? ? ? ?p = subprocess.run(
? ? ? ? ? ?['systemctl', 'is-active', 'docker.service'], capture_output=True)
? ? ? ?if p.returncode != 0:
? ? ? ? ? ?return False
? ? ? ?return p.stdout.decode().strip() == 'active'
? ?@staticmethod
? ?def get_gpu_containers(**kwargs):
? ? ? ?'''
? ? ? ?獲取所有打了 gpu=true 的容器的信息
? ? ? ?'''
? ? ? ?def __filter(data):
? ? ? ? ? ?for k, w in kwargs.items():
? ? ? ? ? ? ? ?if k in data and data[k] != w:
? ? ? ? ? ? ? ? ? ?return False
? ? ? ? ? ?return True
? ? ? ?p = subprocess.run(
? ? ? ? ? ?['docker', 'container', 'ls',
? ? ? ? ? ? '-f', 'label=gpu=true',
? ? ? ? ? ? '--format=json', '--all',
? ? ? ? ? ? '--no-trunc'
? ? ? ? ? ? ], capture_output=True, check=True)
? ? ? ?for item in p.stdout.decode().splitlines():
? ? ? ? ? ?data = json.loads(item)
? ? ? ? ? ?if kwargs and not __filter(data):
? ? ? ? ? ? ? ?continue
? ? ? ? ? ?yield data
# 注意把domain改成你虛擬機的名字,支持數組
@hook(domain='win11', action='prepare', stage='begin')
def win11_prepaer_begin():
? ?'''
? ?在虛擬機啟動前執(zhí)行,停掉所有打了 gpu=true 的容器
? ?'''
? ?if not DockerUtils.is_docker_running():
? ? ? ?return
? ?for item in DockerUtils.get_gpu_containers(State='running'):
? ? ? ?subprocess.run(['docker', 'stop', item['ID']])
# 注意把domain改成你虛擬機的名字,支持數組
@hook(domain=['win11'], action='release', stage='end')
def win11_release_end():
? ?'''
? ?在虛擬機關機后執(zhí)行,啟動所有打了 gpu=true 的容器
? ?'''
? ?if not DockerUtils.is_docker_running():
? ? ? ?return
? ?for item in DockerUtils.get_gpu_containers(State='exited'):
? ? ? ?subprocess.run(['docker', 'start', item['ID']])
def main():
? ?'''
? ?libvirt 會傳入的參數如下:
? ? ? ?
? ? ? ?/etc/libvirt/hooks/qemu guest_name prepare begin -
? ?
? ?其中 guest_name 是你虛擬機的名字,后兩個參數的意義參考libvirt的官方文檔:
? ? ? ?https://libvirt.org/hooks.html#etc-libvirt-hooks-qemu
? ?'''
? ?if len(sys.argv) != 5:
? ? ? ?return
? ?domain, action, stage = sys.argv[1:4]
? ?for _hook in _hooks:
? ? ? ?if domain == _hook[0] if not isinstance(_hook[0], list) else domain in _hook[0] \
? ? ? ? ? ? ? ?and action == _hook[1] and stage == _hook[2]:
? ? ? ? ? ?_hook[3]()
if __name__ == '__main__':
? ?main()

0x04: Prometheus + Grafana GPU監(jiān)控
dcgm文檔:https://docs.nvidia.com/datacenter/dcgm/latest/contents.html
grafana的板子:https://grafana.com/grafana/dashboards/12239-nvidia-dcgm-exporter-dashboard/
在容器中部署,同樣打 gpu=true 的標,方便上邊的hook識別,最終效果如下
version: "3"
services:
?dcgm-exporter:
? ?labels:
? ? ?gpu: true
? ?environment:
? ? ?- NVIDIA_DRIVER_CAPABILITIES=all
? ? ?- NVIDIA_VISIBLE_DEVICES=all
? ?restart: always
? ?image: nvcr.io/nvidia/k8s/dcgm-exporter:2.1.4-2.3.1-ubuntu20.04
? ?runtime: nvidia
? ?cap_add:
? ? ?- SYS_ADMIN
? ?deploy:
? ? ?resources:
? ? ? ?reservations:
? ? ? ? ?devices:
? ? ? ? ? ?- capabilities: [gpu]
