Article

Proxmox VE で 1 台の自宅サーバに Linux Desktop VM を構築し、RTX 3060 Ti を GPU パススルーするまで

Intel NUC 系マシンに Proxmox VE を導入し、iGPU をホスト用、RTX 3060 Ti を Ubuntu Desktop VM 用として GPU パススルーしました。IOMMU 確認、VFIO 設定、PVE の VM 設定、トラブルシュート、実ゲーム検証までをまとめます。

Ubuntu ホスト上の KVM ゲストで nvidia-smi が成功

はじめに

自宅サーバ兼ホームラボ用途で、スペックに余裕のある 1 台の物理マシンを Proxmox VE、以下 PVE、で仮想化基盤化しました。

目的は、物理マシンを増やさずに、Linux デスクトップ環境、たまに使う Windows 環境、Samba サーバ環境などを分離して運用することです。特に重要だったのは、Linux Desktop VM に NVIDIA GPU をパススルーし、物理マシンとほぼ同じ操作感で使えるかどうかでした。

最終的には、PVE 上の Ubuntu Desktop VM に RTX 3060 Ti をパススルーし、実ディスプレイ出力、HDMI 音声、USB ゲームパッド、キーボード操作、Xbox Cloud Gaming の Fortnite まで、ネイティブマシンと違和感ないレベルで動作させることができました。

物理マシン構成

物理マシン構成は以下です。

項目内容
用途自宅サーバ、ホームラボ、Linux Desktop VM、Samba、Windows VM
物理マシンIntel NUC9i7QN 系
CPU12 コア相当の構成
メモリ32GB
ホスト OSProxmox VE
ホスト表示用 GPUIntel UHD Graphics 630
パススルー対象 GPUNVIDIA GA104 / GeForce RTX 3060 Ti Lite Hash Rate
GPU PCI ID`10de:2489`
GPU Audio PCI ID`10de:228b`
内蔵ストレージSSD 500GiB、PVE インストール先
外付けストレージUSB 3.2 SSD 1TiB、USB SSD 500GiB x2
既存データ領域Samba 公開用 HDD 6TiB、既存データ維持前提

構成上の重要な方針は以下のように、二系統のGPUを明確に使い分ける点です。

  • iGPU は PVE ホスト用
  • RTX 3060 Ti は Linux Desktop VM 用

こうすることで、PVE ホストは管理基盤に徹し、重い GUI やゲーム用途は VM 側に逃がすことができます。

仮想マシン構成

確定した仮想マシンの構成は以下です。

役割構成
PVE ホストiGPU 使用、Web UI / SSH 管理、基盤専用
Ubuntu Desktop VMRTX 3060 Ti パススルー、HDMI 音声、USB 入力、ゲーム用途
Windows VM仮想 GPU + RDP 前提、稀に必要な Windows アプリ用
Samba VM今後構築予定。6TiB HDD をできれば VM にディスク単位で渡す方針
LXC小物サービス用。Docker Hub イメージとは別物として扱う

ストレージは以下の方針にしました。

PVE Storage用途
`local``Container template` のみ
`local-lvm``Disk image`, `Container`。VM 本体や LXC 実体用
`usb-1tb``Disk image`, `ISO image`。外付け USB 3.2 SSD 1TiB

local は 100GB 程度で容量が少ないため、ISO のような嵩張るものは usb-1tb に寄せています。

事前検証: Ubuntu ホスト上で IOMMU と GPU パススルー可能性を確認

PVE を本格導入する前に、もともと入っていた Ubuntu Desktop 上で KVM/libvirt を使い、GPU パススルーの成立性を検証しました。

まず IOMMU / VT-d が有効かを確認しました。

bash
sudo dmesg | grep -Ei 'DMAR|IOMMU'

重要だったログは以下です。

text
DMAR-IR: Enabled IRQ remapping in x2apic mode
iommu: Default domain type: Translated
pci 0000:01:00.0: Adding to iommu group 1
pci 0000:01:00.1: Adding to iommu group 1
DMAR: Intel(R) Virtualization Technology for Directed I/O

VT-dVirtualization Technology for Directed I/O の略です。このログから、Intel VT-d / IOMMU が OS から認識され、IOMMU グループも作成されていると判断しました。

