OpenVZ on Debian: ploop を試す

最新版の OpenVZ カーネルには ploop という機能が同梱されており、これを利用することでコンテナの I/O 性能と管理の利便性を大きく向上させることができます。この記事では Debian の OpenVZ システムで実際に ploop のコンテナを使ってみた感想などを書いてみます。

検証は KVM 上で動作する Debian 7.3 システムと最新の OpenVZ カーネル・管理ツールで行いました。セットアップ手順については 過去の記事 を参照してください。

root@kvz02:~# uname -a
Linux kvz02 2.6.32-openvz-042stab084.17-amd64 #1 SMP Fri Dec 27 17:00:12 MSK 2013 x86_64 GNU/Linux
root@kvz02:~# vzctl --version
vzctl version 4.6.1

基本

ploop は OpenVZ プロジェクトによって開発されたループバックブロックデバイスのドライバです。機能的にはカーネル標準の loop ドライバと似ていますが、 qcow2 や vmdk に似たサイズ可変のイメージフォーマットを使用しており、差分イメージの作成も可能となっているなど、今日の仮想化技術に必要とされる機能を備えています。

ploop をサポートする OpenVZ コンテナ管理ツール vzctl (3.1 以降) ではコンテナレイアウトとして従来の simfs に加えて ploop が選択できます。 ploop レイアウトではコンテナごとに専用のブロックデバイスとファイルシステムを作成するため、ひとつのファイルシステムを多数のコンテナで共有する simfs に比べ、パフォーマンスの向上が期待できます。 OpenVZ Wiki によればひとつのファイルシステムのジャーナルを多数のコンテナが共有することは OpenVZ コンテナの I/O ボトルネックのひとつであったとしています。

ploop レイアウトを使うには vzctl create コマンドに --layout ploop オプションをつけてコンテナを作成します。 --diskspace 8G のように ploop デバイスのサイズの指定もできます。このサイズはあとから増減することができます。

また vzctl convert コマンドを使うと simfs と ploop で相互にレイアウトを変換できます。ただしこれは対象のコンテナが停止中である必要があります。また変換には長い時間がかかりますので、不測の事態に備えて変換前にバックアップをとっておくべきでしょう。

実際に ploop レイアウトでコンテナを作成し、起動してみましょう。

root@kvz02:~# vzctl create 1100 --ostemplate ubuntu-13.10-x86_64 --layout ploop --diskspace 8G
Creating image: /srv/vz/private/1100.tmp/root.hdd/root.hdd size=8388608K
Creating delta /srv/vz/private/1100.tmp/root.hdd/root.hdd bs=2048 size=16777216 sectors v2
Storing /srv/vz/private/1100.tmp/root.hdd/DiskDescriptor.xml
Opening delta /srv/vz/private/1100.tmp/root.hdd/root.hdd
Adding delta dev=/dev/ploop26988 img=/srv/vz/private/1100.tmp/root.hdd/root.hdd (rw)
mke2fs 1.42.5 (29-Jul-2012)
Discarding device blocks: done                            
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
524288 inodes, 2096635 blocks
104831 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=4294967296
64 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done 

tune2fs 1.42.5 (29-Jul-2012)
Setting maximal mount count to -1
Setting interval between checks to 0 seconds
Creating balloon file .balloon-c3a5ae3d-ce7f-43c4-a1ea-c61e2b4504e8
Error in reread_part (ploop.c:1106): BLKRRPART /dev/ploop26988: Device or resource busy
Mounting /dev/ploop26988p1 at /srv/vz/private/1100.tmp/root.hdd/root.hdd.mnt fstype=ext4 data='' 
Unmounting device /dev/ploop26988
Opening delta /srv/vz/private/1100.tmp/root.hdd/root.hdd
Adding delta dev=/dev/ploop26988 img=/srv/vz/private/1100.tmp/root.hdd/root.hdd (rw)
Mounting /dev/ploop26988p1 at /srv/vz/root/1100 fstype=ext4 data='balloon_ino=12,' 
Creating container private area (ubuntu-13.10-x86_64)
Unmounting file system at /srv/vz/root/1100
Unmounting device /dev/ploop26988
Opening delta /srv/vz/private/1100/root.hdd/root.hdd
Adding delta dev=/dev/ploop26988 img=/srv/vz/private/1100/root.hdd/root.hdd (rw)
Mounting /dev/ploop26988p1 at /srv/vz/root/1100 fstype=ext4 data='balloon_ino=12,' 
Performing postcreate actions
Unmounting file system at /srv/vz/root/1100
Unmounting device /dev/ploop26988
CT configuration saved to /etc/vz/conf/1100.conf
Container private area was created

