Docker の Data Volume まわりを整理する


2月くらいに Docker の Data Volume について調べたことを、今更まとめました。
例によって間違いがあったら指摘していただけると嬉しいです。


# Volume 指定のオプションとそれぞれの意味

コンテナ起動時(docker run)の Data Volume を指定するオプションには下記があります。

| オプション | 意味 |
| -------- | ---- |
| `-v <host_path>:<container_path>` | ホストの `<host_path>` を `<container_path>` にマウントしてコンテナを起動 |
| `-v <container_path>` | Data Volume を作成して `<container_path>` にマウントしてコンテナを起動 |
| `--volumes-from <container>` | `<container>` で指定したコンテナの Data Volume を全部マウントしてコンテナを起動 |


`-v <host_path>:<container_path>` と `-v <container_path>` は挙動が違うため、混同していると Data Volume が何者かよくわからなくなります。

前者はホスト上の指定したディレクトリ `<host_path>` を `<container_path>` の位置に、 Data Volume としてマウントします。
動的にファイルをコンテナ内部で使いたい場合や、逆にホスト側のファイルを操作したい場合などに使われます。

一方、後者はコンテナを起動するタイミングでホスト上にディレクトリを作り、それを `<container_path>` の位置に Data Volume としてマウントします。
ホスト上のディレクトリを明示的に指定する場合と大きく違うのは、コンテナを削除するとそのボリュームが消える(正しくは参照が切れる)という点です。
また、マウントされているディレクトリはホスト上のファイルシステムに作られますが、`/var/lib/docker`以下に隔離されているので、ホストに干渉しないという特徴を持ちます。

これらはいずれも Union Mount されるので、コンテナ内部からは一つのファイルシステムとして見えることになります。


# Data Volume とその実体とコンテナの関係

次のようなパラメータで container を run させたとして、これらと `--volumes-from` の関係を図にまとめると以下のような感じになります。

``` bash
$ docker run \
    --name test-data \
    -v /opt/data-volume \
    -v /home/core/test-data:/opt/host-volume \
    busybox

$ docker run \
    --name test-container \
    --volumes-from test-data \
    ubuntu /bin/bash
```

