はじめに
既定では、コンテナ内で作成されたすべてのファイルは、書き込み可能なコンテナ層に保存されます。これはつまり、以下のことを意味します。
- コンテナが存在しなくなった場合、データは失われます。
- コンテナの書き込み可能層はホストマシンに密接に結合されており、
- ファイルシステムを管理するには、Linux カーネルを使用したユニオンファイルシステムを提供するストレージドライバが必要です。この追加の抽象化により、ファイルシステムに直接書き込む「データボリューム」と比較して、パフォーマンスが低下します。
Docker は、ホストマシンにファイルを保存するための 2 つのオプションを提供しています。「ボリューム」と「バインドマウント」です。Linux 上で Docker を実行している場合、「tmpfs マウント」も使用でき、Windows 上で Docker を使用している場合、「名前付きパイプ」も使用できます。

- 「ボリューム」は、Docker によって管理されるホストファイルシステムに保存されます。
- 「バインドマウント」は、ホストシステムのどこにでも保存されます。
- 「tmpfs マウント」は、ホストメモリにのみ保存されます。
元々は、「--mount」フラグが Docker Swarm サービスに使用され、「--volume」フラグが独立したコンテナに使用されていました。Docker 17.06 以降では、独立したコンテナにも「--mount」を使用でき、一般的に「--volume」よりも明確で詳細です。
ボリューム
「データボリューム」または「ボリューム」は、Docker の「ユニオンファイルシステム」を迂回するディレクトリです。
ボリュームには 3 種類あります。
- 匿名ボリューム
- 名前付きボリューム
- ホストボリューム
匿名ボリューム
人気のあるオープンソースの NoSQL データベースである CouchDB のインスタンスを作成し、データベースのデータファイルを保存するために「匿名ボリューム」を使用しましょう。
CouchDB のインスタンスを実行するには、Docker Hub の CouchDB イメージをhttps://hub.docker.com/_/couchdbから使用します。ドキュメントによると、CouchDB の既定値は、「独自の内部ボリューム管理を使用して、ホストシステムのディスクにデータベースファイルを書き込む」ことです。
次のコマンドを実行します。
docker run -d -p 5984:5984 --name my-couchdb -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=passw0rd1 couchdb:3.1
CouchDB は匿名ボリュームを作成し、ハッシュ付きの名前を生成します。ホストシステムのボリュームを確認します。
labex:~/ $ docker volume ls
DRIVER VOLUME NAME
local 1d292aca855adb9de9be7acea88f6d3f8e6a08eef5bfd986a81f073f1906b82f
生成された名前の値を持つ環境変数「VOLUME」を設定します。
export VOLUME=<VOLUME NAME>
そして、ボリュームに生成されたハッシュ名を使用して、作成されたボリュームを調べます。
$ docker volume inspect $VOLUME
[
{
"CreatedAt": "2020-09-24T14:10:07Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/f543c5319ebd96b7701dc1f2d915f21b095dfb35adbb8dc851630e098d526a50/_data",
"Name": "f543c5319ebd96b7701dc1f2d915f21b095dfb35adbb8dc851630e098d526a50",
"Options": null,
"Scope": "local"
}
]
Docker が Docker ホストファイルシステムの/var/lib/docker/volumes/$VOLUME_NAME/_data以下にボリュームを作成して管理していることがわかります。これは、ホストマシン上のパスではなく、Docker が管理するファイルシステムの一部であることに注意してください。
新しいデータベース「mydb」を作成し、「hello world」のメッセージを持つ新しいドキュメントを挿入します。
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1 -d '{"msg": "hello world"}'
コンテナを停止し、再度起動します。
docker stop my-couchdb
docker start my-couchdb
データが永続化されたことをテストするために、データベース内のドキュメントを取得します。
curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/_all_docs
curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1
出力:
## $ curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/_all_docs
{"total_rows":1,"offset":0,"rows":[
{"id":"1","key":"1","value":{"rev":"1-c09289617e06b96bc747fb1201fea7f1"}}
]}
## $ curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1
{"_id":"1","_rev":"1-c09289617e06b96bc747fb1201fea7f1","msg":"hello world"}
ボリュームの共有
--volumes-fromオプションを使用することで、匿名ボリュームを別のコンテナと共有できます。
匿名ボリュームをコンテナ内の/dataディレクトリにマウントしたbusyboxコンテナを作成し、シェルコマンドを使用して、ログファイルにメッセージを書き込みます。
$ docker run -it --name busybox1 -v /data busybox sh
/ ## echo "hello from busybox1" > /data/hi.log
/ ## ls /data
hi.log
/ ## exit
コンテナbusybox1が停止していることを確認しますが、削除しないでください。
labex:~/ $ docker ps -a | grep busybox1
f4dbf9ee7513 busybox "sh" 2 minutes ago Exited (0) About a minute ago busybox1
次に、--volumes-fromオプションを使用して、busybox1によって作成されたボリュームを共有するための 2 番目のbusyboxコンテナbusybox2を作成します。
$ docker run --rm -it --name busybox2 --volumes-from busybox1 busybox sh
/ ## ls -al /data
total 12
drwxr-xr-x 2 root root 4096 Jan 23 07:20.
drwxr-xr-x 1 root root 4096 Jan 23 07:24..
-rw-r--r-- 1 root root 20 Jan 23 07:20 hi.log
/ ## cat /data/hi.log
hello from busybox1
/ ## exit
Docker は、--volumes-fromオプションを使用して共有できる匿名ボリュームを作成し、新しい匿名ボリュームを作成しました。
labex:~/ $ docker volume ls
DRIVER VOLUME NAME
local 0f971b2477d5fc0d0c2b31fc908ee59d6b577b4887e381964650ce6853890dc9
local 1d292aca855adb9de9be7acea88f6d3f8e6a08eef5bfd986a81f073f1906b82f
既存のボリュームとコンテナをクリーンアップします。
docker stop my-couchdb
docker rm my-couchdb
docker rm busybox1
docker volume rm $(docker volume ls -q)
docker system prune -a
clear
名前付きボリューム
「名前付きボリューム」と「匿名ボリューム」は、Docker がそれらの配置場所を管理する点で似ています。ただし、「名前付きボリューム」は、コンテナディレクトリにマウントする際に名前で参照できます。これは、複数のコンテナ間でボリュームを共有したい場合に便利です。
まず、「名前付きボリューム」を作成します。
docker volume create my-couchdb-data-volume
ボリュームが作成されたことを確認します。
$ docker volume ls
DRIVER VOLUME NAME
local my-couchdb-data-volume
次に、「名前付きボリューム」を使用して、名前が「my-couchdb-name-vol」の CouchDB コンテナを作成します。
docker run -d -p 59840:5984 --name my-couchdb-name-vol -v my-couchdb-data-volume:/opt/couchdb/data -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=passw0rd1 couchdb:3.1
CouchDB コンテナが実行され、インスタンスが利用可能になるまで待ちます。
新しいデータベース「mydb」を作成し、「hello world」のメッセージを持つ新しいドキュメントを挿入します。
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:59840/mydb
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:59840/mydb/1 -d '{"msg": "hello world"}'
これで、他のコンテナとボリュームを共有することが簡単になりました。たとえば、busyboxイメージを使用してボリュームの内容を読み取り、busyboxコンテナのディレクトリにボリュームをマウントすることで、「my-couchdb-data-volume」ボリュームを共有します。
labex:~/ $ docker run --rm -it --name busybox -v my-couchdb-data-volume:/myvolume busybox sh
/ #
/ ## ls -al /myvolume
total 40
drwxr-xr-x 4 5984 5984 4096 Jan 23 07:30.
drwxr-xr-x 1 root root 4096 Jan 23 07:31..
drwxr-xr-x 2 5984 5984 4096 Jan 23 07:29.delete
-rw-r--r-- 1 5984 5984 8388 Jan 23 07:30 _dbs.couch
-rw-r--r-- 1 5984 5984 8385 Jan 23 07:29 _nodes.couch
drwxr-xr-x 4 5984 5984 4096 Jan 23 07:30 shards
/ ## exit
特権付きの権限でbusyboxコンテナを実行し、プロセス ID をhostに設定してホストシステムを調べ、Docker が管理するディレクトリに移動することで、Docker が管理するボリュームのファイルシステムを確認できます。
docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh
/ ## ls -l /var/lib/docker/volumes
total 28
-rw------- 1 root root 32768 Nov 10 15:54 metadata.db
drwxr-xr-x 3 root root 4096 Nov 10 15:54 my-couchdb-data-volume
/ ## exit
クリーンアップします。
docker stop my-couchdb
docker rm my-couchdb
docker volume rm my-couchdb-data-volume
docker system prune -a
docker volume prune
clear
ホストボリューム
ホストマシンから直接ボリュームディレクトリにアクセスしたい場合、Docker が管理するディレクトリではなく、「ホストボリューム」を作成できます。
現在の作業ディレクトリ(コマンドpwdで示される)にあるdataというディレクトリを使用します。または、ホストマシン上の独自のデータディレクトリを選択します。たとえば、/home/couchdb/dataです。まだ存在しない場合は、docker が$(pwd)/dataディレクトリを作成します。CouchDB の既定のデータディレクトリであるコンテナディレクトリ/opt/couchdb/dataに、CouchDB コンテナ内の「ホストボリューム」をマウントします。
次のコマンドを実行します。
cd /home/labex/project
docker run -d -p 5984:5984 --name my-couchdb -v $(pwd)/data:/opt/couchdb/data -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=passw0rd1 couchdb:3.1
ディレクトリdataが作成されたことを確認します。
$ ls -al
total 20
drwxrwxr-x 3 labex labex 4096 Aug 29 14:14.
drwxr-x--- 25 labex labex 4096 Aug 29 14:14..
drwxr-xr-x 3 5984 5984 4096 Aug 29 14:14 data
そして、CouchDB がここにデータファイルを作成したことを確認します。
$ ls -al data
total 32
drwxr-xr-x 3 5984 5984 4096 Aug 29 14:14.
drwxrwxr-x 3 labex labex 4096 Aug 29 14:14..
-rw-r--r-- 1 5984 5984 4257 Aug 29 14:14 _dbs.couch
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:14.delete
-rw-r--r-- 1 5984 5984 8385 Aug 29 14:14 _nodes.couch
また、docker が管理するボリュームは作成されていないことを確認します。なぜなら、今回は「ホストボリューム」を使用しているからです。
docker volume ls
そして
docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh
sh-5.1## ls -l /var/lib/docker/volumes
total 28
brw------- 1 root root 252, 3 Jan 23 15:15 backingFsBlockDev
-rw------- 1 root root 32768 Jan 23 15:33 metadata.db
drwx-----x 3 root root 4096 Jan 23 15:26 my-couchdb-data-volume
sh-5.1## exit
新しいデータベース「mydb」を作成し、「hello world」のメッセージを持つ新しいドキュメントを挿入します。
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1 -d '{"msg": "hello world"}'
CouchDB がshardsフォルダを作成したことに注意してください。
$ ls -al data
total 40
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15.
drwxrwxr-x 3 labex labex 4096 Aug 29 14:14..
-rw-r--r-- 1 5984 5984 8388 Aug 29 14:15 _dbs.couch
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:14.delete
-rw-r--r-- 1 5984 5984 8385 Aug 29 14:14 _nodes.couch
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15 shards
shardsディレクトリの内容を一覧表示します。
$ ls -al data/shards
total 16
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15.
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15..
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:15 00000000-7fffffff
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:15 80000000-ffffffff
そして、最初のシャードを表示します。
$ ls -al data/shards/00000000-7fffffff/
total 20
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:15.
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15..
-rw-r--r-- 1 5984 5984 8346 Aug 29 14:15 mydb.1693289721.couch
シャードは、データベース内のデータの水平分割です。データをシャードに分割し、各シャードのコピーをクラスタ内の異なるノードに分散させることで、ノードの損失に対するデータの耐久性を高めます。CouchDB は自動的にデータベースをシャード化し、ドキュメントのサブセットをノード間で分散させます。
クリーンアップします。
docker stop my-couchdb
docker rm my-couchdb
sudo rm -rf $(pwd)/data
docker system prune -a
バインドマウント
Docker は、volume構文よりもmount構文を推奨しています。バインドマウントは、ボリュームと比較して機能が制限されています。ファイルまたはディレクトリは、コンテナにマウントされる際に、ホストマシン上の完全パスで参照されます。バインドマウントは、ホストマシンのファイルシステムに特定のディレクトリ構造が存在することに依存しており、Docker CLI を使用してバインドマウントを管理することはできません。バインドマウントは、コンテナ内で実行されるプロセスを介してホストファイルシステムを変更する可能性があることにも注意してください。
コロン区切り文字(:)で区切られた 3 つのフィールドを持つ-v構文の代わりに、mount構文はより冗長で、複数のキー-値ペアを使用します。
- type: bind、volume または tmpfs
- source: ホストマシン上のファイルまたはディレクトリのパス
- destination: コンテナ内のパス
- readonly
- bind-propagation: rprivate、private、rshared、shared、rslave、slave
- consistency: consistent、delegated、cached
- mount
cd /home/labex/project
mkdir data
docker run -it --name busybox --mount type=bind,source="$(pwd)"/data,target=/data busybox sh
コンテナ内でコマンドを入力します。
echo "hello busybox" > /data/hi.txt
exit
ホストマシン上にファイルが作成されたことを確認します。
cat data/hi.txt
[オプション] OverlayFS
OverlayFS は、Linux 用の「ユニオンマウントファイルシステム」の実装です。Docker ボリュームが何であるかを理解するには、Docker における層とファイルシステムの仕組みを理解するのが役立ちます。
コンテナを起動するには、Docker は読み取り専用のイメージを取得し、その上に新しい読み書き可能な層を作成します。層を 1 つのものとして表示するために、Docker はユニオンファイルシステムまたは OverlayFS(オーバーレイファイルシステム)、具体的にはoverlay2ストレージドライバを使用します。
Docker ホストが管理するファイルを見るには、Docker のプロセスファイルシステムにアクセスする必要があります。--privilegedと--pid=hostフラグを使用することで、busyboxのようなコンテナ内からホストのプロセス ID 名前空間にアクセスできます。その後、Docker の/var/lib/docker/overlay2ディレクトリに移動して、Docker によって管理されるダウンロードされた層を確認できます。
Docker 内の現在の層のリストを表示するには、
$ docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh
/ ## ls -l /var/lib/docker/overlay2
total 16
drwx------ 3 root root 4096 Sep 25 19:44 0e55ecaa4d17c353191e68022d9a17fde64fb5e9217b07b5c56eb4c74dad5b32
drwx------ 5 root root 4096 Sep 25 19:44 187854d05ccd18980642e820b0d2be6a127ba85d8ed96315bb5ae37eb1add36d
drwx------ 4 root root 4096 Sep 25 19:44 187854d05ccd18980642e820b0d2be6a127ba85d8ed96315bb5ae37eb1add36d-init
drwx------ 2 root root 4096 Sep 25 19:44 l
/ ## exit
ubuntuイメージをダウンロードして再度確認します。
docker pull ubuntu
docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh
再度、層のリストを表示するコマンドを入力します。
ls -l /var/lib/docker/overlay2/ & exit
ubuntuイメージをダウンロードすると、4 つの新しい層が暗黙的にダウンロードされることがわかります。
- a611792b4cac502995fa88a888261dfba0b5d852e72f9db9e075050991423779
- d181f1a41fc35a45c16e8bfcb8eee6f768f3b98f82210a43ea65f284a45fcd65
- dac2f37f6280a076836d39b87b0ae5ebf5c0d386b6d8b991b103aadbcebaa7c6
- f3e921b440c37c86d06cd9c9fb70df50edad553c36cc87f84d5eeba734aae709
overlay2ストレージドライバは、本質的にホスト上の異なるディレクトリを重ねて、1 つのディレクトリとして提示します。
- ベース層または lowerdir
diff層または upperdir- オーバーレイ層(ユーザビュー)
workディレクトリ
OverlayFS は、ベースイメージとダウンロードされた読み取り専用(R/O)層を含む下部ディレクトリをlowerdirと呼びます。
上部ディレクトリはupperdirと呼ばれ、読み書き可能(R/W)なコンテナ層です。
統一ビューまたは「オーバーレイ」層はmergedと呼ばれます。
最後に、workdirは必須で、オーバーレイが内部で使用する空のディレクトリです。
overlay2ドライバは、最大 128 個の下部 OverlayFS 層をサポートしています。lディレクトリには、シンボリックリンクとして短縮された層識別子が含まれています。

クリーンアップします。
docker system prune -a
clear
まとめ
この実験では、「ボリューム」と「バインドマウント」を使用してコンテナ内のデータを管理する方法を学びました。また、overlay2ストレージドライバと、それが層を管理するために「ユニオンファイルシステム」をどのように使用するかについても学びました。