root@kvz02:~# vzctl set 1100 --save --name vm100 --hostname vm100.t0.keshi.org --ipadd 10.10.0.100 --nameserver 10.10.0.1
Name vm100 assigned
CT configuration saved to /etc/vz/conf/1100.conf

root@kvz02:~# vzctl start 1100
Starting container...
Opening delta /srv/vz/private/1100/root.hdd/root.hdd
Adding delta dev=/dev/ploop26988 img=/srv/vz/private/1100/root.hdd/root.hdd (rw)
/dev/ploop26988p1: clean, 28689/524288 files, 186494/2096635 blocks
Mounting /dev/ploop26988p1 at /srv/vz/root/1100 fstype=ext4 data='balloon_ino=12,' 
Container is mounted
Adding IP address(es): 10.10.0.100
Setting CPU units: 1000
Container start in progress...

コンテナ内部で df を実行してサイズを調べてみます。

root@kvz02:~# vzctl exec 1100 df -h /
Filesystem         Size  Used Avail Use% Mounted on
/dev/ploop26988p1  7.9G  600M  6.9G   8% /

この結果によれば 7.9GB のファイルシステムに 600MB の中身があることがわかります。これに対して実際に ploop イメージファイルが占めるサイズを調べると次のようになりました。

root@kvz02:~# ls -lh /srv/vz/private/1100/root.hdd/
total 622M
-rw-r--r-- 1 root root  790  1月  6 07:41 DiskDescriptor.xml
-rw------- 1 root root    0  1月  6 07:41 DiskDescriptor.xml.lck
-rw------- 1 root root 494M  1月  6 07:42 root.hdd

root@kvz02:~# du -h /srv/vz/private/1100/root.hdd/root.hdd
622M	/srv/vz/private/1100/root.hdd/root.hdd

イメージファイル root.hdddu で得たファイルサイズが ls で得たファイルサイズよりも大きいという気味の悪い状態になっていますが (逆はよくある)、 622MB とほぼ使ったぶんだけのサイズであることがわかります。

ploop レイアウト

ploop レイアウトでは、コンテナのプライベート領域は次のような構成になります。

イメージファイル /srv/vz/private/CTID/root.hdd/root.hdd を ploop デバイス /dev/ploop???? に割り当てた上で、その中に GPT のパーティションテーブルと ext4 ファイルシステムを作り、コンテナのマウントポイント /srv/vz/root/CTID/ にマウントしています。

ファイルシステムのサイズ変更

ploop で特筆すべきは、 ext4 ファイルシステム限定ではありますがオンライン (マウントしたまま・コンテナ稼働したまま) でのファイルシステムサイズ変更をサポートしていることです。これにより simfs の vzquota と同様の使い勝手でコンテナのストレージ使用量を制限できます。

コンテナのディスクサイズの変更は simfs の場合と同様に vzctl set オプションの --diskspace を使って簡単にできます。なお --diskinodes の指定は無視されます。

先ほどのコンテナはディスクサイズに 8GB で作成しました。これを 16GB に拡大してみましょう。

root@kvz02:~# vzctl set 1100 --save --diskspace 16G
Storing /srv/vz/private/1100/root.hdd/DiskDescriptor.xml.tmp
Growing dev=/dev/ploop26988 size=16777216 sectors (new size=33554432)
Storing GPT
resize2fs 1.42.5 (29-Jul-2012)
Filesystem at /dev/ploop26988p1 is mounted on /srv/vz/root/1100; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/ploop26988p1 is now 4193280 blocks long.

UB limits were set successfully
CT configuration saved to /etc/vz/conf/1100.conf

