Dockerのボリューム
はじめに
dockerでコンテナを立ち上げる際にホストのデータを共有したいことがあると思います。
今回はそんなデータの共有方法について記事にしたいと思います。
前提
今回の検証は全てAWSのEC2(Amazon Linux2023)上で行っています。
今回はボリュームの仕組みを確認するだけなので、Dockerfile等は使用しません。
コンテナの起動には全てコマンドを使用しています。
ボリュームの種類
Dockerではホストとデータを共有する方法がいくつかあります。
- bind mount
- volume
- tmpfs mount
今回はこれらの特徴と、実際にどのように動くのかを確認していきます。
ちなみに共有と書いていますが、正確にはコンテナからホストのディレクトリを参照しているという方が正しいと思います。
bind mount
概要
bind mountはホスト上のディレクトリをコンテナでマウントする方法です。
これは恐らく一番イメージしやすいと思います。
ローカル環境の作業ディレクトリをコンテナと共有するイメージです。
図にするとこんな感じです。
やってみよう
では実際にやってみます。
まずはロール環境に新しいディレクトリとファイルを作成します。
$ mkdir volumedir
$ echo "Hello World" > volumedir/test01.txt
では、こちらのvolumedirというディレクトリを共有したいと思います。
まずはコンテナを作成します。
今回はubuntuのコンテナを作成します。
$ docker run -it --rm --name volumetest01 -h volumetest \
--mount type=bind,src=$HOME/volumedir,dst=/voldir \
ubuntu:latest /bin/bash
一応コマンドについて分かりにくい部分を解説しておきます。
今回の肝は
--mount typeで共有方法を指定する。
srcでホスト側の共有したいディレクトリを指定する。
dstでコンテナ側に作成する共有ディレクトリの名前を指定する。
という部分です。
では、コンテナを作成したのでディレクトリが共有されているか確認していきます。
作成したvoldir内にtest01.txtが存在することを確認します。
root@volumetest:/# ls voldir
test01.txt
中身も見ておきましょう。
root@volumetest:/# cat voldir/test01.txt
Hello World
先ほどホスト側で作成したものと同じ内容になっています。
では、新たにファイルを追加してみます。
root@volumetest:/# echo "konnitiha sekai" > voldir/test02.txt
root@volumetest:/# cat voldir/test02.txt
konnitiha sekai
では、これがホスト側で追加されているか確認します。
コンテナからログアウトしてファイルが増えているか確認します。
root@volumetest:/# exit
$ ls volumedir
test01.txt test02.txt
cat volumedir/test02.txt
konnitiha sekai
先ほど追加したファイルがホスト側にも存在します。
これでbind mountによりファイルが共有されていることが確認できました。
もちろんファイルの削除等も同期しています。
volume
概要
volumeはホストの/var/lib/docker/volumes以下のディレクトリをコンテナから参照する方法です。
volumeを使うことでコンテナ内で作成したデータを永続化することができます。
イメージ図はこんな感じです。
先ほどのbind mountとの違いは、ホスト側のディレクトリが固定されているという点です。
しかし、ディレクトリを共有するという点は同じで、イマイチ違いが分かりづらいですね。
個人的にはこのvolumeはホストのディレクトリ共有と考えると理解しにくいと思っています。(あくまでも個人的な意見です)
このvolumeは付け外し可能なストレージ(USBみないな感じ)として考えると分かりやすいと思います。
では、実際に操作しながら見ていきましょう
やってみよう
まずは先ほどと同じようにコンテナを作成していきます。
変更点は--mount typeをvolume
に、srcをtestvolume
としています。
$ docker run -itd --rm --name volumetest01 -h volumetest \
--mount type=volume,src=testvolume,dst=/voldir \
ubuntu:latest /bin/bash
コンテナとvolumeが作成されました。
まずはホストの/var/lib/docker/volumesを確認します。
$ sudo ls /var/lib/docker/volumes
testvolume
testvolume
が作成されています。
volumeを作成すると、指定した名前のディレクトリができることが分かりました。
また、mount typeをvolumeにするとdocker volume ls
コマンドで作成したvolumeを確認することができます。
$ docker volume ls
DRIVER VOLUME NAME
local testvolume
testvolumeという名前のvolumeがあることが分かります。
これが付け外し可能なストレージです。
ホストに作成されたvolumeはDcokerエンジンから見ると一つのコンポーネントとして見ることができるということです。
イメージとしてはこんな感じです。
では確認のためにもう一つコンテナを作成して、同じvolumeを使用できるか確認します。
volumetest02
という名前のコンテナを作成します。
volumeは先ほど作成したtestvolume
を指定します。
$ docker run -itd --rm --name volumetest02 -h volumetest \
--mount type=volume,src=testvolume,dst=/voldir \
ubuntu:latest /bin/bash
二つのコンテナが起動していることを確認しておきます。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1234567890ab ubuntu:latest "/bin/bash" 11 seconds ago Up 10 seconds volumetest02
abcdefghijkl ubuntu:latest "/bin/bash" 26 minutes ago Up 26 minutes volumetest01
コンテナが作成できたので、まずは現在のvolumeを確認しておきます。
ここでは先ほどのtestvolume
を使用するので、数が増えてなければ正解です。
$ docker volume ls
DRIVER VOLUME NAME
local testvolume
予想通り、最初に作成したtestvolume
のみですね。
では実際にvolumeが共有されていることを確認しましょう。
まずはvolumetest01
のコンテナでvoldirにtest01.txtを作成します。
$ docker exec -it volumetest01 bash -c "echo 'Hello World' > /voldir/test01.txt"
では、volumetest02
でファイルが存在することを確認します。
$ docker exec -it volumetest02 cat /voldir/test01.txt
Hello World
Hello Worldが表示されたので共有されていることが確認できました。
念の為にホスト側の方も確認します。
データは/var/lib/docker/volumes/<volume名>/_data
に入っています。
$ cat /var/lib/docker/volumes/testvolume/_data/test01.txt
Hello World
こちらも問題なく確認できました。
このようにvolumeは複数のコンテナで使用することができます。
また、複数のvolumeを一つのコンテナで使用することでできます。
これとは別に--volumes-fromというコマンドでvolumeをコンテナ間で共有する方法もあるのですが、長くなってしまうのでまた別の記事で紹介したいと思います。
tmpfs mount
概要
tmpfs mountはホストのメモリをファイルシステムとして利用する方法です。
メモリを使用するメリットはなんと言っても処理が早い!です。
ただし、メモリは揮発性なのでデータの永続化には使えません。
そのためtmpfs mountは使い所が限られてきます。
tmpfs mountのイメージはこんな感じです。
やってみよう
ではvolumeとtmpfs mountでデータの書き込み速度にどの程度の差があるのか検証してみます。
volumeは先ほどのコンテナを使います。
ではtmpfs mountでコンテナを作成します。
docker run -itd --rm --name volumetest03 -h volumetest \
--mount type=tmpfs,dst=/voldir,tmpfs-mode=1770,tmpfs-size=20000000000 \
ubuntu:latest /bin/bash
tmpfs-mode
はdstで指定したディレクトリの権限を制御するためのオプションです。
tmpfs-size
はディレクトリのサイズをバイトで定義します、今回の場合は20GBです。
tmpfsはホストのメモリを使うのでsrc
オプションはありません。
では、2つのコンテナの準備はできたので、それぞれのコンテナで10GBのデータを書き込む時間を比較します。
まずはvolumeのコンテナから確認します。
$ sync; time docker exec -it volumetest01 bash -c "dd if=/dev/zero of=/voldir/bigdate01 bs=1G count=10"
10737418240 bytes (11 GB, 10 GiB) copied, 55.5468 s, 193 MB/s
real 0m55.755s
user 0m0.027s
sys 0m0.005s
約55秒ほどかかりました。
ではtmpfsではどのくらいかかるのでしょうか。
$ sync; time docker exec -it volumetest03 bash -c "dd if=/dev/zero of=/voldir/bigdate01 bs=1G count=10"
10737418240 bytes (11 GB, 10 GiB) copied, 3.50335 s, 3.1 GB/s
real 0m3.656s
user 0m0.021s
sys 0m0.008s
約3秒でした、volumeに比べてかなり早いです。
ホストのスペックに依存しますが、メモリを使用しているので非常に処理が早いですね。
ボリュームタイプの確認方法
コンテナのボリュームタイプはホストから確認することもできます。
docker inspect
コマンドで特定のコンテナを指定します。
$ docker inspect <container-name>
"Mounts": [
{
"Type": "volume",
"Name": "volume",
"Source": "/var/lib/docker/volumes/volume/_data",
"Destination": "/root/ctdir01",
...
Type
という部分がボリュームタイプになります。
それぞれの共有の使い分け
基本的にはvolumeを使うことが多いかなと思います。
Dockerの公式でもbind mountに比べてvolumeが優れている点がたくさん紹介されています。
volumeはデータの永続化以外にも、コンテナとストレージを分離することができます。
これにより簡単に共有、移植することができます。
bind mountはローカル環境で開発中のソースコードをリアルタイムでコンテナに反映させたい場合などに使います。
tmpfsは高速データ処理なんかでコンテナを使用する際に使うのかな、と思います。
まとめ
実際にコンテナを運用するときはDockerfile,docker-compose.ymlなどを使うことが多いと思いますが、裏で動いている仕組みは同じなので、これを理解すればファイル共有でのトラブルにも対応できるようになるんじゃないかと思います。
また、データ専用のコンテナもあったりするので、そちらも勉強して記事を書こうと思います。
参考資料
Discussion