ゲストのインストール (qemu-kvm直接編)

GUI ツール virt-manager (仮想マシンマネージャ) を使えば、いとも簡単にゲストがインストールできてしまう。しかし、最初からそれに頼り切っていては QEMU/KVM の仕組みが理解できないので面白くないし、コマンドラインに比べると virt-manager には幾つか制約があり応用が利かない。そこで、当節では敢えてコマンドラインにこだわる。方法としては、qemu-kvm を直接コールするやり方と、コマンドライン版のゲストインストールヘルパー virt-install を使うやり方とがある。現実的には virt-install がお勧めだが、KVM をよりよく知るために、先にqemu-kvm を直接コールするやり方について述べる。

ゲストネットワークインターフェイス補助スクリプト

MACアドレス生成スクリプト (qemumacgen.py)

Xen00:16:3e が専用 OUI として IEEE でリザーブされているように、QEMU では 54:52:00 を使うように決められているようだ (ただし、2010/1 現在、oui.txt にはまだ登録されていない)。そこで、ゲストのインストール時にその範囲内の MACアドレスをランダムに生成するため、python スクリプトを用意しておく。Xen で使った xenmacgen.py をほんのちょっといじっただけのものだ。この qemumacgen.pyPATH の通った場所、例えば /usr/local/bin/ にコピーして、実行ビットを立てておく。

TAPつなぎ込みスクリプト (qemu-ifup/qemu-ifdown)

qemu(-kvm) は、ゲストプラットフォームの起動時、TAP インターフェイスを使うように指定すると、親ブリッジへの TAP 組み込みの際にスクリプトをコールしてくる。TAP をどのホストブリッジにどうつなぎ込むかをそのスクリプトに委ねてくるわけだ。qemu がデフォルトで呼ぼうとするスクリプトは /etc/qemu-ifup。スクリプトのパスは、`-net tap' のオプション引数 script= によって指定すれば変えることもできる。筆者が現在使っている /etc/qemu-ifup (root:root 755) を示そう。

#!/bin/sh
 
# See which name I was called...
ME=$(basename $0)

ifext=${ME#qemu-ifup-}
 
case $ifext in
  qemu-ifup)
    ifname=br0;;
  *)
    ifname=$ifext;;
esac
 
echo "Executing /etc/$ME"
echo "Bringing up $1 for bridged mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to ${ifname}..."
sudo /usr/sbin/brctl addif $ifname $1
sleep 2

qemu はこのスクリプトを、その時に生成する TAP の名前 (`-net tap' のオプション引数 ifname= で指定するか自動生成) を引数にしてコールしてくる。後半部で $1 で受け取っているのがそれだ。ただし、ひとつ困ったことに、script= オプションは `script="/etc/qemu-ifup virbr0"' のように引数を指定することができないため、親ブリッジをゲスト毎に換えるのが難しい。そこで上記のスクリプトでは、シンボリックリンクを利用することにした。qemu-ifup ファイルはそのままさわらずに、例えば qemu-ifup へのシンボリックリンク qemu-ifup-virbr0 を作っておけば、コールされるスクリプト名の末尾の -virbr0 から親ブリッジ名 "virbr0" が解釈されるようになっている。

もうひとつ、今度は QEMU ゲストの終了時に呼ばれる /etc/qemu-ifdown も作っておかなければならない。こちらも引数は TAP 名だ。起動時のスクリプトとは異なり、こちらのパスは固定になっていてオプション引数で換えることはできない。そこで、こちらでは、/sys 疑似ファイルシステムから当該 TAP の属するブリッジを判定できるよう細工をした。細工の部分は今後の拡張に備えてインクルードファイル /etc/qemu-if-common に独立させたので、忘れずにダウンロードしてホストの /etc/ 直下に配備 (root:root 644) していただきたい。では /etc/qemu-ifdown (root:root 755) の中身を示そう。

#!/bin/sh
 
ME=$(basename $0)
 
[ -f /etc/qemu-if-common ] && . /etc/qemu-if-common
 
# Find parent bridge name from the given TAP name.
set_ifname $1
 
echo "Executing /etc/$ME"
if [ -z "$ifname" ]; then
    echo "Can not detect target bridge name"
    exit 1
