Overlay でLXC コンテナを作成する

# 非overlay なコンテナ
普段Linux の仮想化を行うときに動作の軽さの観点からLXC をよく使うのですが、コンテナを複製するときに普通にlxc-copy(旧lxc-clone) を使用した場合、数百MB あるファイルがそのまま複製されてしまい、ディスク領域を効率的に利用することができません。
今回はそれを解消するため、コンテナを複製するときにoverlay を使用して効率よくファイルを管理するように対応をしてみました。

# overlay とは
overlay を使用して、コンテナのrootfs ファイルを読み取り専用の領域と読み書き可能な領域を分け、その両方のファイルをマージしたものを新コンテナとして使用することができるようになります。
複製した新しいコンテナで発生したファイルの変更は読み取り変更領域からコピーされ、読み書き可能領域で管理されるようになるため、必要最低限の差分ファイルのみ変更管理をすれば良くなるため、ディスクの領域を効率 良く使用することができます。

今回はoverlay を方式のLXC コンテナの作成についてをメインに説明していきますので、overlay の詳しい説明については他の参考資料を提示することにして、ここでは割愛させていただきます。

- OverlayFSについて
    - http://qiita.com/awakia/items/a9296840349f0b6f4482
- 実践! OverlayFS
    - http://qiita.com/awakia/items/af9b46c322905cdce1d7

# 環境
今回LXC のoverlay を試した環境は次の通りです。

* 記号表示サンプル(一例)
<table><tbody>
  <tr>
    <th> Linux ディストリビューション</th>
    <td> Fedora24</td>
  </tr>
  <tr>
    <th> カーネルバージョン</th>
    <td> 4.6.3-300.fc24.x86_64</td>
  </tr>
  <tr>
    <th> パッケージ</th>
    <td> libvirt-daemon-driver-lxc-1.3.3.1-4.fc24.x86_64, lxc-templates-2.0.3-1.fc24.x86_64, lxc-libs-2.0.3-1.fc24.x86_64, lxc-2.0.3-1.fc24.x86_64</td>
  </tr>
</tbody></table>

LXC のインストール手順については割愛します。
今回はすでにLXC がインストールされている前提で説明を進めていきます。

## 今回の手順を行った時に直面した問題について
今回Fedora template なLXC をoverlay で複製する手順について検証していったのですが、その際にいくつかの問題に直面しました。
このいくつかの問題に対して、あらかじめ対策をしておきます。
この対策方法は今後改善される可能性があるので、そうなった場合は本手順は無視して下さい。

### コンテナ上で一部のパッケージ(例えばhttpd)をインストールするときに出るエラーの対応
Fedora テンプレートのコンテナ上で一部のパッケージ(今回の場合はhttpd)をインストールしようとすると、以下のようなエラーが出力されます。

```text
Error unpacking rpm package httpd-2.4.23-3.fc24.x86_64
Error unpacking rpm package httpd-2.4.23-3.fc24.x86_64
error: unpacking of archive failed on file /usr/sbin/suexec;57870c2e: cpio: cap_set_file
httpd-2.4.23-3.fc24.x86_64 was supposed to be installed but is not!
```


このエラーが出てしまった場合は、ホストOS 側のテンプレート設定ファイルの中にある`setfcap` の文字を取り除いてください。

```bash:/usr/share/lxc/config/fedora.common.conf
......
##lxc.cap.drop = setfcap sys_nice sys_pacct sys_rawio
lxc.cap.drop = sys_nice sys_pacct sys_rawio
```


取り除いたらコンテナを一度シャットダウンし、そのあとに起動してください。

```text
fedora00~# shutdown -h now
# lxc-start -n fedora00
# lxc-ls -f
NAME        STATE   AUTOSTART GROUPS IPV4            IPV6
fedora00    RUNNING 0         -      192.168.122.246 -
fedora_base STOPPED 0         -      -               -

# ssh root@192.168.122.246

// 作業時点では、コンテナ上で"shutdown -r now" では反映されませんでした...
```


### overlay 形式でlxc-copy するときに出るエラー
契機は不明だが、`lxc-copy` コマンドを実行すると以下のようなメッセージが含まれる`overlayfs` デバイスが見つからないといったエラーが出ることがある。

```console
...No such device - overlayfs:
```


普段は出ないので、再現したときにもう少し調査をしてみようと思います。
事象は、以下リンクのものと同様と思われます。

```text
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=797694
```

## fedora コンテナを作成する
それでは実際にoverlay を使ってコンテナを作成していきましょう。
まずは、コンテナのcopy 元となるコンテナを`fedora_base` という名前で作成します。

```console
# lxc-create --name fedora_base --template fedora
or
# lxc-create --name fedora_base --template fedora --backingstore=dir
```


lxc-create コマンドは`--backingstore` オプションを何も指定しない場合はバッキングストアはdir 形式となり、rootfs ファイルのすべてのコピーが`/var/lib/lxc/fedora_base` ディレクトリに作成されるようになります(非overlay な状態)。
複製元となるコンテナはこのdir 形式でバッキンぐストアを作成する必要があります。

