状態: ドラフト (2026-10-17)、ソースコードリーディング + 実測値(一部)。 基準は KubeVirt
v1.9.0(コミット79d34c17)です。GPU をアタッチした VM は停止中のため、 そのドメイン XML はコードから描いた 予想図であり(未実測)、実測は KubeVirt CR、ノードの allocatable、virt-handler のログを読み取ったものです。
KubeVirt ソースコードリーディング 7: デバイスパススルー
目次
1. 直感: 3つの箇所が同じデバイスを認識している必要がある
PCI デバイスを VM に渡すには、3つの異なる箇所が同じ事実を認識している必要があります。
- ホストカーネル: そのデバイスを元のドライバーから切り離し、
vfio-pciにバインドする必要があります。これにより、ユーザー空間(QEMU)が IOMMU の保護下でデバイスを直接操作できるようになります。 - Kubernetes: デバイスが「ノードのリソース」として見えなければ、スケジューラーはそのノードにポッドを配置できず、kubelet もポッドにデバイスファイルを渡すことができません。この橋渡しをするのがデバイスプラグインです。
- libvirt: ドメイン XML に
<hostdev>があってこそ、QEMU がその PCI アドレスをゲストにアタッチできます。
KubeVirt は 1 を行いません(管理者の役割)。2 と 3 をつなぐ処理を 4 つのコンポーネントが手分けして行います。
2. 図解: 7つのステップ
3. コードパス
① 許可リスト — KubeVirt CR
KubeVirt CR の spec.configuration.permittedHostDevices.pciHostDevices[] に pciVendorSelector(벤더:장치 (16進数)と resourceName を記述します。機能フラグ HostDevices も有効にする必要があります。ここに載っていないデバイスは、VMI に名前が指定されていても使用できません。virt-controller がポッドを描画する前に validatePermittedHostDevices()
(renderresources.go:493で確認します。
② デバイスマネージャーが sysfs をスキャンする — virt-handler
virt-handler 内の DeviceController.Run()(device_controller.go:481は、KubeVirt の設定が変更されるたびに refreshPermittedDevices() が呼び出されるようにコールバックを設定します
(:497)。この関数が
updatePermittedHostDevicePlugins()(:208を経由して discoverPermittedHostPCIDevices()(pci_device.go:258を呼び出します。この関数が /sys/bus/pci/devices を走査して行う処理は次の 3 つです。
- デバイスの
벤더:장치が許可リストに含まれているか。 - ドライバーが
vfio-pciであるか。(:278そうでない場合、shouldPermitNvidia()(nvidia.go:23がもう一度確認します。NVIDIA ドライバーにバインドされているデバイスであっても、vGPU プロファイルを持つ仮想関数(VF)であれば受け入れ、それ以外はログを残して破棄します。 - デバイスの IOMMU グループ番号と NUMA ノードを読み取ります。
③ デバイスプラグイン — kubelet へのリソース登録
リソース名ごとに PCIDevicePlugin が 1 つずつ起動し(Start, pci_device.go:62)、kubelet のデバイスプラグインソケットに登録します。kubelet に提示するデバイス ID は PCI アドレスではなく IOMMU グループ番号です
(constructDPIdevices, :132)。VFIO はグループ単位でデバイスを渡すため、同じグループのデバイスを異なる VM に割り当てることができないためです。登録が完了すると、ノードの status.allocatable に
nvidia.com/<GPU 이름>: 1 のように表示されます。同じデバイスマネージャーが devices.kubevirt.io/kvm·tun·vhost-net などの一般デバイスも同じ方式で公開します。
④ ポッドがリソースを要求する — virt-controller
VMI に spec.domain.devices.gpus[].deviceName: nvidia.com/<GPU 이름> がある場合、第 3 回のテンプレートがリソースレンダラーオプション
WithGPUsDevicePlugins()(renderresources.go:149を使って
compute コンテナの limits にそのリソースを 1 つずつ追加します。メモリオーバーヘッドには 1Gi が加算されます
(hypervisorbackend.go:130)。コメントによると、VFIO は DMA のためにゲスト RAM 全体をロックするためとのことです。判定基準は vmitrait.HasVFIO()
(vmitrait.go:33であり、ホストデバイス、GPU、SR-IOV のいずれか 1 つでも存在する場合となります。
⑤ Allocate — デバイスファイルとアドレスの受け渡し
スケジューラーがそのリソースの空きがあるノードにポッドを配置すると、kubelet はデバイスプラグインの Allocate()
(pci_device.go:152を呼び出します。返されるのは次の 2 つです。
- デバイスファイル:
/dev/vfio/vfioと/dev/vfio/<그룹 번호>(formatVFIODeviceSpecs, common.go:275)、iommufd を使用するカーネルの場合はデバイスごとの cdev も含まれます。 - 環境変数:
PCI_RESOURCE_接頭辞(types.go:3672)にリソース名を大文字に変換し、/·.を_に置き換えた名前(ResourceNameToEnvVar, util.go:89)、値はカンマ区切りの PCI アドレスです。例:PCI_RESOURCE_NVIDIA_COM_<GPU>=0000:01:00.0.
この環境変数がノード(handler)とポッド(launcher)を結ぶ唯一の絆です。launcher は sysfs を見ず、この値のみを信頼します。
⑥ launcher が <hostdev> を作成する
第 5 回の generateConverterContext() が getGPUDevices()(manager.go:1138を呼び出し、 gpu.CreateHostDevices()(gpu/hostdev.go:39がアドレスプールを作成します。プールは ⑤ の環境変数を読み取って満たされます(AddressPool.load,
addresspool.go:46)。GPU ごとにアドレスを 1 つずつ取り出し、 createPCIHostDevice()(hostdev.go:141が
api.HostDevice を作成し、コンバーターの HostDeviceDomainConfigurator
(compute/host_device.go:42がドメインにアタッチします。同じ GPU のリストが vGPU(mediated device)の場合は MDEV_PCI_RESOURCE_… 変数と CreateMDEVHostDevices 側に向かいます。v1.9.0 では、デバイスプラグインの代わりに DRA(Dynamic Resource Allocation)で受け取るパス(hostdevice/dra/)も機能フラグの後ろに導入されています。
⑦ 予想される XML(未実測)
コード通りであれば、GPU 1枚(名前 gpu1)のドメイン XML はこのような形になります。
<hostdev mode='subsystem' type='pci' managed='no'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</source>
<alias name='ua-gpu-gpu1'/>
</hostdev>
managed='no' が重要です。 managed='yes' の場合、libvirt は VM の起動時にデバイスをホストドライバーから切り離して vfio-pci にバインドしてくれますが、ポッド内の非 root の libvirt にはその権限がありません。そのため、 バインドする作業はホスト側で事前に 行う必要があり(第 1 節の 1)、KubeVirt はすでにバインドされているデバイスのみをカウントします(②)。エイリアスは、ユーザーエイリアス接頭辞 ua- に GPU 接頭辞 gpu- と名前が結合されます。また、このようなデバイスを持つ VMI について、virt-handler はライブマイグレーション不可としてマークします(vmiContainsNonMigratablePCIHostDevices, vm.go:1304).
4. 実測: 許可リストは有効なのにリソースが 0
小さなクラスターの GPU ノード 1 つを VM 専用として使用するように設定した状態を読み取りました(2026-10-17)。
-
① KubeVirt CR に機能フラグ
HostDevices、およびpciHostDevicesが 1 つ(pciVendorSelector: 10DE:xxxx,resourceName: nvidia.com/<GPU 이름>)存在していました。この GPU を要求する VM 1 台はrunStrategy: Haltedで停止していました。 -
③ しかし、そのノードの allocatable は
nvidia.com/<GPU 이름>: 0でした。同じノード上のコンテナ用 GPU リソース(nvidia.com/gpu、別のデバイスプラグインが公開するもの)は 1 と見えていました。同じノードで KubeVirt の一般デバイス(devices.kubevirt.io/kvm·tun·vhost-net)はそれぞれ 1k でした。 -
② そのノードの virt-handler のログには、この行が繰り返し出力されていました(
pos: nvidia.go:33).Skipping non-vfio-pci device without vGPU profile 0000:01:00.0 (10de:xxxx, driver: nvidia)デバイスのドライバーが
vfio-pciではなくホストのnvidiaであり、vGPU プロファイルもなかったため ② でフィルタリングされていました。そのため ③ のデバイス数が 0 になっています。この状態で VM を起動すると、ポッドがリソース不足で Pending のままになるはずです(起動はしていません)。 解決策は KubeVirt 側ではなくホスト側で 該当の GPU をvfio-pciにバインドすることであり(カーネルパラメータまたはドライバーバインド)、コンテナ用 GPU デバイスプラグインが同じ GPU を公開しないようにする作業も同時に行う必要があります。 -
この行は 2026-10-16 の一日(UTC)だけで 641 回、その virt-handler が再起動してからは 7,889 回記録されていました。平均して 2 分弱ごとにデバイスを再スキャンしていることになります。何がそれほど頻繁に ② を呼び出しているのかについては、今回は追跡していません。
-
⑤ の痕跡。 同じ virt-handler のログにおいて、別の VM のポッドが作成された瞬間(2026-10-14T14:38:34Z)に一般デバイスプラグインの
Generic Allocate: resourceName: kvm·tun·vhost-netの 3 行(generic_device.go:240)が 1ms の間隔で記録されていました。kubelet がポッドを配置してデバイスプラグインの Allocate を呼び出す瞬間はこのように見えます。
5. 制限事項
- GPU をアタッチした VM を起動していないため、⑤ の環境変数、⑥・⑦ の XML、ゲスト内の
lspciはすべて 未実測です。ホスト側でデバイスを vfio-pci に移動させる作業はノードの設定変更にあたるため、この記事の範囲(リーディング)を超えています。 - IOMMU グループに複数のデバイスが存在する場合(同じグループのオーディオ機能など)の処理方法については、コードのリーディングのみ行いました。
- vGPU・SR-IOV・DRA のパスについては名前のみに言及しました。
一言メモ。 「許可リストに追加したのに見えないな?」と思った時は、virt-handler のログから
Skippingを探してみてください。KubeVirt はすでに vfio-pci にバインドされているデバイスしかカウントしません。バインドするのはホスト側の役割です!
参考資料
すべて 2026-10-17 に閲覧しました。
device-manager: https://github.com/kubevirt/kubevirt/tree/v1.9.0/pkg/virt-handler/device-managerhostdevice: https://github.com/kubevirt/kubevirt/tree/v1.9.0/pkg/virt-launcher/virtwrap/device/hostdevice- KubeVirt ユーザーガイド、ホストデバイス割り当て: https://kubevirt.io/user-guide/compute/host-devices/
- Kubernetes デバイスプラグイン: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
- Linux カーネル VFIO ドキュメント: https://docs.kernel.org/driver-api/vfio.html
- libvirt ドメイン XML、ホストデバイス: https://libvirt.org/formatdomain.html#host-device-assignment