BridgeなしでKVMを動かしたときの現実的な問題
私のhomelabはProxmox VEで12台のVMとコンテナを管理しています — productionに上げる前に何でも試せるplayground環境です。Proxmoxに移行する前は、Ubuntu Server 22.04上でピュアなKVMを使っていました。最初のつまずき:VMを作成したあと、virt-managerがNATモードを使用中と表示しているにもかかわらず、VM同士がpingできず、外部からSSH接続も不可能な状態になりました。
原因:KVMはデフォルトでlibvirtが作成する仮想ブリッジvirbr0を経由したNATモードを使用します — デフォルトのサブネットは192.168.122.0/24で、自宅LANとは完全に隔離されています。個人のlab環境では便利ですが、VMがNATの裏に隠れてしまいます。VMにLAN上の実IPを持たせるには、物理NICに紐づくネットワークブリッジを作成し、VMがそのブリッジに接続するTAPインターフェースを使えるようにする必要があります。
BridgeとTAP — 本質を直接解説
KVMのドキュメントはこの2つの概念を別々に説明することが多く、エラーが発生したときにどの層でデバッグすればよいか分かりにくいです。設定に入る前にまとめておきます。
ネットワークブリッジ(br0)
BridgeはLayer 2の仮想スイッチとして動作します。物理NIC(enp3s0)をbridgeに追加すると、IPアドレスはbr0に移行し — enp3s0にはなくなります。Bridgeは物理ネットワークと接続された仮想インターフェースの両方からトラフィックを受け取ります。Bridgeに接続したVMは実際のルーターのDHCPからIPを取得します — スイッチにLANケーブルを挿したマシンと全く同じ動作です。
TAPインターフェース
TAPはLayer 2(Ethernetフレーム)で動作する仮想ネットワークインターフェースです。QEMU/KVMがVMを起動すると、host上にTAPインターフェース(tap0、vnet0など)を作成します。TAPの一方の端がbridgeに接続され、もう一方の端がguest OSから見える仮想NICになります。
データの流れ:ゲストNIC → TAP → Bridge → 物理NIC → ルーター/LAN。
なぜTUNではなくTAPを使うのか?TUNはLayer 3(IPパケット)で動作しますが、VMはARP、ブロードキャスト、VLANタギングを処理するためにLayer 2が必要です。TAPだけがその要件を満たせます。
実際の設定 — ステップバイステップ
ステップ1:物理NICの特定
ip link show
# 接続中のインターフェース名を探す(例:enp3s0またはeth0)
ip addr show enp3s0
# 現在のIPアドレス、サブネットマスク、ゲートウェイをメモする
作業前の注意:enp3s0をbridgeに追加した直後、このインターフェースのIPアドレスは消えます — IPはbr0に移行します。enp3s0経由でSSH接続中の場合、コマンド実行と同時に接続が切断されます。最も安全な方法はコンソールから直接作業するか、すべてのコマンドをスクリプトにまとめてnohup sh bridge-setup.sh &で一括実行することです — 完了後はbr0の新しいIPでSSH接続し直してください。
ステップ2:NetplanでBridgeを作成
Ubuntu ServerはデフォルトでNetplanを使用します — これは永続的な設定方法で、再起動後も設定が消えません。まずバックアップを取ります:
sudo cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak
DHCP設定:
# /etc/netplan/00-installer-config.yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: no # 物理インターフェースのDHCPを無効化
dhcp6: no
bridges:
br0:
interfaces: [enp3s0] # enp3s0をbridgeに追加
dhcp4: yes # BridgeのIPをDHCPで取得
parameters:
stp: false # Spanning Treeを無効化(lab環境では通常不要)
forward-delay: 0
ルーターにDHCPがない場合や固定IPが必要な場合は静的IPを設定します:
bridges:
br0:
interfaces: [enp3s0]
addresses: [192.168.1.100/24]
routes:
- to: default
via: 192.168.1.1
nameservers:
addresses: [1.1.1.1, 8.8.8.8]
parameters:
stp: false
forward-delay: 0
設定を適用します:
sudo netplan apply
# Bridgeが作成されているか確認
ip addr show br0
bridge link show
ステップ3:TAPインターフェースを手動で作成(仕組みを理解するために)
libvirtはVM起動時にこのステップを自動で行います。しかし、一度手動で行うことで仕組みをしっかり理解でき — VMがネットワークエラーを報告したときのデバッグも速くなります:
# TAPインターフェースを作成
sudo ip tuntap add tap0 mode tap
# インターフェースを有効化
sudo ip link set tap0 up
# TAPをbridgeに追加
sudo ip link set tap0 master br0
# 確認 — tap0がリストに表示されるはず
bridge link show br0
テスト後は削除します:
sudo ip link set tap0 nomaster
sudo ip tuntap del tap0 mode tap
ステップ4:VMがBridgeを使用するようにlibvirtを設定
これが私が最もよく使う方法です — libvirtがTAPを自動管理します:
# ネットワーク定義のXMLファイルを作成
cat << 'EOF' > /tmp/br0-network.xml
<network>
<name>br0-network</name>
<forward mode="bridge"/>
<bridge name="br0"/>
</network>
EOF
# libvirtにネットワークを登録
virsh net-define /tmp/br0-network.xml
virsh net-start br0-network
virsh net-autostart br0-network
# 確認
virsh net-list --all
VMのNICをbridgeに接続します。方法1 — virsh editで編集する:
virsh edit vm-name
# <interface type='network'> を探して以下に変更する:
# <interface type='bridge'>
# <source bridge='br0'/>
# <model type='virtio'/>
# </interface>
または実行中のVMに直接アタッチする:
virsh attach-interface --domain vm-name \
--type bridge \
--source br0 \
--model virtio \
--config --live
ステップ5:VMを起動して確認
virsh start vm-name
virsh console vm-name # またはvirt-viewerを使用
# ゲストOS内:
ip addr show # 192.168.1.xのIPが表示されるはず
ping 192.168.1.1 # ゲートウェイにping
# LAN内の別のマシンから:
ping <VMのIPアドレス> # pingが通るはず
ssh user@<VMのIPアドレス> # NAT不要で直接SSH
Bridgeが動作しないときのデバッグ
よく遭遇する4つのエラーと素早い対処法:
- VMがDHCP経由でIPを取得できない:
bridge fdb show br0を実行 — TAPインターフェースが表示されるはずです。表示されない場合は手動で接続します:ip link set vnetX master br0。libvirtはhost再起動後にTAPを作成するものの、bridgeへの接続を忘れることがあります。 netplan apply実行後にSSH接続が切れた:IPがbr0に移行しています。コンソールでip addr show br0を実行して新しいIPを確認し、接続し直してください。- Bridge設定が正しいのにパケットがドロップされる:libvirtがiptablesのFORWARDルールを追加します。
iptables -L FORWARD -n -vで確認してください。policyがDROPの場合、ルールを追加します:iptables -I FORWARD -i br0 -j ACCEPT。 - iptablesルールが正しいのにBridgeがパケットを転送しない:
sysctl net.bridge.bridge-nf-call-iptablesを確認してください。値が1の場合、bridgeがすべてのパケットに対してiptablesを呼び出しています —0に変更してみてください:sysctl -w net.bridge.bridge-nf-call-iptables=0。
まとめ
Bridge + TAPは現在主流のほぼすべてのハイパーバイザーが採用している基本的なネットワークアーキテクチャです — Proxmox VE、OpenStack Nova、Debianのlibvirtもすべてこれと同じ原理を使用しており、違いは設定の自動化レベルだけです。
ゲストNIC → TAP → Bridge → 物理NICの流れを理解してから、VMのネットワーク関連チケットのほとんどを10分以内に解決できるようになりました。問題はたいていこの3点のいずれかにあります:TAPがbridgeに接続されていない、iptablesがFORWARDをブロックしている、またはsysctlのbridge-nfが干渉している。
さらに深く学びたい場合、次のステップとして2つの方向性があります:macvtap — VMが物理NICのMACレイヤーを直接使用し、bridgeをバイパスすることでレイテンシを低減する方法、またはSR-IOV — 物理NICがVirtual Functionsを作成してVMに直接割り当て、bare-metalに近いパフォーマンスを実現する方法です。一般的なhomelab環境ではbridge + TAPで十分であり、デバッグも最もしやすい構成です。