fi
echo "Removing $1 from ${ifname}..."
sudo /usr/sbin/brctl delif $ifname $1
echo "Shutting down $1 ..."
sudo /sbin/ifconfig $1 down
sleep 2

sudoers の設定

QEMU/KVM の記事を検索してみると、なぜだか分からないが、ゲストOS の起動を root 以外のユーザ権限で実行したがっている記事が多い。前述の qemu-ifup/down を見て気付かれた方もおられるだろうが、当ページでもそれに倣って sudo を噛ませるようにしている。常に root でやればいいじゃん、と思う方はスクリプト内にある "sudo" を全部とっぱらっていただけばよろしい。一方、ユーザ権限で実行できるようにしたい方は、/etc/sudoers ファイルを編集しておく必要がある。例として、ホストマシン hogeserver 上の hoge という UNIXユーザでゲストを起動できるようにする場合の処方を示す。root 権限で、visudo とコマンドして編集に入る。以下が sudoers ファイルの要点部分だ。

Host_Alias MYBOX = hogeserver
 
User_Alias ADMINS = hoge
 
Cmnd_Alias NETWORKING = /sbin/route, /sbin/ifconfig, /bin/ping, \
 /sbin/dhclient, /usr/bin/net, /sbin/iptables, /usr/bin/rfcomm, \
 /usr/bin/wvdial, /sbin/iwconfig, /sbin/mii-tool, /usr/sbin/brctl
 
Cmnd_Alias V12N = /usr/libexec/qemu-kvm
 
#Defaults requiretty
 
root ALL=(ALL) ALL
 
ADMINS MYBOX=(ALL) NOPASSWD: NETWORKING, V12N

qemu-kvm 実行ファイルの在処は、RHEL/CentOS 5.4 では /usr/libexec/ に変わった (Fedora Core 9 では /usr/bin/ だった)。中盤にある `Default requiretty' の行は、このようにコメントアウトしておかないとエラーが出る。sudo についてもっとよく理解したければ、別ページにまとめたのでそちらを参照していただくといいだろう。

ディスクソースの作成

あらかじめ、ゲストのローカルディスクとなる "物" を作成しておく。"物" の提供方法には主として、通常の実ディスクパーティション、LVM の LV、ディスクイメージファイルの 3種類がある。ここでは、qcow イメージファイルとして用意する方法と、LVMLV として用意する方法 (覚え書きのみ) について記載する。Linux ゲストなら、少なくともメインハードディスク用とスワップ用のふたつを準備しておこう。

qcowイメージファイルの場合

これには qemu-img というコマンド (RedHat 系では qemu-img パッケージで提供される) を使用する。例えば、サイズ 30GB のイメージファイル /var/lib/kvm/images/rhel5u.img を作成するコマンドは下記のようになる。/var/lib/kvm/images というディレクトリは今のところの筆者の好みであって、デフォルトでは存在しないのであらかじめしておかなければならない。

root# qemu-img create -f qcow2 /var/lib/kvm/images/rhel5u.img 30G

ここで、-f オプションで指定している qcow2 は仮想ディスクイメージのフォーマット。qcow2 は即ち "QEMU Copy On Write Ver.2" で、一種のスパースファイルだが、従来の qcow よりは高速で信頼性も高く、その気になれば暗号化 (-e オプション) もできるそうである。qemu-img のデフォルトフォーマットである raw は、筆者の Xen のページでも触れている従来のフォーマットにあたる。

qemu-img で作成されるのはスパースファイルなので、作成直後にはホストファイルシステム上のほんの小さな領域しか占めておらず、ゲストによって実際に何かが書き込まれるに従って大きくなる。フォーマットや想定上のサイズを確かめたい時には、下記のコマンドで調べることができる。

root# qemu-img info /var/lib/kvm/images/rhel5u.img 

LVM LV (覚え書き)

作成や削除が簡単で、仮想OS の管理に向いている。作成は、実際のところ、今日日の RedHat系なら "論理ボリューム管理 (system-config-lvm)" ツールを使うのが手っ取り早い。以下はコマンドラインのための覚え書き。

論理パーティションの作成

あらかじめ、PV に組み込むためのパーティションまたはディスク丸ごとが必要。LVM のパーティションType は 8e

PVの作成

PV 作成に先立って、VG情報テーブル格納ファイルなどを作らせるため `vgscan' を実行したほうがよい (情報古いかも)。

PV作成:

