LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第19回LXCの構築・活用 [5] ─いろいろなストレージバックエンドの利用(1:overlayfs、aufs)

前回はLXCでも利用できるファイルシステムであるoverlayfsを紹介しました。

Linuxではいろいろなファイルシステムが使えますので、コンテナイメージの保存場所にいろいろなファイルシステムを使い、ファイルシステムが持つさまざまな便利な機能を直接使うことがあるでしょう。

LXCでも前回紹介したoverlayfsを始めとするいくつかのストレージバックエンドをサポートしており、コンテナイメージに関わる操作をする場合はLXCのコマンドやライブラリから直接ストレージバックエンド上のコンテナを操作できます。

具体的には以下の操作をする場合にストレージバックエンドを指定して操作できます。

  • コンテナの作成 lxc-create
  • コンテナのクローン lxc-clone

lxc-snapshotコマンドでコンテナのスナップショットを取る場合も、内部的にはストレージバックエンドを意識しますが、コマンドでの指定はありません。

いずれのコマンドも第8回第9回で簡単に説明しました。今回以降の数回で、ストレージバックエンドの使用にフォーカスを当ててもう少し細かく見ていきたいと思います。

今回の記事中では実際の操作はUbuntu 14.04 LTS上で実行しています。

ストレージバックエンドの種類

lxc-createlxc-clone共にストレージバックエンドは-Bオプションで指定します。ここで指定できるストレージバックエンドには表1に挙げるような種類があります。

表1 LXCで指定できるストレージバックエンド
名称 lxc-createでの使用 lxc-cloneでの使用 説明
aufs ×
best × btrfs,zfs,lvm,dirの順に試す
btrfs
dir ディレクトリ
lvm LVM
loop ループバックデバイス
none × dirのエイリアス
overlayfs ×
zfs

lxc-createlxc-cloneのそれぞれで「○」が付いているストレージバックエンドしか指定できません。

コピーによるクローン

それではさっそくクローンを試してみましょう。まずはコンテナの作成からです。/var/lib/lxcが普通にext4である環境でコンテナを作成しましょう。

$ sudo lxc-create -n ct01 -t download -B dir -- -d ubuntu -r trusty -a amd64

-B dirは指定しなくても同じです。ここではわかりやすいように明示的に指定しています。

クローンを実行する前にlxc-cloneのオプションを確認しておきましょう。

表2 lxc-cloneコマンドのオプション
オプション オプションの意味
-s クローンをスナップショットで取得。LVMとbtrfsとzfsの時に指定可能。aufsとoverlayfsの時に指定が必要
-p オリジナルのコンテナの『コンテナの保存場所』
-P クローン先のコンテナの『コンテナの保存場所』
-B 元のコンテナと違うバックエンドストレージを使う場合にバックエンドストーレジ形式を指定
-o クローン元のコンテナ名
-n クローン先のコンテナ名

最低限必要なオプションはクローン元を指定する-oとクローンで新たに作成するコンテナ名を指定する-nです。

また、-sスナップショットによるクローンを指定しない場合は、コンテナイメージをrsyncでコピーします。

では、一番シンプルなクローンを試してみましょう。time はクローンにかかった時間を計測するためにつけています。

$ sudo time -p lxc-clone -o ct01 -n clone01
Created container clone01 as copy of ct01
real 4.43
user 2.14
sys 1.71
$ sudo lxc-ls
clone01  ct01

lxc-cloneの終了後にlxc-lsを実行すると、clone01コンテナが作成されているのがわかります。

クローン先のコンテナの設定ファイルはlxc-cloneコマンドが自動的に作成します。クローン元のコンテナの設定ファイルを一度内部的に展開した後に必要部分を書き換えて出力しますので、クローン元でlxc.includeを使って共通ファイルをincludeしている場合でも、クローン先の設定ファイルではlxc.includeを使わずに作成されます。

たとえばクローン元のct01は以下のように非常にシンプルな記述です。

$ sudo cat /var/lib/lxc/ct01/config | grep -v "^#"

lxc.include = /usr/share/lxc/config/ubuntu.common.conf
lxc.arch = x86_64

lxc.rootfs = /var/lib/lxc/ct01/rootfs
lxc.utsname = ct01

lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxcbr0

一方、クローン先のclone01は以下のように全ての設定が書かれており、かなりサイズが大きくなっています。

$ sudo cat /var/lib/lxc/clone01/config
lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0
lxc.mount.entry = sysfs sys sysfs defaults 0 0
    :(略)
lxc.utsname = clone01
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxcbr0
lxc.network.hwaddr = 00:16:3e:11:b2:90
lxc.cap.drop = sys_module
lxc.cap.drop = mac_admin
lxc.cap.drop = mac_override
lxc.cap.drop = sys_time
lxc.rootfs = /var/lib/lxc/clone01/rootfs
lxc.pivotdir = lxc_putold

rsyncによるコピーが行われていますので、クローン元とクローン先のコンテナイメージの容量は以下のようにほぼ同じになっています。

$ sudo du -sh /var/lib/lxc/ct01/rootfs (クローン元の容量)
379M    /var/lib/lxc/ct01/rootfs
$ sudo du -sh /var/lib/lxc/clone01/rootfs (クローン先の容量)
383M    /var/lib/lxc/clone01/rootfs

aufs、overlayfsを使ったクローン

この2つのUnion Filesystemは、元のコンテナとの差分のみを記録し、元のコンテナイメージとの重ねあわせてコンテナイメージを作成します。このため、当然元となるコンテナがないといけませんのでクローン時にのみ指定できます。

aufs、overlayfsを使うことでコンテナイメージを差分で管理できるようになりますので、ベースとなるイメージを用意しておいてクローンすることにより、そのベースとなるイメージは変更せずにコンテナを起動して使えます。コンテナの利便性が高まり、利用の可能性が広がりますね。

クローン元のストレージバックエンドがdirの場合はaufsとoverlayfsが、クローン元のストレージバックエンドがaufsもしくはoverlayfsの場合は、元のストレージバックエンドと同じストレージバックエンドだけが指定できます。

aufs、overlayfsでクローンを作成する場合は、スナップショットを取得するための-sオプションを指定する必要があります。

では、先ほどのct01コンテナのクローンをoverlayfsでスナップショットとして作成してみましょう。

$ sudo time -p lxc-clone -o ct01 -n overlayfs01 -s -B overlayfs
Created container overlayfs01 as snapshot of ct01
real 0.04
user 0.00
sys 0.00

timeの出力を見ると、一瞬で処理が終了しているのがわかります。先ほどのコピーによるクローンに比べてかなり短い時間を示していますね。

クローンで作成したコンテナ用のディレクトリを見てみましょう。

$ sudo ls -F /var/lib/lxc/overlayfs01/
config  delta0/  lxc_rdepends  olwork/  rootfs/

configファイルとrootfsディレクトリは通常のコンテナにも存在しますね。目的も同じです。この2つも含めてクローン先のコンテナ用ディレクトリの中身をまとめておきます。

config
コンテナの設定ファイル
delta0
overlayfsの上層側ディレクトリ
lxc_rdepends
このコンテナがどのコンテナに依存しているかを記述したファイル。依存するコンテナの「保存場所(/var/lib/lxc)」「コンテナ名(ct01)」が書かれています
olwork
overlayfsのworkdirオプションで指定するディレクトリ。ここで使っているUbuntu 14.04 LTSの古いバージョンのoverlayfsでは使いません
rootfs
コンテナイメージのルート("/")。通常のコンテナと違いコンテナが起動していない時は空で、overlayfsで下層側と上層側のディレクトリを重ねあわせてここにマウントします

LXCはクローンで作成したコンテナがoverlayfsを使用していることを、設定ファイル内のlxc.rootfsの設定で認識します。

$ sudo grep lxc.rootfs /var/lib/lxc/clone01/config
(コピーによるクローンのlxc.rootfsの確認)
lxc.rootfs = /var/lib/lxc/clone01/rootfs
$ sudo grep lxc.rootfs /var/lib/lxc/overlayfs01/config
(overlayfsによるクローンのlxc.rootfsの確認)
lxc.rootfs = overlayfs:/var/lib/lxc/ct01/rootfs:/var/lib/lxc/overlayfs01/delta0