root@kvz02:~# vzctl exec 1100 df -h /
Filesystem         Size  Used Avail Use% Mounted on
/dev/ploop26988p1   16G  608M   15G   4% /

df の結果、たしかにサイズが 16GB に拡張していることがわかります。ここには記していませんがイメージファイルのサイズもほとんど増加していません。

次は 1GB に縮小してみましょう。

root@kvz02:~# vzctl set 1100 --save --diskspace 1G
dumpe2fs 1.42.5 (29-Jul-2012)
Changing balloon size old_size=0 new_size=16106127360
Opening delta /srv/vz/private/1100/root.hdd/root.hdd
Successfully inflated balloon from 0 to 16106127360 bytes
TRUNCATED: 1 cluster-blocks (1048576 bytes)
tune2fs 1.42.5 (29-Jul-2012)
Setting reserved blocks count to 13055
UB limits were set successfully
CT configuration saved to /etc/vz/conf/1100.conf

root@kvz02:~# vzctl exec 1100 df -h /
Filesystem         Size  Used Avail Use% Mounted on
/dev/ploop26988p1  763M  608M  104M  86% /

こんどはサイズが 1GB よりかなり少ない 763MB になっているのが気になります。この原因はいまのところはっきりと理解していないのですが、巨大なバルーンファイル (後述) のオーバーヘッドによるものではないかと推測しています。

ploop ballooning

ploop がサポートする ext4 ファイルシステムは、それ自身にオンラインでサイズ拡大できる能力がありますがサイズ縮小はできません。 ploop ではこの問題を解決するために ballooning というテクニックを使っています。詳細は README を参照してください。

具体的には、ファイルシステム内に特殊なバルーンファイルというものを作り、このサイズを要求に応じて増減させています。その際、イメージファイル内で末尾にある有効なデータブロックをバルーンファイルが占めるデータブロックに移動させることで、イメージファイルサイズを減らそうとします。

ballooning のために OpenVZ カーネルでは ext4 のコードにもかなり手が入っています。具体的には、マウント時に balloon_ino オプションで指定した inode 番号のファイル (これがバルーンファイルとなります) は一切見えなくなり、 statfs で取得できるファイルシステムのサイズも、バルーンファイルのサイズが差し引かれるようになっています。

コンテナ領域を balloon_ino オプションを変更して再マウントすると、実際にバルーンファイルを見ることができます。

root@kvz02:~# mount /dev/ploop26988p1 /srv/vz/root/1100 -o remount,balloon_ino=0

root@kvz02:~# vzctl exec 1100 ls -la  /
total 15728728
drwxr-xr-x 22 root root        4096 Dec 18 14:38 .
drwxr-xr-x 22 root root        4096 Dec 18 14:38 ..
-rw-------  1 root root 16106127360 Jan  5 17:41 .balloon-c3a5ae3d-ce7f-43c4-a1ea-c61e2b4504e8
drwxr-xr-x  2 root root        4096 Dec 18 14:39 bin
drwxr-xr-x  2 root root        4096 Dec 18 14:37 boot
drwxr-xr-x  5 root root         700 Jan  5 17:42 dev
drwxr-xr-x 85 root root        4096 Jan  5 17:42 etc
-rw-r--r--  1 root root           0 Dec 18 14:37 fastboot
drwxr-xr-x  2 root root        4096 Dec 18 14:37 home
drwxr-xr-x 12 root root        4096 Jan  5 17:42 lib
drwxr-xr-x  2 root root        4096 Dec 18 14:37 lib64
drwx------  2 root root       16384 Dec 18 14:37 lost+found
drwxr-xr-x  2 root root        4096 Dec 18 14:37 media
drwxr-xr-x  2 root root        4096 Dec 18 14:37 mnt
drwxr-xr-x  2 root root        4096 Dec 18 14:37 opt
dr-xr-xr-x 51 root root           0 Jan  5 17:42 proc
drwx------  2 root root        4096 Dec 18 14:37 root
drwxr-xr-x 14 root root         500 Jan  5 17:42 run
drwxr-xr-x  2 root root        4096 Dec 18 14:38 sbin
drwxr-xr-x  2 root root        4096 Dec 18 14:37 srv
drwxr-xr-x  7 root root           0 Jan  5 17:42 sys
drwxrwxrwt  2 root root        4096 Jan  5 17:42 tmp
drwxr-xr-x 10 root root        4096 Dec 18 14:38 usr
drwxr-xr-x 12 root root        4096 Dec 18 14:38 var