コンテナを作成したら、コンテナのroot パスワードを設定します。

```console
# chroot /var/lib/lxc/fedora_base/rootfs passwd
```


以上でcopy 元コンテナの作成は完了です。

## コンテナをoverlay で複製する
`lxc-copy` コマンドを使用して、バックエンドをoverlay 形式で作成します。
コマンドの指定は`lxc-copy` コマンドに`--backingstorage overlayfs` と`--snapshot`を追加します。


```console:lxc-copy
# lxc-copy --name fedora_base --newname fedora00 --backingstorage overlayfs --snapshot
```


ここで、一旦overlayfs で作成したコンテナのrootfs を確認してみましょう。

```console
# find /var/lib/lxc/fedora00/ -type f
/var/lib/lxc/fedora00/config
/var/lib/lxc/fedora00/delta0/etc/hostname
/var/lib/lxc/fedora00/lxc_rdepends
# ls -l /var/lib/lxc/fedora00/rootfs/
total 0
```

複製した直後の新コンテナは上記のファイルしかありません。
しかも、コンテナのディレクトリ配下の`rootfs` 配下はファイルがありません。

これはコンテナの複製がoverlay 形式で行われることによって、複製元となるコンテナ(fedora_base)の`rootfs` ディレクトリを`lower`、複製したのコンテナ(fedora00) の`delta0` ディレクトリを`upper` としてoverlay マウントしている状態になります。