GPU とオーディオ function は次のように見えていました。

bash
lspci -nn | grep -E 'VGA|3D|Audio'
text
00:02.0 VGA compatible controller [0300]: Intel Corporation CoffeeLake-H GT2 [UHD Graphics 630] [8086:3e9b]
00:1f.3 Audio device [0403]: Intel Corporation Cannon Lake PCH cAVS [8086:a348] (rev 10)
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060 Ti Lite Hash Rate] [10de:2489] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)

IOMMU グループの中身は以下でした。

bash
for d in /sys/kernel/iommu_groups/1/devices/*; do
  lspci -nns "${d##*/}"
done
text
00:01.0 PCI bridge [0604]: Intel Corporation 6th-10th Gen Core Processor PCIe Controller (x16) [8086:1901] (rev 07)
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060 Ti Lite Hash Rate] [10de:2489] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)

00:01.0 の PCI bridge と、GPU 本体、GPU Audio が同じグループです。理想は GPU と Audio のみのグループですが、この程度であれば十分素直な構成だと判断しました。無関係な SATA、NIC、USB コントローラが混ざっていなかったためです。

Ubuntu ホスト上で VFIO bind を検証

最初は RTX 3060 Ti がホスト側の NVIDIA ドライバに掴まれていました。

bash
lspci -nnk -s 01:00.0
lspci -nnk -s 01:00.1
text
01:00.0 ...
        Kernel driver in use: nvidia
        Kernel modules: nvidiafb, nouveau, nvidia_drm, nvidia

01:00.1 ...
        Kernel driver in use: snd_hda_intel
        Kernel modules: snd_hda_intel

そこで一時的にホストドライバから外し、vfio-pci に bind しました。

bash
sudo modprobe vfio
sudo modprobe vfio_pci
sudo modprobe vfio_iommu_type1

echo 0000:01:00.1 | sudo tee /sys/bus/pci/devices/0000:01:00.1/driver/unbind
echo 0000:01:00.0 | sudo tee /sys/bus/pci/devices/0000:01:00.0/driver/unbind

echo 10de 228b | sudo tee /sys/bus/pci/drivers/vfio-pci/new_id
echo 10de 2489 | sudo tee /sys/bus/pci/drivers/vfio-pci/new_id

最初に GPU 本体を unbind しようとした際、以下のログが出ました。

text
NVRM: Attempting to remove device 0000:01:00.0 with non-zero usage count!

これは NVIDIA ドライバがまだ GPU を使用中であることを意味します。対処として、SSH 経由で入った上で display manager や NVIDIA 関連サービスを停止し、NVIDIA 関連モジュールを外してから再実行しました。

最終的に以下のように vfio-pci に bind できました。

text
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060 Ti Lite Hash Rate] [10de:2489] (rev a1)
        Kernel driver in use: vfio-pci
        Kernel modules: nvidiafb, nouveau, nvidia_drm, nvidia

01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)
        Kernel driver in use: vfio-pci
        Kernel modules: snd_hda_intel

Ubuntu ホスト上の KVM/libvirt でゲスト VM に GPU を渡す

次に、Ubuntu ホスト上の KVM/libvirt VM に GPU を渡しました。ゲスト内で NVIDIA GA104 と HDMI Audio が見えることを確認し、NVIDIA ドライバを入れました。

当初は nvidia-driver-580-open を指定して入れましたが、nvidia-smi 実行時に以下のようなメッセージが出ました。

text
Make sure that the latest NVIDIA driver is installed and running.

その後、個別指定をやめて以下を実行しました。

bash
sudo ubuntu-drivers install
sudo reboot

再起動後、nvidia-smi が成功しました。

Ubuntu ホスト上の KVM ゲストで nvidia-smi が成功
Ubuntu ホスト上の KVM ゲストで nvidia-smi が成功

さらに、NVIDIA 側の物理端子にディスプレイを接続したところ、ゲスト VM の画面が遅延なく表示されました。HDMI 音声もディスプレイ側スピーカーから出力され、USB Host Device として Logitech F310 Gamepad を渡したところ、VM 側で認識されました。