root@kvz02:~# vzctl exec 1100 df -h /
Filesystem         Size  Used Avail Use% Mounted on
/dev/ploop26988p1   16G   16G  104M 100% /

.balloon-c3a5ae3d-ce7f-43c4-a1ea-c61e2b4504e8 というのがバルーンファイルです。ファイルシステムを 16GB に拡大した後に 1GB に縮小したので、バルーンファイルのサイズはちょうど 15GB になっています (15×1024×1024×1024 = 16106127360)。また df の結果でも本来のファイルシステムサイズ 16GB が現れています。

スナップショット

ploop で利用可能になった便利な機能としてスナップショット機能があります。

次のコマンドでコンテナのスナップショットが作成されます。コンテナが稼働中であればその時点のプロセスの状態も同時にすべて保存されます。

vzctl snapshot CTID

作成したスナップショットは次のコマンドで一覧表示できます。横に長いのでパイプで less -S などにつなげて見るのがよいでしょう。各スナップショットには UUID が割り当てられており、復元や削除の操作ではこれで対象のスナップショットを指定します。

vzctl snapshot-list CTID | less -S

次のコマンドでコンテナの状態を指定した UUID のスナップショットに復元します。それまでのファイルシステムの変更やプロセスの状態はすべて失われますので注意してください。失いたくない場合は別のスナップショットを作ってから復元します。

vzctl snapshot-switch CTID --id UUID

次のコマンドででスナップショットを削除します。このとき子スナップショットまたは最新状態との間でブロックデバイスのマージ作業が発生します。

vzctl snapshot-delete CTID --id UUID

特定のスナップショットの中身を参照したいときには次のコマンドでマウント・アンマウントできます。ただしマウントはリードオンリーとなります。

vzctl snapshot-mount CTID --id UUID --target DIR
vzctl snapshot-umount CTID --id UUID

実際にスナップショットを作成してみましょう。

root@kvz02:~# vzctl snapshot 1100
Creating snapshot {0469f5a1-79cc-4e0b-9fd1-33e9314d9283}
Storing /srv/vz/private/1100/Snapshots.xml.tmp
Setting up checkpoint...
	suspend...
	get context...
Checkpointing completed successfully
Storing /srv/vz/private/1100/root.hdd/DiskDescriptor.xml.tmp
Creating delta /srv/vz/private/1100/root.hdd/root.hdd.{4d30eb8d-162f-4190-8027-ff8fcdb1f52f} bs=2048 size=33554432 sectors v2
Creating snapshot dev=/dev/ploop26988 img=/srv/vz/private/1100/root.hdd/root.hdd.{4d30eb8d-162f-4190-8027-ff8fcdb1f52f}
ploop snapshot {0469f5a1-79cc-4e0b-9fd1-33e9314d9283} has been successfully created
Setting up checkpoint...
	join context..
	dump...
Checkpointing completed successfully
Resuming...

稼働中のコンテナのプロセス状態をチェックポイント機能で保存するとともに、新しい差分ディスクイメージを追加しています。

同様にスナップショットを 3 個作成したあとに vzctl snapshot-list で見ると次のようになります。

root@kvz02:~# vzctl snapshot 1100
root@kvz02:~# vzctl snapshot 1100
root@kvz02:~# vzctl snapshot-list 1100
PARENT_UUID                            C UUID                                   DATE                NAME                            
                                         {0469f5a1-79cc-4e0b-9fd1-33e9314d9283} 2014-01-06 07:48:01 
{0469f5a1-79cc-4e0b-9fd1-33e9314d9283}   {9b8d68f5-30b2-4099-8510-388703d170d1} 2014-01-06 07:48:02 
{9b8d68f5-30b2-4099-8510-388703d170d1} * {d3709ccf-5a52-4424-8c56-5bf507fee9f6} 2014-01-06 07:48:07 

このように各スナップショットは UUID で親子関係とともに記録されています。 C* になっているのが、現在マウント・変更しているスナップショットです。

