原文(truenas-build-nvidia-vgpu-driver-extensions-systemd-sysext)

在 Proxmox VE 上运行 TrueNAS 是获得出色 VM 和存储管理的最佳方式。TrueNAS 的内置 VM 管理难以使用,提供的 VM 选项有限,并且不允许修改系统设置。虽然它在 25.04 之后使用 Incus 作为其 VM 后端,但 TrueNAS WebUI 和中间件仍然需要大量工作。

vGPU 已成为一种流行的方法,使 Proxmox VE 上的虚拟机能够利用 GPU 卡进行视频编码和解码。但是,TrueNAS 在作为 VM 客户机运行时,默认使用标准的 Nvidia 驱动程序,而不是专用的Grid驱动程序(NVIDIA 的商业驱动程序)。因此,虽然某些 Tesla 卡可能会正确加载,但它们通常实际上无法将这些默认驱动程序使用其编码和解码单元。

在Proxmox VE上运行TrueNAS是获得出色VM和存储管理的最佳方式。TrueNAS的内置VM管理难以使用,提供的VM选项有限,并且不允许修改系统设置。虽然它在25.04之后使用Incus作为其VM后端,但TrueNAS WebUI和中间件仍需要大量工作。

vGPU已成为一种流行的方法,使Proxmox VE上的虚拟机能够利用GPU卡进行视频编码和解码。然而,当TrueNAS作为VM客户机运行时,默认使用标准的Nvidia驱动程序,而不是专门的Grid驱动程序(Nvidia的商业驱动程序)。因此,虽然一些特斯拉卡可能会正确加载nvidia-smi,但它们通常无法将其编码和解码单元与这些默认驱动程序一起使用。

好消息是,从TrueNAS SCALE 24.10+开始,TrueNAS现在使用systemd sysext加载编译期间预打包的Nvidia驱动程序。此扩展可以在需要时加载。

如何工作

打包的扩展源代码背后的核心原则非常简单:

  1. 比较self.chroot和self.chroot_base目录下的内容。

  2. 提取任何新的或修改过的文件。

  3. 仅保留usr/(不包括usr/src/)下的文件;其他则被删除。

  4. 通过删除不属于该扩展的文件和空目录来清理self.chroot。

  5. 添加了一个名extension-release.d的文件,用于标识该扩展。

  6. 最后,将self.chroot打包成SquashFS文件并保存到dst_path。

以下是Python代码:

    def build_extension(self, name, dst_path):
        changed_files = [
            os.path.relpath(filename, self.chroot)
            for filename in map(
                lambda filename: os.path.join(os.getcwd(), filename),
                run(
                    ["rsync", "-avn", "--out-format=%f", f"{self.chroot}/", f"{self.chroot_base}/"],
                    log=False,
                ).stdout.split("\n")
            )
            if os.path.abspath(filename).startswith(os.path.abspath(self.chroot))
        ]

        sysext_files = [f for f in changed_files if f.startswith("usr/") and not (f.startswith("usr/src/"))]

        for root, dirs, files in os.walk(self.chroot, topdown=False):
            for f in files:
                path = os.path.relpath(os.path.abspath(os.path.join(root, f)), self.chroot)
                if path not in sysext_files:
                    os.unlink(os.path.join(root, f))

            for d in dirs:
                try:
                    os.rmdir(os.path.join(root, d))
                except NotADirectoryError:
                    os.unlink(os.path.join(root, d))  # It's a symlink
                except OSError as e:
                    if e.errno == errno.ENOTEMPTY:
                        pass
                    else:
                        raise

        os.makedirs(f"{self.chroot}/usr/lib/extension-release.d", exist_ok=True)
        with open(f"{self.chroot}/usr/lib/extension-release.d/extension-release.{name}", "w") as f:
            f.write("ID=_any\n")

        run(["mksquashfs", self.chroot, dst_path, "-comp", "xz"])