コピーによるクローンで作られたコンテナのlxc.rootfsは、lxc-createで作られたコンテナと同様にディレクトリを示しています。一方、overlayfsによるスナップショットとして作られたコンテナのlxc.rootfsはコロンで区切られた設定となっています。

aufs、overlayfsの場合はこのようにファイルシステムと下層側ディレクトリ、上層側ディレクトリをコロンで区切って指定します。

lxc.rootfs = (aufsもしくはoverlayfs):(下層側ディレクトリ):(上層側ディレクトリ)

overlayfsの場合の実際の設定を見てみると、下層側のディレクトリとしてクローン元のコンテナイメージ用ディレクトリを、上層側のディレクトリとしては、新たにクローン先の上層側ディレクトリ用に作られdelta0というディレクトリを指定しているのがわかります。つまりクローン元のコンテナイメージは読み込み専用の下層側として使うわけですね。

クローン直後のコンテナイメージの容量を確認してみましょう。

$ sudo du -sh /var/lib/lxc/overlayfs01/delta0
12K /var/lib/lxc/overlayfs01/delta0

まだコンテナの起動を行っていないので、非常に少ない容量になっていますね。delta0ディレクトリの中を見てみると/etc/hostnameのみ存在します。LXCはコンテナ内のホスト名を変更するために/etc/hostnameの名前を変更します(コンテナに/etc/hostnameファイルが存在する場合のみです⁠⁠。

$ sudo tree -A /var/lib/lxc/overlayfs01/delta0
/var/lib/lxc/overlayfs01/delta0
└── etc
    └── hostname

1 directory, 1 file
$ sudo cat /var/lib/lxc/overlayfs01/delta0/etc/hostname 
overlayfs01

コンテナを起動すると、クローン元のコンテナイメージの上にdelta0ディレクトリを重ねあわせて、rootfsディレクトリにマウントします。実際にコンテナを起動して、重ねあわせによるマウントが行われているのを確認してみましょう。

$ sudo lxc-start -n overlayfs01 -d
$ sudo lxc-attach -n overlayfs01 -- cat /proc/mounts | grep overlayfs
/var/lib/lxc/ct01/rootfs / overlayfs rw,relatime,lowerdir=/var/lib/lxc/ct01/rootfs,upperdir=/var/lib/lxc/overlayfs01/delta0 0 0

このマウントはコンテナのマウント名前空間内で行われますので、ホスト上では見えません。そこで上記のようにlxc-attachでコンテナ内に入り込んでマウントの確認をしています。

試しにコンテナ内にファイルを作成してみましょう。

$ sudo lxc-attach -n overlayfs01 -- touch /root/testfile (コンテナにファイルを作成)
$ sudo lxc-stop -n overlayfs01
$ sudo tree -A /var/lib/lxc/overlayfs01/delta0 (上層側ディレクトリ以下の確認)
/var/lib/lxc/overlayfs01/delta0
    :(略)
├── etc
│   ├── hostname
│   ├── mtab
│   └── nologin -> (overlay-whiteout)
├── root
│   └── testfile
├── run
│   └── plymouth
└── var
    :(略)
    └── log
        ├── boot.log
        ├── dmesg
        ├── dmesg.0
        ├── kern.log
        ├── syslog
    :(略)
    13 directories, 35 files
$ sudo du -sh /var/lib/lxc/overlayfs01/delta0
172K    /var/lib/lxc/overlayfs01/delta0

上記のようにroot/testfileが作成されていますし、その他コンテナ起動後に作成されたログなどのファイルも上層側のディレクトリに作成されているのがわかります。"whiteout"機能もしっかり使われています。上層側ディレクトリの容量も増えていますね。

このようにoverlayfsを使うと素早く、使用する容量も少なくコンテナのクローンが取得できます。overlayfsによるクローンの場合、元のコンテナを下層側に使っており、これまで見たようにコンテナイメージとしては更新されたファイルしかありません。

このような状態でクローン元のコンテナがなくなってしまってはクローンしたコンテナの起動に困りますね。そこでLXCのコマンドやライブラリを使っている限りはこのような問題は起きないような工夫がされています。

クローンを作成すると、クローン元のコンテナ用ディレクトリの下に、自身のクローンが作成されたことを示すファイルが作成されます。

ubuntu@gihyo:~$ sudo ls -F /var/lib/lxc/ct01/
config  lxc_snapshots  rootfs/
ubuntu@gihyo:~$ sudo cat /var/lib/lxc/ct01/lxc_snapshots
1

上記のようにlxc_snapshotsというファイルが作成され、いくつスナップショットが取られているかが書かれています。この状態でクローン元のコンテナであるct01を削除しようとすると、以下のようにエラーとなります。

$ sudo lxc-destroy -n ct01
lxc_container: lxccontainer.c: lxcapi_destroy: 2063 container ct01 has dependent snapshots
Destroying ct01 failed

もちろん、ct01用のディレクトリやファイルはLXCのコマンドを使わないでrmコマンドなどで消去できますので、そのような操作をした場合にはクローンしたコンテナは起動しません。注意が必要です。

クローンで作成されたコンテナのクローン

overlayfsによるクローンで作成されたコンテナの更にクローンをoverlayfsで作成するとどうなるでしょうか。さっそくやってみましょう。

$ sudo time -p lxc-clone -o overlayfs01 -n overlayfs02 -s -B overlayfs
Created container overlayfs02 as snapshot of overlayfs01
real 0.05
user 0.00
sys 0.00

overlayfs01コンテナをクローンしたときと同様に一瞬でクローンが作成されました。できあがったコンテナを先ほどと同様に確認していきましょう。

まずはlxc.rootfsがどのように設定されているのか確認です。

$ sudo grep lxc.rootfs /var/lib/lxc/overlayfs02/config
lxc.rootfs = overlayfs:/var/lib/lxc/ct01/rootfs:/var/lib/lxc/overlayfs02/delta0

overlayfs01コンテナでも下層側ディレクトリに指定されていたct01のコンテナイメージがそのまま下層側に指定されています。上層側ディレクトリはoverlayfs02コンテナ用のディレクトリですね。

それではクローン元のoverlayfs01コンテナで行われた変更はどうやってクローン先のoverlayfs02コンテナに反映されているのでしょうか? 実は、overlayfsのコンテナからoverlayfsのコンテナへクローンすると、rsyncによって上層側のディレクトリがコピーされます。

つまりクローン時のクローン元のコンテナのファイルがコピーされるだけなので、クローンした後にクローン元overlayfs01の状態が変わっても、それはクローン先overlayfs02には反映されません。

overlayfs01コンテナとoverlayfs02コンテナの上層側ディレクトリの使用量を比べてみましょう。

ubuntu@gihyo:~$ sudo du -sh /var/lib/lxc/overlayfs01/delta0
172K    /var/lib/lxc/overlayfs01/delta0
ubuntu@gihyo:~$ sudo du -sh /var/lib/lxc/overlayfs02/delta0
172K    /var/lib/lxc/overlayfs02/delta0

同じだけ使用していますね。使用量だけ見てもわかりませんので、実際にそれぞれのdelta0ディレクトリのツリーを比較してみましょう。

$ diff <(sudo tree /var/lib/lxc/overlayfs01/delta0) <(sudo tree /var/lib/lxc/overlayfs02/delta0)
1c1
< /var/lib/lxc/overlayfs01/delta0
---
> /var/lib/lxc/overlayfs02/delta0

ちょっとわかりづらいコマンドの実行ですね。上記の例はそれぞれのコンテナのdelta0ディレクトリを引数に指定してtreeコマンドを実行した出力をdiffで比較しています。treeコマンドの1行目は引数で指定したディレクトリの部分だけが違っているのがわかります。

存在するファイルやディレクトリはクローン直後は同じであることがわかりますが、前述のようにコンテナの/etc/hostnameの変更はされています。

$ sudo cat /var/lib/lxc/overlayfs02/delta0/etc/hostname
overlayfs02

そして、下層側ディレクトリで指定されているct01にクローン時にできたlxc_snapshotsファイルの中身は、依存するコンテナが2つになったので"2"となっています。

$ sudo cat /var/lib/lxc/ct01/lxc_snapshots
2

ちなみにこのlxc_snapshotsファイルは、依存するコンテナが全て消去された場合には、中身が"0"となるだけで消去はされません。

aufsによるクローン

ここまでoverlayfsを使ったクローンを紹介してきました。aufsを使う場合も、ここまでの例で"overlayfs"と指定していた部分を"aufs"に置き換えてそのまま実行できます。確認しておきましょう。

$ sudo time -p lxc-clone -o ct01 -n aufs01 -s -B aufs (aufsによるクローン)
Created container aufs01 as snapshot of ct01
real 0.17
user 0.00
sys 0.00
$ sudo ls -F /var/lib/lxc/aufs01/ (クローン先コンテナ用ディレクトリ内の確認)
config  delta0/  lxc_rdepends  rootfs/
$ sudo grep lxc.rootfs /var/lib/lxc/aufs01/config (lxc.rootfsの設定の確認)
lxc.rootfs = aufs:/var/lib/lxc/ct01/rootfs:/var/lib/lxc/aufs01/delta0

overlayfsよりは少し時間がかかっているようですね。でも一瞬でクローンが終わるのには変わりません。コンテナ用ディレクトリの下にできるファイルやディレクトリは、overlayfsでのみ必要なworkdir用ディレクトリ以外は同じですね。lxc.rootfsに設定される値も"overlayfs"となっていた部分が"aufs"になっているだけです。

$ sudo du -sh /var/lib/lxc/aufs01/delta0 (コンテナイメージの容量の確認)
20K /var/lib/lxc/aufs01/delta0
$ sudo tree -A /var/lib/lxc/aufs01/delta0 (コンテナのルート以下の確認)
/var/lib/lxc/aufs01/delta0
└── etc
    └── hostname

1 directory, 1 file
$ sudo cat /var/lib/lxc/aufs01/delta0/etc/hostname (ホスト名書き換えの確認)
aufs01

overlayfsと同様に、クローン直後はaufsで差分を記録する側のディレクトリであるdelta0以下には/etc/hostnameが存在しているだけですので使用量はわずかです。

ではコンテナを起動してマウントされている様子を見てみましょう。

$ sudo lxc-start -n aufs01 -d
$ sudo lxc-attach -n aufs01 -- cat /proc/mounts | grep aufs
(コンテナ内でマウント情報を確認)
/var/lib/lxc/ct01/rootfs / aufs rw,relatime,si=8b3011feb4cad46c 0 0

マウントされていますね。この情報からはわかりませんが、delta0ディレクトリを読み書き可能で、元のコンテナのコンテナイメージを読み込み専用と指定してマウントしています。

$ sudo lxc-stop -n aufs01
$ sudo du -sh /var/lib/lxc/aufs01/delta0
176K    /var/lib/lxc/aufs01/delta0

コンテナの起動によってログなどが増えたため、delta0ディレクトリの容量が増えていることがわかります。

overlayfsと同様に、aufsを使ったクローンで作成したコンテナを更にaufsでクローンできます。

$ sudo lxc-clone -o aufs01 -n aufs02 -s -B aufs (aufsコンテナをaufsクローン)
Created container aufs02 as snapshot of aufs01
$ sudo grep lxc.rootfs /var/lib/lxc/aufs02/config (クローン先のlxc.rootfsの確認)
lxc.rootfs = aufs:/var/lib/lxc/ct01/rootfs:/var/lib/lxc/aufs02/delta0

aufsで差分を保存するためのディレクトリの中身がrsyncによってコピーされ、元のコンテナと重ね合わされるのもoverlayfsの場合と同じです。

一般ユーザによるクローン

Ubuntu 14.04 LTSのカーネルや、Plamo 5.3の3.17.6カーネルにはパッチが当たっており、ユーザ名前空間内で特権があればoverlayfsを使ってコンテナがクローンできます。つまりLXCで一般ユーザ権限がコンテナを起動できるように設定されていれば、overlayfsを使ってクローンを作成できます。

$ id -u
1000
$ lxc-clone -o ct01 -n overlay01 -s -B overlayfs
Created container overlay01 as snapshot of ct01
$ lxc-start -n overlay01 -d
$ lxc-attach -n overlay01 -- cat /proc/mounts | grep overlay
/home/ubuntu/.local/share/lxc/ct01/rootfs / overlayfs rw,nodev,relatime,lowerdir=/home/ubuntu/.local/share/lxc/ct01/rootfs,upperdir=/home/ubuntu/.local/share/lxc/overlay01/delta0 0 0

vanilla kernelだと、ユーザ名前空間内の特権ユーザではoverlayfsをマウントできませんので、3.18以降のoverlayfsをサポートするカーネルを使っても、一般ユーザはコンテナをoverlayfsでクローンできません。

同様にユーザ名前空間内の特権ユーザではaufsをマウントできませんので、一般ユーザはaufsを使ったクローンはできません。

※)
最近のaufsではallow_usernsというモジュールパラメータを有効にすると、ユーザ名前空間内の特権ユーザがaufsをマウントできるようになっているようです。そこで筆者が一般ユーザが起動したLXCコンテナでもaufsが使えるようなパッチを投稿し、マージされました。ただし、Ubuntuのカーネルに適用されているaufsのパッチは古いバージョンのようで、最新のUbuntu環境でもこのモジュールパラメータは使えないようです。