2 番めに保存したスナップショットに切り替えてみます。

root@kvz02:~# vzctl snapshot-switch 1100 --id 9b8d68f5-30b2-4099-8510-388703d170d1 
Switching to snapshot {9b8d68f5-30b2-4099-8510-388703d170d1}
Storing /srv/vz/private/1100/Snapshots.xml.tmp
Setting up checkpoint...
	suspend...
	get context...
Checkpointing completed successfully
Opening delta /srv/vz/private/1100/root.hdd/root.hdd.{4d30eb8d-162f-4190-8027-ff8fcdb1f52f}
Storing /srv/vz/private/1100/root.hdd/DiskDescriptor.xml.tmp
Creating delta /srv/vz/private/1100/root.hdd/root.hdd.{599805d1-1f73-41cd-9a2b-0b9ccab0b2fa} bs=2048 size=33554432 sectors v2
ploop snapshot has been successfully switched
Killing...
Unmounting file system at /srv/vz/root/1100
Unmounting device /dev/ploop26988
Container is unmounted
Restoring container ...
Opening delta /srv/vz/private/1100/root.hdd/root.hdd
Adding delta dev=/dev/ploop26988 img=/srv/vz/private/1100/root.hdd/root.hdd (ro)
Adding delta dev=/dev/ploop26988 img=/srv/vz/private/1100/root.hdd/root.hdd.{4d30eb8d-162f-4190-8027-ff8fcdb1f52f} (ro)
Adding delta dev=/dev/ploop26988 img=/srv/vz/private/1100/root.hdd/root.hdd.{599805d1-1f73-41cd-9a2b-0b9ccab0b2fa} (rw)
/dev/ploop26988p1: clean, 28727/1048576 files, 4153637/4193280 blocks
Mounting /dev/ploop26988p1 at /srv/vz/root/1100 fstype=ext4 data='balloon_ino=12,' 
Container is mounted
	undump...
Adding IP address(es): 10.10.0.100
Setting CPU units: 1000
	resume...
Container start in progress...
Restoring completed successfully
Storing /srv/vz/private/1100/root.hdd/DiskDescriptor.xml
Removing /srv/vz/private/1100/root.hdd/root.hdd.{ac43e723-cab7-44fb-8daa-dc7a73fda889}
ploop snapshot {c80a009b-81f5-4c74-88b9-a86d3b493bbb} has been successfully deleted
Container has been successfully switched to another snapshot

コンテナ内部ではスナップショット作成時に存在したプロセスがすべて稼働しています。

概念的には VirtualBox のスナップショット機能とほぼ同じです。バックアップやライブマイグレーションなどいろいろ便利な応用が考えられます。詳細は OpenVZ Wiki の Backup などを参照してください。

まとめ

OpenVZ の独自機能である ploop とスナップショットについて紹介してみました。

ploop は可変サイズや差分のディスクイメージに対応したループバックブロックデバイスですが、それだけでなく独自に ext4 ファイルシステムのオンライン拡大・縮小をサポートするなど、従来の simfs でのコンテナの使い勝手を損なうことがないようだいぶ頑張っている印象です (執念に近いものすら感じます)。

スナップショット機能もチェックポイント機能と組み合わせてプロセス状態の保存・復元もサポートするなど、コンテナ仮想化でありながら完全仮想化にも比肩する機能を備えています。

上記機能はみな vzctl コマンドひとつで簡単に使えるよう整備されており、やはり先行するだけあって OpenVZ が LXC に比べ使い勝手で一日の長があるように感じられます。

スナップショット機能の実装は今後各コンテナ技術の優劣を語る上でキーポイントになりそうです。 ploop や device mapper でブロックデバイスレベルの差分を扱う他にも、 btrfs や ZFS のようなホストファイルシステムのスナップショット機能を活用する方法、あるいは docker のように aufs を使うといったアプローチもあるでしょう。これからどのようなアイディアが出てくるか、楽しみな分野です。

今回は時間の都合で使ってみるだけで終わってしまいましたが、今後 simfs と ploop の性能比較などもしてみたいと思います。

2014/01/06 10:01:00 JST