以下是具体针对英伟达驱动程序的相关部分:

    def download_nvidia_driver(self):
        prefix = "https://us.download.nvidia.com/XFree86/Linux-x86_64"

        version = get_manifest()["extensions"]["nvidia"]["current"]
        filename = f"NVIDIA-Linux-x86_64-{version}-no-compat32.run"
        result = f"{self.chroot}/{filename}"

        self.run(["wget", "-c", "-O", f"/{filename}", f"{prefix}/{version}/{filename}"])

        os.chmod(result, 0o755)
        return result

    def install_nvidia_driver(self, kernel_version):
        driver = self.download_nvidia_driver()

        self.run([f"/{os.path.basename(driver)}", "--skip-module-load", "--silent", f"--kernel-name={kernel_version}",
                     "--allow-installation-with-running-driver", "--no-rebuild-initramfs"])

        os.unlink(driver)

所以,修改它实际上很简单:

  • 替换下载链接。

  • 调整执行选项(网格驱动程序有不同的选项)。

PS:在选择驱动程序时,请注意内核兼容性。例如,在当前的25.04版本(内核版本6.12.15)中,如果你使用的是16.x驱动程序(特斯拉P4的最后一个可用驱动程序),你的网格驱动程序版本需要大于16.9。

以下是修改后的代码的外观:

    def download_nvidia_driver(self):
        # 我不知道在哪里可以找到 grid驱动程序
        prefix = "<put your grid download url without filename in here>"
        #useless
        #version = get_manifest()["extensions"]["nvidia"]["current"]
        filename = f"<filename of your grid driver>"
        result = f"{self.chroot}/{filename}"

        self.run(["wget", "-c", "-O", f"/{filename}", f"{prefix}/{filename}"])

        os.chmod(result, 0o755)
        return result

    def install_nvidia_driver(self, kernel_version):
        driver = self.download_nvidia_driver()
        # fix option
        self.run([f"/{os.path.basename(driver)}", "--silent", f"--kernel-name={kernel_version}"])

        os.unlink(driver)

构建扩展

在这里,我将以25.04.0为例。

官方文档已经提供了一些提示,但以下是一些需要注意的关键事项:

安装依赖(没有注意事项)

sudo apt install build-essential debootstrap git python3-pip python3-venv squashfs-tools unzip libjson-perl rsync libarchive-tools

选择标签分支

使用Tag替换release/xxxxx

# tag name is TS-<version>
git clone -b TS-25.04.0 https://github.com/truenas/scale-build.git

配置环境变量

该代码读取环境变量以确定编译的“version”和“Train”。因此,如果你不指定它们,你只能编译Master train。

export TRUENAS_TRAIN="TrueNAS-SCALE-Fangtooth"
export TRUENAS_VERSION="25.04.0"

开始构建

make checkout

make packages

make update

构建完成后,您需要挂载已编译的文件以提取其内容。操作方法如下:rootfs.squashfs

mkdir -p ./tmpfile/rootfs
mount ./tmp/update/rootfs.squashfs ./tmpfile/rootfs

此时,您的扩展应该可用:nvidia.raw

ls -al ./tmpfile/rootfs/usr/share/truenas/sysext-extensions/nvidia.raw

覆盖现有驱动程序

您需要将正在运行的TrueNAS系统上的文件替换为刚刚编译的文件nvidia.raw/usr/share/truenas/sysext-extensions/nvidia.raw

首先,您需要使数据集可写:/usr

zfs set readonly=off boot-pool/ROOT/<system version>/usr
# For 25.04.0, it would be:
zfs set readonly=off boot-pool/ROOT/25.04.0/usr

PS:如果你之前通过WebUI启用了英伟达驱动程序,你可能需要先运行。这是因为别的控件为只读。 systemd-sysext unmerge systemd-sysext /usr

覆盖它!

cp /the-path-you-upload/nvidia.raw /usr/share/truenas/sysext-extensions/nvidia.raw

复制文件后,只需运行:

systemd-sysext merge
# Don't forget to restart docker
systemctl restart docker

而且,它应该奏效。

nvidia-smi