「そのコンテナのデータ、消えるよ……」とさせないための、イメージ図＋実践で理解するDockerストレージ【Dockerコンテナ・グレートジャーニー③】

# Dockerコンテナグレートジャーニー
Dockerコンテナを０から理解する旅、Dockerコンテナ・グレートジャーニー第3回です。（本記事だけ読んでも大丈夫です）
今回はDockerコンテナを運用するうえで超重要な『ストレージ』について、イメージと実践を交えて解説します。
Dockerコンテナの場合、<font color="red">**適当に使っているといきなりデータが消し飛ぶ**</font>こともあるので、Docker初心者の方も、深く知らずに使っている方も、この機会にDockerストレージについて学んでいきましょう。

## 対象
* Dockerコンテナのストレージについてよくわからない人
* Dockerコンテナの揮発性について知らない人
* bindマウントとvolumeマウントの違いがわからない人

## 旅路（インデックス）
長いので記事を分割しています。
1. [そもそも仮想化とは？　仮想化ではないシステムとは？](https://qiita.com/Toyo_m/items/3060fc5961d62a840bee)
1. [Dockerコマンド基礎（環境構築～ubuntu/httpdコンテナでのコマンド実行）](https://qiita.com/Toyo_m/items/94de894a8558d94b02d1)
1. Dockerのストレージについて<font color="orange">【⇦本記事】</font>
1. [Dockerのネットワークについて](https://qiita.com/Toyo_m/items/52fa81948d5746dd2afc)
1. [Docker-compose でコンテナをまとめる](https://qiita.com/Toyo_m/items/ef14658629d3118003ea)
1. [Docker image と Dockerfile](https://qiita.com/Toyo_m/items/5713f85a9d5ff8d9cc0d)
1. [AWS ECS で Dockerコンテナを走らせる](https://qiita.com/Toyo_m/items/90df3c90353ed6fb3fd8)


# 【主題】dockerのストレージについて
## dockerコンテナ内で保存/編集したデータは揮発性！
「Dockerコンテナのデータが消える」と言いましたが、それはどのようなことを言っているのでしょうか？
その理由を1枚の図にまとめるとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/691affbf-5e61-66ac-09fd-446bceb39ff0.png)
消えるというのには3つの観点があり、
1. コンテナは、一度削除(rm)すると復元できない
DockerにはWindowsの「ごみばこ」のような機能はありません。
1. docker run などで次に作ったコンテナは、以前作ったものとは完全に別物
新しいコンテナ２を立ち上げても、それは最初のコンテナとは全く別のもので、前回の変更を引き継ぎません。
1. Dockerコンテナで行った編集は、Imageには反映されない
コンテナにいくら編集を施しても、その設計図たるImageには変更が反映されません。コンテナは形ある実体、対してImageとは設計図のようなものだからです。（オブジェクト指向で言うなら「クラスとインスタンス」と同じ関係性です）

こういった理由で、コンテナを一度破棄すると編集データが消えてしまい、復元する手段が無いのです。
ちょっとしたタスクに使うコンテナなら別に問題ないですが、例えばMySQLコンテナを立ち上げて重要データを保存していると、そのコンテナが落ちた時にデータが全て消えるのです。それはまずいですよね。

とはいえ実感がわかない方もいると思いますし、まずは実際にデータが消えるのかを実践してみましょう。
初めての方はぜひ実際に手を動かして試してみてください。
そんなことは知ってるよという方は、次章の「[ストレージを永続化する方法](https://qiita.com/drafts/95a2543c07dc5b126208/edit#%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8%E3%82%92%E6%B0%B8%E7%B6%9A%E5%8C%96%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)」までスキップしてください。

### 1. Ubuntu内のデータを編集する
Powershellを立ち上げて、Dockerコマンドでubuntuコンテナを起動、vim(エディタ)を使ってみます。
``` shell
# Ubuntuコンテナを作成(ローカルにイメージがなければダウンロードが発生する)
PS C:\Users\******> docker container run -it --name ubuntu22 ubuntu:22.04
root@acc351c001c4:/#
# vim でテキストファイルを編集...
root@acc351c001c4:/# vim usr/src/test.txt
bash: vim: command not found
```
デフォルトのubuntuにはvimはインストールされていないので使えません。インストールする必要がありますね。下記のコマンドを打ちこんでvimをインストールしましょう。
``` shell
apt-get update
apt-get install vim
```
インストール出来たら、今度こそ編集していきます。

``` shell
vim usr/src/test.txt
```
するとエディタが立ち上がります。好きな文字を入力して保存してください。

（以下Linux詳しくない方向けの説明）
1. `i`を押すと編集モードになります。
1. 好きな文字列を入力します
1. `Esc`を押して編集モードを抜けます。
1. `:wq`を入力してからEnter を押して、編集を保存して閉じます。
1. 確認のため、`cat usr/src/test.txt`を実行します。
1. 変更が確認出来たら`:q`と入力してEnter。保存せずに終了できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/99d573cc-c832-6498-15da-bcfd56860a0f.png)
これで保存できました。

### 2. コンテナを破棄する
「Ctrl+P -> CtrlQ」でコンテナを抜けて、コンテナを破棄しましょう。
``` shell
docker container stop ubuntu22
docker container rm ubuntu22
```
一応`docker container ls -a` でコンテナが完全に消えていることを確認しておきましょう。

### 3. もう一度コンテナを作る
``` shell
# Ubuntuコンテナを再作成
PS C:\Users\******> docker container run -it --name ubuntu22 ubuntu:22.04
root@acc351c001c4:/#
# vim でテキストファイルを編集
root@acc351c001c4:/# vim usr/src/test.txt
bash: vim: command not found
```
おや、先ほどダウンロードしたはずのvimが無くなっていますね。
念のため作成したファイルも確認してみましょう。
``` shell
root@be2bf6e9fd1f:/# ls usr/src/
root@be2bf6e9fd1f:/#
```
予想通り、ファイルが消えてしまいましたね。

### 4. 結果
これでコンテナを破棄した後に編集データが残らないことがわかりました。
ですが本当にデータが保存できないと不便ですよね。コンテナが落ちることはよくありますし、データを誤って消してしまうこともあるでしょう。
Webサービスを作っていて、不具合のたびにコンテナ内の全データが消し飛んでは大変です。

というわけで、この後からはDockerコンテナのストレージを永続化する方法を解説します。

# ストレージを永続化する方法
Dockerでストレージを永続化する方法は、大きく分けて2つあります。
1. ホストOS側のストレージを共有する『**bindマウント**』
1. Dockerが管理する領域を共有する『**volumeマウント**』

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/68730781-7943-2352-90db-a0cc652cbe9f.png)

## bindマウント
bindマウントは、ホストOSとストレージを共有する方式です。

【メリット】
* 設定ファイルの受け渡し
* ホストOSからファイルを操作できる
⇒デバッグに使いやすい

### 実践

ホストPC側でテスト用フォルダを作成して移動します。その下に`bind_strage`のフォルダを作りましょう。
qiita_test/
　|- bind_strage/
　　|- <空>

先ほど作ったコンテナは一度破棄し、新しく作り直します。
``` shell
docker container run -it --name ubuntu_bind --mount type=bind,src=C:/Users/******/qiita_test/bind_strage,target=/usr/src ubuntu:22.04
# ※***の部分には絶対パスを補完してください
```
これでbindマウントは完了です。
注意１）LinuxやMacであれば`src=$(pwd)/bind_strage`で絶対パスが指定できるのですが、Windowsを使っている場合は戻り値のパス区切り文字が`/`ではなく`\`になります。dockerでのパス指定は`/`にしなければならないので、フルパスを手動で入力する必要があります。
注意２）`type=bind ~ target=/usr/src`の中で空白が入ってしまうとエラーになるので注意してください。

### コマンド解説
前回の記事で基本的なコマンドである
```
docker container run -it --name ubuntu_bind ubuntu:22.04
```
の動作については説明していました。このコマンドのオプションがそれぞれ何をしているか分からない場合は、[前回の記事](https://qiita.com/Toyo_m/items/94de894a8558d94b02d1)を参考にしてください。

今回追加された部分はこちらです。
```
--mount type=bind,src=C:/Users/******/qiita_test/bind_strage,target=/usr/src
```
上のコマンドが`--mount`オプションの１セットです。分解して考えると以下のようになります。

* `--mount`
特定のストレージの上に乗る、つまりそこを使えるようにするコマンドです。type, src, target　という引数がありますが、これらがセットになっているコマンドです。
    * type
    マウントの種類を、`type=bind`, `type=volume`のように指定できます。今回はbindマウントを指定しています。
    * src
    ホストOS側のパスを指定します。bindマウントはホストPCとコンテナ内でストレージを共有しますが、共有するフォルダを指定できます。
    * target
    src と逆に、コンテナ側のパスを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/7dc40b16-6f7c-3d8b-ec1b-d9620eb5426c.png)

### ホストOS側でファイルを編集する
コマンドの概要は理解したので、さっそく、実践していきます。
先ほどのコマンドでUbuntuに入れていたら、`ls /usr/src`コマンドでマウントしたフォルダの中身確認してみます。いじっていなければ空のはずです。
次にホストOS側のマウントした`bind_mount/`フォルダ配下に`test.txt`を作成して「test test test desu」と書いてみます。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/2d75ea4a-e369-94b4-3b59-ca346a734ac2.png)

そうしたらコンテナ内でもう一度lsコマンドを実行してみます。ファイルができていますね。
```
root@7a2481b2f930:/# ls /usr/src
test.txt
```
ついでにcatコマンドも。
```
root@7a2481b2f930:/# cat /usr/src/test.txt
test test test desu!
```
というわけで、うまく書き換えができましたね。
ストレージが共有されている様子が見られたかと思います。

### コンテナ内でファイルを書き換える
今度は逆に、Ubuntuコンテナ上でファイルを書き換えてみましょう。またvimをインストールするのも面倒なので下記コマンドでファイルを更新します。
```
echo "File editted from docker containercat" > /usr/src/test.txt
```
これでファイルが書き換わったはずです。
Windows上でも確認してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/f91d8e36-d344-c30a-d3d5-7be22bf3278d.png)
ファイルが書き換わっていることが確認できましたね。

### コンテナを破棄する
最後に、コンテナを破棄してもファイルが残るか確認してみます。「Ctrl+P -> Ctrl+Q」でコンテナを抜けて、以下のコマンドを実行しましょう。
```
docker container stop ubuntu22
docker container rm ubuntu22
docker container ls -a
```
これでコンテナが破棄されました。ではWindows上で、ファイルがそのまま残っていることを確認しましょう。（下はテキストファイルを開きなおした図）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/79e1fdb6-97cf-7a89-3c0c-82e3245834ba.png)

ということで、ホストOSとフォルダを同期させることで、ストレージを永続化することができました。
以上がbindマウントの説明です。

## volumeマウント
volumeマウントは、Dockerが管理する共有ストレージを使用する方法です。

【メリット】
* フォルダの位置を気にする必要がない
* Docker上で管理できる
* 汎用的で使いやすい

bindマウントとは違い、ホストOSから直接動作させることはできません。しかしホストでの不用意な変更で問題が発生する場合もあるので、「ファイルをホストOS上で編集したい！」という要件が無ければ、基本的にはvolumeマウントを推奨します。

### 実践

コンテナを作る前に、Docker内にvolumeを作成する必要があります。以下のコマンドを打ちこんでみてください。
```
docker volume create test_volume
docker volume ls
```
これでDockerコンテナ内にVolumeが作成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/7911251d-da66-70a4-0c99-3248ccaaf413.png)

次に、ストレージにマウントするコンテナを新しく作りましょう。以下のコマンドで作成可能です
```
docker container run -it --name ubuntu_volume --mount type=volume,src=test_volume,target=/usr/src ubuntu:22.04
```
これでvolumeマウントができています。

### コマンド解説
bindマウントとほぼ同じですが、`type=volume`と`src=test_volume`となっている部分が違います。
* `--mount`
bindマウントの時と同じです。`type, src, target`という引数がセットになっているコマンドです。
    * type
    マウントの種類を指定します。今回は`type=volume`を指定します。
    * src
    bindマウントの時は『ホストOSのパス』を指定していました。volumeマウントの場合は『volumeの名前』を指定します。
    * target
    ここはbindマウントの時と同じで、マウントに利用するコンテナ上のフォルダを指定します。コンテナからは、このフォルダにアクセスすることでvolumeを使えます。

### ファイルを編集してみる
volumeマウントの場合はホストOSから直接操作できないので、ubuntuコンテナ上で直接操作します。
以下、コマンドと結果をまとめて記載します。
``` shell
root@ecc5f85afb6b:/# ls usr/src/
root@ecc5f85afb6b:/# echo "volume test desu!" > usr/src/vol.txt
root@ecc5f85afb6b:/# cat usr/src/vol.txt
volume test desu!
```
ということで、vol.txtが作成できました。
それではコンテナを破棄して作り直しましょう。現在はubuntuのメインプロセスにいるので、`exit`コマンドでコンテナごと終了させます。
```shell
root@ecc5f85afb6b:/# exit
exit
PS C:\Users\******> docker container rm ubuntu_volume
ubuntu_volume
```
これでコンテナが削除完了しました。
さっそくコンテナを再作成します。コマンドは先ほどと同じでも大丈夫です。一応コンテナ名だけ変えておきましょう。以下のコマンドを打ちこんでみてください。
```
docker container run -it --name ubuntu_volume2 --mount type=volume,src=test_volume,target=/usr/src ubuntu:22.04
```
ファイルを確認したところ、結果は以下のようになりました。
``` shell
root@ad60cce2b37f:/# ls usr/src/
vol.txt
root@ad60cce2b37f:/# cat usr/src/vol.txt
volume test desu!
```
今回は先ほど作成した`vol.txt`が消えていません。ストレージの永続化に成功しました！
volumeを削除する場合は、マウントしているコンテナをすべて破棄してから以下のコマンドを実行すればOKです。
```
docker volume rm test_volume
```

<br/>
<br/>
以上で今回のメインの解説は終了です。
dockerコンテナのストレージの独自性と、ストレージを永続化する方法について、イメージ付きの理解が深まっていれば幸いです。

# その他ストレージ関連のTips
メインの解説には載せきれなかった小話です。

## `-v`コマンドを用いたマウント
今回はストレージのマウントに`--mount`オプションを使用しました。しかしDockerコマンドを知っている人の中で「-vじゃないの？」と感じた方もいるかもしれません。
それも間違いではなく、`-v`オプションを使用してストレージをマウントすることも可能です。
今回使ったコマンドを`-v`オプションで書き直すと、以下のようになります。

bindマウント
```shell
# --mount を使う場合
docker container run -it --name ubuntu_bind --mount type=bind,src=C:/Users/******/qiita_test/bind_strage,target=/usr/src ubuntu:22.04
# -v を使う場合
docker container run -it --name ubuntu_bind --mount type=bind,C:/Users/******/qiita_test/bind_strage:/usr/src ubuntu:22.04
```

volumeマウント
```shell
# --mount を使う場合
docker container run -it --name ubuntu_volume --mount type=volume,src=test_volume,target=/usr/src ubuntu:22.04
# -v を使う場合
docker container run -it --name ubuntu_volume -v test_volume:/usr/src ubuntu:22.04
```

基本的に`-v`オプションを使う方が短くなるのですが、いかんせんわかりにくいです。ぱっと見ただけではbindマウントなのかvolumeマウントなのかが判断しにくいです。
また、`-v`だとvolumeの名前を間違えたときに新規で作られてしまいます。例えば、`test_volume`のストレージにマウントしたかったのに`test_voluem`とタイプミスをしてしまった場合、既存のvolumeをマウントせずに新しいvolumeが作成されるので、意図しない結果になります。
このような理由から、[公式サイト](https://docs.docker.jp/storage/index.html#id3)でも`--mount`の使用が推奨されています。ただ、`-v`コマンドを用いたソースや記事は存在していると思うので、頭の片隅に置いておくと混乱しないかと思います。

## tmpfs
本記事ではbindマウントとvolumeマウントの2つのマウント方法を紹介しましたが、実は3つ目のマウントタイプがあったりします。
ここでは実践ではなく、イメージ図を用いての紹介を行います。

基本的にDockerコンテナでデータを保存した場合、PCのストレージ領域のどこかにデータが保存されます。しかし、tmpfs のマウントではデータはメモリ上に保存され（キャッシュのような形式）、高速な読み書きが可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618997/58175352-a0cb-48cd-7aa3-e043dc1dc8ae.png)

通常のデータ保存、bindマウント、volumeマウント、tmpfs の違いを表にしてみました。

|   | 通常のデータ保存 | Bindマウント | Volumeマウント | tmpfs |
|---|---|---|---|---|
| **コンテナ停止時のデータ** | 消えない | 消えない | 消えない | 消える |
| **コンテナ破棄時のデータ** | 消える | 消えない | 消えない | 消える |
| **データ保存先** | コンテナのファイルシステム | ホストの指定ディレクトリ | Dockerが管理するディレクトリ | メモリ(RAM) |
| **読込速度** | 中速 | 中速 | 中速 | 高速 |
| **管理者** | Docker | ホストユーザー | Docker | Docker |


# 最後に
以上でDockerのストレージに関する解説は終了です。
Dockerのストレージに関する知識は、なんとなくのイメージででも持っておくと今後の設計や実装に役に立つでしょう。
ストレージの特性を理解したうえで、コンテナを使いこなしてください！

# 参考

公式ドキュメント

https://docs.docker.jp/storage/index.html

この書籍で勉強しました。少し古いですが、かなりわかりやすいです。

https://www.amazon.co.jp/%E3%81%95%E3%82%8F%E3%81%A3%E3%81%A6%E5%AD%A6%E3%81%B6%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9-docker%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E6%A7%8B%E7%AF%89-%E5%A4%A7%E6%BE%A4-%E6%96%87%E5%AD%9D/dp/4296106422