root# pvcreate [-v] PV_Dev [PV_Dev...]
VGの作成

system-config-lvm で作成される VG の PhysicalExtentサイズは 32mb。それと同じにするなら -s オプションで指定。

root# vgcreate -s PE_Size[kKmMgGtT] VG_Name PV_Dev [PV_Dev...]
LVの作成
root# lvcreate -L LV_Size[kKmMgGtT] -n LV_Name VG_Name
ブロックデバイスファイルの場所

/dev/VG_Name/LV_Name

ただし、上記実体を指すシンボリックリンク /dev/mapper/VG_Name-LV_Name も作られるので、こちらを使っても正解。

qemu-kvm

qemu-kvm バイナリは、KVM を利用するよう特別にコンパイルされた qemu だ。KVM を使用しない同等品は qemu-system-x86_64 で、qemu パッケージによってインストールされる。コマンドオプションは共通している。

qemu/qemu-kvm コマンドの主なオプション
オプション 説明
-name ゲストの定義名。SDL や VNC のターミナルの名称にも使われる。
-smp N 仮想プラットフォームを、N 個の CPU を持つ SMP システムとしてエミュレートする。
-m X ゲストに与えるメモリ量。単位はMB。
-pidfile file ゲスト qemu-kvm プロセスの PID を file に出力する。ゲストを強制終了する必要に迫られた時に、いちいち ps で調べることなく、例えば `kill -TERM $(cat /var/run/centos5u.pid)' などとできるので便利。
-hda file ゲストの第0 ハードディスクにするデバイスファイルかイメージファイル。他に、第1 も指定したければ -hdb で、第2 なら -hdc、第3 なら -hdd で指定できる。virtio を利用する場合には代わりに後述の -drive を使用しなくてはならない。
-cdrom file ゲストの CD デバイスに割り当てるホスト上のデバイスファイルかイメージファイル。例えば Fedora Core 9 で実 CD デバイスを指定する場合、/dev/sr0 かそのシンボリックリンクになっている /dev/cdrom を指定すればよい。
-fda file ゲストのフロッピーデバイスに割り当てるホスト上のデバイスファイルかイメージファイル。
-drive options 上記の `-hda' や '-cdrom' などの代わりに、より詳細なパラメータでディスクデバイスを定義したい場合に使用する。例:
-drive file=/var/lib/kvm/images/centos5u.img,if=virtio,index=0,boot=on
file=file ドライブとしてゲストに提供するデバイスファイルやディスクイメージファイル。
if=iface このドライブの接続インターフェイス。ide, scsi, sd, mtd, floppy, pflash, virtio のいずれか。
index=N if=ideif=virtio などの場合に、ドライブ番号を指定する時に使用する。序数は if 毎にゼロオリジン。例えば if=ideindex=2 とすればセカンダリIDEのマスターとしてエミュレートされる。
bus=X,unit=Y if=scsi などの場合に、バス序数とユニットID を指定する。例えば、`bus=0,unit=6' はバス 0 の ID 6 に接続されたディスクをエミュレートする
media=type ディスクのメディアタイプ。disk または cdrom。デフォルトは disk のよう
cache=mode ハイパーバイザ(つまりホストOS側)でのディスクキャッシュ制御。writeback はR/Wとも有効、writethrough はRのみ、none はR/Wとも無効。none が一番速いはずだが、検証の結果、none だとゲストインストールにさえ失敗する。デフォルトの writeback (つまり記述しない) が最も安定か
boot=on ブート可能ディスクとするか。デフォルトは off なので、-drive オプションで規定したドライブを起動ディスクにする場合は必ず指定しなければならない
-boot X ブートデバイスの指定。ハードディスクなら c、CD なら d、フロッピーなら a、ネットワークブートなら n
-net ... ゲストのネットワークインターフェイスの定義。ちょっと複雑なので実例で見ていただこう。
-soundhw X ゲストにサウンドカードを与える場合に指定。X はモデル名で、指定できるモデルのリストは `qemu-kvm -soundhw ?' で見られる。
-usb ゲストに UHCI USB デバイスを与える。
-serial dev ゲストのシリアルポートをホストのデバイスファイル dev へリダイレクトする。/dev/ を前置きする必要はない。通常、仮想シリアルコンソールデバイス (pseudo tty) pts を指定する。
-parallel dev ゲストのパラレルポートをホストのデバイスファイル dev へリダイレクトする。ゲストにパラレルポートを与える必要はあまりないので、与えたくなければ none を指定。
-monitor dev ゲストのモニタをホストのデバイスファイル dev へリダイレクトする。qemu の man ページによると、シリアルと同じデバイスにせよというように読み取れる。
-vnc host:port QEMU はデフォルトではゲストのVGA 出力を SDL で表示するが、このオプションによって、出力先を VNC に変えることができる。詳しくは後述の例を参照。
-k code ゲストに対してエミュレートするキーボードのレイアウト。ホストに日本語キーボードをつないでいるのなら ja を指定。
-localtime ゲストの仮想BIOSクロックを UTC でなくホストのローカルタイムで与える。ゲストの時刻が 9時間ずれる場合はこれが怪しい。qemu側を -localtime にしたなら、Windowsゲスト上ではタイムゾーンをホストのものと合わせ、Linuxゲストなら時刻設定の "システムクロックでUTCを使用" のチェックを外すべき。
-no-kvm-pit-reinjection 「RHELゲストはデフォルトでタイムソースとして PIT (Programmable Interval Timer) を使用しているが、ティック (tick) 欠落時には TSC (Time Stamp Counter) からも読み取ろうとする。そのため、kvm が調子外れのタイミングで PIT IRQ 割り込みを行なう結果となり、ゲストのクロックが同期を失う」(Bugzilla bug-517278 より訳) 現象の対策として、libvirt-0.6.3-20.1.el5_4 (RHEL 5.4 用アップデートパッケージなど) からは、libvirt 経由で kvm ゲストを起動すると、Linuxゲスト/Windowsゲストに限らずこのオプションが加えられるようになったようだ。ゲストクロックが狂う場合の調整法については RHEL 5.4 Virtualization Guide でも解説されている。ネット検索するなら、キーワードは "no-kvm-pit-reinject" のほうがいろいろヒットする。
-daemonize コマンドしたコンソールから QEMU プロセスを切り離してバックグラウンド化する。バッチスクリプトからゲストを起動する時や、ターミナルをすぐに他事に使用したい時に使うとよい。

