First attempt to use guestfish/libguestfs

はじめに

Fedora 11にはVMディスクイメージ内のファイル操作のためのツール(guestfish)が用意されています。

VMディスクイメージ内のファイルにアクセスするには、rawイメージならば単純にループバックマウントすれば良いだけですが、qcow2フォーマット等の場合だとqemu-nbdを使う(qemu-nbdの使いかた - KVM日記)などイメージフォーマットを解釈できるツールの併用が必要になり、作業が煩雑になってしまいます。(さらにLVMだったりするとかなり悲惨です;<)

guestfishを使うと、イメージフォーマットやパーティションの位置、マウントポイントや一時ファイルなどを意識することなくスクリプトで、もしくはインタラクティブにファイル操作が可能になります。

準備

以下ではホストOS、ゲストOSともにFedora 11、guestfish(libguestfs)はバージョン1.0.57をhttp://libguestfs.org/download/からダウンロードしてコンパイルしたものを用います。libguestfsをコンパイルするとguestfishが生成されます。guestfishはlibguestfsライブラリを使ってファイル操作を実行します。(libguestfsをコンパイルする途中にfebootstrapが起動してなにやらyum installが始まりますが、これは後で説明するゲスト用カーネルとinitrdを生成するためです。気長に待ちましょう。)

あと準備としてゲストVMディスクイメージを用意します。私はすでにFedora 11がインストールされたイメージを持っていたので、これをベースとするqcow2イメージを作ってそれを使いました。

host$ qemu-img create -b f11.img -f qcow2 f11.qcow2.img

簡単な使い方

では試しに上記イメージ(f11.qcow2.img)内の/root/ディレクトリ(rootパーティションは/dev/sda3にあります)をlsしてみましょう。引数なしでguestfishを起動するとインタラクティブモードになります。(virshに似ていますね。) なおプロンプトは">"です。

host$ guestfish


Welcome to guestfish, the libguestfs filesystem interactive shell for
editing virtual machine filesystems.

Type: 'help' for help with commands
     'quit' to quit the shell

 ><fs> add-drive f11.qcow2.img
 ><fs> launch
 ><fs> mount /dev/sda3 /
 ><fs> ls /root/
.bash_history
.bash_logout
.bash_profile
.bashrc
.cshrc
.tcshrc
anaconda-ks.cfg
install.log
install.log.syslog

このように無事ファイルリストを得ることができました。

次はホストにあるファイルをコピーしてみましょう。

host$ echo hoge > /tmp/fuga

 ><fs> ls /tmp/
.ICE-unix
yum.log
 ><fs> upload /tmp/fuga /tmp/fuga
 ><fs> ls /tmp/
.ICE-unix
fuga
yum.log
 ><fs> cat /tmp/fuga
hoge

うまくいきました。

guestfishではゲスト内にあるファイルをviで編集することもできます。

 ><fs> edit /tmp/fuga
 ><fs> cat /tmp/fuga
hoge
fuga

作業が終わったら以下のようにguestfishを終了させます。

 ><fs> umount-all
 ><fs> kill-subprocess
 ><fs> quit

launchやumount-allだけでなくlsやmountも実はguestfishの内部コマンドです。(commandというディスクイメージ内の任意のコマンドを実行するコマンドもあります。)使用可能なコマンドはオンラインマニュアル等を参照してください。

内部実装

さてここからは内部実装の話題です。(むしろこっちがメイン?)

guestfishはどうやってVMディスクイメージの内部にアクセスしているのでしょうか?実はguestfishはサブプロセスとしてqemu(VM)を起動し、そのqemuVMディスクイメージのマウントやファイルアクセスを行なっています。

上記例では、launchコマンドの箇所でqemuが起動しています。psで見てみると下ようなqemuプロセスが立ち上がっているのがわかります。