最終的には、Google Chrome 上の Xbox Cloud Gaming で Fortnite をプレイし、映像、音声、ゲームパッド入力ともに物理マシンと違和感ない操作感であることを確認しました。

この時点で、ハードウェアとしては GPU パススルーに十分向いていると判断しました。

PVE インストールとストレージ設計

その後、物理マシンに PVE をインストールしました。インストール先は内蔵 SSD 500GiB です。

PVE インストール直後、ストレージは自動的に以下のように分かれていました。

  • local: 約 100GB
  • local-lvm: 約 360GB

最初は再インストールして 1 つにまとめることも考えましたが、PVE の標準構成として自然なため、そのまま使うことにしました。

追加で USB 3.2 接続の外付け SSD 1TiB を XFS で初期化し、UUID 指定で /etc/fstab に登録しました。USB 外付けなので、デバイス名ではなく UUID で指定し、nofail を付ける方針です。

fstab
UUID=<uuid> /mnt/ext1tb xfs defaults,nofail 0 2

PVE の Web UI から DatacenterStorageAddDirectory で、usb-1tb という名前の Directory storage として登録しました。

最終的な content type は以下です。

text
local: Container template
local-lvm: Disk image, Container
usb-1tb: Disk image, ISO image

Container template は LXC コンテナの元になるテンプレートで、Docker image とは別物です。Container は PVE 管理下の LXC コンテナ実体です。PVE が Docker Hub の image を直接 LXC として扱うわけではないため、Docker Hub image を使う場合は、基本的に VM の中で Docker を動かす方針が自然です。

PVE ホスト側の IOMMU 確認

PVE 上でも IOMMU を確認しました。

bash
dmesg | grep -Ei 'DMAR|IOMMU'

PVE 上では Ubuntu ホスト時代と少しログが変わりました。特に iGPU 00:02.0 が IOMMU group に入り、以下のような DMAR fault が出ていました。

text
DMAR: [DMA Read NO_PASID] Request device [00:02.0] fault addr 0x0 [fault reason 0x06] PTE Read access is not set

00:02.0 は Intel UHD 630、つまり PVE ホスト表示用の iGPU です。今回の本命は dGPU の RTX 3060 Ti なので、これは dGPU パススルー自体の失敗を意味するものではないと判断しました。

実害が出る場合は intel_iommu=igfx_off によって iGPU だけ IOMMU 対象から外す選択肢もあります。ただし、この時点では PVE ホストの Web UI / SSH と dGPU パススルー検証を優先し、まずは進めました。

PVE ホストで dGPU を vfio-pci に取らせる

PVE インストール直後、dGPU はまだ vfio-pci ではなく nouveausnd_hda_intel に掴まれていました。

bash
lspci -nnk -s 01:00.0
lspci -nnk -s 01:00.1
text
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060 Ti Lite Hash Rate] [10de:2489] (rev a1)
        Kernel driver in use: nouveau
        Kernel modules: nvidiafb, nouveau

01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)
        Kernel driver in use: snd_hda_intel
        Kernel modules: snd_hda_intel

そこで、起動時から vfio-pci に取らせる設定を入れました。

/etc/modules は obsolete と表示されたため、/etc/modules-load.d/ を使いました。

bash
cat >/etc/modules-load.d/vfio.conf <<'EOF'
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
EOF

vfio-pci に対象 PCI ID を指定します。

bash
cat >/etc/modprobe.d/vfio.conf <<'EOF'
options vfio-pci ids=10de:2489,10de:228b
EOF

nouveau なども blacklist しました。

bash
cat >/etc/modprobe.d/blacklist-gpu.conf <<'EOF'
blacklist nouveau
blacklist nvidiafb
EOF

GRUB のカーネル引数は以下の方針です。

text
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"

必要に応じて iGPU の DMAR fault 対策として以下も候補になります。

text
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on,igfx_off iommu=pt"

設定反映後、initramfs を更新して再起動しました。

bash
update-grub
update-initramfs -u -k all
reboot

再起動後、以下のように dGPU と Audio の両方が vfio-pci に取られました。

text
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060 Ti Lite Hash Rate] [10de:2489] (rev a1)
        Kernel driver in use: vfio-pci
        Kernel modules: nvidiafb, nouveau

