kvm環境で、ホストOSの終了時にゲストOSをサスペンドさせるお話とupstartのお話(on Ubuntu Server 10.10)

皆さん仮想化してますか?
近頃ナウなヤングの間でばっかうけな仮想化ソリューションLinux kvmですけれど、これを実運用しようと思うと、色々手間暇かけてやる必要があります。
ということで、今回は「kvm環境でホストOSのシャットダウン時、ゲストOSを安全に守る方法」として、ホストOSの終了時にゲストOSをハイバネーションさせる(メモリ内容をファイルに書きだしてサスペンド)方法を紹介します。

環境

今回の環境はUbuntu server 10.10です。また、kvmの管理ツールとしてvirshが入っているものとします。
本当はLTSである10.04を使いたかったのですが、10.04のaptから入るkvm環境だと、何故かWindowsゲストのレジュームに失敗してしまうため、10.10を対象としました。

概要

実はlibvirt-suspendonrebootとかいうスクリプトが用意されていまして、これを/etc/init.dに置いてやると、さもサスペンドが実現できる……かのような錯覚に陥ります。しかし、initにupstartを採用しているUbuntuでは非同期にサービスの起動・終了が行われます。

つまり、サスペンドの処理が完了する前にkvm関連のサービス(libvirtdとか)がぶち殺されてしまうことがあり得るということです。

解決方法は、upstartの設定を書いてやることです。ハイバネーションを行うjobを定義し、ハイバネーションのjobが完了した後にlibvirtdが終了するように依存関係を記述します。

実際の設定

では、実際に設定をみてみます。

ハイバネーション処理を行うjobの定義

まず、/etc/init/kvm-guest.confを作成し、以下の内容を記述します。

description "kvm-guest"
author "you"

start on started libvirt-bin
stop on starting rc RUNLEVEL=[06] # ランレベルが0または6の時に実行

pre-start script
    for domain in ${suspenddir}/*dump; do
        if [ -f $domain ]; then
            domain=$(basename $domain .dump)
            echo "resuming $domain ..."
            virsh restore ${suspenddir}/${domain}.dump && rm ${suspenddir}/${domain}.dump
        fi
    done
end script

post-stop script
        suspenddir=/var/lib/libvirt/autosuspend
    for domain in /etc/libvirt/qemu/*xml; do
        domain=$(basename $domain .xml)
        state=$(virsh domstate $domain)
        if [ "$state" = "running" ]; then
            virsh save ${domain} ${suspenddir}/${domain}.dump
        fi
    done
end script

処理内容はlibvirt-suspendonrebootの処理内容を丸コピペしたものです。
"pre-start script"から"end script"までがレジュームの処理となります。この部分は"start on started libvirt-bin"の記述により、システム起動時にlibvirt-binジョブ完了の後(つまりlibvirtd起動後)に呼び出され、レジューム処理(virsh restoreの実行)を行います。
"post-stop script"から"end script"までがハイバネーション処理となり、システム終了時に呼び出されて実行されます(virsh save)。
なお、ハイバネーション時に作成されるメモリのダンプファイルは"/var/lib/libvirt/autosuspend"ディレクトリ以下に作成されるので、このディレクトリを作成しておいてください。

sudo mkdir /var/lib/libvirt/autosuspend
libvirtd、及びqemu-kvmのinitファイルの編集:依存関係の記述

次に、libvirtdの定義ファイルを編集し、サスペンド処理のジョブが完了した後にlibvirtが終了されるように依存関係を記述します。
/etc/init/libvirt.conf :

description "libvirt daemon"
author "Dustin Kirkland <kirkland@canonical.com>"

start on runlevel [2345]
# stop on runlevel [!2345] # デフォルトのものをコメントアウト
stop on stopped kvm-guest # kvm-guestジョブが完了後にこのジョブが実行

expect daemon
respawn
……(省略)……

次に、qemu-kvmの定義ファイルを編集し、libvirtdが終了した後にqemu-kvmのジョブが実行されるように、依存関係を記述します*1

# qemu-kvm

description "KVM"
author "Dustin Kirkland <kirkland@canonical.com>"

start on runlevel [2345]
stop on stopped libvirt-bin # 追記:libvirt-binジョブ完了後にこのジョブが実行

pre-start script
…(省略)
追加、変更の反映

以上でkvm-guest.confの作成、libvirt-bin.conf、qmeu-kvm.confの変更を行いました。これらの反映を行います。

sudo ln -s /lib/init/upstart-job /etc/init.d/kvm-guest
# 良くわかんないけど、upstartのジョブは/etc/init.d/以下にjob名でシンボリックリンクを張っているんだよなあ……
sudo reboot
# "sudo initctl reload JOBNAME"でもいいはずだけど、ちゃんと反映されない時があったので念のため

virsh saveを行えるようにする

さて、上の設定を行ったところで、実はこの段階ではvirsh saveが実行できず、ハイバネーションが実行できない場合があります*2

virsh # save MACHINENAME /var/lib/libvirt/autosuspend/MACHINENAME.dump
# このまま固まって応答がない、/var/lib/libvirt/autosuspend/MACHINENAME.dumpのファイルサイズが一向に大きくならない

これは、セキュリティツールであるapparmor*3による制限です。この場合はapparmorの設定を変更して下さい。設定ファイルは/etc/apparmor.d以下のディレクトリにあります。

なお、10.04の環境においては、"/etc/apparmor.d/abstractions/libvirt-qemu"に、以下の設定を書き加えることで動作しました。

owner /var/lib/libvirt/autosuspend/** rw,

設定を変更した後には、apparmorの再起動を行って下さい。

sudo service apparmor restart

最終確認

さて、最後にserviceコマンドを用いてsuspend/resumeがきちんとできるか確認を行います。

# 仮想マシンが動いている状態で
sudo service kvm-gueset stop
# サスペンドに暫く時間が掛かる
ls /var/lib/libvirt/autosuspend/MACHINENAME.dump # dumpファイルができていたら、サスペンド成功。
sudo service libvirt-bin start
sudo service kvm-guest start
virsh list
 Id Name                 State
----------------------------------
 1 MACHINENAME           running

後は、再起動テストなどをして確認を行って下さい。

最後に

割とsaveコマンドの挙動が怪しかったり、virsh saveの実行完了までに時間が掛かったりするので、acpiでshutdownを発行するのも良いかと思います*4。その場合は、マシンがきちんとshut off状態になるまで待ってあげるなどの工夫をしてあげて下さい。
「なんでわざわざゲストOSのハイバネーション用ジョブを作ってるの?libvirt-bin.confにpre-stopで記述すればいいんじゃ?」と思った人、鋭いです。私も初めそのつもりでやっていたのですが、どうやらpre-stopはシステムシャットダウン時には呼ばれず、initctl stopを用いた場合のみにしか呼ばれないようです*5。10.04が出たばかりの頃はそんな挙動じゃなかったんですけれど……。
今回のエントリについてですが、検証するのに手間が掛かることから、割と記憶に頼っている部分が多いです。また、Upstartの挙動とかも割と自信ないです。抜けや間違いがありましたら、是非コメント欄やメール、twitterなどで教えて頂けると有難いです。

*1:おそらくこれはやらなくても大丈夫だとは思いますが、念のため

*2:10.04にて再現。10.10のクリーンインストールの状態では未検証のため不明

*3:プログラムごとにアクセスできるライブラリ、およびファイルを制限するセキュリティツール

*4:FreeBSDはacpiでのシャットダウンがうまくいかないからハイバネーションにこだわったんですけどNE……

*5:http://www.usupi.org/sysad/189.htmlより。私の環境でも確かにそうであることを確認