午前2時のボトルネック
Grafanaのダッシュボードが赤く染まったのは、午前2時のことでした。KVM仮想マシン上で動作している高トラフィックのデータベースクラスターで15%のパケットロスが発生し、レイテンシは2msから200ms以上に跳ね上がっていました。ホスト側を確認すると、CPUはデータベースのクエリで苦労しているわけではありませんでした。代わりに、ksoftirqdプロセスが100%に張り付いていました。標準のLinuxブリッジが、毎秒200万個の小さなパケットを処理しきれなくなっていたのです。ソフトウェアベースのネットワークスタックが、巨大なボトルネックとなっていました。
Single Root I/O Virtualization (SR-IOV) は、ハードウェア共有の方法を変えることでこの問題を解決します。ホストOSに仮想スイッチをエミュレートさせるのではなく、SR-IOVは物理ネットワークカード(NIC)をハードウェアレベルで複数のスライスに分割します。これらのスライスは独立したPCIeデバイスとして動作します。これらをVMに直接パススルーすることで、トラフィックはホストカーネルを完全にバイパスできるようになります。
本番環境において、これは単なる贅沢品ではありません。通信事業者の5Gコア、高頻度取引(HFT)プラットフォーム、NVMe-over-Fabricsストレージアレイのような、ハイパフォーマンスなワークロードには必須の要件です。
クイックスタート:5分でSR-IOVを有効にする
最新のIntel X710やMellanox ConnectX-5カードをお持ちなら、すぐに実行できます。UbuntuやRHELベースのシステムでの最短手順を紹介します。
1. BIOS/UEFIでIOMMUを有効にする
BIOSで VT-d (Intel) または AMD-Vi (AMD) が有効になっていることを確認してください。これにより、ハードウェアが仮想デバイスのメモリを安全にマッピングできるようになります。これが無効だと、カーネルはハードウェアをゲストに直接割り当てる試みをブロックします。
2. カーネルの起動パラメータを更新する
起動時にIOMMUドライバを初期化するようLinuxカーネルに指示する必要があります。/etc/default/grubを編集し、GRUB_CMDLINE_LINUX_DEFAULTの行を修正します。
# Intel CPU用 (例: Xeon Scalable)
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=on iommu=pt"
# AMD CPU用 (例: EPYC)
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash amd_iommu=on iommu=pt"
iommu=pt (パススルー) フラグは非常に重要です。これにより、カーネルが不要なデバイスを管理しようとするのを防ぎ、全体的なパフォーマンスが向上します。変更を適用して再起動します:
sudo update-grub
sudo reboot
3. 仮想ファンクション (VF) を生成する
ip linkを使用して物理NICを特定します。この例ではeno1を使用します。4つの仮想スライスを作成するには、PCIデバイスのsysfsエントリに直接書き込みます:
echo 4 | sudo tee /sys/class/net/eno1/device/sriov_numvfs
ip link show eno1を実行します。メインインターフェースの下にvf 0からvf 3が表示され、それぞれに固有のMACアドレスを割り当てられるようになります。
アーキテクチャ:PFとVFの違い
SR-IOVを効果的に管理するには、2つのPCIeファンクションタイプの関係を理解する必要があります。
- フィジカルファンクション (PF): メインのPCIeデバイスです。管理者として動作し、グローバルな構成設定や仮想スライスの作成・削除を担当します。
- バーチャルファンクション (VF): 軽量でハードウェア支援によるファンクションです。管理機能はありませんが、非常に効率的にデータを転送します。
標準的なブリッジ構成では、すべてのパケットは回線からNIC、ホストカーネルを経て、最終的にVMへと届きます。このコンテキストスイッチがCPUサイクルを消費します。SR-IOVでは、NIC内部のハードウェアスイッチがルーティングを処理します。パケットはDMA (Direct Memory Access) を介して、回線からVMのメモリへと直接移動します。これにより、レイテンシが約50〜100マイクロ秒から、10マイクロ秒未満にまで短縮されます。
本番環境向けの堅牢化
手動のechoコマンドはシステムの再起動で消えてしまいます。安定した本番環境には、永続的な設定が必要です。
Udevルールによる永続化
ハードウェアが検出された直後にVFが生成されるよう、udevルールを作成します。/etc/udev/rules.d/80-sriov.rulesを作成します:
ACTION=="add", SUBSYSTEM=="net", KERNEL=="eno1", ATTR{device/sriov_numvfs}="8"
ハードウェアレベルでのVLANタギング
SR-IOVの大きな利点の1つは、VLANタギングをNICハードウェアにオフロードできることです。これにより、ゲストVMが不要なトラフィックを見るのを防げます。ホスト側から強制的に設定できます:
# VF 0をVLAN 100に強制割り当て
sudo ip link set eno1 vf 0 vlan 100
KVMへのVFの割り当て
lspci | grep "Virtual Function"を使用して、VFのPCIアドレスを確認します。04:10.0のような形式で表示されます。LibvirtのXML設定に、以下のブロックを追加します:
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x04' slot='0x10' function='0x0'/>
</source>
</hostdev>
現場で得た教訓
SR-IOVの構成には、ハードウェア固有の癖への対処が伴うことがよくあります。避けるべき3つの一般的な落とし穴を挙げます。
1. ライブマイグレーションの壁
VMが特定のホストAのNICに物理的に紐付けられるため、標準的なツールではホストBへのライブマイグレーションができません。これを解決するには、ゲスト内で フェイルオーバーボンド を使用します。VirtIOインターフェース(移行用)とSR-IOVインターフェース(速度用)を組み合わせます。VMが移動すると、一時的にVirtIOドライバにフォールバックし、移動先ホストで新しいVFが見つかるまで通信を維持します。
2. 割り込みリソースの枯渇
各VFはMSI-X割り込みベクトルを必要とします。128個のベクトルしかサポートしていないカードで64個のVFを作成しようとし、ホストに32個のCPUコアがある場合、リソースが不足する可能性があります。多くのエンジニアは、1ポートあたり8〜16個のVFが安定性の「スイートスポット」であると考えています。
3. トラストフラグ
デフォルトでは、VFはセキュリティのために制限されています。VMでコンテナオーケストレーターを実行したり、自身のMACアドレスを変更したりする必要がある場合は、明示的に信頼(trust)を許可する必要があります:
sudo ip link set eno1 vf 0 trust on
終わりに
SR-IOVは、仮想マシンとベアメタルの間のパフォーマンスの差を効果的に埋めてくれます。マイグレーションが複雑になり、特定のハードウェアが必要になりますが、パフォーマンスの向上は否定できません。iperf3を使用した私のテストでは、安定した10Gbpsのラインレートを維持しつつ、ホストのCPU負荷を40%削減できました。ネットワークがボトルネックになっているなら、まずここを確認すべきです。