ゲストインストールの実例

ここでは、以下の条件をベースにゲストOS を qemu-kvm コマンドでインストールすることにする。

インストール用QEMU起動スクリプト

筆者は、qemu-kvm のコマンドをいちいち全部打つのが面倒臭いので、簡素なスクリプト qemu-install を作ってインストール時の起動に使っている。下記のようなものだ。スクリプトの引数はゲストの名称。これを、どこでも構わないのだが、例として /usr/local/bin/ に置いておくことにしよう (root:root 755)。実際には、いろいろなパターンに合わせてこのスクリプトを複製・編集することになるだろう。その際、序盤の決まり切ったルーティンやファンクション定義が各々のファイルにあるのはカッコ悪いので、その部分を別体インクルードファイルにした。それ qemu-install-common.sh も同一ディレクトリにひとつだけ置いておいていただきたい。

サンプルパターン 1

仮想ハードディスクは、メイン用及びスワップ用にそれぞれイメージファイルを IDEディスクとして提供。virtio を使わないパターン。

#!/bin/bash
 
dir=$(dirname $0)
[ -f ${dir}/qemu-install-common.sh ] || exit 1
. ${dir}/qemu-install-common.sh
 
mac_gen $1 0
mac_gen $1 1
 
exec sudo /usr/libexec/qemu-kvm \
 -name $1 \
 -smp 2 \
 -m 768 \
 -no-kvm-pit-reinjection \
 -pidfile /var/run/$1.pid \
 -hda /var/lib/kvm/images/$1.img \
 -hdb /var/lib/kvm/images/${1}-swap.img \
 -cdrom /dev/cdrom \
 -boot d \
 -net nic,macaddr=$newmac0,model=e1000,vlan=0 \
   -net tap,vlan=0,ifname=tap0,script=/etc/qemu-ifup \
 -net nic,macaddr=$newmac1,model=e1000,vlan=1 \
   -net tap,vlan=1,ifname=tap1,script=/etc/qemu-ifup-virbr0 \
 -soundhw ac97 \
 -usb \
 -serial pty \
 -parallel none \
 -localtime \
 -monitor pty \
 -vnc 127.0.0.1:0 \
 -k ja
