2011/12/07(Wed)Linux KVM Tips & IPv6 LiveMigration
2011/12/07 22:55
VMを停止させずに別のサーバへ移動できるマイグレーションが面白そうということで、ちょっと触ってみた。
検証環境
- 検証マシン
- hp 8200 Elite SF (Core i7-2600, Q67 Express, 8GB)
hp dx7500 SF (Core2 Duo E8400, G45 Express, 4GB)
hp EliteBook 2730p (Core2 Duo L9400, GS45 Express, 4GB)
DELL vostro 3300 (Core i5-520M, HM57 Express, 4GB)
※KVMはIntel VT対応が必須。VT対応しているCPUでも、BIOSでDisabledにされていたり、そもそも設定項目がない場合もあるので注意。メーカー製PCは基本的にDisabledになっていることが多い。
Linuxなら/proc/cpuinfoに"vmx"(Intelの場合), "svm"(AMDの場合)があるかを確かめておく必要がある。
ちなみにWindowsならVirtualChecker等を使えば把握できる。 - ディストリビューション
- Fedora 14
- カーネル
- Linux mica 2.6.35.14-103.fc14.i686.PAE #1 SMP Thu Oct 27 15:58:03 UTC 2011 i686 i686 i386 GNU/Linux
- qemu
- 以下のバージョンにパッチをあてたもの
qemu-system-x86-0.13.0-1.fc14.i686
qemu-common-0.13.0-1.fc14.i686
qemu-img-0.13.0-1.fc14.i686
gpxe-roms-qemu-1.0.1-3.fc14.noarch - libvirt
- 以下のバージョンにパッチをあてたもの
libvirt-0.8.3-10.fc14.i686
libvirt-debuginfo-0.8.3-10.fc14.i686
libvirt-python-0.8.3-10.fc14.i686
libvirt-devel-0.8.3-10.fc14.i686
libvirt-client-0.8.3-10.fc14.i686 - virt-manager
- virt-manager-0.8.7-2.fc14.noarch
環境構築
基本的にはyumで一発。普通に使う分にはこれでよさそう。sudo yum install virt-manager libvirt qemu-system-x86ただし、後述のパッチを当てたりするために、一旦アンインストールし、独自rpmをインストールしたりしている。
ちなみにFedoraのデフォルトカーネルでは、KVM自体が組み込まれており、「仮想マシンマネージャー」ことvirt-managerを立ち上げれば(要X Window System)自動的にカーネルモジュールがロードされるはず。(以下はIntel PCの場合)
$ lsmod | grep kvm kvm_intel 35769 4 kvm 213316 1 kvm_intel
virt-manager, libvirt, qemuの関係
はじめ、この関係が理解できなくて苦労した…。- virt-manager
- 「仮想マシンマネージャー」ということで、仮想マシンを作ったり消したり、構成を変更したり、あと仮想マシンのコンソール(画面)を見る事も可能。
要するに、ただのガワ。 - libvirt
- ググると分かるように「仮想化API」という代物らしい。
virt-managerは基本的にlibvirtのAPIを叩いているので、KVM以外の仮想化技術もそのまま使える。(Xen, Linux Containers, OpenVZ, User Mode Linux, Virtual Box, VMware ESX, Open Nebula等)
しかも、x86, x86_64, IA64, Powerなどの複数のアーキテクチャに対応し、C, Python, Perl, Ruby, Java等からAPIを叩けるらしい。
libvirt APIを対話シェル(引数に書いてバッチ処理も可能)から扱うためのvirshというコマンドが用意されている。
またlibvirt自体はサービスとして常駐可能なため、常に起動させておく*1と、virt-manager経由で他のサーバ上のVMを操作する(コンソールを除くだけでなく構成変更やマイグレーションその他も)ことが可能である。 - qemu
- 「エミュレータ」というジャンルで、あるプラットフォーム上で別プラットフォーム環境を完全エミュレーションするらしい。
つまりQEMU単体でも仮想化は可能だが、Intel VTなどのハードウェア支援を受けられないため、とてつもなく遅い。
というわけで、今回Linuxに統合されているKVMというハイパーバイザを利用し、その上でQEMUを使ってエミュレータを動かすという形になる。
基本的に
このサンプルでもそうだが、VTなどの仮想化支援機能を使うせいか、一般的には推奨されていないであろうrootでのアプリケーション起動が前提となっているものが多い。virt-managerのGUIには初期状態で何も登録されていないが、[ファイル]-[接続を追加...]とやれば、自ホストやネットワーク上の他ホストが追加できる。
他ホストを捜査する場合、root経由でsshを行う必要があるため、/etc/ssh/sshd_configにPermitRootLogin yesを設定しておく必要がある。
毎回パスワードを求められるのが鬱陶しい場合は、ローカルユーザとローカルのrootの公開鍵を、相手ホストの~root/.ssh/authorized_keysに書いておくと良い。
なおvirt-managerがイマイチで、ホスト名欄にはIPv6アドレスを生で記述できない。*2
/etc/hostsまたはDNSでAAAAを解決できるようにしておけば、相手がIPv6しか喋れないホストでもlibvirt経由でアクセスできる。
ブリッジデバイス
作り方
基本的に初期状態では、NATデバイスしか出来ていない。仮想マシンにおいてブリッジ、いわゆるサーバに刺さっているネットワークセグメントと同一のネットワークを利用したいシーンでは、ブリッジ専用のI/Fを介して接続しなければならない。
そして、そのブリッジ機能はLinux bridgeに任されているので、brctlを叩いてブリッジI/Fを用意する必要がある……のであるが、network-scriptsを直に設定したり、virt-managerから設定する事でブリッジI/Fは簡単に作れる。
次の節に構成図を示しているように、eth0という物理I/Fはただの出入り口という扱いになるため、アドレスやゲートウェイを割り振るデバイスはbr0になる。
eth0に設定しても無視されるので注意。
太字の部分に特に注意して(大文字小文字も)、あとはeth0の設定をコピペすればよい。
$ cat /etc/sysconfig/network-scripts/ifcfg-br0 DEVICE=br0 TYPE=Bridge ONBOOT=yes HWADDR=XX:XX:XX:XX:XX:XX BOOTPROTO=dhcp IPV6INIT=yes DELAY=0 STP=no $ cat /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 TYPE=Ethernet ONBOOT=yes BRIDGE=br0ちなみに、DELAY=0が死ぬほど重要。
書き忘れるとForwarding delayというのが15秒にセットされ、マイグレーションしてからその時間だけネットワークが不通になる模様。STPで必要な事なのか、通信を安定させる目的か分からないけど、とにかく注意。
virt-managerのGUIでは、「ブリッジの設定」の[設定]ボタンの中にその設定がある。
構成図
先ほどのブリッジの状態ではこんな風になっている。Guest VM | VM Server [eth0] <===> [vnet0] <===> [br0] <===> [eth0]ここでまた見慣れないvnet0というデバイスが出来ているのだが、いわゆるこれはtapデバイスというもので、VM内のeth0がサーバ側でvnet0と見えていると思うと直感的かな。
したがってVMをシャットダウンすると、ホスト側ではvnet0が見えなくなる。
ブリッジコントローラで見るともっと分かりやすいかもしれない。
$ brctl show bridge name bridge id STP enabled interfaces br0 8000.xxxxxxxxxxxx no eth0 vnet0 virbr0 8000.000000000000 yesつまりbr0は、「eth0とvnet0をブリッジしている状態」なのである。
例えVM(もしくはその中で利用するNIC)が増えて場合でも、vnet1, vnet2, ...をbr0に属させる事で、この例ではeth0をそれぞれのVMで共有する事が出来る。
virbr0
ちなみに上の例では使われていないvirbr0というのが、libvirtをインストールした時点で出来ているNATデバイスである。どこにNATされるかはシステム依存の設定になっているようだが、VMの設定でNATデバイスに接続したときには、$ brctl showでvnet0がvirbr0に属しているように見えるはず。
エラー: internal error could not get interface XML description
一応ここまでで普通にVMが使えてはいるのだが、/var/log/messagesに不審なメッセージが定期的に出ていた。Jul 12 22:37:21 vostro2 libvirtd: 22:37:21.234: error : interfaceGetXMLDesc:355 : internal error could not get interface XML description (netcf: NETLINK socket operation failed - couldn't find ifindex for interface `eth1`) Jul 12 22:37:21 vostro2 libvirtd: 22:37:21.246: error : interfaceGetXMLDesc:355 : internal error could not get interface XML description (netcf: NETLINK socket operation failed - couldn't find ifindex for interface `eth2`) Jul 12 22:37:21 vostro2 libvirtd: 22:37:21.255: error : interfaceGetXMLDesc:355 : internal error could not get interface XML description (netcf: NETLINK socket operation failed - couldn't find ifindex for interface `eth3`)これって正しくインタフェース情報が登録されていない?
virt-manager経由でなく、network-scriptsをいじってブリッジデバイスを作ったりしたので、その過程でlibvirtに正しくI/FのXMLが定義されなかったんだろうと思う。
undefineしてやってやったらこのエラーは消えた。どこかで定義し直した覚えはないし、使えているので問題ないようなのだが。
# virsh iface-undefine eth1 # virsh iface-undefine eth2 # virsh iface-undefine eth3ちなみに、上とは別のマシンだが、eth0, eth1が存在し、br0 <=> eth0としている構成ではこういう風に見えた。
virsh # iface-list Name State MAC Address -------------------------------------------- br0 active XX:XX:XX:XX:XX:XX eth1 active XX:XX:XX:XX:XX:XX lo active 00:00:00:00:00:00 virsh # iface-dumpxml eth0 error: failed to get interface 'eth0' error: Interface not found: couldn't find interface with MAC address 'eth0' virsh # iface-dumpxml br0 <interface type='bridge' name='br0'> <protocol family='ipv4'> <ip address='192.168.xxx.xxx' prefix='24'/> </protocol> <protocol family='ipv6'> <ip address='2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx' prefix='64'/> <ip address='fe80::xxxx:xxxx:xxxx:xxxx' prefix='64'/> </protocol> <bridge> <interface type='ethernet' name='vnet0'> </interface> <interface type='ethernet' name='eth0'> </interface> </bridge> </interface> virsh # iface-dumpxml eth1 <interface type='ethernet' name='eth1'> <protocol family='ipv4'> <ip address='10.xxx.xxx.xxx' prefix='24'/> </protocol> <protocol family='ipv6'> <ip address='2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx' prefix='64'/> <ip address='fe80::xxxx:xxxx:xxxx:xxxx' prefix='64'/> </protocol> </interface>
エラー: getaddrinfo(127.0.0.1,5900): Name or service not known
virt-managerからVMを起動する際、コンソール(画面)を開くとgetaddrinfo(127.0.0.1,5900): Name or service not knownと怒られる事があった。
これは何故かというと、QEMU/KVMではコンソール接続にQEMUの-incomingオプションを利用してVNCプロトコルでアクセスしているらしい。
その部分がIPv4でしかlistenしていないようなのだが、VMのホストOS側にIPv4アドレスが1つも付いていないと(ループバックを除く)このエラーが起きる。
いやー、接続しようとしている127.0.0.1:5900は、loに127.0.0.1というアドレスが付いているのだから、別に他のI/FにIPv6しかついてなくてもうまくいくはずなんだけどな…。
getaddrinfoがloをうまく解決できていないのかどうかまでは見ていないけど、とにかくbr0等になんかIPv4アドレスを振ってやれば解決する。
未解決のエラー
ライブマイグレーションを行う際、こんなメッセージが/var/log/messagesに出ることがある。要は失敗した事だけは分かるのだが、ほとんど意味をなしていないので、全然当てにならない。
(ライブマイグレーション送信側) Jul 12 22:37:44 vostro2 libvirtd: 22:37:44.489: error : qemuDomainMigrateSetMaxDowntime:11781 : invalid argument in qemuDomainMigrateSetMaxDowntime: unsupported flags (0xbc614e) Jul 12 22:37:44 vostro2 libvirtd: 22:37:44.498: error : qemuDomainMigrateSetMaxDowntime:11781 : invalid argument in qemuDomainMigrateSetMaxDowntime: unsupported flags (0xbc614e) Jul 12 22:37:46 vostro2 libvirtd: 22:37:46.124: error : qemuMonitorTextMigrate:1182 : operation failed: migration to 'tcp:vostro1:49174' failed: migration failed#015#012 Jul 12 22:40:36 vostro2 libvirtd: 22:40:36.698: error : qemuMonitorTextMigrate:1182 : operation failed: migration to 'tcp:vostro1:49175' failed: migration failed#015#012 (ライブマイグレーション受信側) Jul 12 22:44:06 vostro1 libvirtd: 22:44:06.255: error : qemuDomainHasManagedSaveImage:5650 : Domain not found: no domain with matching uuid '91df7b16-fcfb-3803-cd4f-103e53dbe1b3' Jul 12 22:44:06 vostro1 libvirtd: 22:44:06.261: error : qemuDomainGetJobInfo:11684 : Domain not found: no domain with matching uuid '91df7b16-fcfb-3803-cd4f-103e53dbe1b3' Jul 12 22:44:06 vostro1 libvirtd: 22:44:06.263: error : qemudDomainDumpXML:6743 : Domain not found: no domain with matching uuid '91df7b16-fcfb-3803-cd4f-103e53dbe1b3'
qemuとqemu-kvm
実際にソースコードをいじろうと思ってハマったのだが、QEMUとQEMU/KVMは別物である。といっても、全く違うのではなくてQEMU/KVMの方がQEMUからブランチしているのだろうけど、間違えてqemu-xxxx.tar.gzというのをQEMUのサイトからダウンロードしてLinuxをブートしてみたら、妙に遅すぎて使い物にならなかった。
KVMをサポートしている*3QEMUは、こっちが正解。qemu-kvm-xxxx.tar.gzというファイル名になっているはず。
QEMUコンパイルの注意
qemu-kvm-0.13.0のSRPMを素の状態でコンパイルしてみたが、何故か最終的にエラーになってしまうので悩んだ。しかも、単なるコンパイルエラーではなくて、OOMKillerに殺されているという状況。
物理メモリが約2.5GB利用可能で、さらに1GBのswap領域があるコンパイル環境では、全てのメモリとスワップを使いつぶしたあげく、失敗してしまう。(hp Elitebook 2730p)
以下はそのログ。
gcc -I/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/slirp -m32 -fstack-protector-all -Wold-style-definition -Wold-style-declaration -I. -I/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0 -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -Wstrict-prototypes -Wredundant-decls -Wall -Wundef -Wendif-labels -Wwrite-strings -Wmissing-prototypes -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i686 -mtune=atom -fasynchronous-unwind-tables -DHAS_AUDIO -DHAS_AUDIO_CHOICE -I/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/fpu -I/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/tcg -I/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/tcg/i386 -DTARGET_PHYS_ADDR_BITS=64 -I.. -I/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/target-i386 -DNEED_CPU_H -MMD -MP -MT i8254.o -MF ./i8254.d -O2 -g -c -o i8254.o /home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/hw/i8254.c {standard input}: Assembler messages: {standard input}:15420942: Warning: end of file not at end of a line; newline inserted {standard input}:15421105: Error: unknown pseudo-op: `.by' gcc: Internal error: Killed (program cc1) Please submit a full bug report. See <http://bugzilla.redhat.com/bugzilla> for instructions. make[1]: *** [translate.o] Error 1 make[1]: Leaving directory `/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/sparc64-linux-user' make: *** [subdir-sparc64-linux-user] Error 2 make: *** Waiting for unfinished jobs.... {standard input}: Assembler messages: {standard input}:9258539: Warning: end of file not at end of a line; newline inserted {standard input}:9258676: Error: can't resolve `.text' {.text section} - `.Ltext' {*UND* section} gcc: Internal error: Killed (program cc1) Please submit a full bug report. See <http://bugzilla.redhat.com/bugzilla> for instructions. make[1]: *** [translate.o] Error 1 make[1]: Leaving directory `/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/x86_64-softmmu' make: *** [subdir-x86_64-softmmu] Error 2 {standard input}: Assembler messages: {standard input}:10119177: Warning: end of file not at end of a line; newline inserted gcc: Internal error: Killed (program cc1) Please submit a full bug report. See <http://bugzilla.redhat.com/bugzilla> for instructions. make[1]: *** [translate.o] Error 1 make[1]: *** Deleting file `translate.o' make[1]: Leaving directory `/home/kero/rpmbuild/BUILD/qemu-kvm-0.13.0/sparc32plus-linux-user' make: *** [subdir-sparc32plus-linux-user] Error 2 エラー: /var/tmp/rpm-tmp.CRYtEl の不正な終了ステータス (%build) RPM ビルドエラー: /var/tmp/rpm-tmp.CRYtEl の不正な終了ステータス (%build)どうやらこれは、gccのバグを踏んでいるようで、新しいブランチでは解決されたらしい。ソース1,ソース2
検証環境のgcc
$ gcc --version gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4)これだけのためにgccを用意するのも大変なので、今回はPAEカーネルをインストール*4して、4GB全てのメモリが可能な状態にしてやればうまくいった。
IPv6 Live Migration
IPv6 Ready!という内容をちらほら目にしたので、案外楽に行くかと思ったのだけど、いざソースコードを眺めていると、IPv4前提の書き方が多い事…。多分IPv6 Readyといってるのは、VM内のゲストOSでv6が使えるという事なんでしょうね。
今回試したライブマイグレーションは、対象環境のバージョンではIPv6で扱えませんでした。
IPv6マイグレーション対応パッチ
幸いにも、qemu-develのMLにAllow ipv6 for migrationというパッチを投稿している人が居たので、これを参考にQEMUにパッチしてみたら扱えるようになった。また、virt-managerからQEMUを呼び出す部分にもIPv4依存な部分があるので、これも併せてパッチが必要である。
ちなみに、QEMUはマルチプラットフォームではあるが、検証は最初に示したLinux環境のみで行っているので注意。
qemu-ipv6mig.patch
libvirt-ipv6mig.patch
そして、このパッチをrpmを作るときに組み込むために、qemu.specとlibvirt.specをそれぞれ改変する必要がある。
それぞれのバージョンのsrpmをダウンロードしてきて、rpm -Uvh *.srpmすると、~/rpmbuild/SPECS/辺りにあるはず。
基本的にPatch~という行と、%patch~という行を足すだけで良い。
このままだとバージョン番号が紛らわしいので、Version:行やRelease:行を適当に変える事をオススメ。
▼qemu.spec
Patch41: 0041-vmmouse-adapt-to-mouse-handler-changes.patch
Patch42: 0042-vhost-net-patches-for-qemu-0.13.0-tarball.patch
Patch43: qemu-ipv6mig.patch
BuildRoot: %_tmppath/%name-%version-%release-root-%(%__id_u -n)
BuildRequires: SDL-devel zlib-devel which texi2html gnutls-devel cyrus-sasl-deve
---------
%patch41 -p1
%patch42 -p1
%patch43 -p1
%build
# By default we build everything, but allow x86 to build a minimal version
# with only similar arch target support
▼libvirt.spec
# Patch 7 CVE-2011-2511
Patch7: %name-%version-remote-protect-against-integer-overflow.patch
Patch8: libvirt-ipv6mig.patch
BuildRoot: %_tmppath/%name-%version-%release-root
URL: http://libvirt.org/
BuildRequires: python-devel
---------
%patch6 -p1
%patch7 -p1
%patch8 -p1
%build
%if ! %with_xen
%define _without_xen --without-xen
あ、念のためコンパイル方法も書いておくと…# cd ~/rpmbuild/SPECS # rpmbuild -bb qemu.spec # rpmbuild -bb libvirt.spec~/rpmbuild/RPMS/に完成したものができるはず。
後は、一旦yumで入れたものを消してrpmで突っ込むだけ。
virt-managerはlibvirtに依存しているので一旦消す必要がある点と、rpmインストール直後はlibvirtdが起動していないので起動し直すことに注意。
# yum remove virt-manager python-virtinst libvirt libvirt-client libvirt-python qemu-system-x86 qemu-img qemu-common # rpm -ivh libvirt-0.8.3-10.fc14.i686.rpm libvirt-client-0.8.3-10.fc14.i686.rpm libvirt-python-0.8.3-10.fc14.i686.rpm qemu-system-x86-0.13.0-1.fc14.i686.rpm qemu-img-0.13.0-1.fc14.i686.rpm qemu-common-0.13.0-1.fc14.i686.rpm # yum install virt-manager python-virtinst # service libvirtd startついでに、yumで更新されないようにexcludeリストを書いておく。
# vim /etc/yum.conf exclude=libvirt* qemu*
パッチの後処理
さて。これを適用することで、これまで0.0.0.0でしかlistenしていなかったものが、anyというホスト名でlistenすることになる。
本当は"::"でlistenさせたかったのだが、QEMUの-incomingオプションは、"-incoming tcp:0.0.0.0:49123"のような形式で書かれているため、::だとparseの部分で問題が生じるためである。
したがって、このパッチを当てた上で、/etc/hostsにanyを解決するように書く必要がある。
# echo ":: any" >> /etc/hostsまた、マイグレーションするホスト群のアドレスも直には書けないので、ホスト名でhostsから引けるようにしておく。
# echo "2001:db8::1234 vostro1" >> /etc/hosts # echo "2001:db8::1235 vostro2" >> /etc/hosts
マイグレーションテスト
これでテストしてみる。(vostro2というホストからvostro1というホストへのマイグレーション)libvirt経由で起動したQEMUのマイグレーションでは、TCP 49152~49215が使用されるようなので、ポートの開け忘れに注意。
もちろんlibvirt経由で試しても良いが、今回はqemuを直接叩く方法で検証してみる。
qcow(シンプロビジョニング形式?)で1Gのディスクを作成 # qemu-img create -f qcow /tmp/test.qcow 1G # cd qemu-kvm-0.13.0/x86_64-softmmu/ # sudo ./qemu-system-x86_64 -m 1024 -hda /tmp/test.qcow -boot c -monitor stdio -incoming tcp:any:49200 VNC server running on `127.0.0.1:5900` QEMU 0.13.0 monitor -type 'help' for more informationこの状態でlistenしている事を確認。
$ netstat -an | grep 49200 tcp 0 0 :::49200 :::* LISTENマイグレーションスタート。
OSはインストールしていないので、すっからかんの状態で一瞬で移動するはず。
(qemu) migrate -d tcp:vostro1:49200 (qemu) info migrate Migration status: completedlibvirtやvirt-managerからマイグレーションを行う場合も同様に、IPv6アドレスはそのまま書けないので、AAAAが引けるホスト名で書いてやるとマイグレーション可能となる。