Harvester上のVMとVyOS間のネットワーク帯域を計測したら、アップロード方向だけ妙に遅かったので原因を調査して改善しました。
環境
- Harvester上のRKE2クラスタ(シングルノード)
- VM: Ubuntu 24.04 (virtio-net, 4 vCPU)
- ルーター: VyOS 1.5 rolling
- 物理NIC: 10GbE SFP+ DAC (ens1)
- VM↔VyOS間はVLAN30で接続
計測してみる
iperfでVM↔VyOS間の帯域を計測してみます。
# アップロード (VM -> VyOS)
iperf3 -c 10.30.0.1 -t 10
# ダウンロード (VyOS -> VM)
iperf3 -c 10.30.0.1 -t 10 -R
| 方向 | スループット | 再送 |
|---|---|---|
| Upload (VM → VyOS) | 3.61 Gbps | 6,935 |
| Download (VyOS → VM) | 5.69 Gbps | 0 |
ダウンロードは5.7Gbpsで再送なし。一方アップロードは3.6Gbpsで再送が大量に発生しています。
全二重なのでダウンロードが問題ないなら物理的な問題ではないはず。ソフトウェア的な原因を探ります。
原因調査
Harvesterホスト側
最初はVM側のvirtio NICのキュー数やバッファサイズを疑いましたが、以下を確認して除外しました。
- virtio multiqueueを有効化 → 帯域変わらず
- VM側のCPU使用率 → 7%で余裕あり
- ホスト側のtap/veth/ブリッジのドロップ → 全て0
VyOS側のsoftnet_stat
VyOSの /proc/net/softnet_stat を確認したところ、原因が見つかりました。
# CPU0: 処理パケット / ドロップ / time_squeeze
68c8077d 00000000 00008ab6 ← CPU0のtime_squeezeが35,510回!
0489011a 00000000 00000000
049a4d01 00000001 00000000
9a75cf8b 00000003 000000ab
time_squeeze はソフト割り込みの処理時間(netdev_budget、デフォルト300パケット)内にパケットを捌ききれず、処理が中断された回数です。
VyOSのeth3(10GbE)の受信割り込みがCPU0に集中し、高帯域受信時にソフト割り込み処理が追いつかなくなっていました。
ダウンロード方向はVyOSが送信側なのでこの問題は発生せず、アップロード方向だけ影響を受けていたわけです。
対策
VyOS側
VyOSのconfigureモードで以下を設定しました。
# ソフト割り込みの処理バジェット拡大
set system sysctl parameter net.core.netdev_budget value 600
# 受信キューバックログ拡大
set system sysctl parameter net.core.netdev_max_backlog value 5000
加えてRPS (Receive Packet Steering) を有効にして、受信処理を複数CPUに分散させます。
# eth3の全RXキューでCPU0-3を使う
for q in /sys/class/net/eth3/queues/rx-*/rps_cpus; do echo f | sudo tee $q; done
RPSはsysfs経由の設定で再起動で消えるため /config/scripts/vyos-postconfig-bootup.script に追記して永続化しました。
VM側
TCPバッファの拡大とBBRの有効化を /etc/sysctl.d/99-network-tuning.conf で永続化しています。
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_congestion_control = bbr
結果
| 方向 | Before | After |
|---|---|---|
| Upload | 3.61 Gbps / 再送 6,935 | 4.05 Gbps / 再送 327 |
| Download | 5.69 Gbps / 再送 0 | 5.68 Gbps / 再送 0 |
アップロードは12%向上、再送は95%減。ダウンロードへの悪影響もありませんでした。
おまけ: Harvester VMのmultiqueue設定
調査過程でVMのvirtio NICがシングルキューだったので、ついでにmultiqueueも有効にしました。
今回の帯域改善には直接効きませんでしたが、複数のTCPストリームが同時に流れる場合には効果があるはずです。
kubectl patch vm main -n lifeline --type merge \
-p '{"spec":{"template":{"spec":{"domain":{"devices":{"networkInterfaceMultiqueue":true}}}}}}'
KubeVirtの VirtualMachineClusterPreference に preferredNetworkInterfaceMultiQueue: true を設定すれば、VM作成時にデフォルトで有効にすることもできます。
補足: virtioのリングバッファが小さい理由
チューニング後もアップロード方向で少量の再送(327回)が残りました。これはBBRが定期的に帯域をプローブ(送信レートを意図的に上げて利用可能帯域を探る)する際に、virtioのリングバッファ(256エントリ)を一時的に溢れさせることが原因です。ダウンロード方向ではVyOSの物理NIC(リングバッファ最大8192)がバーストを吸収できるため再送0になります。
ではなぜvirtioのリングバッファはこんなに小さいのか。
物理NICのリングバッファはNIC上のハードウェアメモリ(SRAM)に配置され、NICメーカーが自由にサイズを決められます(今回のens1は最大8192)。一方virtio-netのリングバッファ(virtqueue)はゲストとホスト間の共有メモリ上のリング構造で、QEMUがVM作成時にサイズを決定します。virtioの仕様はシンプルさと低レイテンシを優先しており、デフォルト・最大値とも256エントリです。リングを大きくするとVM起動時のメモリ確保やvhostスレッドの処理コストが増え、レイテンシも悪化するためです。
QEMU 8.0以降では rx_queue_size / tx_queue_size を最大1024まで指定できますが、KubeVirtでは現時点でこのパラメータを公開していないため、Harvester環境では256固定となります。