そうなることにより、以降、fedora00 上に新しいファイルが作成された場合には、fedora00 のdelta0 ディレクトリにファイルが新規に作成され、fedora_base から存在するファイルに対して変更が発生した場合はfedora_base のrootfs ディレクトリからfedora00 のdelta0 ディレクトリにファイルがコピーされ、fedora00 のdelta0 上のファイルに対して変更が加えられ、fedora00 コンテナ上のファイル(merge 領域)として反映されます(copy on write)。
![LXC_CloneWithOverlay0000.png](https://qiita-image-store.s3.amazonaws.com/0/70152/771c179b-822e-2483-a8f4-96bd784332be.png)


それでは、新しくoverlay で作成したコンテナを起動させてみましょう。

```console
# lxc-start -n fedora00
......
# lxc-ls -f
NAME        STATE   AUTOSTART GROUPS IPV4            IPV6
fedora00    RUNNING 0         -      192.168.122.116 -
fedora_base STOPPED 0         -      -               -
```


コンテナを確認すると、192.168.122.116 のIP アドレスが今回は割り与えられました。
ssh でゲストOS へログインして、httpd をインストールしてみます。


```console
# ssh root@192.168.122.116
fedora00~# dnf install -y httpd
...
# exit
```


httpd のインストールが完了したらLXC ホストOS 側でもう一度、fedora00 のファイル状況を確認してみましょう。


```console
[root@myserver ~]# find /var/lib/lxc/fedora00/delta0 -type f
......
/var/lib/lxc/fedora00/delta0/etc/httpd/conf.d/README
/var/lib/lxc/fedora00/delta0/etc/httpd/conf.d/autoindex.conf
/var/lib/lxc/fedora00/delta0/etc/httpd/conf.d/userdir.conf
/var/lib/lxc/fedora00/delta0/etc/httpd/conf.d/welcome.conf
/var/lib/lxc/fedora00/delta0/etc/httpd/conf/httpd.conf
......
```

すると、overlay のupper 領域であるfedora00 のdelta0 にhttpd のファイルが作成されたことが確認できます。
これは以下の図のように更新対象のファイルが`lowerdir` から`upperdir` にコピーされ、`upperdir` 上でファイルが更新(もしくは新規作成)されるといったコピーオンライトが行われていることになります。
![LXC_CloneWithOverlay0001.png](https://qiita-image-store.s3.amazonaws.com/0/70152/5e475aac-01e2-1443-a070-d4cf92a29e13.png)


### 複製先のfedora00 コンテナが起動している状態で、複製元のfedora_base コンテナを起動して変更するとどうなる？
複製元のfedora_base コンテナのファイルを変更すると複製先のfedora00 コンテナのコピーオンライトされていないファイルは変更されてしまうのか実験してみました。
fedora00 が既に起動している状態で複製元の`fedora_base` を起動します。


```console
# lxc-start -n fedora_base
# lxc-ls -f
NAME        STATE   AUTOSTART GROUPS IPV4            IPV6
fedora00    RUNNING 0         -      192.168.122.116 -
fedora_base RUNNING 0         -      192.168.122.50  -
```


複製元のコンテナにログインし、ファイルを作成してみます。

```console
# ssh root@192.168.122.50
fedora_base~# echo "bar" > /root/foo.txt
fedora_base~# cat /root/foo.txt
bar
fedora_base~# exit
```


次に複製先のコンテナへログインし、先ほど作成したファイルと同じものが存在するか確認してみます。

```console
# ssh root@192.168.122.116
fedora00~# cat /root/foo.txt
bar
```


複製先のコンテナにもファイルができちゃっていました(overlay であるということを考えれば、ある意味当然ですが…)。
現状、コンテナをoverlay で複製した場合、複製元のコンテナは変わらず更新可能な状態となっている(lxc で読み取り専用にしてくれない)ため、overlay で複製した場合は意識して複製元を更新しないようにしておく必要が あります。

## 同じ複製元から追加でコンテナを複製する
1 つの元コンテナから複数のoverlay を使ったコンテナを作ることも、問題なくできます。

`fedora_base` コンテナから`fedora00` コンテナを作成しましたが、同じ複製元コンテナ`fedora_base` から2 つ目のコンテナ`fedora01` を作成するコマンドは次の通りです。


```console
# lxc-copy --name fedora_base --newname fedora01 --backingstore overlayfs --snapshot
```


このコンテナでは、overlay の構成としては、`lowerdir=/var/lib/lxc/fedora_base/rootfs`, `upperdir=/var/lib/lxc/fedora01/delta0` となります。
当然`fedora01` は通常のコンテナとして使用することができますし、既存の`fedora00` に変更影響を及ぼすこともありません。

## overlay なコンテナからoverlay なコンテナを複製する
overlay なコンテナからoverlay なコンテナを複製することができます。
例えば、以下は`fedora_base` からoverlay で複製した`fedora00` コンテナから、overlay で`fedora00_00` を複製するには以下の通りです。


```console
# lxc-copy --name fedora00 --newname fedora00_00 --backingstore overlayfs --snapshot
```


ここで、一点注意していただきたいのは、/var/lib/lxc 配下のファイルを確認するとわかるのですが、新しく作成されたコンテナ`fedora00_00` のoverlay ディレクトリ構成は`lowerdir=/var/lib/lxc/fedora_base`, `upperdir=/var/lib/lxc/fedora00_00/delta0` となる点です。
これだと`fedora00` のデータが`fedora00_00` に引き継がれていないように見えますが、`lxc-copy` コマンド実行時に`/var/lib/lxc/fedora00/delta0` ディレクトリにあるファイルは`/var/lib/lxc/fedora00_00/delta0` ディレクトリにコピーされるため、結果としてデータが引き継がれていることになります。
よって上記overlay ディレクトリ構成になっていることからもわかるように`fedora00` コンテナと`fedora00_00` コンテナとの間にはoverlay 関係は無いため、`fedora00` コンテナで発生した変更は`fedora00_00` コンテナ には影響し無いことになります。

## 複製元 コンテナから引き継がれたファイルを複製先コンテナで削除した場合のoverlay の挙動
複製元コンテナ`fedora_base` 上にあるファイルを複製先コンテナで削除した場合、overlay はどのような振る舞いをするか、見てみましょう。
今回は`fedora_base` コンテナにある`/root/foo.txt` を`fedora00` 上で削除してみます。


```console
# lxc-ls -f
NAME        STATE   AUTOSTART GROUPS IPV4           IPV6
fedora00    RUNNING 0         -      192.168.122.50 -
fedora_base STOPPED 0         -      -              -

# ssh root@192.168.122.50
fedora00 ~# rm -f /root/foo.txt
fedora00 ~# exit
# ls -la /var/lib/lxc/fedora00/delta0/root/
total 4
dr-xr-x--- 2 root root   42 Jul 19 11:53 .
drwxr-xr-x 6 root root   51 Jul 15 18:03 ..
-rw------- 1 root root  186 Jul 19 11:53 .bash_history
c--------- 1 root root 0, 0 Jul 19 11:53 foo.txt
```


すると、上記のようにmajour no 0、minor no 0 なキャラクタデバイスができていることが確認できます。
このようにして`upperdir` にキャラクタデバイスを作成することで、`lowerdir` にあるファイルをマスクすることで、複製されたコンテナ上では削除されたファイルとしてコンテナ上では表示されないようになります。
![LXC_CloneWithOverlay0002.png](https://qiita-image-store.s3.amazonaws.com/0/70152/30e64c57-1d6d-6a38-9839-d3754596efc0.png)

# overlay コンテナの削除
overlay なコンテナの削除は通常のコンテナの削除と同じ手順でできます。

```console
# lxc-destroy -n fedora00
```


# 最後に
以上、overlay を使用したLXC コンテナの複製はいかがでしたでしょうか。
普段LXC コンテナを複製するときはoverlay を意識して使用することで、ディスク使用量を大幅に抑えることができます。
またコンテナ複製時にコピーするファイル数が通常のコンテナ複製よりも圧倒的に少ないため、例えば大量なAnsible 検証用コンテナを短時間で準備することができたりして、生産性向上に大きく貢献することができるでしょ う。


## 参考
- LXC でoverlayfs を使用する
    - https://the.binbashtheory.com/using-overlayfs-with-lxc/

- [Linux][Kernel][lxc] overlayfs と LXC 非特権コンテナの snapshot によるクローンAdd Starmomo-samurai
    - http://d.hatena.ne.jp/defiant/20140515/1400134893

- 削除されたファイルは、マイナーNo0, メジャーNo0 なキャラクタデバイスでマスクされる
    - https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt