72
55

More than 3 years have passed since last update.

Dockerのボリュームのマウントのされ方についてちょっとまとめました

Last updated at Posted at 2021-03-30

令和(3年3月30日)最新版です。

はじめに

Dockerのボリュームの記事は他にもあり大変参考になりますが、情報が古く私が疑問に抱いているところが解消できない部分もあったため改めて調査し記事にしました。
なんかこういう記事を乱立させるのもよくない気がしますが…。

本記事はdocker runにおける-v vol:/voldocker-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

参考記事

結論

使い分け

  • ホスト側の「設定ファイル」を読み込ませるのならバインドマウント
  • コンテナ側の「コンテンツデータ」を保存するのなら名前付きボリューム匿名ボリューム

※理由は詳細説明を御覧ください。
※上記は必ずしもそうではなく「そういう場合が多い」程度に受け取っていただけたらと思います。

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
    • 匿名ボリュームと同じ動作。必ずコンテナが真となる。
    • ただし、runcomposevolumesオプション指定があった場合は無視される。

DockerfileのCMD文のタイミング

  • volumesでコンテナ内のディレクトリがホストの内容になったあとに、CMDが実行される。

例:
img01.png

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オプションで指定できるのが 「ボリューム」「バインドマウント」 になります。
本記事はこちらについて解説します。

img02.png

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です。

Dockerfile
FROM ubuntu

RUN mkdir /a ; \
    echo "Container">/a/container.txt

名前付きボリューム

名前付きボリュームはdocker rundocker-compose upでコンテナ立ち上げ時に自動生成させた場合と、既にあるボリュームを参照する場合で場合分けします。

自動生成させたとき

下記docker-compose.ymldocker-compose upします。

docker-compose.yml
version: "3.9"

services:

    ubuntu:
      build: .
      volumes:
        - testvol:/a
      tty: true

volumes:
  testvol:

すると、test_testvolという名前のボリュームが/var/lib/docker/volumes/test_testvol/_dataに作られます。

そして、コンテナ内のファイルがボリューム内に書き込まれます。(同期されます。)

見た目上の動きは

コンテナ → ホスト です。

img20.png

既にある「空」のボリュームを参照したとき

事前にdocker volume create testvolでボリュームを作成しておきます。
下記docker-compose.ymldocker-compose upします。

docker-compose.yml
version: "3.9"

services:

    ubuntu:
      build: .
      volumes:
        - testvol:/a
      tty: true

volumes:
  testvol:
    external: true

コンテナ内のファイルが作成しておいたボリューム内に書き込まれます。(同期されます。)

見た目上の動きは
コンテナ → ホスト です。

img21.png

既にある「ファイルが存在する」ボリュームを参照したとき

事前にdocker volume create testvolでボリュームを作成しておきます。

ファイルを置いておきます。

$ sudo touch /var/lib/docker/volumes/testvol/_data/host.txt

下記docker-compose.ymldocker-compose upします。

docker-compose.yml
version: "3.9"

services:

    ubuntu:
      build: .
      volumes:
        - testvol:/a
      tty: true

volumes:
  testvol:
    external: true

すると、コンテナ内はcontainer.txtがなくなり、host.txtだけになります。

見た目上の動きは
ホスト → コンテナ です。

img22.png

匿名ボリューム

匿名ボリュームは簡単です。volumesでコンテナのパスだけを指定することによって、Docker管理下のvolumesディレクトリに匿名ボリュームが作成されます。
DockerfileのVOLUMEも同等の機能です。
必ずコンテナ内が真となり、コンテナ内のデータは消えません。

下記docker-compose.ymldocker-compose upします。

docker-compose.yml
version: "3.9"

services:

    ubuntu:
      build: .
      volumes:
        - /a
      tty: true

すると、/var/lib/docker/volumes配下にランダムな文字列のディレクトリが生成され、そこにデータが配置されます。

見た目上の動きは
コンテナ → ホスト です。

img23.png

バインドマウント

必ずホストが真となります。
また、作業フォルダ等に配置されたディレクトリをマウントできるため、設定ファイル等をマウントする際に有効かと思います。
SSL証明書等のホスト上の決められたディレクトリにあるものをコンテナ内にマウントするときにも使います。

下記docker-compose.ymldocker-compose upします。

docker-compose.yml
version: "3.9"

services:

    ubuntu:
      build: .
      volumes:
        - ./testvol:/a
      tty: true

すると、./testvolが無いときはDockerによって作成されコンテナにマウントされます。
./testvolを事前にユーザーが作成していた場合も、./testvolの内容がコンテナに上書きされます。
いずれにせよコンテナ内のデータは破棄され、ホスト上のディレクトリが真となります。

img24.png

各種ボリュームの挙動についての説明は以上となります。

補足:CMDについて

ついでに軽く調べましたのでWordPressのDockerfileを例に説明します。

現時点最新版(5.7)のWordPressのDockerfileには下2行にこう記述があります。
注:作業フォルダは上位のPHPDockerfileにて"/var/www/html"になっています

Dockerfile(wordpress5.7)
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]

意味はコンテナ起動時に
docker-entrypoint.sh apache2-foregroundを実行するという意味です。

docker-entrypoint.shを見てみましょう。

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.phpwp-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とかボリュームの作成方法がありますが、私自身触ったことないので理解できておりません。
ボリュームについても読み取り専用や権限等の他の設定項目があります。まだ調査しきれてないので掲載できず申し訳ありません。

以上で終わります。

72
55
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
55