01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)
        Kernel driver in use: vfio-pci
        Kernel modules: snd_hda_intel

Kernel modulesnouveausnd_hda_intel が残っていても、実際に使用中のドライバが vfio-pci であれば問題ありません。見るべきは Kernel driver in use です。

Ubuntu Desktop VM の作成

PVE 上に Ubuntu Desktop VM を作成しました。

設定は以下です。

項目
BIOSOVMF
Machineq35
CPU typehost
Cores6
Memory16384MB
System Disk`local-lvm`, 80GB
ISO`usb-1tb` 上の Ubuntu Desktop ISO
SCSI ControllerVirtIO SCSI single
PVE の Ubuntu Desktop VM 設定概要
PVE の Ubuntu Desktop VM 設定概要

CPU type: host は、物理 CPU の機能をできるだけゲストに見せる設定です。単一ホストで GPU パススルー VM を使う今回の用途では、互換性重視の qemu より host の方が自然です。

Machine: q35 は、PCI Express 前提の比較的新しい仮想チップセットです。RTX 3060 Ti のような PCIe デバイスをパススルーするため、古い i440fx より q35 を選びました。

EFI Disk については、最初 pre-enrolled-keys=1 になっていましたが、NVIDIA ドライバ周りで Secure Boot が余計な変数になる可能性があるため外しました。

EFI Disk の pre-enrolled-keys を外した状態
EFI Disk の pre-enrolled-keys を外した状態

PVE で PCI Device を追加した際の起動失敗

Ubuntu Desktop VM は、デフォルト GPU の状態では起動できました。その後、VM を停止し、PCI Device として 01:00.001:00.1 を別々に追加したところ、VM が起動しなくなりました。

PCI デバイス追加後に起動できなくなったときの VM 設定
PCI デバイス追加後に起動できなくなったときの VM 設定

原因は、同一 GPU の multi-function デバイスを別々に追加したことだと判断しました。

RTX 3060 Ti は以下のように、同じ PCI スロット上の別 function として見えています。

  • 01:00.0: GPU 本体
  • 01:00.1: HDMI / DisplayPort Audio

PVE では、01:00.0 のみを選び、All FunctionsPCI-Express を有効にして 1 つの PCI Device として追加する方が素直でした。

実際にこの設定に変更すると、VM は起動できました。ゲスト内で lspci すると、GPU 本体と HDMI Audio の両方が認識されました。

ゲスト OS から GPU と HDMI Audio が見えた状態
ゲスト OS から GPU と HDMI Audio が見えた状態

ここで重要なのは、ゲスト内での PCI bus 番号がホストと一致する必要はないことです。ホストでは 01:00.0 でも、ゲスト内では 02:00.0 などとして見えることがあります。

ゲスト内で NVIDIA ドライバを導入

ゲストの Ubuntu Desktop 内で NVIDIA ドライバを導入しました。

bash
sudo ubuntu-drivers install
sudo reboot

再起動後、nvidia-smi は成功しました。

bash
nvidia-smi

ただし、この時点では nvidia-smi は成功するものの、NVIDIA 側の物理ディスプレイには映像が出ませんでした。

nvidia-smi は成功しているが Display が Off の状態
nvidia-smi は成功しているが Display が Off の状態

nvidia-smi の表示では Disp.A Off になっていました。これは、GPU 自体はゲストで使えているものの、物理ディスプレイ出力が有効な表示先として扱われていない状態です。

物理ディスプレイに映らない問題の解決

解決策は、PVE 側の PCI Device 設定で passthrough GPU を Primary GPU にすることでした。

変更後、NVIDIA 側に接続した物理ディスプレイに Ubuntu Desktop VM の画面が表示されました。

今回の学びとして、nvidia-smi の成功は「ドライバが GPU を使えている」ことを示しますが、「物理端子から映像が出る」ことまでは保証しません。PVE 側で passthrough GPU を Primary GPU にする設定が必要でした。

実用検証

最終的に、PVE 上の Ubuntu Desktop VM で以下を確認しました。

  • NVIDIA 側の物理ディスプレイ出力
  • HDMI 音声出力
  • USB ゲームパッド入力
  • キーボード入力
  • Google Chrome 上の Xbox Cloud Gaming / Fortnite
  • 約 1 時間程度のゲームプレイ
  • ゲームパッドの挿抜

