令和(3年3月30日)最新版です。
はじめに
Dockerのボリュームの記事は他にもあり大変参考になりますが、情報が古く私が疑問に抱いているところが解消できない部分もあったため改めて調査し記事にしました。
なんかこういう記事を乱立させるのもよくない気がしますが…。
本記事はdocker run
における-v vol:/vol
やdocker-compose.yml
内のvolumes:
による指定を対象に書いています。
tmpfs
等の他のストレージ管理方法については言及しませんのでご了承ください。
環境
- Ubuntu
- 20.04.2 LTS
- Docker
- verison: 20.10.5, build 55c4c88
- Docker Compose
- version: 1.28.5, build c4eb3a1
- ファイルフォーマット: 3.9
参考記事
- DockerのVolume機能について実験してみたことをまとめます - Qiita
- Dockerのvolume問答 - Qiita
- Docker、ボリューム(Volume)について真面目に調べた - Qiita
- Docker の Volume がよくわからないから調べた - Qiita
結論
使い分け
- ホスト側の「設定ファイル」を読み込ませるのならバインドマウント
- コンテナ側の「コンテンツデータ」を保存するのなら名前付きボリュームか匿名ボリューム
※理由は詳細説明を御覧ください。
※上記は必ずしもそうではなく「そういう場合が多い」程度に受け取っていただけたらと思います。
DockerfileのVOLUME文とvolumesオプションを併用したときの挙動
- Dockerfileの
VOLUME
文は匿名ボリューム化を意味する。 -
docker run
またはdocker-compose.yml
内で指定したvolumes
オプションが優先される。-
volumes
オプションがあれば、DockerfileにVOLUME
文を記述しても無視される。 - ただし、
VOLUME /a
で/a
ディレクトリを匿名ボリューム化して、docker run -v ./hoge:/b
としても/a
の匿名ボリュームは生成される。あくまで、コンテナ内のパスが重複した場合にvolumes
オプションが優先されるだけで、volumes
オプションが全てを上書きするわけではない。
-
ホスト・コンテナ両方にファイルが既に有った場合の挙動
- 基本的にホストが真となる。
- ホストとコンテナ間での"マージ"のような動作はない。
各マウント方法ごとによる違いは下記。
- バインドマウント
- 必ずホストが真となる。
- 名前付きボリューム
- Composeでボリュームを自動生成した場合はコンテナが真となる。
-
external
等で既にファイルの存在するボリュームをコンテナにマウントした場合はホストが真となる。 -
external
等で空のボリュームをコンテナにマウントした場合はコンテナが真となる。
- 匿名ボリューム
- 必ずコンテナが真となる。
- Dockerfileの
VOLUME
- 匿名ボリュームと同じ動作。必ずコンテナが真となる。
- ただし、
run
やcompose
でvolumes
オプション指定があった場合は無視される。
DockerfileのCMD文のタイミング
-
volumes
でコンテナ内のディレクトリがホストの内容になったあとに、CMDが実行される。
CMD
がコンテナ実行時に自動実行されると考えると、CMD
コマンドが実行されたあとにホストディレクトリに上書きされてしまうように思うが、CMD
はホストディレクトリをマウントしたのちに実行される。
上図の例だとコンテナの/a
ディレクトリの中身が
container.txt
↓
container.txt
cmd.txt <========= echo "cmd">/a/cmd.txt
↓
host.txt
ではなく
container.txt
↓
host.txt
↓
host.txt
cmd.txt <========= echo "cmd">/a/cmd.txt
と変わる。
イメージを配布する際にユーザによるvolumes
オプションによってコンテナ内のデータが消えないような設計にするにはCMDを使うと良いかもしれないです。
実際にWordPressのDockerfileはそういう作りになっていました。(後述:[ページ内リンク])
詳細説明
まずはボリュームとは何か、を少しだけ触れます。
ストレージマウントタイプの種類
Dockerが管理できるマウントの種類は大きく4つあります。
- ボリューム
- Dockerコマンドで管理することのできるマウント。**「名前付き」とランダムな文字列の「匿名」**がある。
- バインドマウント
- Dockerコマンドで管理することのできないマウント。ディレクトリ構造はホスト環境に依存する。
- tmpfsマウント
- 一時的なマウント
- 名前付きパイプ
- 使ってないのでわかりません😢
(参考)Manage data in Docker | Docker Documentation
必ずしも 「マウント = ボリューム」 ではないことに注意してください。
ボリューム管理はDockerにおけるデータ管理方法のひとつです。
ここらへんの用語はDockerのドキュメント内においても定まってないような気がしてなりません。
volumes
オプションで指定できるのが 「ボリューム」 と 「バインドマウント」 になります。
本記事はこちらについて解説します。
volumes
オプションの種類
ボリューム (Volumes)
「名前付きボリューム(Named volume)」 と
「匿名ボリューム(Anonymous volume)」 の2種類があります。
管理の仕方としては一緒です。違いは名前が任意につけたものかランダムな文字列かの違いだけです。
docker volume ls
などでボリューム一覧を出したときに表示されます。
docker volume rm <VOLUME>
でDockerコマンドから削除も可能です。
「Dockerによって管理される」 というのが最大の特徴です。
ホストのファイルシステム・ディレクトリ構造に依存しないので可搬性が高いです。
環境に依存しないためコンテナ技術の思想とマッチしたボリューム管理方法です。
使うケースによりますが、2021年3月時点ではこの方法がDocker公式により推奨されています。
In general, you should use volumes where possible. Bind mounts are appropriate for the following types of use case:
訳:
一般的には可能な限りボリュームを使うべきです。バインドマウントは以下のようなときに適切でしょう。(略)
バインドマウント (Bind mounts)
古くからあるマウント方法のようです。
ホストマシン上のディレクトリをDockerコンテナ内のディレクトリにマウントすることが出来ます。
Dockerによって管理されません。
よって、docker volume ls
などでボリューム一覧を出したときに表示されません。
ホストの環境に依存するのでコンテナ技術の「データ管理」としては好ましくない設計でしょう。
また、公式で注意もされてますが
One side effect of using bind mounts, for better or for worse, is that you can change the host filesystem via processes running in a container, including creating, modifying, or deleting important system files or directories. This is a powerful ability which can have security implications, including impacting non-Docker processes on the host system.
バインドマウントを利用する際の副作用として、これが良いことか悪いことかはわかりませんが、コンテナー 内に動作するプロセスを通じて ホスト のファイルシステムに変更がかけられるということです。 たとえばシステムにとって重要なファイル、ディレクトリを生成、編集、削除ができてしまいます。 セキュリティに影響を及ぼしかねない強力な能力がここにあって、ホストシステム上の Docker 以外のプロセスへも影響します。
つまり、ホスト上の/etc/***
などの設定ファイル等ともコンテナと同期が取れてしまいますが、コンテナ上で変更があった場合ホスト側にもその行動が反映されてしまいます。
便利な場面もあるかと思いますが、危険でもあります。
以上の管理方法がvolumes
で設定できるマウント方法になります。
volumes
の設定だけで**「名前付き」「匿名」「バインドマウント」**の3つあります。これがボリューム管理をややこしくさせている要因となっているように感じます。
各種挙動説明
以下の説明はすべて次に示すDockerfileをもとにbuildしています。
また作業フォルダは~/test
です。
FROM ubuntu
RUN mkdir /a ; \
echo "Container">/a/container.txt
名前付きボリューム
名前付きボリュームはdocker run
やdocker-compose up
でコンテナ立ち上げ時に自動生成させた場合と、既にあるボリュームを参照する場合で場合分けします。
自動生成させたとき
下記docker-compose.yml
をdocker-compose up
します。
version: "3.9"
services:
ubuntu:
build: .
volumes:
- testvol:/a
tty: true
volumes:
testvol:
すると、test_testvolという名前のボリュームが/var/lib/docker/volumes/test_testvol/_data
に作られます。
そして、コンテナ内のファイルがボリューム内に書き込まれます。(同期されます。)
見た目上の動きは
コンテナ → ホスト です。
既にある「空」のボリュームを参照したとき
事前にdocker volume create testvol
でボリュームを作成しておきます。
下記docker-compose.yml
をdocker-compose up
します。
version: "3.9"
services:
ubuntu:
build: .
volumes:
- testvol:/a
tty: true
volumes:
testvol:
external: true
コンテナ内のファイルが作成しておいたボリューム内に書き込まれます。(同期されます。)
見た目上の動きは
コンテナ → ホスト です。
既にある「ファイルが存在する」ボリュームを参照したとき
事前にdocker volume create testvol
でボリュームを作成しておきます。
+
ファイルを置いておきます。
$ sudo touch /var/lib/docker/volumes/testvol/_data/host.txt
下記docker-compose.yml
をdocker-compose up
します。
version: "3.9"
services:
ubuntu:
build: .
volumes:
- testvol:/a
tty: true
volumes:
testvol:
external: true
すると、コンテナ内はcontainer.txt
がなくなり、host.txt
だけになります。
見た目上の動きは
ホスト → コンテナ です。
匿名ボリューム
匿名ボリュームは簡単です。volumes
でコンテナのパスだけを指定することによって、Docker管理下のvolumesディレクトリに匿名ボリュームが作成されます。
DockerfileのVOLUME
も同等の機能です。
必ずコンテナ内が真となり、コンテナ内のデータは消えません。
下記docker-compose.yml
をdocker-compose up
します。
version: "3.9"
services:
ubuntu:
build: .
volumes:
- /a
tty: true
すると、/var/lib/docker/volumes
配下にランダムな文字列のディレクトリが生成され、そこにデータが配置されます。
見た目上の動きは
コンテナ → ホスト です。
バインドマウント
必ずホストが真となります。
また、作業フォルダ等に配置されたディレクトリをマウントできるため、設定ファイル等をマウントする際に有効かと思います。
SSL証明書等のホスト上の決められたディレクトリにあるものをコンテナ内にマウントするときにも使います。
下記docker-compose.yml
をdocker-compose up
します。
version: "3.9"
services:
ubuntu:
build: .
volumes:
- ./testvol:/a
tty: true
すると、./testvol
が無いときはDockerによって作成されコンテナにマウントされます。
./testvol
を事前にユーザーが作成していた場合も、./testvol
の内容がコンテナに上書きされます。
いずれにせよコンテナ内のデータは破棄され、ホスト上のディレクトリが真となります。
各種ボリュームの挙動についての説明は以上となります。
補足:CMDについて
ついでに軽く調べましたのでWordPressのDockerfileを例に説明します。
現時点最新版(5.7)のWordPressのDockerfileには下2行にこう記述があります。
注:作業フォルダは上位のPHPDockerfileにて"/var/www/html"になっています
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]
意味はコンテナ起動時に
docker-entrypoint.sh apache2-foreground
を実行するという意味です。
docker-entrypoint.shを見てみましょう。
~省略~
if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then
~省略~
echo >&2 "WordPress not found in $PWD - copying now..."
~省略~
tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}"
echo >&2 "Complete! WordPress has been successfully copied to $PWD"
fi
必要なところだけ抜粋しています。
if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then
とあるので、index.php
とwp-includes/version.php
が両方共存在しないとき、ダウンロードしているWordPress本体を/var/www/html
に解凍させる仕組みです。
なので、/var/www/html
にホストの空のディレクトリをバインドマウントさせても、中身が消えてないように見えます。
/var/www/html
に展開された後にホスト側と同期をとるので、ホストにもWordPressのファイル群が見える仕組みです。
if文に入らないように、index.phpだけ残してコンテナにマウントさせてコンテナを起動させれば
~/wordpress/html$ ll
total 16
drwxr-xr-x 2 www-data www-data 4096 Mar 30 02:43 ./
drwxrwxr-x 4 dockeruser dockeruser 4096 Mar 30 02:28 ../
-rw-r--r-- 1 www-data www-data 261 Mar 27 22:50 .htaccess
-rw-r--r-- 1 root root 405 Mar 30 02:43 index.php
dockeruser@DESKTOP-II2RT72:~/wdpress/wordpress$ ll
total 24
drwxr-xr-x 2 www-data www-data 4096 Mar 30 02:43 ./
drwxrwxr-x 4 dockeruser dockeruser 4096 Mar 30 02:28 ../
-rw-r--r-- 1 www-data www-data 261 Mar 27 22:50 .htaccess
-rw-r--r-- 1 root root 405 Mar 30 02:43 index.php
-rw-r--r-- 1 www-data www-data 5325 Mar 30 02:43 wp-config.php
このように、展開されませんでした。
(wp-config.phpはdocker-entrypoint.sh
内の他の記述によりコピーされてます。)
補足:externalとname
external: true
の記述はname:
でも代用できます。
version: "3.9"
services:
ubuntu:
build: .
volumes:
- testvol:/b
tty: true
volumes:
testvol:
name: testvol
これと
version: "3.9"
services:
ubuntu:
build: .
volumes:
- testvol:/b
tty: true
volumes:
testvol:
external: true
これは同じ動きをします。
補足:Data volume containerについて
初出がどこかわかりませんが…もっと古いDockerのバージョンであった考え方でしょうか?
Data volume containerはコンテナをボリューム専用のコンテナにし、データサーバーのようにして使います。
そうすることによって、volume_from <コンテナ名>
でボリュームを共有できるようにしていました。
今(Composeフォーマット3.9)では名前付きボリュームでコンテナ間の共有はできますしvolume_from
自体、バージョン3で廃止されています。
他にも
他にもNFSとかSambaとかボリュームの作成方法がありますが、私自身触ったことないので理解できておりません。
ボリュームについても読み取り専用や権限等の他の設定項目があります。まだ調査しきれてないので掲載できず申し訳ありません。
以上で終わります。