RHEL/CentOS 5.x, Fedora Core 7/8 における Xen 仮想ネットワークの概要。Fedora Core 8 (Xen-2.1.2) では、ほとんどすべてが libvirt に牛耳られており xenbrX のでき方や定義方法が異なるため、まず「Fedora Core 8 における仮想ネットワーク」の項を読んでおいたほうがいいかもしれない。
さて、この時点で dom0 上で `ifconfig` すると、以下のネットワークデバイスがアクティブになっている。(`ifconfig -a' すると本当はもっといろいろと用意されていることが分かる)。Fedora Core 8 では仕組みが少々異なる。
IF名 | 説明 | IPアドレス |
lo | dom0 のループバックインターフェイス | 127.0.0.1/8 |
eth0 | dom0 の仮想ネットワークインターフェイス | 指定したもの |
peth0 | 本当の物理 (Physical) ネットワークインターフェイス。xend 起動前には eth0 だったもの | - |
xenbr0 | xend 起動時に `/etc/xen/scripts/network-bridge start` コールによって作られる仮想ブリッジ(ハブ) | - |
vif0.0 | 仮想ネットワークインターフェイス。仮想ブリッジ xenbr0 のひとつの物理ポート、つまりハブのひとつの口として位置づけられ、eth0 からのクロスケーブルがそこに刺さっているイメージ | - |
virbr0 | もうひとつの仮想ブリッジ、というより NATルータ。xend でなく libvirtd によって作られる。dnsmasq サービスを利用して DNSキャッシュ/DHCPサーバ機能も提供する。右記の IP は CentOS 5.1, Fedora Core 7/8 でのデフォルトで、他のディストリビューションや、Xenをソースインストールした時には異なることがある | 192.168.122.1/24 |
単純化するため、NIC を 1つだけ持つマシンを例として図に示そう。グリーン系のオブジェクトはネットワークインターフェイスの類で、薄い緑のものは仮想、濃い緑は実在の NIC を表す。()内の IP は 例えば のアドレスだ。Dom0 枠内のものは、仮想であれ物理であれ、dom0 の持つオブジェクト。virbr0 はブリッジあるいはルータだと書いてきたが、dom0 の持つもうひとつのネットワークインターフェイスだとも言える (※A)。
図のゲストドメイン domU#1 は、ここまでの構築例で示した通り virbr0 ("default"ネットワーク) に参加している。そこで、domU#1 が 192.168.122.y へパケット (例えば ping) を送った時には、特にアドレス変換は行われず virbr0 まで直通だ。かたや、dom0 の実IPアドレス 172.18.10.1 へ宛てて送ると、DomU#1 のデフォルトゲートウェイは通常 virbr0 を指しているので、そこを経て、virbr0 の WANポートといえる dom0 の eth0 へ届く。さらに、DomU#1 が、今度は図のどのサブネットにも属さない例えば 10.0.0.1 へパケットを送った時には、virbr0 から、virbr0 の作成時に libvirtd の設定するカーネルパケットフィルタ (iptalbes/Netfilter) によってパケットのソースアドレスを 172.18.10.1 に書き換えられた上で xenbr0 -> peth0 を経て外界へ出て行く。それがもし回答の必要なパケットだったなら、返信は逆の道を辿り、Netfilter によって自動的に 逆DNAT されて virbr0 経由で domU#1 の eth0 に返ってくることとなる。livbirtd の自動設定する iptables ルールが MASQUERADE だから理解しにくいのだが、dom0 の eth0 が図のように固定アドレス (172.18.10.1) であれば、この iptables ルールは、
-t nat -A POSTROUTING -s 192.168.122.0/24 -j SNAT --to-source 172.18.10.1
と読み替えることができる。噛み砕くと「送信元IPアドレスが 192.168.122.0/24 にマッチするパケットは、次のインターフェイスに渡す直前でパケットの送信元IPアドレスを 172.18.10.1 に書き換える」というルールだ。ここに書いてあることが ○×△%&?! にしか見えない人は Iptablesチュートリアルの NAT概論の節や MASQUERADE ターゲットの項、「テーブルとチェーンの道のり」の節を読むべし。
このようにして、デフォルトの構築状態では、dom0, DomU#1, DomU#N... は xenbr0 に参加していようと virbr0 に接続していようと、互いに通信することができるようになっているわけだ (本当は大きな制限があるが、それは次項「Xenネットワークの作られる仕組み」の「libvirtd の virbr0」にて)。通信できない場合は、もしかすると あの問題 に遭遇しているのかもしれない。
※A 「virbr0 はブリッジあるいはルータだと書いてきたが、dom0 の持つもうひとつのネットワークインターフェイスだとも言える」
この根拠は、virbr0 のアクティブになっている dom0 上で `ip route show' をすると下記のようなルーティングテーブルが得られることだ;
172.18.10.0/24 dev eth0 proto kernel scope link src 172.18.10.1
192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1
<以下略>
上で解説した仮想インターフェイスががどんな行程で作られるかを知っておくと、Xen仮想ネットワークのカスタマイズの際に役に立つ。それらは、標準では、/etc/xen/scripts/ にあるシェルスクリプト network-bridge (※) と vif-bridge によって構築される。そして、これらのスクリプトが呼び出されることは /etc/xen/xend-config.sxp 内で定義されている。デフォルトでは、network-bridge のほうは xend 起動時つまり dom0 のブート時、そして vif-bridge は各ゲストドメインの起動時にコールされる。詳しくは「Xenネットワークのカスタマイズ」の「xenbrX の追加と動作の明確化」の項で述べる。
Fedora Core 8 では、構成や作られ方が少々異なる。
※ RHEL/CentOS 5.3 以降には、network-bridge-bonding というスクリプトも存在する。これは、bonding インターフェイスを実インターフェイスとした仮想ブリッジを作成する際に、network-bridge の代わりに使うスクリプト。それについては、「Xenネットワークの操作とカスタマイズ」及び、「Xenネットワーク クイックレシピ」の「bondingインターフェイスの単純ブリッジと通常の単純ブリッジ」で触れる。
network-bridge が行うこと (デフォルトの時の動作)
vif-bridge が行うこと (ゲスト#N が起動する時の例)
もうひとつのブリッジ(ルータ) virbr0 は、libvirtd に対する設定ファイル /etc/libvirt/qemu/networks/default.xml の中で "default" という名前の仮想ネットワークとして定義されている。参考のため、CentOS 5.1 にデフォルトでインストールされる default.xml をサンプルとして置いておく。192.168.122.1/24 というこのブリッジの IPアドレスもその中で設定されている。説明の順とは逆になるが、INIT での起動順序は xend よりも libvirtd のほうが先になっている。
動作を明記した資料がまだ見つからないが、libvirtd はこの時、以下のようなことをしていると推測できる;
4. については、CentOS 5.1 の libvirt 0.2.3 がデフォルトで設定する iptablesルールセットを iptables-save コマンドでダンプしたものを下に示す。 (`iptables -L' で見てみたい時は `-t nat' オプションも指定しないと MASQUERADE ルールは見えないことをお忘れなく)。
*nat :PREROUTING ACCEPT [20306:2695674] :POSTROUTING ACCEPT [102:8062] :OUTPUT ACCEPT [133:10214] -A POSTROUTING -s 192.168.122.0/255.255.255.0 -j MASQUERADE COMMIT *filter :INPUT ACCEPT [760755:6046333452] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [694643:6984427594] -A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT <--53はDNS--> -A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT -A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT <--67はbootpつまりDHCP--> -A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT -A FORWARD -d 192.168.122.0/255.255.255.0 -o virbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT -A FORWARD -s 192.168.122.0/255.255.255.0 -i virbr0 -j ACCEPT -A FORWARD -i virbr0 -o virbr0 -j ACCEPT -A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable -A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable -A FORWARD -m physdev --physdev-in vif7.0 -j ACCEPT COMMIT
各テーブルのデフォルトポリシーが「許可」になった、あまり厳格ではないルールだが、赤字で示した行が一癖ある。「フォワードで virbr0 から出て行こうとするパケットはブロックし、送信元に ICMP の port-unreachable パケットでお断りの旨伝える」というルールだ (ただし、入口も出口も virbr0 であるパケットはその前のルールで既に ACCEPT されていてこのルールの評価は受けない)。これがあるおかげで、マシン外部のホストから virbr0 の背後にいるゲストドメインへはパケットが届かない。そこで試しに、下記のような実験をしてみた;
route add 192.168.122.0 mask 255.255.255.0 172.18.10.1 metric 1
これをもっときちんと組み上げたのが、「Xenネットワーク クイックレシピ」の「単純ブリッジとLAN用L3スイッチ(非NATのvirbrX)」だ。
Fedora Core 8 では xm から libvirt (0.4.2) へと軸足が移ったことなどにより、Xenネットワークの仕様が変わった。まず、network-bridge スクリプトの動作が違う。生成される xend ブリッジは、実在NIC の IP, MACアドレスばかりでなく名前もを奪い取るようになった。
前の図と見比べてみるといいだろう。
network-bridge の行うこと (デフォルト時)
|
また、vif と親ブリッジとの接続を仕切るスクリプトを、ゲストドメインの定義ファイル内で、仮想インターフェイス毎に個別に指定できるようになった (デフォルトでは vif-bridge)。定義ファイルのサンプルはこちら。
構成完了後には eth0 はもはや仮想L2スイッチのような存在となり、xenbrX との区別がない。後述するカスタマイズ例では、`xenbrX' を指定する部分で代わりに `eth0' を指定すればよい。
まず、何を変えるには何を行えばよいかの概略をまとめ、その後、ケーススタディ的な設定例を挙げることにする。気の短い人はさっそく クイックレシピ へどうぞ。そこここに登場する virsh, brctl などのコマンドについて知りたければ「コマンドラインツール」の章を参照していただきたい。
root# virsh net-autostart default --disable
このコマンドは /etc/libvirt/qemu/ 下にある networks/autostart/default.xml ファイルを削除する。それは、"default" という名称のオブジェクトを定義している実ファイル networks/default.xml へのシンボリックリンクだ。これによって、次回の libvirtd 起動時からは virbr0 が生成されなくなり、それに付随する iptables ルールも登録されなくなる。
変更を反映するためには、確実を期してホストマシンを再起動した方がいいが、どうしても今すぐに virbr0 を殺したければ手動停止もできる;
root# virsh net-destroy default root# brctl show <--確認
逆に、再びオートスタートを復活させるには下記。然るべき iptables ルールセットもまた設定されるようになる;
root# virsh net-autostart default
これも、すぐに作成させたければ手動起動もできる;
root# virsh net-start default root# brctl show <--確認
新たな virbrX を作成すると最終的には /etc/libvirt/qemu/networks/ 下に定義ファイルができるわけだが、後述の net-undefine をするとそのファイルは削除されてしまうため、定義ファイルの管理ディレクトリを決めておいて、原本はそこに作成するべきだ。ここからの説明では、原本管理ディレクトリを /etc/virnet、新たに作成する仮想ネットワークブリッジを virbr1、その定義名を virnet1 とする。
/etc/virnet/virnet1.xml ファイルをテキストエディタで作成する。オフィシャルサイト libvirt.org にフォーマットの解説がある。
<network> <name>virnet1</name> <--libvirt上での定義名 <bridge name="virbr1" stp="off" forwardDelay='0' /> <--ブリッジのプロパティ <forward mode="nat" dev="eth0"/> <--この仮想ブリッジをルータとして機能させる場合は書く (省略可) <ip address="192.168.200.1" netmask="255.255.255.0"> <--ブリッジに持たせるIP <dhcp> <--dhcpブロックは、DNSキャッシュ/DHCP機能をオンにするなら書く (省略可) <range start="192.168.200.2" end="192.168.200.254" /> </dhcp> </ip> </network>
[捕捉]
<!-- <forward/> -->のようにコメントアウトしておくことも可能。
新しい定義ファイルを作ったら、まず libvirt にそれを認識させなくてはならない;
root# virsh net-define /etc/virnet/virnet1.xml
これにより、元のファイルが /etc/libvirt/qemu/networks/ にコピーされる。単純なコピーでなくパースしてから吐き出されるので、コメントは除かれ、オブジェクトを一意に識別するための <uuid>ランダムなID</uuid> ブロックが挿入される。今すぐアクティブにしたいなら、
root# virsh net-start virnet1
既に登録済みの virbrX の設定を変更する場合は、/etc/libvirt/qemu/networks/ 下の定義ファイルを直接編集して net-destroy や net-start するだけでは変更は反映されない。変更は原本のほうで行い、反映するには再度 net-define を行ってやる必要がある。net-undefine せずに再登録をすると /etc/libvirt/qemu/networks/ 下の定義ファイルが上書きされる。libvirtd の起動とともに自動的にアクティブにされるようにする手順は前に書いた。
これを行うと、今アクティブな当該ブリッジは net-destroy できなくなるので、下記のように必ず先に手動停止しておくこと。また、/etc/libvirt/qemu/networks/ にある当該の定義ファイルが削除されてしまうため、既定の "default" ブリッジにこれをやる時には前もって定義ファイルをバックアップしておいたほうがいい。当項での凡例に基づけば、/etc/virnet/ にコピーしておくのが妥当だろう。net-undefine を行うと、そのブリッジの自動起動も同時に解除される。
root# virsh net-destroy virnet1 <--手動停止 root# virsh net-undefine virnet1 <--登録抹消
ここでは、xend の起動時にデフォルトで作成される xenbr0 と同様の仮想ブリッジを、もっと増やしたい場合や、自動判定に頼る部分を減らして生成動作を明確にする方法を述べる。
xend 起動時に xenbrX をどう作るかは、xend の主設定ファイルである /etc/xen/xend-config.sxp の中の
(network-script FILE)
にどのスクリプトがセットされているかによって決定される。デフォルトの設定状態では、
(network-script network-bridge)
となっている。
それが指し示すシェルスクリプト /etc/xen/scripts/network-bridge は、特定の引数を受け入れるように作られているのだが、このデフォルト状態では引数をほとんど省略したカタチで実行され、自動に任されている。xenbr1 を追加したり、意図通りの実デバイスに対してブリッジを作らせるには、明示的な引数で必要な回数だけ network-bridge を間接的にコールするラッパースクリプトをカスタムメイドして、そのファイル名を `(network-script xxx)' で指定すればよい。
network-bridge スクリプトのコール書式:
network-bridge (start|stop|status) \ [vifnum=X] [bridge=xenbrX] [netdev=ethX] [antispoof=(no|yes)]
start か stop か status は xend やその他の管理ユーティリティを通じて自ずといずれかが送られる。それ以降の var=value は省略可能な引数。各変数の意味と、省略した時のデフォルト値を以下に示す。
変数 | 説明 | 省略時デフォルト値 |
vifnum | 作られる vif0:X の X の数字がこれによって決まる。他の変数決定の材料にもなる | マシンのデフォルトゲートウェイとなっているデバイスの末尾の数字、つまり通常は eth0 の `0'。デフォルトゲートウェイが判断できない時も `0' となる。Fedora Core 7/8 (Xen-2.1) にはない |
bridge | 作られるブリッジのデバイス名 | ・`xenbr' + $vifnum (RHEL/CentOS 5.x) ・$netdev (Fedora Core 7/8) |
netdev | ブリッジにつなぐ実デバイス | ・`eth' + $vifnum ( RHEL/CentOS 5.x) ・マシンのデフォルトゲートウェイとなっているデバイス、つまり通常は eth0 (Fedora Core 7/8) |
antispoof | yes にするとカーネルパケットフィルタの FORWARDテーブルのデフォルトポリシーが「破棄」にセットされ、パケットのうち、このスクリプトで作られる vif0:X か $netdev から入ってくるものしかフォワードしない設定となる | no |
この他に、network-bridge スクリプト内部だけで使われる pdev という変数がある。その値は `p' + $netdev となり、最終的にはそれがブリッジ作成完了後の実デバイスの名前になる。 netdev=eth0 なら pdev は peth0、netdev=dummy0 なら pdummy0 という具合だ。
#!/bin/sh dir=$(dirname $0) ${dir}/network-bridge "$@" vifnum=0 bridge=xenbr0 netdev=eth0 antispoof=no ${dir}/network-bridge "$@" vifnum=1 bridge=xenbr1 netdev=eth1 antispoof=no
#!/bin/sh dir=$(dirname $0) ${dir}/network-bridge "$@" bridge=eth0 netdev=eth0 antispoof=no ${dir}/network-bridge "$@" bridge=eth1 netdev=eth1 antispoof=no
この (例えば) network-bridge-custom ファイルを /etc/xen/scripts/ に置いて実行ビットを立てた上で、/etc/xen/xend-config.sxp の例の部分を、
(network-script network-bridge-custom)
と書き換える。ちょっと凝った network-bridge-custom を作って「Xenネットワーク クイックレシピ」で紹介しているので、気に入ったら使っていただきたい。
RHEL/CentOS 5.3 になってから、bonding インターフェイスを親とする仮想ブリッジを作成したい時のために、network-bridge-bonding というスクリプトが追加された (Red Hat Bugzilla 参照)。従来は (私はやったことがなかったが)、network-bridge スクリプトを流用していたため、仮想ブリッジを作った途端に接続が失われたり、フラッピング (フェイルオーバーを繰り返す現象) が起こったりしていたらしい。network-bridge-bonding スクリプトは、仮想ブリッジの作成行程を工夫したり /proc/sys/net/BONDING_IF/ 下のパラメータを操作するルーティンを組み込んだりしてそれを解決している。
network-bridge-bonding の引数は、network-bridge とは少々異なっている;
変数 | 説明 | 省略時デフォルト値 |
network-bridge と違ってブリッジの生成に vif は使われないのでこの引数は存在しない | - | |
bridge | 作られるブリッジのデバイス名 | $netdev |
netdev | ブリッジにつなぐ実 (bonding) デバイス | マシンのデフォルトゲートウェイとなっているデバイス |
antispoof | yes にするとカーネルパケットフィルタの FORWARDテーブルのデフォルトポリシーが「破棄」にセットされ、パケットのうち、$netdev から入ってくるものしかフォワードしない設定となる | no |
ご覧のように、コール様式は Fedora Core 7/8 の network-bridge のほうに似ている。また、スクリプト内部で自動的に命名される pdev は p${netdev} となる。つまり、元の実 bonding インターフェイスが bond0 なら、それは pbond0 に改名され、最終的にできる仮想ブリッジが bond0 という名前になる。
network-bridge-bonding も、前述の network-bridge の時と同様に network-bridge-custom のようなカスタムラッパースクリプトの中で使うことができる。カスタムラッパースクリプトの中で、network-bridge と併用することも可能だ。例として、eth0 と eth2 を bonding した bond0 と、bonding していない eth1 の両方に対して各々仮想ブリッジを作成する network-bridge-custom の記述例を挙げておこう。
#!/bin/sh dir=$(dirname $0) ${dir}/network-bridge-bonding "$@" bridge=bond0 netdev=bond0 antispoof=no ${dir}/network-bridge "$@" bridge=xenbr0 netdev=eth1 antispoof=no
このように仮想ブリッジに bonding インターフェイスをひとつでも含む場合には、上記のように、network-bridge に対しても vifnum=x は指定せず自動採番に任せたほうがいいだろう。
前記の network-bridge に似て xend-config.sxp の定義、
(vif-script vif-bridge)
に応じて /etc/xend/scripts/vif-bridge スクリプトが呼ばれて設定されるのだが、vif-bridge は変数を受け取る作りにはなっていない。こちらの場合は、ゲストドメインの定義の方で変更する。
xm形式なら、/etc/xen/ にある当該ゲストドメイン定義ファイルの
vif = [ "...", "..." ]
という部分だ。virt-manager で生成されたものを見れば分かると思うが、[] の中はこのようになっている;
"MAC=第1仮想NICに付けるMACアドレス,bridge=接続先仮想ブリッジ","MAC=第2仮想NICに付けるMAC,bridge=接続先仮想ブリッジ",...
libvirt XML形式では、xmldump したドメイン定義ファイルの中の
<interface type='bridge'> <mac address='この仮想NICに付けるMACアドレス'/> <source dev='接続先仮想ブリッジ'/> <script path='vif-bridge'/> </interface>
というブロックひとつがそのゲストドメインの仮想NICひとつを定義している。追加したければ、同様の <interface> ブロックを追加してやればいい。
いずれの形式でも、MACアドレスの指定を省略すると、00:16:3e: で始まる MACアドレスがゲストドメインの起動時にランダムに生成されはするが、起動の度に変化してしまうので、必ず指定すべき。この 00:16:3e:xx:xx:xx という物理アドレスは IEEE によって XenSource にリザーブされており、他のベンダーID (OUI = Organizationally Unique Identifier) とかぶる心配はなく、仮想マシン内で自由に使うことができる (IEEE の OUI 割り当てリストはここで見られる)。ただし、これら MACアドレスの後半 3オクテットは必ず一意でなければならない。人間が考える値は意外とランダムでないので、RedHat の「仮想化ガイド」で紹介されている Pythonスクリプトを真似して利用するといいだろう。こんな感じ (xenmacgen.py.txt) だ。vif 定義の具体的な例は クイックレシピ で見ていただくとしよう。
Xenネットワークの全体像で述べたように、libvirt の仮想ブリッジを NAT ルータとして作成すると、dom0 の IPアドレスが動的割り当てかスタティックかはお構いなしで、iptables のマスカレードルールが設定される。しかし、Linux の MASQUERADE はカーネル内部では SNAT であり、実はコネクションの度に (例えば) eth0 の IPアドレスを調べるという処理が発生している。しかもそれはカーネルにとってあまり軽い処理ではない (このあたりの話は Iptablesチュートリアル の「natテーブル」や「MASQUERADEターゲット」の項で語られている)。
サーバ用途の場合なら dom0 の実 IP はほとんどの場合固定アドレスなので、マシンのリソースを節約するために MASQUERADE ルールを SNAT ルールに書き換えてやるとよい。何度か述べたように、virbrX の作成は xenbrX の作成より前に行われる。つまり、これをやる場所として妥当なのは /etc/xen/xend-config.sxp、ひいては /etc/xen/scripts/network-bridge-custom の中ということになる。設定例を示そう。
/etc/xen/xend-config.sxp の
(network-script network-bridge)
を
(network-script network-bridge-custom)
に書き換える。そして、下記のような /etc/xen/scripts/network-bridge-custom を書き、実行ビットを立てる。virbr0 の IPアドレスが 192.168.122.x/255.255.255.0 で、dom0 の eth0 が 172.18.10.1 の場合を例としている;
#!/bin/sh dir=$(dirname $0) iptables -t nat -F POSTROUTING iptables -t nat -A POSTROUTING -s 192.168.122.0/24 -j SNAT --to-source 172.18.10.1 ${dir}/network-bridge "$@" vifnum=0 bridge=xenbr0 netdev=eth0 antispoof=no
たいていの仮想ネットワークトポロジーで使えるよう、NATルールの最適化も含めた汎用的なスクリプトを作った。使うにはちょっとした手順があるので、詳しくは「Xenネットワーク クイックレシピ」の「単純ブリッジと内部専用L2スイッチ」のところで。