サンプルパターン 2

仮想ハードディスクは、メイン用及びスワップ用にそれぞれ LVM LVvirtio ディスクとして提供。ネットワークインターフェイスにも virtio を使用。インストールソースにはホスト上の ISO イメージを使用する。

なお、RedHat 系のゲストでは、RHEL/CentOS の 5.3 以降及び RHEL 4.8 以降や Fedora Core 9 以降なら virtio ドライバを標準で備えているので、このようなやり方でインストール時からいきなり virtio 化できる。RHEL 4.7 以前や RHEL 3.x では、インストール後に kmod-virtio というパッケージを追加する必要があるため、いきなりの virtio 化は難しそうだ。Windowsゲストの場合も、インストール時にはディスクにもネットワークにも virtio は使わないほうがいい (インストール後の virtioついては後で述べる)。

#!/bin/bash
 
dir=$(dirname $0)
[ -f ${dir}/qemu-install-common.sh ] || exit 1
. ${dir}/qemu-install-common.sh
 
mac_gen $1 0
mac_gen $1 1
 
exec sudo /usr/libexec/qemu-kvm \
 -name $1 \
 -smp 2 \
 -m 768 \
 -no-kvm-pit-reinjection \
 -pidfile /var/run/$1.pid \
 -drive file=/dev/mapper/VolGroup00-LogVol00,if=virtio,index=0,boot=on \
 -drive file=/dev/mapper/VolGroup00-LogVol01,if=virtio,index=1 \
 -cdrom /var/tmp/CentOS-5.4-x86_64-bin-DVD.iso \
 -boot d \
 -net nic,macaddr=$newmac0,vlan=0,model=virtio \
   -net tap,vlan=0,ifname=tap0,script=/etc/qemu-ifup \
 -net nic,macaddr=$newmac1,vlan=1,model=virtio \
   -net tap,vlan=1,ifname=tap1,script=/etc/qemu-ifup-virbr0 \
 -soundhw ac97 \
 -usb \
 -serial pty \
 -parallel none \
 -localtime \
 -monitor pty \
 -vnc 127.0.0.1:0 \
 -k ja

インストールの開始

では、インストールを開始しよう。スクリプトでインストールソースに実デバイスを指定した場合はホストのドライブにインストールDVD を挿入しておく。そうしたら、上記のスクリプトをコールする。ここでは root で行なうことにするが、sudoers に設定した hoge でも実行できる。引数はゲストの定義名だ。

root# /usr/local/bin/qemu-install rhel5u 

すかさず、ホスト上の VNC Viewer でゲストへのセッションを開く。接続先は、qemu-kvm-vnc オプションで指定した 127.0.0.1:0。ゲストのインストール画面が表示されるはずだ。私の qemu-install セットでは、ランダムに生成されたゲストNIC の MACアドレスはターミナルに出力されるとともに、覚えのために /var/tmp/qemu-install-GUESTNAME-macX.txt にも残すようになっている。

あとは通常のインストールと同じ...なのだが、Fedora Core 9 のホスト上でやってみると、インストール完了後のリブート時、QEMU の仮想BIOS のところで止まってしまう現象が見られた。どうやらゲストのリブートがうまくいかないようだ。Fedora Core 10 や RHEL/CentOS 5.4 ではそんな現象はなかった。そうなってしまった場合には、ターミナルでゲストの仮想シリアルに接続し、QEMU エミュレーションを停止させる。まず、新たなターミナルコンソールをひとつ起動し、下記のように minicom で接続する。接続先デバイスは、qemu-install の時に表示された、

char device redirected to /dev/pts/1
char device redirected to /dev/pts/2 

の、上の方のデバイスだ。よって、上記の例の場合には、

root# minicom -p /dev/pts/1