![data volume container.001.png](https://qiita-image-store.s3.amazonaws.com/0/11679/23298b0d-756f-9aa9-db9f-271c8e5e2adb.png "data volume container.001.png")


コンテナ起動時に -v オプションで作られる Data Volume の実際のディレクトリは `docker inspect` で調べることができます。

``` bash
$ docker run --name test-data -v /opt/data-volume -v /home/core/test-data:/opt/host-volume busybox
$ docker inspect --format='{{range $vol, $path := .Volumes}}{{$vol}}:{{$path}}{{"\n"}}{{end}}' test-data
/opt/data-volume:/var/lib/docker/vfs/dir/d040c42f82bf9b9ccddbd489df1c60ca7d396efd9098e8ad64c316974b56a05c
/opt/host-volume:/home/core/test-data

$ docker run -i -t --name test-container --volumes-from test-data ubuntu /bin/bash
$ docker inspect --format='{{range $vol, $path := .Volumes}}{{$vol}}:{{$path}}{{"\n"}}{{end}}' test-container
/opt/data-volume:/var/lib/docker/vfs/dir/d040c42f82bf9b9ccddbd489df1c60ca7d396efd9098e8ad64c316974b56a05c
/opt/host-volume:/home/core/test-data
```

この場合 `/var/lib/docker/vfs/dir/d040c42f82bf9b9ccddbd489df1c60ca7d396efd9098e8ad64c316974b56a05c` が起動時に作られた Data Volume の実体です。
ホストのディレクトリを指定してマウントしている場合と同様、このディレクトリ以下を編集すると、コンテナ内に反映されることになります。


# Data Volume Container

container 内から永続データを扱う方法について、[Managing Data in Containers](https://docs.docker.com/userguide/dockervolumes/) で紹介されている通り、`-v` オプションで Data Volume を持った状態のコンテナ(Data Volume Container と呼んでいるようです)を用意して、その Data Volume を `--volumes-from` でアプリケーションを動かすコンテナから参照するという方法があります。


Data Volume Container を使うことによって、永続データが隔離された状態になるので、ホスト側がコンテナに与える影響を最小限に抑えられていい感じになります。
逆にホストから簡単に参照したい場合(開発中とか)は、コンテナを通さないとアクセスできないので面倒かもしれません。

ちなみに Data Volume Container として立ち上げる際のイメージに busybox が指定されているのをよく見かけます。
別にこれは BusyBox が特殊な機能を持っているわけではなく、単に極めて容量が軽い Linux ディストリビューションだから選択されているものと思われます。
BusyBox も、コンテナのデータを保持するためのディストリビューションに自分が使われるとは夢にも思わなかったに違いない...。

コンテナが stop していてもメタデータは残るため、通常 Data Volume Container は stop された状態で存在し続けることになります。
Volumes を維持するためのメタデータが Data Volume Container の正体とも言えるんじゃないでしょうか。
(Data Volume Container にやや強引さを感じる一端)

# Data Volume はいつ消える？

Data Volume を作成したコンテナを削除(`docker rm <container>`)すると、メタデータが消えて Data Volume への参照は失われますが、Data Volume の実体は削除されないようです。
どこかのタイミングで Docker によってクリアされるのかもしれませんが、今の所消えるところを確認できていません。

実運用においては、頻繁に Data Volume を作ることは通常ないと思われますが、開発環境で実験をしたりしていると大量のゴミが溜まっていきます。
そのため、時々 [docker-cleanup-volumes](https://github.com/chadoe/docker-cleanup-volumes) などを使って掃除をすると良さそうです。
docker-cleanup-volumes は、どのコンテナからも参照されていないホスト上の Data Volume を `rm -rf` してくれるスクリプトです。

``` bash
# Dry-run で消える Data Volume のディレクトリ一覧を確認
$ docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /var/lib/docker:/var/lib/docker \
    martin/docker-cleanup-volumes --dry-run

# Data Volume のディレクトリをすべて削除
$ docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /var/lib/docker:/var/lib/docker \
    martin/docker-cleanup-volumes
```

# Data Volume Container のバックアップ

Data Volume Container のバックアップをとる方法ですが、[Managing Data in Containers](https://docs.docker.com/userguide/dockervolumes/) で挙げられているように、新しいコンテナ経由で `--volumes-from` で Data Volume にアクセスし、固めて取り出すといった方法をとる必要があります。
`docker commit` でなんとなくバックアップ用のイメージを作れそうですが、Data Volume は commit で作ったイメージには含まれません。

手動で tar に固めるのもなんだか泥臭いので、 [docker-backup](https://github.com/docker-infra/docker-backup) などを使うと良さげです。
中身は公式で紹介されている方法同様、立ち上げたコンテナに Data Volume をマウントして tar に固めたものを取り出しているだけですが、Data Volume Container の復元までサポートしてくれるし、面倒な作業をコマンドひとつに落とすことができます。


test-data という名前の Data Volume Container を、 --volumes-from で読み込んでいる test-container があるとして...

``` bash
# 既存環境でバックアップ

$ docker run --rm \
     -v /var/run/docker.sock:/var/run/docker.sock \
     -v /var/lib/docker/vfs/dir:/var/lib/docker/vfs/dir \
     -v $(pwd):/backup \
     dckr/docker-backup store /backup/test-container.tar test-container

$ ls
test-container.tar
```

``` bash
# 新しい環境で復元

$ docker inspect --format='{{range $vol, $path := .Volumes}}{{$vol}}:{{$path}}{{"\n"}}{{end}}' test-data
Error: No such image or container: test-data

$ ls
test-container.tar

$ docker run --rm \
     -v /var/run/docker.sock:/var/run/docker.sock \
     -v /var/lib/docker/vfs/dir:/var/lib/docker/vfs/dir \
     -v $(pwd):/backup \
     dckr/docker-backup restore /backup/test-container.tar

$ docker inspect --format='{{range $vol, $path := .Volumes}}{{$vol}}:{{$path}}{{"\n"}}{{end}}' test-data
/opt/data-volume:/var/lib/docker/vfs/dir/8e9b9bfcc37a8fd0800a84a08320b2d87678d69015d9fa1ad827bfad4157d732
```