【Docker】データ管理(データマウント)
データ管理について
コンテナ内に作成したデータ(ファイル)は、基本的にはそのコンテナ内でのみ操作・利用できます。 つまりコンテナを削除すればコンテナ内のデータも一緒に削除されます。 またコンテナ内のデータをホストマシンや他のコンテナに移動させることは簡単ではありません。
そこでDockerでは、「ボリューム」と「バインドマウント」という方法を使用し、ホストマシン上にデータを保存することでデータの永続化及び共有を行います。 また一時的なデータの保存を目的とし、ホストマシンのメモリにデータを保存する「tmpfs マウント」と呼ばれる方法もあります。
以下よりこれらデータ管理に関することについて説明をしていきます。
ボリューム
ボリュームはDockerによって作成・管理するデータ領域です。 ホストマシン上のデータ領域ではありますが、基本的にはホストマシンからのアクセスは行わず(できず)、Dockerで管理するものとなります。
ボリュームの管理
ボリュームはdocker volume create
コマンドによってあらかじめデータ領域を作成しておく必要があります。
イメージとしては、保存先のディレクトリを作成している感じです。
$ docker volume create test_volume
test_volume
作成したボリュームは、docker volume ls
で一覧表示でき、docker volume inspect
で詳細情報を表示できます。
$ docker volume ls
DRIVER VOLUME NAME
local test_volume
$ docker volume inspect test_volume
[
{
"CreatedAt": "2024-02-18T13:08:07Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/test_volume/_data",
"Name": "test_volume",
"Options": null,
"Scope": "local"
}
]
詳細情報のMountpoint
は、ボリュームのデータが保存されているホストマシン上のパスになりますが、前述の通り基本的にはホストマシンからアクセスできません。
ボリュームを削除する場合は、docker volume rm
またはdocker volume prune
を使用します。
docker volume prune
は未使用のボリュームを一括で削除します。
$ docker volume rm test_volume
test_volume
$ docker volume prune
ボリュームのマウント
コンテナにボリュームをマウントするには、docker container run
の-v(--volume)
または--mount
オプションを使用します。
基本的な機能は同じですが、--mount
の方がより詳細な設定が可能です。
以下コマンドは同じ結果となります。
$ docker container run -it -v test_volume:/data ubuntu:20.04 bash
# {ボリューム:マウント先のコンテナのパス} を指定
$ docker container run -it --mount type=volume,src=test_volume,dst=/data ubuntu:20.04 bash
# type: マウントの種類(volume)
# src: ボリューム名
# dst: マウント先のコンテナのパス
マウントした情報はdocker container inspect
のMounts
で確認できます。
$ docker container inspect --format "{{json .Mounts}}" 3076aeee0e8d
[{"Type":"volume","Name":"test_volume","Source":"/var/lib/docker/volumes/test_volume/_data","Destination":"/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
試しにマウントしたコンテナからボリュームにデータを保存し、別のコンテナからボリュームを参照してみます。
$ docker container run -it -v test_volume:/data ubuntu:20.04 bash
root@4b7cbe2513f7:/# ls
bin boot data dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
root@4b7cbe2513f7:/# touch data/test.txt
root@4b7cbe2513f7:/# echo "Hello World" > data/test.txt
$ docker container run -it -v test_volume:/data2 ubuntu:20.04 bash
root@ab5efdb8e84f:/# ls data2
test.txt
root@ab5efdb8e84f:/# cat data2/test.txt
Hello World
結果の通り、コンテナ間でデータ共有ができることがわかります。
マウントは複数可能で、その数だけオプションを指定します。
$ docker container run -it --mount type=volume,src=test_volume,dst=/data --mount type=volume,src=test1_volume,dst=/data1 ubuntu:20.04 bash
また必要に応じてボリュームを読み取り専用に設定できます。
-v
オプションはマウント先のディレクトリに:ro
を付与し、--mount
オプションの場合はreadonly
を加えます。
$ docker container run -it -v test_volume:/data:ro ubuntu:20.04 bash
$ docker container run -it --mount type=volume,src=test_volume,dst=/data,readonly ubuntu:20.04 bash
ボリュームのバックアップ
ボリュームのバックアップは次の手順で行います。
- バックアップ対象のボリュームをマウントする
- バックアップの保存先としてホストマシンのディレクトリをマウントする(バインドマウント)
- 1でマウントしたディレクトリを圧縮し、できた圧縮ファイルを2で作成したディレクトリに保存する
この手順を踏まえ、以下のコマンドでバックアップを行います。
$ docker container run --rm --mount type=volume,src=db,dst=/data --mount type=bind,src=$(pwd),dst=/backup ubuntu tar cvf /backup/backup.tar /data
db
というボリュームを/data
にマウントし、tar
コマンドによって圧縮しています。
圧縮結果は、ホストマシンのカレントディレクトリをマウントした/backup
にbackup.tar
として保存しています。
結果的に、ボリュームdb
がbackup.tar
としてバックアップされたことになります。
復元についても考え方は同じです。
- 復元先のボリュームをマウントする
- バックアップファイルがあるホストマシンのディレクトリをマウントする(バインドマウント)
- 2でマウントしてディレクトリにある圧縮ファイルを、1でマウントしたディレクトリに展開する
$ docker container run --rm --mount type=volume,src=db2,dst=/data --mount type=bind,src=$(pwd),dst=/backup ubuntu bash -c "cd /data && tar xvf /backup/backup.tar --strip 1"
圧縮ファイルを保存したカレントディレクトリを/backup
にマウントし、そこにあるbackup.tar
を展開先であるdb2
ボリュームをマウントした/data
に展開しています。
バインドマウント
バインドマウントはホストマシン上のディレクトリをコンテナにマウントします。
ボリュームとは異なり、ホストマシン上のディレクトリを直接指定するため、ホストマシンから干渉できます。
マウント方法はボリュームと同様にdocker container run
の-v
または--mount
オプションを使用します。
$ docker container run -it -v ./data:/data ubuntu:20.04 bash
# {ホストマシンのディレクトリ:マウント先のコンテナのパス}
$ docker container run -it --mount type=bind,src=./data,dst=/data ubuntu:20.04 bash
# type: マウントの種類(bind)
# src: ホストマシンのディレクトリ
# dst: マウント先のコンテナのパス
以下がバインドマウントの実行例です。 コンテナからホストマシンのデータを参照・更新できていることがわかると思います。
$ mkdir data
$ touch data/test.txt
$ echo "Hello World" > data/text.txt
$ docker container run -it -v ./data:/data ubuntu:20.04 bash
root@14fa8687b465:/# cat data/test.txt
Hello World
root@14fa8687b465:/# echo "Hello New World" > data/test.txt
root@14fa8687b465:/# exit
exit
$ cat data/test.txt
Hello New World
ボリュームとバインドマウントの違い
ボリュームとバインとマウント、どちらもホストマシン上のデータ領域ではありますが、前述の通りボリュームはホストマシンからアクセスできず、Dockerによって管理されます。 つまり、ホストマシンから直接干渉されない点が大きな違いです。 バインドマウントはホストマシン上のディレクトリを直接マウントするため、どうしてもホストマシンからの影響を受けます。 そのためデータの共有や永続化を目的とする場合、一般的にはボリュームを使用することが推奨されています。
バインドマウントのユースケースとして、例えば開発時にビルドした結果をマウントし、実行はコンテナ内で行うなど、ホストマシン側からもコンテナ側からもアクセスが必要な場合に限ります。 前述のように、ホストマシン側からのアクセスが必要ない場合は、ボリュームを使用することが推奨されています。
tmpfs
ボリュームとバインドマウントはホストマシンのストレージをマウントしましたが、tmpfsはメモリにマウントし、一時的なデータ領域として使用します。 他の2つとは異なり、コンテナ停止時に削除されます。 またコンテナ間での共有もできません。
tmpfsのマウントは--mount
オプションまたは--tmpfs
オプションを利用します。
以下はどちらも/app
ディレクトリにマウントしています。
$ docker container run -it --mount type=tmpfs,dst=/app ubuntu:20.04 bash
# type: マウントの種類(tmpfs)
# dst: マウント先のコンテナのパス
$ docker container run -it --tmpfs /app --name ubuntu1 ubuntu:20.04 bash
以下はコンテナを停止することで、データが削除されていることを確認しています。
$ docker container run -it --tmpfs /app --name ubuntu1 ubuntu:20.04 bash
root@8b602e98dbed:/# ls
app bin boot dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
root@8b602e98dbed:/# touch app/test.txt
root@8b602e98dbed:/# ls app
test.txt
root@8b602e98dbed:/# exit
exit
$ docker container start ubuntu1
ubuntu1
$ docker container exec -it ubuntu1 bash
root@8b602e98dbed:/# ls app
# データなし
tmpfsの利用目的は、コンテナ内のデータに影響を与えずに、一時的なデータを保存することです。 例えば一連の処理の流れで作成して削除するような中間ファイルは、tmpfsに保存することでコンテナ内の他のデータに影響を与えず、かつ処理速度の面からもメリットがあります。
MySQLの構築
ここまでの内容をもとに、データの永続化を行なったMySQLのコンテナを作成します。
永続化にはdb-store
というボリュームを使用することとします。
$ docker volume create db-store
MySQLイメージ:https://hub.docker.com/_/mysql
MySQLイメージの概要にある通り、コンテナ作成時に以下の設定を行います。
- 環境変数
MYSQL_ROOT_PASSWORD
にルートユーザーのパスワードを設定 - ボリュームは
/var/lib/mysql
にマウント
$ docker container run --name test-mysql -e MYSQL_ROOT_PASSWORD=password --mount type=volume,src=db-store,dst=/var/lib/mysql -d mysql
コンテナを作成したら、実際にコンテナに接続してデータを作成してみます。
$ docker container exec -it test-mysql bash
bash-4.4# mysql --password=password
mysql> create database test;
mysql> use test;
mysql> create table user(id int primary key, name varchar(10));
mysql> insert into user (id, name) values
-> (1, 'Taro'),
-> (2, 'Jiro'),
-> (3, 'Saburo');
mysql> select * from user;
+----+--------+
| id | name |
+----+--------+
| 1 | Taro |
| 2 | Jiro |
| 3 | Saburo |
+----+--------+
3 rows in set (0.00 sec)
簡単な例ではありますが、test
というデータベースにuser
テーブルを作成し、3つのデータを登録しました。
ではこのコンテナを削除して、同様に別のコンテナを作成します。
$ docker container stop test-mysql
$ docker container rm test-mysql
$ docker container run --name test-mysql2 -e MYSQL_ROOT_PASSWORD=password --mount type=volume,src=db-store,dst=/var/lib/mysql -d mysql
作成したtest-mysql2
コンテナには先ほどと同じdb-store
ボリュームをマウントしています。
ここから先ほど作成したデータが参照できるか確認をします。
$ docker container exec -it test-mysql2 bash
bash-4.4# mysql --password=password
mysql> select * from test.user;
+----+--------+
| id | name |
+----+--------+
| 1 | Taro |
| 2 | Jiro |
| 3 | Saburo |
+----+--------+
3 rows in set (0.01 sec)
結果の通り、データがしっかりとボリュームに反映され、永続化できていることが確認できました。
ここでは予め作成したボリュームをマウントしましたが、実はMySQLイメージではコンテナ作成時にボリュームが自動的に作成され、マウントされます。
$ docker container run --name test-mysql -e MYSQL_ROOT_PASSWORD=password -d mysql
$ docker volume ls
DRIVER VOLUME NAME
local 282ecdb80caf7f7cd121f530f6357a84383e75b99f82c184e54a039df719d1cf
$ docker container inspect --format '{{json .Mounts}}' test-mysql
[
{
"Type":"volume",
"Name":"282ecdb80caf7f7cd121f530f6357a84383e75b99f82c184e54a039df719d1cf",
"Source":"/var/lib/docker/volumes/282ecdb80caf7f7cd121f530f6357a84383e75b99f82c184e54a039df719d1cf/_data",
"Destination":"/var/lib/mysql",
"Driver":"local",
"Mode":"",
"RW":true,
"Propagation":""
}
]
しかし、ボリューム名がランダムな上に長いため、コンテナとの関連性を考えた時に自身で作成したボリュームを使用するほうがよいでしょう。