※ ゲストの VGA を SDL で表示している時 (qemu-kvm 起動時に `-vnc' オプションを付けなかった時) は、Ctrl + Alt + 2 で QEMU コンソールに移れる。戻るには Ctrl + Alt + 1

接続すると、

(qemu)

というプロンプトになるので、quit と打ち込む。新たなターミナルを起こせといったのは、ここで /dev/pts/1 デバイスファイルは消滅するためコンソールにその旨のエラーがわーっと出てターミナルごと閉じるしかなくなるからだ。この現象を避けたければ、「ゲストの終了」で後述する kill.mini を使ってもらうといい。

※ minicom で、ゲストプラットフォームを生かしたままエミュレータコンソールだけを閉じたい場合には、Ctrl + a, x (モデムをリセットして終了) または Ctrl + a, q (モデムをリセットせずに終了) をタイプする。

複数枚に渡る CD や ISOイメージからインストールする場合の操作

インストール中にインストーラーから 「次のCDを挿入せよ」 と指示が出たら、上記で示したように minicom でゲストプラットフォームの仮想シリアルに接続し、次の QEMU コマンドを叩く。どちらの場合も共通の前準備として、まず仮想ブロックデバイスのデバイス名を調べる。

(qemu) info block
ide0-hd0: type=hd removable=0 file=/var/lib/kvm/images/rhel5mu.img ro=0 drv=qco2
ide1-cd0: type=cdrom removable=1 locked=0 [not inserted]
floppy0: type=floppy removable=1 locked=0 [not inserted]
sd0: type=floppy removable=1 locked=0 [not inserted]

物理CDの場合:

ホスト上で CD を入れ替えてから、仮想シリアルターミナル上で次のようにコマンド。

(qemu) change ide1-cd0 /dev/cdrom 

ISOイメージの場合:

(qemu) eject ide1-cd0
(qemu) change ide1-cd0 /PATH/TO/CD2.iso

通常起動用スクリプトへの移行

今後も virsh に頼らずハードボイルドに直接起動で行くなら、インストール時のスクリプトをコピーして通常起動用に書き換える。より現実的に libvirt に定義を取り込むのなら、ここはすっ飛ばして、代わりに「libvirtへのゲスト定義の取り込み」をやってもらうといいだろう。

上記のサンプル2 でインストールした rhel5u ゲストの場合、以下のような qemu-start-rhel5u ファイルを作成する。置き場所はどこでも構わない (root:root 755)。今度はインクルードファイルは要らない。

#!/bin/bash
name=rhel5u
memsize=768
disk0=/dev/mapper/VolGroup00-LogVol00
disk1=/dev/mapper/VolGroup00-LogVol01
tap0=tap2
newmac0="54:52:00:64:a6:35"
tap1=tap3
newmac1="54:52:00:64:ef:32"
vncport="127.0.0.1:1"
 
exec sudo /usr/libexec/qemu-kvm \
  -daemonize \
  -name $name \
  -smp 2 \
  -m $memsize \
  -no-kvm-pit-reinjection \
  -pidfile /var/run/${name}.pid \
  -drive file=${disk0},if=virtio,index=0,boot=on \
  -drive file=${disk1},if=virtio,index=1 \
  -drive file=,if=ide,index=2,media=cdrom \
  -boot c \
  -net nic,macaddr=$newmac0,vlan=0,model=virtio \
    -net tap,vlan=0,ifname=${tap0},script=/etc/qemu-ifup \
  -net nic,macaddr=$newmac1,vlan=1,model=virtio \
    -net tap,vlan=1,ifname=${tap1},script=/etc/qemu-ifup-virbr0 \
  -soundhw ac97 \
  -usb \
  -serial pty \
  -parallel none \
  -localtime \
  -monitor pty \
  -vnc $vncport \
  -k ja

ポイント:

これを例えば /usr/local/bin/ に置いたとしたら、実行ビットを立てた後、今度は引数なしで、

/usr/local/bin/qemu-start-rhel5u

とコールすればゲストが起動する。インストール用のスクリプトとは異なり -daemonize オプションが指定してあるため、コマンド発行元ターミナルはすぐにシェルプロンプトに戻る。上記の例なら、ホスト上の VNC Viewer127.0.0.1:1 に接続すればゲストの起動画面が現れるはずだ。

ゲスト上の VNCサーバの整備

QEMU 内蔵の VNCサーバよりも使い勝手がよくてホストOS 以外からも接続できる本物の VNCサーバを設定しておくと便利だ。設定方法は VNCのページで詳しく述べている。

ゲストの終了

単刀直入なのは、ゲスト自体の上でシャットダウンを掛けることだ。また、virt-manager で管理していれば GUI や virsh のコマンドで簡単に礼儀正しく終了させることができる。しかし、それらでは当たり前すぎるので、ここではそれ以外の方法について述べよう。

QEMUシリアルコンソールでシャットダウン

minicom で当該ゲストの仮想シリアルに接続し、プロンプトで、

(qemu) system_powerdown

と打ち込む。RHEL 4/CentOS 4 ではこれだけで何の問題もなく礼儀正しくシャットダウンする。

ただし、RHEL 5/CentOS 5 では、ゲスト側にちょっとした調整が要る。これらのディストリビューションのデフォルトでは、OS が ACPI のシャットダウン信号 (電源ボタンを押した時と同じ) を受け取った際、ユーザに確認を求めるようになっている。そのため、X Window にログオンしているユーザがいるといつまで経ってもシャットダウンに突入しないのだ。挙動を変えるには、ゲストの X Window 上で システム -> 設定 -> 他の個人設定 -> 電源管理 を開き、全般 タブの 電源ボタンを押した時 を、デフォルト値である 確認を求める から 停止 に変更しておく。また、これは各ユーザ毎の設定であるため、X Window を開く可能性のあるすべてのユーザで施しておかなければならない。なお、誰も X Window セッションを開いていない場合や、そもそも VNC接続さえしていない時には、この問題は発生しない。

ちょっと試しに、このシャットダウン処理を minicom のスクリプトに書いて自動化してみた。shutdown.mini。ホスト上のどこかにコピーしておき、

root# minicom -p /dev/pts/1 -S /PATH/TO/shutdown.mini

という風に使うと、見事にゲストをシャットダウンできた。

そしてもうひとつ、仮想プラットフォームが BIOS 部分でストップしてしまったり再起動の繰り返しに入ったりして行儀正しく終了するのが不可能になった時用のスクリプト kill.mini も作成した。これを前記のように使うと、/dev/pts/* の消失による大量のエラーメッセージをやり過ごすことができる。

ただし shutdown.minikill.mini もあまり作り込んでいないので、お好きな方は man runscript ででも調べて作り込んでいただきたい。

SSH経由でシャットダウン

ゲスト上で直接シャットダウンを掛けるのと同じくらい安直な方法。ただし、ゲストへの仮想ネットワークがきちんと機能していない時には使えないのが問題といえば問題。

libvirtへのゲスト定義取り込み

と、ここまで理解しておけば、virt-manager でゲストをインストールした場合でも、virt-manager に打ち込んでゆくパラメータの意味や、libvirt が裏で何をやっているのかだいたい理解できるはずだ。

さんざん泥臭いやり方ばかり説明してきたわけだが、やはり、インストール後にゲストを操作・管理するには virsh を利用するほうが現実的かつ効率的。そのためには、ゲスト定義を libvirt に取り込んでやる必要がある。Xen のページでも説明したように、libvirt にゲスト定義を取り込むには XML フォーマットで書いたゲスト定義ファイルが必要となる。「通常起動用スクリプトへの移行」 で述べたような QEMU コマンドを libvirt XML へチマチマと書き直してもいいのだが、virt-install コマンドの import 機能を使うとかなり手間が省ける。`virt-install --import' を介せば、libvirt XML を書くよりはずっと少ない労力で libvirt に定義を作ることができるのだ。ただし、残念ながら現在の virt-install はかなり限られた種類の QEMU オプションしか扱えないので、取り込み後に libvirt 上で定義を再調整する必要はある。

以下に、前述の サンプルパターン2 でインストールしたゲスト (RHEL5) を libvirt に取り込む例を挙げる。

第1段階: virt-install によるゲストリソースのインポート

既に存在するゲストのリソース一式を qemu-kvm 直接起動の時とほぼ同じ条件で使うようなゲスト定義を、virt-installimport オプションを使って libvirt に登録する。ある程度使い回しがきくようシェルスクリプトにした (qemu-virt-import)。virt-install については次章で詳しく述べる。

#!/bin/bash
name=rhel5u
memsize=768
disk0=/dev/mapper/VolGroup00-LogVol00
disk1=/dev/mapper/VolGroup00-LogVol01
newmac0="54:52:00:64:a6:35"
newmac1="54:52:00:64:ef:32"
vncport=5901
 
exec virt-install \
  --import --force \
  --name=$name \
  --ram=$memsize \
  --vcpus=2 \
  --disk path=${disk0},bus=virtio \
  --disk path=${disk1},bus=virtio \
  --disk path=/dev/cdrom,device=cdrom \
  --os-variant=rhel5 \
  --accelerate \
  --network=bridge:br0 --mac=$newmac0 \
  --network=network:virnet0 --mac=$newmac1 \
  --vnc \
  --vncport=$vncport \
  --keymap=ja

これをコピーして対象ゲストに合わせて編集したら、実行ビットを立てておく。そして、もし当の資源を使うゲストが動作中なら必ずシャットダウンしてから、root 権限で上記スクリプトを実行する。virt-installimport オプションは、"ゲストOSのインストールの行程は正常に完了した" ことにして libvirt への定義登録だけを行ない、その次の "再起動" 行程からを行なうという動作だ。よって、上記スクリプトを実行した結果、定義が libvirt に問題なく作成されれば、いきなりゲストが起動され、Virt Viewer (VNCクライアント) が起動してゲスト画面が表示される。この段階では、libvirt 上にとりあえずの定義のカタチが形成されることが目的なので、立ち上がることだけ分かったら、次の「修正作業」に備えてゲストをシャットダウンしておく。なお、登録だけ行い、起動させたくない場合は、上記オプションに加えて `--noreboot' も付加しておくといい。

なお、上記で `--sound' (「サウンドハードウェアあり」) を書いてないのは、virt-install ではハードウェアモデルまでは指定できないから。どんなモデルがエミュレートされるかが virt-install の仕様まかせではっきりしないなら、この段階では与えないほうがいいと思うからだ。

第2段階: libvirt 上での定義修正

先ほど登録したゲストの定義を、virsh コマンドを使って修正・補完する。

root# virsh edit rhel5u

とコマンドすると、テンポラリな XML ファイルにダンプされた定義情報が vi によって開かれるので、下記のサンプルの要領で修正する (rhel5u_imported.xml)。なお、一旦 XML ファイルとしてきちんと手元に置いてから再取り込みする方法もあるが、そのあたりの詳しい話は、以前書いた Xen の記事の「libvirt の XML 形式」や「コマンドラインツール」の項を参照していただくとしよう。libvirt XML の記述法は libvirt のオフィシャルページ で調べられる。vi の使い方に困った人はこちら

<domain type='kvm'>
  <name>rhel5u</name>
  <uuid>63813ee3-9708-313c-7cd6-3eb3cca9621e</uuid>
  <memory>786432</memory>
  <currentMemory>786432</currentMemory>
  <vcpu>2</vcpu>
  <os>
    <type arch='x86_64' machine='pc'>hvm</type>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <clock offset='localtime'/>         <-- virt-install では必ず utc になってしまうので修正
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
  <devices>
    <emulator>/usr/libexec/qemu-kvm</emulator>
    <disk type='block' device='cdrom'>
                                      <-- 実リソースなしCDROMは、'<source/>'ブロックを書かないことによって作れる
      <target dev='hdc' bus='ide'/>
      <readonly/>
    </disk>
    <disk type='block' device='disk'>
      <source dev='/dev/mapper/VolGroup00-LogVol00'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <disk type='block' device='disk'>
      <source dev='/dev/mapper/VolGroup00-LogVol01'/>
      <target dev='vdb' bus='virtio'/>
    </disk>
    <interface type='bridge'>
      <mac address='54:52:00:64:a6:35'/>
      <source bridge='br0'/>
      <target dev='vnet0'/>        <-- libvirt ではTAPはファイルディスクリプタを使って指定され、/etc/qemu-ifup は使われない
      <model type='virtio'/>       <-- NIC をvirtio で提供するなら付け加える
    </interface>
    <interface type='network'>
      <mac address='54:52:00:64:ef:32'/>
      <source network='virnet0'/>
      <target dev='vnet1'/>
      <model type='virtio'/>
    </interface>
    <sound model='ac97'/>          <-- サウンドデバイスを与えるなら挿入
    <serial type='pty'>
      <source path='/dev/pts/2'/>
      <target port='0'/>
    </serial>
    <console type='pty' tty='/dev/pts/2'>
      <source path='/dev/pts/2'/>
      <target port='0'/>
    </console>
    <input type='mouse' bus='ps2'/>
    <graphics type='vnc' port='5901' autoport='no' keymap='ja'/>
  </devices>
</domain>

その他の留意点: