VMネットワークの四つのモード
一言でいうと
VMネットワーキングの選択は、誰がパケットを処理するかで分かれます。ユーザー空間(SLIRP)、カーネル(TAP)、カーネルブリッジ、そしてカーネル内のvirtio処理(vhost-net)です。
なぜ必要なのか
「VMからインターネットには出られるのに、外からVMには入れません」。この報告の原因は、ほとんどいつもuser(SLIRP)モードです。開発環境で既定値として使われるので、その特性を知らないと、本番にそのまま出てしまいます。
どう動くのか
user (SLIRP)
QEMUがユーザー空間にTCP/IPスタックをまるごと実装しています。ゲストが送ったパケットをQEMUが解釈して、ホストの普通のソケットとして作り直して送ります。
- 特権がまったく必要ありません。このラボ環境で唯一使える理由です。
- ゲストは
10.0.2.0/24の帯域に置かれ、ゲートウェイは10.0.2.2、DNSは10.0.2.3です。 - 外からゲストに入れません。
hostfwdで個別のポートを開ける必要があります。 - ICMPが制限されています。ゲストで
pingが通らない場合が多いですが、正常です。 - 性能が低いです。すべてのパケットが、ユーザー空間をもう一度経由します。
tap
カーネルの仮想イーサネットデバイスを作り、QEMUがその片端を握ります。パケットがカーネルのネットワークスタックにそのまま上がってくるので、ホストから見ると本物のインターフェースのように見えます。その代わり、TAPデバイスを作るには、特権(または、あらかじめ作っておいたデバイスに対する権限)が必要です。
bridge
TAPをLinuxのブリッジに挿します。ブリッジに物理NICも一緒に挿してあると、VMが物理ネットワークに直接つながったかのように動作します。VMがDHCPで社内ネットワークのアドレスを受け取り、ほかのサーバーからそのアドレスにそのまま接続できます。本番の標準的な構成です。
ip link add br0 type bridge
ip link set eth0 master br0
ip link set br0 up
qemu-system-x86_64 -netdev tap,id=n0,ifname=tap0,script=no -device virtio-net-pci,netdev=n0
vhost-net
virtioのリングバッファの処理をカーネルの中で行います。QEMUのユーザー空間を往復しないので、レイテンシとCPU使用が大きく減ります。-netdev tap,...,vhost=on。
| モード | 特権 | 外→内の接続 | 性能 | 用途 |
|---|---|---|---|---|
| user | 不要 | hostfwdだけ | 低い | 開発/テスト |
| tap | 必要 | 可能 | 中 | 分離されたVMネットワーク |
| bridge | 必要 | 可能(物理ネットワークに直結) | 中–高 | 本番 |
| vhost-net | 必要 | 可能 | 高い | 高性能ワークロード |
現場での姿
開発環境でうまく動いていたものが、ステージングで動かない場合です。開発はuserモードなのでhostfwdで22番だけを開けておき、ステージングはbridgeなので、すべて開いています。逆に、hostfwdだけでアクセスしていたものをブリッジに移すと、ファイアウォールを新しく設計し直す必要があります。今まではQEMUが事実上ファイアウォールの役割を果たしていたからです。
MTUの問題。ブリッジの上にVXLANのようなオーバーレイが重なると、実効MTUが減ります。VMの中では依然として1500なので、大きなパケットが静かに消えます。ゲストのMTUを下げるか、MSSクランピングが必要です。
コンテナネットワーキングと何が同じで何が違うのか
ここまで見た4つのモードは、コンテナ側でもほぼそのまま繰り返されます。名前が違うだけで、していることが同じなので、片方を理解すれば、もう片方もついてきます。
- VMのuserモード ≈ コンテナのポートフォワーディング。中から外には出られますが、外から中には、開けたポートからしか入れません。
- VMのtap ≈ コンテナのvethペア。カーネルに仮想インターフェースを作り、片方を中に、片方を外に置きます。
- VMのbridge ≈ Dockerの既定のブリッジネットワーク。複数を同じブリッジに挿して、互いに通信させます。
- VMのvhost-net ≈ データプレーンをカーネルやeBPFに下ろすこと。ユーザー空間を往復しないので、速くなります。
違う点もはっきりしています。コンテナはホストカーネルを共有するので、ネットワーク名前空間だけが分かれますが、VMは自分のカーネルを持っています。そのため、VMの中でカーネルパラメーターを変えてもホストには影響がありませんが、コンテナの中のカーネル設定の多くは、ホストと共有されるか、そもそも変えられません。逆に、VMは起動時間が長く、メモリをまるごと確保します。
実務の判断に置き換えると、次のとおりです。カーネル自体を扱う必要があったり(モジュールのロード、別のカーネルバージョン)、分離のレベルが本当に高くなければならなかったり、Linuxではないものを動かしたりするなら、VMです。それ以外は、コンテナがほとんどいつも軽くて速いです。そして、両者を組み合わせて使うのはよくあることです。Kubernetesのノード自体がVMで、その上でコンテナが動く構成が代表的で、このとき、ネットワーク層が2重に積み重なるため、前に述べたMTUの問題が特によく現れます。
次にすること
このモジュールは概念だけを扱います。前のvm-bootのラボで-netdev user,hostfwd=...を使った理由を、もう説明できるはずです。特権のない環境で唯一可能な選択だったからです。