結果として、映像、音声、入力ともに、物理マシンと違和感ない操作感でした。特に GPU パススルーによる実ディスプレイ出力は、一般的な VM コンソール画面のようなもやっとした表示ではなく、解像感も自然でした。

Windows VM についての判断

Windows VM については、GPU パススルーしない方針にしました。

理由は、Windows は稀に必要な Windows 専用アプリケーションを使うための補助用途だからです。RTX 3060 Ti を Linux Desktop VM と Windows VM で排他利用すること自体は可能ですが、同時起動できず、シャットダウン順序や GPU 解放、入力機器の切り替えなど運用コストが増えます。

今回の用途では、Windows VM は仮想 GPU + RDP / PVE コンソールで十分です。

Samba VM と 6TiB HDD の今後の方針

Samba 用の 6TiB HDD は既存データを消さずに使う前提です。PVE ホストに直接 Samba を入れるのではなく、Samba 専用 VM を作り、その VM に 6TiB HDD を渡す方針です。

PVE ホストは仮想化基盤に徹するべきです。ホストに Samba や Docker などを直接載せ始めると、責務分離が崩れ、トラブル時の切り分けが難しくなります。

Docker Hub image と PVE の LXC について

PVE が直接管理するコンテナは LXC です。Docker Hub の image をそのまま PVE の LXC コンテナとして一元管理するわけではありません。

Docker Hub の image を使う場合は、基本的には Linux VM を 1 台作り、その中で Docker を動かす方が素直です。LXC の中で Docker を動かすこともできますが、ネストしたコンテナ運用になるため、最初の本番構成では避ける判断が妥当です。

詰まった点と解決策まとめ

詰まった点原因解決策
Ubuntu ホストで GPU を unbind できないNVIDIA ドライバの usage count が残っていたdisplay manager や NVIDIA 関連サービスを止め、モジュールを外してから unbind
`nvidia-driver-580-open` 後に `nvidia-smi` が失敗ゲスト内 NVIDIA ドライバ構成が不十分だった可能性`ubuntu-drivers install` で推奨ドライバを導入
PVE 上で dGPU が `nouveau` に掴まれる起動時に `vfio-pci` に早期 bind していなかった`options vfio-pci ids=10de:2489,10de:228b` と blacklist、initramfs 更新
PVE 上で PCI Device 追加後に VM が起動しないGPU 本体と HDMI Audio を別々に追加した`01:00.0` を 1 つ追加し、`All Functions` + `PCI-Express` を有効化
`nvidia-smi` は成功するが物理画面が出ないpassthrough GPU が VM の primary display になっていなかったPCI Device 設定で `Primary GPU` を有効化
iGPU 側に DMAR fault が出るPVE 上で iGPU も IOMMU 管理対象になっていた実害がなければ様子見。必要なら `intel_iommu=igfx_off` を検討

この記事から得られる知見

今回の構成では、GPU パススルーの成否を決める重要要素は、ソフトウェア設定だけではありませんでした。むしろ、ハードウェアの素直さが大きく効いています。

特に重要だったのは以下です。

  • iGPU と dGPU を分けられること
  • dGPU と HDMI Audio が素直な IOMMU グループにいること
  • PVE ホストが dGPU を掴まず vfio-pci に渡せること
  • PVE では multi-function GPU を All Functions 付きで 1 つの PCI Device として扱うこと
  • nvidia-smi 成功後も、物理出力には Primary GPU 設定が必要な場合があること

今回のマシンは、GPU パススルー用途としてかなり当たりの構成でした。

今後の課題

今後進める予定の作業は以下です。

  1. Samba VM を作成し、6TiB HDD を安全に公開する
  2. Windows VM を仮想 GPU + RDP 前提で作成する
  3. PVE ホスト設定、Linux Desktop VM、Samba VM のバックアップ方針を決める
  4. 外付け SSD 500GiB x2 の使い道を、バックアップ用、検証用などに整理する
  5. 必要に応じて Docker 用の軽量 Linux VM を作る

参考文献