overlayfs、aufsを使ったクローンの際の注意点

クローン元のコンテナ

先にも述べたようにoverlayfs、aufsを使うと、ベースとなるコンテナを用意しておき、そこからの差分を重ねあわせすることによりコンテナの差分管理ができますので便利です。

しかし、ベースとなるコンテナ自体が変化すると、重ねあわせた結果も変化してきますので注意が必要です。

ベースとなる側、重ねあわせる側の両方が変化すると、重ねあわせた結果の予測が困難ですので注意しましょう。

overlayfsを使う際のファイルシステム

LXCではコンテナイメージの保存場所に使われているファイルシステムが何であるかに関わらず、ストレージバックエンドとして"dir"を指定してコンテナを作成できます。

たとえば、btrfsである領域に"dir"を指定してコンテナを作れます。このようにして作られたコンテナをクローンする場合でも、ストレージバックエンドとして"aufs"、"overlayfs"が指定できます。しかし、前回説明したように、現時点ではoverlayfsはext4上でないと動きません。

LXCでdirストレージバックエンドを使用している場合に、実際のファイルシステムが何であるのかLXCは認識していませんので、btrfs上にdirストレージバックエンドを指定して作成したコンテナをoverlayfsでクローンしても、LXCではエラーにはなりません。しかし、正常に動作しませんので注意してください。

