Docker volume の公式ドキュメントが分かりやすかった話

# はじめに
最近、[くろかわこうへい](https://www.youtube.com/channel/UCX30pfp4p82rIiSJmBygADw)さんや、[KENTA](https://www.youtube.com/channel/UC_HLK-ksslL-Z_2wiIZDlMg)さんの動画をよく見る中で「あれ、Docker できないのやばくね？」と、ふと思いDocker の学習を始めました。参考書やネットでちょっと調べていくと「あれ、思ってたよりも難しくないじゃん」と思っていた矢先に、volume が出てきて一気につまづきまいした。記事などもいろいろ読んで調べてみたものの、よくわからず挫折しかけていた時に、公式ドキュメントに出会い読んでみると「何これわかりやすいじゃん！」ってなりました(笑)。


ということで、公式ドキュメントを読んで自分なりに理解したことをまとめます。


# 実行環境
* Windows Home 10
* Docker Toolbox v19.03.1

# この記事を読むとわかること
* volume とは
* Dockerをデータの永続化の具体的な方法
* データのマウントの具体的な挙動及び実装方法
  * bind mount
  * volume
  * tmpfs mount 

# Volume には 2 つの意味がある
後述するが、データを永続化させる方法は `bind mount` と `volume` の2 種類ある。ただ、どちらも `-v` や `--volume` を使う。つまり、**volume** という単語には 2 つの意味があることになる。

* データをマウントするための機能（-v, --volume）
* Dockerのリソース内にあるコンテナの実行データを保管する場所

この記事では前者の機能を示す `volume` を データのマウントと定義する。

# なぜデータのマウントが必要か
そもそもなぜ、データのマウントが必要なのか？

それは、コンテナ内部にデータを保存しても、コンテナ破棄すると消えてしまうため、データを永続化する際は、コンテナの外にデータを置く必要があるため。

# データをマウント方法は 3 つ
### 概要

データのマウント方法は 3 つある。データを永続化させる場合は `Bind mount` か `Volume` を使う。

 * Bind mount
 * Volume
 * Tmpfs mount

| 種類        |  データの保管場所  | 永続化 |
|:-----------:|:-----------------:|:-------:|
| Bind mount  | ホストディレクトリ | ○ |
| Volume      | Docker のリソース | ○ |
| Tmpfs mount | メモリ            | × |

![data-mount](https://docs.docker.com/storage/images/types-of-mounts.png)
出典：[docker docs](https://docs.docker.com/storage/)

### Bind mount
* 特徴
  * **ホストディレクトリやファイル**がコンテナにマウントされる
  * コンテナ内のデータの変更に応じて**ホスト側**のファイルも変更される


* 用途
  * データを永続化させたい
  * 開発環境でホスト側のソースコードの修正を反映させたい
    
        ※ 本番環境のイメージではマウントでデータをコンテナに反映させるのではなく、`copy`を使うことが推奨されている。

* 実装方法
  * `-v` または `--volume` を使う

         オプションの設定が **1 つ**のフィールドに固まっており、コンテナにデータのマウント先のディレクトリがなければ**自動的に作成**される。

  * `--mount` を使う

         オプションの設定が**複数**のフィールドに分かれているコンテナにマウント先のディレクトリが無ければ、**エラー**を返す。(※筆者の環境ではディレクトリが自動作成された)

* 例
volume_testディレクトリをマウントしてみる。ディレクトリの中は text.txt のみ。

```
volume_test
    test.txt
```

テキストファイルの内容。

```test.txt
test
```

volume_testディレクトリに移動する。

まずは、`-v` （`--volume`）コマンドでカレントディレクトリを `ubuntu` コンテナ内のすでに**存在する** `/usr/src/var/tmp` にマウントしてみる。

```
> docker run -it --name bindmount -v $(pwd):/usr/src/var/tmp ubuntu bash
root@eab26b27a693:/# cd usr/src/var/tmp
root@eab26b27a693:/usr/src/var/tmp# ls
test.txt
```

きちんと ディレクトリのデータがマウントされていることが分かる。ファイルの内容も`test` から `mount success!` に変更してみる。

```
root@eab26b27a693:/usr/src/var/tmp# cat test.txt
test
root@eab26b27a693:/usr/src/var/tmp# echo mount success! > test.txt
root@eab26b27a693:/usr/src/var/tmp# cat test.txt
mount success!
```

実際にホスト側のファイルを確認してみると変更が反映されている！

次に、 `ubuntu` コンテナ内に**存在しない** `/volume_test` にマウントしてみる。

```
> docker run -it --name bindmount -v $(pwd):/volume_test ubuntu bash
root@b3e9bf711b7e:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var  volume_test
root@b3e9bf711b7e:/# cd volume_test
root@b3e9bf711b7e:/volume_test# ls
test.txt
```

**存在しない**ディレクトリにマウントした際には新しくディレクトリが作られている。先程と同様にファイルの内容も`test` から `mount success!` に変更してみる。

```
root@b3e9bf711b7e:/volume_test# cat test.txt
test
root@b3e9bf711b7e:/volume_test# echo mount success! > test.txt
root@b3e9bf711b7e:/volume_test# cat test.txt
mount success!
```

こちらでもホスト側のファイルを確認してみると変更が反映されている！

次に `--mount` コマンドでカレントディレクトリを `ubuntu` 内のすでに**存在する** `/usr/src/var/tmp` にマウントしてみる。`-v` とは違いマウントの仕方やホスト側のディレクトリ、マウントのディレクトリといった複数のオプションをカンマつなぎで記述する。

```
> docker run -it --name bindmount --mount type=bind,source="$(pwd)",target=/usr/src/var/tmp ubuntu bash
root@f77d388c204e:/# cd usr/src/var/tmp
root@f77d388c204e:/usr/src/var/tmp# ls
test.txt
```
 
`--mount` を使った場合でも同様にマウントできた。実際にコンテナ内のファイルを編集した際は上記の内容と同様に、ホスト側のファイルも編集された。

次に、`ubuntu` コンテナに**存在しない**ディレクトリ( `volume_test` )にマウントしてみる。

```
docker run -it --rm --name bindmount --mount type=bind,source="$(pwd)",target=/volume_test ubuntu bash
root@a738ae0ebbd0:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var  volume_test
```

`volume_test` が存在している!!! マウントできた!?

公式ドキュメントによると、
> If you use --mount to bind-mount a file or directory that does not yet exist on the Docker host, Docker does not automatically create it for you, but generates an error.
出典：[docker docs](https://docs.docker.com/storage/bind-mounts/)

つまり、`--mount` を使ってコンテナにバインドマウントする際は、コンテナ内にすでに存在するディレクトリにマウントしないとエラーが出る。なぜできたのだろう??

バインドマウントできているか確かめてみる。

```
root@a738ae0ebbd0:/# cd volume_test/
root@a738ae0ebbd0:/volume_test# cat test.txt
test
root@a738ae0ebbd0:/volume_test# echo mount sucess! > test.txt
root@a738ae0ebbd0:/volume_test# cat test.txt
mount sucess!
```

ホスト側のファイルも変更されているので、バインドマウントができている。なぜだろう？？

ちなみに`-v`, `--mmount` どちらを使っても**マルチコンテナ間**でもデータの共有は可能。


### Volume

* 特徴
  * docker リソース内のデータがコンテナにマウントされる
  * ホストディレクトリに依存しない


* 用途
  * データを永続化させたい
  * 開発環境でソースコードの修正を反映させる以外の目的

* 実装方法
`bind mount` と同じ。`-v` または `--mount` を使う。

* 例
まずは `volume_test` という名前のボリュームを作る。

```
$ docker volume create volume_test
volume_test
$ docker volume ls
local               volume_test
```

まずは、`-v` （`--volume`）コマンドでカレントディレクトリを `ubuntu` コンテナ内のすでに**存在する** `/usr/src/var/tmp` にマウントしてみる。また、ボリュームが**存在しない**場合は自動でボリュームが作成される。`test.txt` に mount success! と入力してコンテナを閉じる。

```
$ docker run -it --name volume_mount -v volume_test:/usr/src/var/tmp ubuntu bash                                        root@fe23da2e739c:/# cd /usr/src/var/tmp
root@fe23da2e739c:/usr/src/var/tmp# ls
root@fe23da2e739c:/usr/src/var/tmp# touch test.txt
root@fe23da2e739c:/usr/src/var/tmp# echo mount success! > test.txt
root@fe23da2e739c:/usr/src/var/tmp# cat test.txt
mount success!
root@fe23da2e739c:/usr/src/var/tmp# exit
```

コンテナを再度立ち上げて、test.txtが存在するか確認してみる。

```
$ docker container rm volume_mount
volume_mount
$ docker run -it --name volume_mount -v volume_test:/usr/src/var/tmp ubuntu bash
root@e54d18741135:/# cat usr/src/var/tmp/test.txt
mount success!
```

マウントできている！`ubuntu` コンテナに**存在しない**ディレクトリにマウントしても同様の結果が得られる。

次に、次に `--mount` コマンドでカレントディレクトリを `ubuntu` 内のすでに**存在する** `/usr/src/var/tmp` にマウントしてみる。先程と同様に、`test.txt` に mount success! と入力してコンテナを閉じる。

```
$ docker run -it --name volume_mount --mount source="volume_test",target=/usr/src/var/tmp ubuntu bash                   
root@03074d3166a9:/# echo > usr/src/var/tmp/test.txt
root@03074d3166a9:/# cat usr/src/var/tmp/test.txt

root@03074d3166a9:/# echo mount success! > usr/src/var/tmp/test.txt
root@03074d3166a9:/# cat usr/src/var/tmp/test.txt
mount success!
root@03074d3166a9:/# exit
```

再度、`volume_test` をマウントしてコンテナを立ち上げる。

```
$ docker run -it --name volume_mount --mount source="volume_test",target=/usr/src/var/tmp ubuntu bash
root@9fe26cd9575f:/# cat usr/src/var/tmp/test.txt
mount success!
```

マウントできている！`ubuntu` コンテナに**存在しない**ディレクトリにマウントしても同様の結果が得られる。

ちなみに`-v`, `--mmount` どちらを使っても**マルチコンテナ間**でもデータの共有は可能。
 
### Tmpfs mount
* 特徴
  * **マルチコンテナ間で**データの共有はできない
  * コンテナが止まるとホストメモリ上に保存されたファイルは消える
  * **Docker on Linux** でのみ動作する

* 用途
  * 一時的にデータを退避させたい
  * セキュリティ的な理由で、ホストディレクトリやDocker リソース内の書き込み可能な領域にファイルを保管したくない

* 実装方法
  * `-tmpfs` を使う

         マウントディレクトリ以外のオプションの設定が不可、コンテナにデータのマウント先のディレクトリがなければ**自動的に作成**される。

  * `--mount` を使う

         オプションの設定が**複数**のフィールドに分かれているコンテナにマウント先のディレクトリが無ければ、**エラー**を返す。

# まとめ
* データの永続化の種類は二つ
    * bind mount
    * volume


それぞれの違いは、永続化データの保存領域。使い分けは、開発においてソースコードの修正をコンテナにも反映させたいといったホストOSのファイルに依存することなら **bind mount** そうでなければ、**volume** を使う。

* 一時的にデータを退避させるなら **tmpfs mount** を使う。

初心者なので、至らぬところはあるかと思いますがご指摘いただいたけると幸いです :bow_tone1:

# 参考
* https://docs.docker.com/storage/volumes/
* https://qiita.com/gounx2/items/23b0dc8b8b95cc629f32