📦

Dockerのボリューム

2023/10/08に公開

はじめに

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が優れている点がたくさん紹介されています。
https://matsuand.github.io/docs.docker.jp.onthefly/storage/volumes/

volumeはデータの永続化以外にも、コンテナとストレージを分離することができます。
これにより簡単に共有、移植することができます。

bind mountはローカル環境で開発中のソースコードをリアルタイムでコンテナに反映させたい場合などに使います。
tmpfsは高速データ処理なんかでコンテナを使用する際に使うのかな、と思います。

まとめ

実際にコンテナを運用するときはDockerfile,docker-compose.ymlなどを使うことが多いと思いますが、裏で動いている仕組みは同じなので、これを理解すればファイル共有でのトラブルにも対応できるようになるんじゃないかと思います。

また、データ専用のコンテナもあったりするので、そちらも勉強して記事を書こうと思います。

参考資料

https://book.impress.co.jp/books/1122101059

GitHubで編集を提案

Discussion