LXCのoverlayfs対応

前回説明したように、3.18カーネル以降でovarlayfsのファイルシステムタイプが"overlay"となりました。

しかし執筆時点では、LXC 1.0系列はタイプ名の"overlay"への変更に対応できていませんので、最新のLXC 1.0.7では3.18カーネルのoverlayfsは使えません。最新の開発版であるLXC 1.1系列は既に対応が終わっていますので使えます。すでに1.0系列用のブランチでもパッチがマージされていますので、次の1.0.8で対応されるはずです。

なお、"overlay"というタイプ名への変更に対応できていないだけで、workdirオプションが必要になった新しい仕様には対応しています。3.14~3.17カーネルにoverlayfsのパッチを当てて構築したカーネルの場合、つまりタイプ名は"overlayfs"でworkdirオプションが必要という場合は、筆者のパッチで対応していますので、LXC 1.0.7以降であれば使えます。Plamo 5.3のカーネルが当てはまります。

まとめ

今回はoverlayfsとaufsを使ったコンテナのクローンを細かく見ました。

LXCでこれらのファイルシステムを使って、コンテナのストレージバックエンドとしてdirを使う最も一般的なケースで、コンテナの差分管理ができるようになっていることがご理解いただけたのではないでしょうか。

次回以降も、今回紹介できなかったストレージバックエンドを使った場合のLXCの使い方や動きを紹介していきます。

LXC 1.1.0

今回の原稿を書いている最中にLXCの初のメジャーバージョンアップである1.1.0がリリースされました。

LXC 1.1.0の主な新機能は

  • CRIUを使ったコンテナのチェックポイント・リストア
  • initとしてsystemdを使ったコンテナのサポート

の2点です。

他にも細かい新機能や修正が多数ありますので、詳しくはLXCのNewsページをご覧ください。日本語訳もあります。

おすすめ記事

記事・ニュース一覧