Docker Container におけるデータ管理の基礎と仕組み
Written by @ryysud Aug 11, 2018 10:32 · 4212 words · 9 minutes read
モチベーション
Docker を利用して、ローカルでの開発環境をコンテナで構築することが多いのですが、コンテナ内のデータ管理に関して「Docker ホストのディレクトリをマウントしてコンテナ内とデータ同期が取れる!嬉しい!」ぐらいの知識でずっとやってきて、最近やっと理解が浅いことに焦り始めたのがこの記事を書こうと思ったきっかけです。
2018年8月時点での公式の情報を元にまとめたものなので、今後のバージョンアップでいくつか変更が発生するかもしれませんが悪しからず。
とりあえず公式ドキュメントを読んで理解を深める
公式ドキュメント
Manage data in Docker - docker docs
雑メモ
Docker にはコンテナが Docker ホストにデータを永続化するために Volumes と Bind mounts という2つの仕組みがあり、それらのオプションを利用することで、コンテナが停止した後でも Docker ホストにデータを永続化することが可能になります。Linux 上で Docker を稼働させている場合には tmpfs mounts も利用可能なようです。
Volumes と Bind mounts と tmpfs mounts の違い
それぞれ Docker ホストでのデータの持たせ方に違いがあります。
種別 | Docker ホスト内でデータが格納される場所 | 備考 |
---|---|---|
Volumes | Docker の管理下にある特定のファイルシステム ※ Linux だとデフォルトで /var/lib/docker/volumes |
Docker プロセス以外はファイルに触らない方が良い Docker でデータの永続化を図るならこれがベスト |
Bind mounts | コンテナ起動時に指定されたファイルシステム | どんなプロセスがデータを修正しても問題なし Docker ホストのディレクトリ構造に依存している |
tmpfs mounts | メモリ | ファイルシステムに書かれることはない Docker on Linux でのみ利用可能 |
上記の表でわかりづらければ、公式ドキュメントに載っている図がとてもわかりやすいのでおすすめです。ちなみに、どのデータ永続化の方法であろうと、コンテナからのデータの見え方に影響は及ぼしません。
動作検証で利用する環境
Docker for Mac の 2018年8月時点 での最新バージョン 18.06.0-ce で動作検証を進めていきます。
$ docker version
Client:
Version: 18.06.0-ce
API version: 1.38
Go version: go1.10.3
Git commit: 0ffa825
Built: Wed Jul 18 19:05:26 2018
OS/Arch: darwin/amd64
Experimental: false
Server:
Engine:
Version: 18.06.0-ce
API version: 1.38 (minimum version 1.12)
Go version: go1.10.3
Git commit: 0ffa825
Built: Wed Jul 18 19:13:46 2018
OS/Arch: linux/amd64
Experimental: true
Volumes に関して
利点
- Bind mounts よりバックアップやマイグレーションが楽
- Docker API や Docker CLI 経由での管理が可能
- Linux コンテナと Windows コンテナの両方で機能する
- 複数コンテナで Volume を共有することが可能
- Volume Driver を利用することでリモートホストやクラウドに Volume を格納することが可能
- Volume Driver を利用することで Volume の暗号化なども可能
作成方法
以下2つの方法があります。
① docker create volume コマンドを実行
# 名前付き Volume を作成する場合
$ docker volume create [Volume名]
# 匿名 Volume を作成する場合
$ docker volume create
② コンテナかサービスを作成するタイミングで –mount または -v (–volume) オプションを付与して実行
–mount と -v (–volume) の違いは、–mount の方が –volume より明示的で冗長なものになっていることと、swarm mode で稼働させる際には –mount しか利用することが出来ないなどの違いなどが挙げられます。
# 匿名 Volume を作成する場合
-v [Volume をマウントするコンテナ内のパス]
--volume [Volume をマウントするコンテナ内のパス]
# 名前付き Volume を作成する場合
-v [Volume名]:[Volume をマウントするコンテナ内のパス]
--volume [Volume名]:[Volume をマウントするコンテナ内のパス]
--mount 'type=volume,src=[Volume名],dst=[Volume をマウントするコンテナ内のパス]'
作成した Volume をコンテナにマウントすることで Volume の利用が可能になります。上の表で説明したように Volume の実体は Docker の管理下にある特定のファイルシステム(Linux だとデフォルトで /var/lib/docker/volumes)なので、結果的な挙動は、Bind mounts と非常に似ているなあと感じました。
また、Volume には “名前付き” と “匿名” の2種類が存在していて、匿名の場合は Volume 作成時に Docker ホスト内で一意となるランダムな名前が自動で付与されるようです。
実際にコマンドを実行して挙動を確認していく
Volume の作成(① docker create volume コマンド実行するパターン)
# 名前付き Volume の作成
$ docker volume create my-vol
# 匿名 Volume の作成(引数なしだと匿名 Volume が作成される)
$ docker volume create
87252a07e154a3f0b95bae2a64c844c56f6b04ffe451393495ba623ab1d07c9e
# Volume 一覧
$ docker volume ls
DRIVER VOLUME NAME
local 87252a07e154a3f0b95bae2a64c844c56f6b04ffe451393495ba623ab1d07c9e
local my-vol
Volume の検査
$ docker volume inspect my-vol
[
{
"CreatedAt": "2018-08-11T04:06:34Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
$ docker volume inspect 87252a07e154a3f0b95bae2a64c844c56f6b04ffe451393495ba623ab1d07c9e
[
{
"CreatedAt": "2018-08-11T04:06:44Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/87252a07e154a3f0b95bae2a64c844c56f6b04ffe451393495ba623ab1d07c9e/_data",
"Name": "87252a07e154a3f0b95bae2a64c844c56f6b04ffe451393495ba623ab1d07c9e",
"Options": {},
"Scope": "local"
}
]
Volume の削除
Volume の削除は自動的には行われないので、コマンドから明示的に行う必要があります。
# Volume を指定して削除
$ docker volume rm my-vol 87252a07e154a3f0b95bae2a64c844c56f6b04ffe451393495ba623ab1d07c9e
my-vol
87252a07e154a3f0b95bae2a64c844c56f6b04ffe451393495ba623ab1d07c9e
$ docker volume ls
DRIVER VOLUME NAME
# Volume の一括削除
$ docker volume create
54acafc21b31706e605908423a1ded45ecca9ecfe0fcccf49d833d2363e46d3e
$ docker volume create
b1ed197aa7ef5368d128ffff255e4c70a8b2a5d6d6322b9172e929ad493cd95f
$ docker volume ls
DRIVER VOLUME NAME
local 54acafc21b31706e605908423a1ded45ecca9ecfe0fcccf49d833d2363e46d3e
local b1ed197aa7ef5368d128ffff255e4c70a8b2a5d6d6322b9172e929ad493cd95f
$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
54acafc21b31706e605908423a1ded45ecca9ecfe0fcccf49d833d2363e46d3e
b1ed197aa7ef5368d128ffff255e4c70a8b2a5d6d6322b9172e929ad493cd95f
Total reclaimed space: 0B
$ docker volume ls
DRIVER VOLUME NAME
Volume の作成(② –mount または -v (–volume) オプションを付与して実行するパターン)
# --mount を付与するパターンで実行
# -v (--volume) を付与するパターンは後の検証で登場させます
$ docker run -d \
--name=nginx-test \
--mount source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest
717a6acad80c03016d73e5d0b042815faadfb504450bd4983ab21c93913525d8
$ docker volume ls
DRIVER VOLUME NAME
local nginx-vol
# Volume がコンテナ内の /usr/share/nginx/html にマウントされている様子がわかる
$ docker inspect nginx-test | jq '.[].Mounts'
[
{
"Type": "volume",
"Name": "nginx-vol",
"Source": "/var/lib/docker/volumes/nginx-vol/_data",
"Destination": "/usr/share/nginx/html",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
]
# Volume をマウントするコンテナ内のパス直下に格納されているファイルを確認
$ docker exec nginx-test ls -l /usr/share/nginx/html
total 8
-rw-r--r-- 1 root root 537 Jun 5 12:00 50x.html
-rw-r--r-- 1 root root 612 Jun 5 12:00 index.html
# Docker for Mac で稼働している仮想マシンに入って Volume の実体を確認するとデータが同じであることがわかる
$ screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty
linuxkit-025000000001:~# ls -l /var/lib/docker/volumes/nginx-vol/_data
total 8
-rw-r--r-- 1 root root 537 Jun 5 12:00 50x.html
-rw-r--r-- 1 root root 612 Jun 5 12:00 index.html
コンテナが無くなっても Volume は削除されずに残り再利用が可能なことを確認
# -v (--volume) を付与するパターンで実行
$ docker run -d \
--name=nginx-test \
-v nginx-vol:/usr/share/nginx/html \
nginx:latest
bc30617cd6b888c625be0000f39ba4cbe7486f7d2c16a73d64ce6eb50daad3ea
# Volume が作成されたことを確認
$ docker volume ls
DRIVER VOLUME NAME
local nginx-vol
# Volume をマウントしたコンテナ内のパス直下にファイルを追加する
$ docker exec -it nginx-test bash
root@bc30617cd6b8:/# ls -l /usr/share/nginx/html
total 8
-rw-r--r-- 1 root root 537 Jun 5 12:00 50x.html
-rw-r--r-- 1 root root 612 Jun 5 12:00 index.html
root@bc30617cd6b8:/# touch /usr/share/nginx/html/test.html
root@bc30617cd6b8:/# ls -l /usr/share/nginx/html
total 8
-rw-r--r-- 1 root root 537 Jun 5 12:00 50x.html
-rw-r--r-- 1 root root 612 Jun 5 12:00 index.html
-rw-r--r-- 1 root root 0 Aug 11 06:35 test.html
root@bc30617cd6b8:/# exit
# コンテナを削除
$ docker rm -f nginx-test
nginx-test
# Volume は削除されていないことを確認
docker volume ls
DRIVER VOLUME NAME
local nginx-vol
# Docker for Mac で稼働している仮想マシンに入って Volume の実体を確認すると追加されたファイルも含まれた形でデータが残っていることがわかる
$ screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty
linuxkit-025000000001:~# ls -l /var/lib/docker/volumes/nginx-vol/_data
total 8
-rw-r--r-- 1 root root 537 Jun 5 12:00 50x.html
-rw-r--r-- 1 root root 612 Jun 5 12:00 index.html
-rw-r--r-- 1 root root 0 Aug 11 06:35 test.html
# 削除されずに残っている Volume を指定する形で再度コンテナを起動してみる
$ docker run -d \
--name=nginx-test \
-v nginx-vol:/usr/share/nginx/html \
nginx:latest
889cb7cbbc0fc8809ad51508eee2f7db04aaa6626ea31c63abbe62b6205d0a33
# 既に存在する Volume を指定したので Volume が新規作成されることはない
$ docker volume ls
DRIVER VOLUME NAME
local nginx-vol
# Volume をマウントしたコンテナ内のパス直下を確認すると以前のコンテナで利用していたデータと同じものが確認できた
$ docker exec nginx-test ls -l /usr/share/nginx/html
total 8
-rw-r--r-- 1 root root 537 Jun 5 12:00 50x.html
-rw-r--r-- 1 root root 612 Jun 5 12:00 index.html
-rw-r--r-- 1 root root 0 Aug 11 06:35 test.html
Bind mounts に関して
Bind mounts が1番使われるものかなと勝手に思っています。
単なる経験不足かもしれませんが、私自身メインでこれしか使ったことがありません。
制限事項
Bind mounts は Volumes に比べて、以下のようにいくつか機能的な制限があります。
- ファイルまたはディレクトリは Docker ホストの絶対パスで指定する必要がある
- 相対パスの指定はサポートされていない(Volumes の作成と区別するためにかなって勝手に思ってる)
- マウントするパスを指定する際に Docker ホストに存在していないファイルまたはディレクトリを指定することも可能
使用するに注意すべきところ
Bind mounts を使用してコンテナに Docker ホストの特定のファイルシステムをマウントすることで、コンテナ内のプロセスを介して Docker ホストのファイルシステムに変更が加えることが可能になるので、セキュリティ的な観点で注意が必要となります。
実際にコマンドを実行して挙動を確認していく
データが Docker ホスト側のファイルシステム上で永続化されることを確認
# --mount を付与するパターンで実行(--mount の type を bind と指定する必要がある)
$ mkdir app
$ docker run -d \
--name nginx-test \
--mount type=bind,source=$(pwd)/app,dst=/app \
nginx:latest
4a73dea39da50260d6d8d137072cba6f8b001a4c1523fe836c7bc1595b0abff3
# Bind mounts したコンテナ内のパス直下にファイルを作成してみる
$ docker exec nginx-test touch /app/test.txt
# Bind mounts した Docker ホスト側のディレクトリを確認するとデータが永続化されていることがわかる
$ ls -l app
total 0
-rw-r--r-- 1 yoshida staff 0B 8 11 16:01 test.txt
# Bind mounts した Docker ホスト側のディレクトリ直下にファイルを作成してみる
$ touch app/test2.txt
# Bind mounts したコンテナ内のパス直下を確認すると作成したファイルが反映されていることがわかる
$ docker exec nginx-test ls -l /app
total 0
-rw-r--r-- 1 root root 0 Aug 11 07:01 test.txt
-rw-r--r-- 1 root root 0 Aug 11 07:03 test2.txt
tmpfs mounts に関して
tmpfs mounts は Volume と Bind mounts と違い、コンテナが停止したら、そこに書き込まれたファイルは永続化されません。なので、適した使い方が求められそうな感じですね。ちなみに docker engine の swarm mode は tmpfs mounts で機密情報をコンテナにマウントしているみたいです。
制限事項
- Volume と Bind mounts と違い複数コンテナでデータ共有することは不可能
- Docker on Linux でのみ利用可能
実際にコマンドを実行して挙動を確認していく
データが Docker ホスト側のメモリ上で永続化されることを確認
# --mount を付与するパターンで実行(--mount の type を tmpfs と指定する必要がある)
$ docker run -d \
--name nginx-test \
--mount type=tmpfs,destination=/app \
nginx:latest
5f2d4098a5dc51c8a69a5c50050236a483095329a2ee052e90bac54141d05ab0
# --tmpfs オプションでも同じ挙動
# $ docker run -d \
# --name nginx-test \
# --tmpfs /app \
# nginx:latest
# コンテナのマウント情報をみると tmpfs mounts が適用されていることがわかる
$ docker inspect nginx-test | jq '.[].Mounts'
[
{
"Type": "tmpfs",
"Source": "",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
# Mac のメモリ内を確認する術がちょっと謎なのでギブアップ...。
$ some_command
それぞれ推奨されるユースケース
Volumes
基本的には Volumes を使うのがベストとのこと
- 複数コンテナでデータを共有したい場合
- Docker ホストが特定のディレクトリまたはファイル構造を持つことが保証されていない場合
- ローカルではなく、リモートホストまたはクラウドプロバイダにコンテナのデータを保存したい場合
- ある Docker ホストから別の Docker ホストへのデータのバックアップや復元や移行をしたい場合
Bind mounts
- /etc/resolve.conf などのファイルを Docker ホストとコンテナで共有したい場合
- ビルドした成果物だけが必要な場合(Golang のビルドなど)
- Docker ホストが特定のディレクトリまたはファイル構造を持つことが保証されている場合
tmpfs mounts
- データを Docker ホストとコンテナに残したくない場合
さいごに
公式ドキュメントをしっかりと読んで、手を動かして挙動を確認したことで、Volume、Bind mounts、tmpfs mounts などの Docker が提供するデータ管理の仕組みを理解することができました。データ管理に関して「Docker ホストのディレクトリをマウントしてコンテナ内とデータ同期が取れる!嬉しい!」ぐらいの知識からは脱せたと思うので、非常に良かったなと感じています。
今回は基本的なところだけをピックアップして、基礎の理解に努めたので、今度は Volume Driver を利用してのリモートホストやクラウドへの Volume 格納であったり、Volume の暗号化などもやっていこうと思います!