host$ ps xa |grep qemu-kvm
/usr/bin/qemu-kvm -drive file=f11.qcow2.img,cache=off,if=ide -m 500 -no-reboot -kernel /usr/local/lib/guestfs/vmlinuz.fedora-11.x86_64 -initrd /usr/local/lib/guestfs/initramfs.fedora-11.x86_64.img -append panic=1 console=ttyS0 guestfs=10.0.2.4:6666 -nographic -serial stdio -net channel,6666:unix:/tmp/libguestfsQz9LZs/sock,server,nowait -net user,vlan=0 -net nic,model=virtio,vlan=0 -no-hpet -rtc-td-hack

この中の-kernel, -initrdで指定しているのがlibguestfsのコンパイル時にゴリゴリと生成していたカーネルとinitrdです。

host$ file /usr/local/lib/guestfs/*
/usr/local/lib/guestfs/initramfs.fedora-11.x86_64.img: gzip compressed data, from Unix, last modified: Sat Jul 11 01:02:44 2009, max compression
/usr/local/lib/guestfs/vmlinuz.fedora-11.x86_64:       Linux kernel x86 boot executable bzImage, version 2.6.29.5-191.fc11.x86_64 (mockb, RO-rootFS, root_dev 0x902, swap_dev 0x2, Normal VGA

つまり、guestfishで立ち上げるVMではVMディスクイメージ内部のカーネルは使っていないということですね。

さて今度は引数"-net channel,6666:unix:/tmp/libguestfsQz9LZs/sock,server,nowait"とゲストカーネル引数(-append)に指定している"guestfs=10.0.2.4:6666"に注目してみましょう。

前者は、/tmp/libguestfsQz9LZs/sock (UNIXソケット)へのtcp接続はゲスト内部の6666ポートへ転送しろ、という意味で、qemuの機能です。netstatで見ると確かにqemuがLISTENしていますね。

host$ sudo netstat -nap |grep libguestfs
unix  2      [ ACC ]     STREAM     LISTENING     2203601 1993/qemu-kvm       /tmp/libguestfsQz9LZs/sock
unix  3      [ ]         STREAM     CONNECTED     2203606 1993/qemu-kvm       /tmp/libguestfsQz9LZs/sock

guestfishはこのUNIXソケットを介してゲスト内のデーモンにコマンド実行をお願いしているようです。

では、ゲスト内で動いているのは誰か?それはguestfsdというデーモンです。(実行ファイルはinitrdの中に用意されています。)カーネル引数"guestfs=10.0.2.4:6666"は、カーネルではなく、このguestfsdに渡すために指定されているようです。

guestfishを使って確かめてみましょう。

 ><fs> sh 'ps | grep guestfsd'
    1 ?        00:00:01 guestfsd
 ><fs> command 'netstat -nap'
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address    State       PID/Program name
tcp        0      0 10.0.2.10:40125             10.0.2.4:6666    ESTABLISHED 1/guestfsd
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node PID/Program name    Path
unix  2      [ ]         DGRAM                    3082   64/udevd     @/org/kernel/udev/udevd

たしかに動いてますね。

というわけで、guestfishがlibguestfsで決められたAPIでguestfsdにリクエストを出して、ゲストVMディスクを操作しているらしいということがわかりました。なかなかアクロバティックなことをやっていますね ;-)

まとめ

guestfishはVMディスクイメージ内のファイル操作を、雑多な手順を踏む事なくインタラクティブに実行したり、(上記例では取り上げなかった)スクリプト処理することを可能にするとても便利なツールです。仮想化基盤システムの自動化などに大いに役立つのではないかと思います。

最後に試用中に発見した不具合を報告 ;)

## FIXED!!
#><fs> add-drive ~/f11.qcow2.img
#libguestfs: error: ~/f11.qcow2.img: No such file or directory

 ><fs> download /tmp/fuga /tmp/fuga
libguestfs: error: guestfs_download: internal error: reply callback called twice
libguestfs: error: guestfs_download: internal error: reply callback called twice
libguestfs: error: guestfs_download reply failed, see earlier error messages

ホストのファイル指定に~が使えないようです。(不具合というか単に未実装なだけだと思いますが。)またゲストのファイルをホストにコピーするdownloadコマンドもうまく動きませんでした。

(追記)libguestfsの作者のRichardさんが颯爽と現れて前者の機能を作ってくれました!最新1.0.62では上記コマンドは成功します。すばらしい!