Docker でのファイルマウントについて試してみる (volume / bind mount / tmpfs mount)
Docker を使う時のデータ永続化の方法としていくつか方法があるらしいのでそれぞれ試してみる.
Docker でのデータ永続化
公式ドキュメントを確認してみると、Docker ではデータ永続化の手段として以下の 2 つのファイルシステムのマウント方法を提供しているらしい. (https://docs.docker.com/storage/)
-
ボリューム (volumes)
-
バインド・マウント (bind mounts)
(加えて、永続化しない in-memory ストレージとして tmpfs mount というマウントの設定も存在する.)
この中のどのマウント方式を選んだ場合であっても、コンテナの内部からはどれも全く同様のファイルとして見えるようだ.

各マウント方式の特徴は以下の通り.
-
Volumes
-
Docker 中においてデータを永続化するための最善の方法として推奨されている.
-
ホストのファイルシステムの中で、Docker によって管理される一部分 (Linux では
/var/lib/docker/volumes/) にデータが保存される. -
Docker 以外のプロセスはファイル・システム中のこの部分を書き換えることはできない.
-
-
Bind mounts
-
ホストシステム中のあらゆる場所にデータを保存できる.
-
重要なシステムファイル・ディレクトリも指定できるのでセキュリティ上の問題が生じる可能性もある.
-
Docker のホスト上で動いている Docker 以外のプロセスや Docker コンテナはいつでもこれらのファイルを書き換えることができる.
-
-
tmpfs mounts
-
ホストのメモリ上にのみデータを保存できる. ホストのファイルシステムには書き込まれない.
-
docker-compose でのマウント設定の書き方
docker-compose.yml を使う場合、これらのマウント設定は各サービスの中の volumes に記述する.
volumes の書き方は一行で書く書式と複数行で書く書式があり、特に一行で書く場合は Volumes を使うのか Bind mounts を使うのかの区別がつきにくい.
(https://amateur-engineer-blog.com/docer-compose-volumes/)
まず、 Volumes を使う場合の一行での書き方は以下の通り.
services:
service_name:
volumes:
# パス指定だけを行う場合. (無名ボリューム) Engine に自動的にボリュームを作成させる.
- /var/lib/mysql
# 名前付きボリューム. yaml ファイルの一番外側の volumes の中で名前を定義しておく必要がある.
- mydata_volume:/var/lib/mysql
volumes:
mydata_volume
次に Bind mounts を使う場合の一行での書き方は以下の通り.
services:
service_name:
volumes:
# 絶対パス指定.
- /opt/data:/var/lib/mysql
# 相対パス指定. docker-compose.yml ファイルからの相対パスとなる.
- ./cache:/tmp/cache
# ユーザーディレクトリからの相対パス. アクセスモードを読み込み専用に設定.
- ~/configs:/etc/configs/:ro
複数行で記述する場合には、以下のような項目を個別に記述することとなる.
-
type: マウントタイプ (volume/bind/tmpfs/npipe) を設定する. -
source: マウント元. -
target: マウントされるコンテナのパス. -
read_only: 読み込み専用に設定する. -
bind: バインドオプション.-
propagation: バインドの伝播モード.
-
-
volume: ボリュームオプション.-
nocopy: ボリューム生成時、コンテナからのデータのコピーを無効化する.
-
-
tmpfs: tmpfs オプション.-
size: tmpfs マウントのサイズを指定する.
-
services:
service_name:
volumes:
# Volumes の設定例
- type: volume
source: mydata_volume
target: /data
volume:
nocopy: true
# Bind mounts の設定例
- type: bind
source: ./static
target: /opt/app/static
volumes:
mydata_volume
各マウント方式を試してみる
以下、それぞれのマウント方式を試してみることにする. まずは Bind mounts のためにホスト環境にフォルダを作っておく.
$ mkdir docker-mount-test
$ cd docker-mount-test
$ mkdir mydata_bind
ここでは BusyBox イメージを使って最小限のコンテナ環境で作業してみることにする.
BusyBox は様々な標準 UNIX コマンドを単一バイナリにまとめた Swiss army knife のようなツール. 組み込み Linux 向けであり、root 権限でサーバー操作をミスしてしまって標準 UNIX コマンドへのパスが全く通らなくなってしまった場合などで頼ることもあるらしい. (https://qiita.com/S_Katz/items/a82554447491fb8079f0)
単一の BusyBox のバイナリ中におよそ 400 個もの標準コマンドが入っているらしい. (https://hetarena.com/archives/2864)
多くのコードを複数のコマンドで共有しているためファイルサイズも極めて小さい. 全部入りでも 1 MB 程度. (https://busybox.net/downloads/binaries/)
使用する場合には busybox ps や busybox wget のように busybox コマンドのサブコマンドとして実行したいツールの名前を指定すればよい.
Docker コンテナ中に入れると様々なツールがまとめて手に入って便利かも.
(https://kazuhira-r.hatenablog.com/entry/2019/04/28/022717)
今回は以下のような docker-compose.yml を作成し、1 つの BusyBox コンテナに対して 3 つのマウント方式をそれぞれ試してみる.
version: "3.8"
services:
my-busybox:
image: busybox:stable
volumes:
# Volumes
- type: volume
source: mydata_volume
target: /my_volume
volume:
nocopy: true
# Bind mounts
- type: bind
source: ./mydata_bind
target: /my_bind
# tmpfs mounts
- type: tmpfs
target: /my_tmpfs
# バックグラウンドで busybox コンテナを動かすため、top を実行する.
command: busybox top
volumes:
mydata_volume:
name: mydata_storage
コンテナを起動してみる.
$ docker compose up -d
起動したコンテナに対して inspect を実行してみると、マウント情報がきちんと記載されていることがわかる.
$ docker inspect docker-mount-test-my-busybox-1
[
{
"Id": "565212ba495d1d4e3cf5aed53c277267dd5f5564ffed7d8b941b1d382cb9c0b4",
"Created": "2022-11-02T05:20:55.120213709Z",
"Path": "busybox",
"Args": [
"top"
],
...
"HostConfig": {
...
"Mounts": [
{
"Type": "volume",
"Source": "mydata_storage",
"Target": "/my_volume",
"VolumeOptions": {
"NoCopy": true
}
},
{
"Type": "bind",
"Source": "/host_mnt/Users/annpin/src/bitbucket.org/AnnPin/docker-mount-test/mydata
_bind",
"Target": "/my_bind"
},
{
"Type": "tmpfs",
"Target": "/my_tmpfs"
}
],
...
},
...
"Mounts": [
{
"Type": "volume",
"Name": "mydata_storage",
"Source": "/var/lib/docker/volumes/mydata_storage/_data",
"Destination": "/my_volume",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
},
{
"Type": "bind",
"Source": "/host_mnt/Users/annpin/src/bitbucket.org/AnnPin/docker-mount-test/mydata_bin
d",
"Destination": "/my_bind",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "tmpfs",
"Source": "",
"Destination": "/my_tmpfs",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
"Config": {
...
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"busybox",
"top"
],
"Image": "busybox:stable",
"Volumes": {
"/my_bind": {},
"/my_tmpfs": {},
"/my_volume": {}
},
...
},
...
}
]
次に、 busybox の /bin/sh を実行してコンテナ内に入ってみる.
$ docker compose exec my-busybox busybox /bin/sh
ルートディレクトリ / の中身を確認してみると、 docker-compose.yml で指定したように /my_volume 、 /my_bind 、 /my_tmpfs というディレクトリがマウントされているようだ.
/ # ls
bin etc my_bind my_volume root tmp var
dev home my_tmpfs proc sys usr
この中にそれぞれ echo でファイルを作成した上でコンテナの外に出てみよう.
/ # echo "Hello, volumes!" > /my_volume/volume.txt
/ # echo "Hello, bind mounts!" > /my_bind/bind.txt
/ # echo "Hello, tmpfs mounts!" > /my_tmpfs/tmpfs.txt
/ # cat /my_volume/volume.txt
Hello, volumes!
/ # cat /my_bind/bind.txt
Hello, bind mounts!
/ # cat /my_tmpfs/tmpfs.txt
Hello, tmpfs mounts!
/ # exit
コンテナを終了・削除する.
$ docker compose down
これにより、データ永続化が失敗していた場合には volume.txt 、 bind.txt 、 tmpfs.txt という 3 つのファイルは完全に失われてしまうことになる.
データ永続化が成功したかを確認してみよう. まず、確認が簡単な Bind mounts から見てみる.
Bind mounts を使ってマウントしていた /my_bind ディレクトリの中身はホスト上の mydata_bind 中にすべて保存されているのが確認できる.
これにより、 Bind mounts を使うことでデータ永続化が実現できるのがわかる.
$ ls ./mydata_bind
bind.txt
$ cat ./mydata_bind/bind.txt
Hello, bind mounts!
次に Volumes を使ってマウントしていた /my_volume を確認してみよう.
Volumes の場合、ホスト OS 上の通常のファイルシステム上にはファイルは保存されずに Docker の volume 中に保存される.
まず、 docker volume ls を使って volume の一覧を確認してみよう.
$ docker volume ls
DRIVER VOLUME NAME
...
local ed57a4ac2599f3e340b2c42b21ab17f6986c2ee5685cc062e78bcb29b2173ee4
local ee651ef72a7010385f51691d3f3a39e74aafc4ec7ea608c7d5bff489b0daaf15
local mydata_storage
すると、 docker-compose.yml 中の volumes に指定した mydata_storage という名前の volume が作成されていることが確認できる.
ちなみに、 docker volume ls はフォーマット文字列を指定することで出力を変更することができる.
$ docker volume ls --format "{{.Mountpoint}}"
/var/lib/docker/volumes/0a0f2cd48b8a2b2eb92bdd3633f25f2eca51bcb44ed0fd3f25ae42061fb57d08/_data
/var/lib/docker/volumes/1d73ad43cede9d360a737c01aabea887c6d91149ade4d948f7e613f3ea54e676/_data
/var/lib/docker/volumes/3c4ce8682950529f75d33ecd2de5ef5b375ce782447755eb912dafe685351429/_data
/var/lib/docker/volumes/49b8963ab85ca21b398ed8d390db59d4ece1f9c956a85f7240e69c76f2b62de8/_data
/var/lib/docker/volumes/63f525cc59e323c291ab9f18192e74b360a92a319ad2f7e14aeca0fd831777e7/_data
/var/lib/docker/volumes/4320a5bcef9e03c60ea65e6f329a476ee98c459d356eb8c335209e2e3d67ecd4/_data
/var/lib/docker/volumes/6106d066d6fef6313d217fe1e0f36dfaf12f54074cc9915d9844a202a574bea7/_data
/var/lib/docker/volumes/375869738a11d81204598accd9691a590a26476fc63f32902ce72e0d972c0565/_data
/var/lib/docker/volumes/bbd2d25166a696fea0e5567f886f12dcdd97ad7f1fc0a24406433750bb001a62/_data
/var/lib/docker/volumes/ed57a4ac2599f3e340b2c42b21ab17f6986c2ee5685cc062e78bcb29b2173ee4/_data
/var/lib/docker/volumes/ee651ef72a7010385f51691d3f3a39e74aafc4ec7ea608c7d5bff489b0daaf15/_data
/var/lib/docker/volumes/mydata_storage/_data
この mydata_storage という volume の中身の確認はホスト OS 上では行えず、Docker コンテナを立ち上げることで確認する必要がある.
元のコンテナでなくとも、 --privileged を付与したり pid をホストとコンテナ内で揃えてやることによって権限問題をクリアすることで別のコンテナ内でこの volume をマウントして確認することもできる.
今回は再び同じコンテナを立ち上げ直すことで確認してみよう.
なお、 tmpfs mount したデータについてはデータ永続化が行われずにコンテナの削除と同時に消失しているはずである.
再びコンテナを生成し、コンテナ内に入って確認してみよう.
$ docker compose up -d
$ docker compose exec my-busybox busybox /bin/sh
/ # ls
bin etc my_bind my_volume root tmp var
dev home my_tmpfs proc sys usr
/ # ls /my_volume
volume.txt
/ # cat /my_volume/volume.txt
Hello, volumes!
/ # ls /my_bind
bind.txt
/ # ls /my_bind/bind.txt
/my_bind/bind.txt
/ # ls /my_tmpfs
/ # exit
これにより、 Volumes や Bind mounts ではきちんとデータが永続化できており、tmpfs mounts ではデータの永続化がなされないということが確認できた.
最後に、 mydata_storage volume を削除してからコンテナを起動することで、 /my_volume の中身が空になることを確認しよう.
# コンテナを停止・削除.
$ docker compose down
# mydata_storage volume を削除.
$ docker volume rm mydata_storage
mydata_storage
# 削除されたか確認.
$ docker volume ls
DRIVER VOLUME NAME
...
local ed57a4ac2599f3e340b2c42b21ab17f6986c2ee5685cc062e78bcb29b2173ee4
local ee651ef72a7010385f51691d3f3a39e74aafc4ec7ea608c7d5bff489b0daaf15
# 再度コンテナを起動.
$ docker compose up -d
# コンテナ内に入る.
$ docker compose exec my-busybox busybox /bin/sh
確認してみると、確かに /my_volume の中が空になった.
/ # ls
bin etc my_bind my_volume root tmp var
dev home my_tmpfs proc sys usr
/ # ls /my_volume
/ # ls /my_bind/
bind.txt
/ # cat /my_bind/bind.txt
Hello, bind mounts!
/ # exit
ちなみに、BusyBox コンテナ自体はすごーく軽い.
動いているプロセスを busybox top で調べてみるとこんな感じ.
(busybox top が 2 つ出ているのは docker-compose.yml に command で busybox top と指定したため.)
Mem: 1337872K used, 6803340K free, 342916K shrd, 36940K buff, 698288K cached
CPU: 0.4% usr 0.5% sys 0.0% nic 99.0% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 0.00 0.00 0.00 2/573 12
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
6 0 root S 1388 0.0 2 0.0 busybox /bin/sh
1 0 root S 1384 0.0 2 0.0 busybox top
12 6 root R 1384 0.0 2 0.0 busybox top
自分が動かした覚えのないプロセスが動いてないってのはすごいな.