TT Lab
ブログ

한국어 日本語 中文

はじめる
ブログ

ブログ

KubeVirt ソースコードリーディング 7: デバイスパススルー — GPU がドメイン XML になるまで

状態: ドラフト (2026-10-17)、ソースコードリーディング + 実測値(一部)。 基準は KubeVirt v1.9.0(コミット 79d34c17)です。GPU をアタッチした VM は停止中のため、 そのドメイン XML はコードから描いた 予想図であり(未実測)、実測は KubeVirt CR、ノードの allocatable、virt-handler のログを読み取ったものです。

KubeVirt ソースコードリーディング 7: デバイスパススルー

目次

1. 直感: 3つの箇所が同じデバイスを認識している必要がある

PCI デバイスを VM に渡すには、3つの異なる箇所が同じ事実を認識している必要があります。

  1. ホストカーネル: そのデバイスを元のドライバーから切り離し、 vfio-pci にバインドする必要があります。これにより、ユーザー空間(QEMU)が IOMMU の保護下でデバイスを直接操作できるようになります。
  2. Kubernetes: デバイスが「ノードのリソース」として見えなければ、スケジューラーはそのノードにポッドを配置できず、kubelet もポッドにデバイスファイルを渡すことができません。この橋渡しをするのがデバイスプラグインです。
  3. libvirt: ドメイン XML に <hostdev> があってこそ、QEMU がその PCI アドレスをゲストにアタッチできます。

KubeVirt は 1 を行いません(管理者の役割)。2 と 3 をつなぐ処理を 4 つのコンポーネントが手分けして行います。

2. 図解: 7つのステップ

GPU 한 장이 VMI 스펙에서 도메인 XML 까지 번호 순서. 위 셋은 미리 깔리는 길, 아래 넷은 VM 을 켤 때의 길 ① KubeVirt CR: permittedHostDevices pciVendorSelector 10DE:xxxx → resourceName nvidia.com/<GPU> ② virt-handler 장치 관리자 — sysfs 를 훑는다 vendor:device 가 맞고 드라이버가 vfio-pci 인 장치만 센다 (vfio-pci 가 아니면 vGPU 가능한 VF 인지만 한 번 더 본다) ③ 장치 플러그인 → kubelet 등록 → 노드 allocatable 장치 ID 는 IOMMU 그룹 번호 ④ VMI spec.domain.devices.gpus[].deviceName virt-controller 가 파드 limits 에 그 자원을 1개 넣는다 ⑤ 스케줄러 · kubelet Allocate() /dev/vfio/vfio · /dev/vfio/<그룹> 을 파드에 넣고 환경 변수 PCI_RESOURCE_NVIDIA_COM_<GPU>=0000:01:00.0 ⑥ virt-launcher: 주소 풀 → createPCIHostDevice() 환경 변수에서 PCI 주소를 꺼내 <hostdev> 를 만든다 메모리 오버헤드 +1Gi (VFIO 는 게스트 RAM 전체를 잠근다) ⑦ <hostdev mode='subsystem' type='pci' managed='no'> <source><address domain='0x0000' bus='0x01' …/></source> 실측: ①은 켜져 있는데 그 GPU 노드의 allocatable 은 0 virt-handler 로그(nvidia.go:33): 「Skipping non-vfio-pci device without vGPU profile … driver: nvidia」 — 호스트 드라이버가 쥠 → ② 에서 걸러져 ③ 이 0. vfio-pci 로 바꿔 묶어야 VM 이 받는다
図: 1枚の GPU が VMI のスペックからドメイン XML に至るまでの道のりと、実測で確認されたボトルネック

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 つです。

  1. デバイスの 벤더:장치 が許可リストに含まれているか。
  2. ドライバーが vfio-pci であるか。(:278そうでない場合、 shouldPermitNvidia()(nvidia.go:23がもう一度確認します。NVIDIA ドライバーにバインドされているデバイスであっても、vGPU プロファイルを持つ仮想関数(VF)であれば受け入れ、それ以外はログを残して破棄します。
  3. デバイスの 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 つです。

この環境変数がノード(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)。

5. 制限事項

一言メモ。 「許可リストに追加したのに見えないな?」と思った時は、virt-handler のログから Skipping を探してみてください。KubeVirt はすでに vfio-pci にバインドされているデバイスしかカウントしません。バインドするのはホスト側の役割です!

参考資料

すべて 2026-10-17 に閲覧しました。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます