Rootfs over Virtfsでゲストを起動する

はじめに

Virtfsを使うと、ホストのディレクトリをゲストにmountさせることができます。ここではdebootstrap等でホストに用意したrootfsを使ってゲストをブートさせる方法について説明します。

What's Virtfs?

Virtfsは、"File system pass-through/Paravirtual file system"を実現する機能です。パススルーといえば、ゲスト(カーネル)がホストマシンの物理デバイスに直接アクセスする機能が一般的ですが、virtfsはファイルシステムで似たようなことを実現しています。Virtfsを使うと、ホストのディレクトリをゲストにmountさせることができ、その結果、ゲストのアプリケーションがホストのファイルに直接アクセスできるようになります。もし複数のゲストが同じホストのディレクトリをmountするならば、同じディレクトリが見えることになります。

これだけ聞くとNFSと同じじゃないかと思われるかもしれませんが、virtfsは(仮想)ネットワークデバイスを介した通信を必要としません。代わりに9P over VirtIO*1でゲスト・qemu間のAPI/データ転送を実現しています。(qemuが9Pサーバ、ゲストカーネルが9Pクライアントになります。)

Virtfsの詳しい説明は、開発者が公開している資料を参照ください。

環境

注意

今回はとりあえずブートさせることが目的なので、セキュリティに関しては何も考えていません。(qemuをrootで動かし、virtfsのsecurity_modelをnoneにしています。)

手順

  1. qemu-kvm再ビルド
  2. ゲストrootfs準備
  3. initrdカスタマイズ
  4. ブート!
  5. 動作確認

qemu-kvm再ビルド

残念ながらUbuntu添付のqemu-kvm (0.14.0+noroms4)はvirtfsが有効になっていません。まずは、再ビルドしてvirtfsが使えるようにします。

といっても、ソースコードを修正する必要はなく、以下のようにlibattr1-devパッケージをインストールした状態でビルドすればOKです。

sudo apt-get build-dep --no-install-recommends qemu-kvm
sudo apt-get install libattr1 libattr1-dev
apt-get source qemu-kvm
cd qemu-kvm-0.14.0+noroms/
dpkg-buildpackage

ゲストrootfs準備

debootstrapを使えば簡単に用意することができます。

mkdir natty_root
sudo debootstrap natty natty_root

initrdカスタマイズ

ブート時に9pファイルシステムをrootにmountできるようにinitrdを修正します。

mkdir initrd
cd initrd
zcat /boot/initrd.img-2.6.38-8-generic|cpio -i
# いろいろ修正(後述)
find . | cpio --quiet --dereference -o -H newc|gzip -c > ../initrd.img-2.6.38-8-virtfs

やらなければならないことは、

  1. qemu引数に指定するmount_tagと9pファイルシステムのmount時に指定する識別名*2を一致させるため、カーネル引数経由で渡すmount_tagをinitrdのinitで解釈できるようにする
  2. 9pファイルシステムのmount前に9p/virtio関連のカーネルモジュールをロードするため、当該モジュールをinitrdに入れておく*3

修正箇所/修正作業は以下のとおりです。

  • init修正、scripts/9pを用意
  • 9p関連のカーネルモジュールをコピー、depmod -a

init(シェルスクリプト)は以下のdiffのように修正しました。

diff -u initrd.orig/init initrd/init
--- initrd.orig/init	2011-05-16 10:37:02.796295655 +0900
+++ initrd/init	2011-05-16 10:28:30.903695192 +0900
@@ -52,6 +52,7 @@
 export blacklist=
 export resume=
 export resume_offset=
+export mount_tag
 
 # mdadm needs hostname to be set. This has to be done before the udev rules are called!
 if [ -f "/etc/hostname" ]; then
@@ -207,6 +208,9 @@
 	hwaddr=*)
 		BOOTIF=${x#BOOTIF=}
 		;;
+	mount_tag=*)
+		mount_tag=${x#mount_tag=}
+		;;
 	esac
 done
 
@@ -326,6 +330,7 @@
 unset readonly
 unset resume
 unset resume_offset
+unset mount_tag
 
 # Chain to real filesystem
 exec run-init ${rootmnt} ${init} "$@" <${rootmnt}/dev/console >${rootmnt}/dev/console 2>&1

scripts/9pは新規に用意しました。(scripts/nfsを参考にしました。)

# 9p filesystem mounting                        -*- shell-script -*-

mountroot()
{
        modprobe virtio_pci
        modprobe 9p
        modprobe 9pnet_virtio
        mount -t 9p -o trans=virtio ${mount_tag} ${rootmnt}
}

initrdに入れなければならなかったカーネルモジュールは以下の3つでした。virtio関連のモジュールは最初から入っていました。

/lib/modules/2.6.38-8-generic/kernel/fs/9p/9p.ko
/lib/modules/2.6.38-8-generic/kernel/net/9p/9pnet.ko
/lib/modules/2.6.38-8-generic/kernel/net/9p/9pnet_virtio.ko

cd initrd/
cp -a /sbin/depmod sbin/
sudo chroot . /sbin/depmod -a 2.6.38-8-generic

以上でinitrdの準備が終わりました。

ブート!

というわけでゲストのブートです。カーネルはホストにあるものを使います。識別名は-append 'mount_tag=natty'と指定、同じ識別名を-virtfsの引数の一部に指定します。

sudo kvm -enable-kvm -kernel /boot/vmlinuz-2.6.38-8-generic -initrd /path/to/initrd.img-2.6.38-8-virtfs -append 'mount_tag=natty single' -virtfs local,path=/path/to/natty_root,mount_tag=natty,security_model=none -curses

上手くいくとブートするはずです。

訂正:試したのはシングルユーザモードだけです。

動作確認

簡単な動作確認です。確かにrootfsが9pファイルシステムになっています。

guest# mount
natty on / type 9p (rw)
none on /proc type proc (rw,noexec,nosuid,nodev)
none on /sys type sysfs (rw,noexec,nosuid,nodev)
none on /dev type devtmpfs (rw,mode=0755)
none on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
none on /sys/fs/fuse/connections type fusectl (rw)
none on /sys/kernel/debug type debugfs (rw)
none on /sys/kernel/security type securityfs (rw)
none on /dev/shm type tmpfs (rw,nosuid,nodev)
none on /var/run type tmpfs (rw,nosuid,mode=0755)
none on /var/lock type tmpfs (rw,noexec,nosuid,nodev)
guest# touch /tmp/hellovirtfs

↑ゲストで作った(ようにみえる)ファイルが、↓ホストにもちゃんと生成されていることが確認できました。

$ ls /path/to/natty_root/tmp
hellovirtfs

おわりに

なにはともあれ、ホストのファイルシステム上にあるrootfsでゲストがブートできました。

ゲスト環境を構築するときは、virt-installなどを使ってディスクイメージを用意するのが一般的ですが、virtfsの場合、代わりにdebootstrapやOpenVZのprecreated rootfsなどを使えるので多少は楽になるのではないでしょうか。

btrfsのsubvolumeを使うと、qcow2のように一回用意したrootfsで複数の(使い捨て)環境が用意できてさらに便利だと思います。

追記:kvm tools: Add virtio-9pのスレッドでも似たような話をしてますね。ここで紹介したやり方とは違うみたいですが。。。

*1:ゲストカーネル側の機能は実は2.6.24、つまりvirtioが最初にマージされたときにマージされています。

*2:仮想ファイルシステムの場合にnoneと指定することが多いアレ。

*3:もちろんカーネルに組み込んでも良いです。今回はUbuntu配布のカーネルをそのまま使いたかったのでこうしました。