ボリュームの利用
読む時間の目安: 8 分
ボリュームとは、Docker コンテナーにおいて生成され利用されるデータを、永続的に保持する目的で利用される仕組みです。 バインドマウント はホストマシン OS のディレクトリ構造に依存しますが、ボリュームは完全に Docker によって管理されます。 ボリュームにはバインドマウントに比べて、以下のように優れた点があります。
- ボリュームはバインドマウントよりも、バックアップや移行が容易です。
- ボリュームは Docker CLI コマンドや Docker API を利用して管理することができます。
- ボリュームは Linux と Windows 上のコンテナーにおいて動作します。
- ボリュームは複数コンテナー間にて安全に共有できます。
- ボリュームドライバーを用いると、リモートホスト上、あるいはクラウドプロバイダー上のボリュームに保存できるようになります。保存の際にはボリューム内データを暗号化することができ、その他にも種々の機能を利用することができます。
- ボリュームを新たに生成すると、その内容はコンテナーがあらかじめ用意していた内容になります。
- Docker Desktop 上のボリュームは、Mac や Windows ホストからのバインドマウントに比べて、より高い性能を実現します。
さらに永続的にデータを保持するならば、コンテナーの書き込みレイヤーを用いるのではなく、ボリュームを用いることの方が適切となる場合が多々あります。 というのもボリュームであれば、これを利用するコンテナーのサイズを増加させることはありません。 ボリューム内のデータは、コンテナーのライフサイクルから離れたところに存在しているからです。
コンテナーが一時的な状態を保持するデータを生成しているなら、tmpfs マウント を使うことで、そのデータが永続的に保存されないようにすることを考えてください。 あるいは tmpfs マウント を使うことで、コンテナーの書き込みレイヤーに出力されることも防ぐことができるので、コンテナーの性能を向上させることもできます。
ボリュームはバインドプロパゲーションの 1 つであるrprivate
を利用します。
ただしボリュームにおいてバインドプロパゲーションを設定することはできません。
-v または --mount フラグの選択
全般に--mount
の方がわかりやすいものですが、記述は増えます。
両者の最大の違いは、-v
の文法がオプション指定のすべてを 1 項目にとりまとめるものであるのに対して、--mount
の文法はそれを 1 つずつ個別に分けている点です。
以下に両フラグにおける文法を比較します。
ボリュームドライバーのオプションを指定する必要がある場合は、--mount
を用いなければなりません。
-v
または--volume
: 3 つの項目から構成され、それぞれをコロン(:
)で区切ります。 各項目は正しい順に記述する必要があります。 各項目の意味は、そのときどきによって変わります。- 名前つきボリュームの場合、1 つめの項目は、そのボリューム名です。 指定されるホストマシン上において固有の名称であるものです。 匿名ボリュームの場合、1 つめの項目は省略されます。
- 2 つめは、コンテナー内にマウントされるファイルまたディレクトリのパスです。
- 3 つめは任意の指定項目であり、オプション指定をカンマ区切りで指定します。
指定内容には
ro
などがあります。 このオプションに関しては後に説明しています。
--mount
: 複数のキーバリューペアを指定し、各ペアはカンマにより区切ります。 そしてそれぞれのペアは<key>=<value>
という記述を行います。--mount
における記述は-v
や--volume
におけるものよりも長くなります。 しかしキーの並び順に意味はなく、このフラグに与えられたキーバリューの内容は容易に理解することができます。type
はマウントのタイプであり、bind
,volume
,tmpfs
といった値を指定します。 ここで説明しているのはボリュームであるため、常にvolume
であるものとします。source
はマウント元です。 名前つきボリュームの場合は、そのボリューム名です。 匿名ボリュームの場合、この項目は省略します。source
あるいはsrc
といった指定がよく用いられます。destination
には、コンテナー上にてマウントするファイルまたはディレクトリのパスを指定します。destination
、dst
、target
といった指定がよく用いられます。- オプション
readonly
が指定されると、そのボリュームが コンテナーにおける読み込み専用マウント としてマウントされます。 これはreadonly
またはro
として指定が可能です。 volume-opt
オプションは複数の指定が可能です。 オプション名とその値からなるキーバリューペアを指定します。
上位レベルの CSV 解析をエスケープする
利用するボリュームドライバーが、オプションとしてカンマ区切りリストを受け取る場合、そのリストが上位レベルの CSV として解析されないようにエスケープすることが必要になります。
volume-opt
をエスケープするには、そのオプションをダブルクォート("
)で囲み、かつマウントパラメーター全体の文字列をシングルクォート('
)で囲みます。たとえば
local
ドライバーはo
パラメーターにおいて、マウントオプションにカンマ区切りリストを受けつけます。 ここに示す例は、そのリストを正しくエスケープする方法を示しています。$ docker service create \ --mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>,"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"' --name myservice \ <IMAGE>
これ以降においては、可能なら--mount
と-v
の両方の例を示していきます。
先に示すのは--mount
とします。
-v
と--mount
の動作の違い
バインドマウントの場合とは違い、ボリュームのオプションは、--mount
と-v
フラグの両方においてすべて利用できます。
サービスにおいてボリュームを利用する場合は--mount
のみがサポートされます。
ボリュームの生成と管理
バインドマウントとは異なり、ボリュームの生成と管理はコンテナーの外部から行います。
ボリュームの生成
$ docker volume create my-vol
ボリュームの一覧表示
$ docker volume ls
local my-vol
ボリュームの確認
$ docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
ボリュームの削除
$ docker volume rm my-vol
ボリュームを使ったコンテナーの起動
ボリュームがまだ存在していない状態で、そのボリュームを使ったコンテナーを起動すると、Docker はその際にボリュームを生成します。
以下の例はボリュームmyvol2
をコンテナー内の/app/
にマウントするものです。
--mount
と-v
によるそれぞれの例は、同一の結果になります。
ただし 2 つの例を同時に実行することはできません。
実行するためには、実行前にdevtest
コンテナーとmyvol2
ボリュームを削除しておくことが必要になります。
$ docker run -d \
--name devtest \
--mount source=myvol2,target=/app \
nginx:latest
$ docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest
ボリュームが正しく生成されたかどうかをdocker inspect devtest
により確認します。
出力の中でMounts
の項目を見てみます。
"Mounts": [
{
"Type": "volume",
"Name": "myvol2",
"Source": "/var/lib/docker/volumes/myvol2/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
この情報から、マウントはボリュームであることがわかります。 そしてマウント元、マウント先が正しいものであること、マウントが読み書き可能であることがわかります。
コンテナーを停止し、ボリュームを削除します。 ボリュームの削除は別操作になります。
$ docker container stop devtest
$ docker container rm devtest
$ docker volume rm myvol2
docker-compose でのボリューム利用
ボリュームを利用する単一の Compose サービスは、たとえば以下のようなものです。
version: "3.9"
services:
frontend:
image: node:lts
volumes:
- myapp:/home/node/app
volumes:
myapp:
docker-compose up
の初回実行時に、そのボリュームが生成されます。
このボリュームが、それ以降の実行時においても再利用されます。
ボリュームはdocker volume create
の実行によって、Compose の外部に直接生成されているかもしれません。
その後はdocker-compose.yml
内から、以下のようにして参照されます。
version: "3.9"
services:
frontend:
image: node:lts
volumes:
- myapp:/home/node/app
volumes:
myapp:
external: true
Compose においてボリュームを利用する方法に関しては Compose リファレンス を参照してください。
ボリュームを使ったサービスの起動
サービスを起動してボリュームを定義すると、各サービスそれぞれは固有のローカルボリュームを利用します。
local
ボリュームドライバーを利用する場合は、コンテナー間でデータが共有されることはありません。
一方、ボリュームドライバーの中には、ストレージの共有をサポートするものがあります。
Docker for AWS と Docker for Azure では、Cloudstor プラグインを利用して恒常的なストレージをサポートしています。
以下の例はnginx
サービスを 4 つのレプリカを使って起動し、そのレプリカの個々がmyvol2
というローカルボリュームを利用します。
$ docker service create -d \
--replicas=4 \
--name devtest-service \
--mount source=myvol2,target=/app \
nginx:latest
docker service ps devtest-service
を実行して、サービスが起動していることを確認します。
$ docker service ps devtest-service
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
4d7oz1j85wwn devtest-service.1 nginx:latest moby Running Running 14 seconds ago
サービスを削除します。 これによりすべてのタスクが止まります。
$ docker service rm devtest-service
サービスを削除しても、そのサービスによって生成されたボリュームは削除されません。 ボリュームの削除は別操作になります。
サービスにおける文法の違い
docker service create
コマンドは-v
フラグや--volume
フラグをサポートしていません。
サービスコンテナー内にボリュームをマウントするには、--mount
フラグを使う必要があります。
コンテナーからボリュームへのデータコピー
上で示したように起動するコンテナーが、新たなボリュームを生成するとします。
そして(上でいうと/app
のように)マウントされるディレクトリ内に、すでにファイルやサブディレクトリが存在していた場合、このディレクトリの内容はボリュームにコピーされます。
そうしてからコンテナーは、ボリュームをマウントして利用していきます。
このボリュームを利用する別のコンテナーは、コピーを済ませた内容にアクセスすることになります。
この状況を示すために、以下ではnginx
コンテナーを起動して、コンテナー内の/usr/share/nginx/html
の内容を、新たなボリュームnginx-vol
にコピーする例を示します。
/usr/share/nginx/html
内には、Nginx によるデフォルトの HTML ファイルが保存されています。
--mount
と-v
の例は、いずれも同一の結果となります。
$ docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest
$ docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html \
nginx:latest
どちらかの例を実行した後は、以下のコマンドを実行して、コンテナーとボリュームを削除します。 ボリュームの削除は別操作になります。
$ docker container stop nginxtest
$ docker container rm nginxtest
$ docker volume rm nginx-vol
読み込み専用ボリュームの利用
開発アプリケーションによっては、コンテナーからバインドマウントへの書き込みを行って、変更内容を Docker ホストにコピーし直すことが必要になる場合があります。 単にコンテナーが、データの読み込みができさえすればよい場合もあります。 複数のコンテナーは同一のボリュームをマウントすることが可能であり、その場合、一部を読み書き可能なボリュームとしてマウントし、残りは読み込み専用のボリュームとしてマウントする、といったことが同時にできます。
上の例を修正して次の例では、ディレクトリを読み込み専用ボリュームとしてマウントします。
これを実現するには、コンテナー内のマウントポイントの指定に続けて(デフォルトでは空の)オプションリストにro
を加えます。
複数のオプション指定がある場合は、カンマで区切ります。
--mount
と-v
の例は、いずれも同一の結果となります。
$ docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
nginx:latest
$ docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html:ro \
nginx:latest
docker inspect nginxtest
を実行して、読み込み専用のマウントが正しく生成されていることを確認します。
確認するのはMounts
の項です。
"Mounts": [
{
"Type": "volume",
"Name": "nginx-vol",
"Source": "/var/lib/docker/volumes/nginx-vol/_data",
"Destination": "/usr/share/nginx/html",
"Driver": "local",
"Mode": "",
"RW": false,
"Propagation": ""
}
],
コンテナーを停止して削除します。 またボリュームも削除します。 ボリュームの削除は別操作になります。
$ docker container stop nginxtest
$ docker container rm nginxtest
$ docker volume rm nginx-vol
マシン間でのデータ共有
フォールトトレラントなアプリケーションを構築する場合、1 つのサービスに対するレプリカを複数生成して、それらがアクセスするファイルを同一にするような設定を行う場合があります。
開発アプリケーションにおいて、これを実現する方法はいくつかあります。 1 つは、Amazon S3 のようなクラウド上のストレージシステムに、ファイルを保存するようなロジックをアプリケーションに組み入れることです。 別の方法として、NFS や Amazon S3 などの外部ストレージシステムにファイル書き込みを行うドライバーを利用して、ボリュームを生成する方法です。
ボリュームドライバーを使うと、アプリケーションロジックが利用するストレージシステムを抽象化することができます。 たとえば NFS ドライバーを使ったボリュームを持つサービスがあったとします。 そして別のドライバーを使うようにサービスを変更したいとします。 このような場合、たとえばクラウド上にデータを保存するような変更は、アプリケーションロジックを変更することなく実現できます。
ボリュームドライバーの利用
docker volume create
を実行してボリュームを生成する場合、あるいはコンテナーを起動した際にボリュームがまだ生成されていない場合に、ボリュームドライバーを指定することができます。
以下の例ではvieux/sshfs
ボリュームドライバーというものを利用しています。
はじめはスタンドアロンのボリュームを生成する場合であり、次はコンテナー起動時に新たなボリュームが生成される場合です。
初期設定
この例では 2 つのノードがあるものとします。 1 つめが Docker ホストであり、2 つめのノードに対して SSH により接続するものとします。
Docker ホストではvieux/sshfs
プラグインをインストールします。
$ docker plugin install --grant-all-permissions vieux/sshfs
ボリュームドライバーを用いたボリュームの生成
この例では SSH パスワードを指定することにしていますが、2 つのホスト間で鍵を共有する設定しているのであれば、パスワードは省略可能です。
各ボリュームドライバーは、設定可能なオプションを持つことがあります。
オプションの指定には-o
フラグを用います。
$ docker volume create --driver vieux/sshfs \
-o sshcmd=test@node2:/home/test \
-o password=testpassword \
sshvolume
ボリュームドライバーを用いた、ボリューム生成を行うコンテナーの起動
この例では SSH パスワードを指定することにしていますが、2 つのホスト間で鍵を共有する設定しているのであれば、パスワードは省略可能です。
各ボリュームドライバーは、設定可能なオプションを持つことがあります。
ボリュームドライバーにオプション指定が必要な場合、ボリュームマウントを行うには-v
フラグではなく--mount
フラグを用いなければなりません。
$ docker run -d \
--name sshfs-container \
--volume-driver vieux/sshfs \
--mount src=sshvolume,target=/app,volume-opt=sshcmd=test@node2:/home/test,volume-opt=password=testpassword \
nginx:latest
NFS ボリュームを生成するサービスの起動
この例は、サービス生成時に NFS ボリュームを生成する方法を示すものです。
ここでは NFS サーバーとして10.0.0.10
を利用し、NFS サーバーにエクスポートするディレクトリを/var/docker-nfs
とします。
指定するボリュームドライバーはlocal
です。
NFSv3
$ docker service create -d \
--name nfs-service \
--mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,volume-opt=o=addr=10.0.0.10' \
nginx:latest
NFSv4
$ docker service create -d \
--name nfs-service \
--mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,"volume-opt=o=addr=10.0.0.10,rw,nfsvers=4,async"' \
nginx:latest
CIFS/Samba ボリュームの生成
Samba 共有は、ホスト上でのマウントポイントを設定することなく、直接マウントすることができます。
$ docker volume create \
--driver local \
--opt type=cifs \
--opt device=//uxxxxx.your-server.de/backup \
--opt o=addr=uxxxxx.your-server.de,username=uxxxxxxx,password=*****,file_mode=0777,dir_mode=0777 \
--name cif-volume
なお IP アドレスのかわりにホスト名を利用する場合にはaddr
オプションの指定が必要です。
これにより Docker はホスト名による名前解決が可能になります。
データボリュームのバックアップ、復元、移行
ボリュームはバックアップ、復元、移行が簡単にできます。
--volumes-from
フラグを使うと、ボリュームをマウントする新たなコンテナーが生成されます。
ボリュームのバックアップ
たとえばdbstore
という名のコンテナーを新規生成します。
$ docker run -v /dbdata --name dbstore ubuntu /bin/bash
次のコマンドでは以下のことを行います。
- 新規のコンテナーを起動し、
dbstore
コンテナーからボリュームをマウントします。 - ホストディレクトリを
/backup
としてマウントします。 /backup
ディレクトリ内に入って、dbdata
ボリュームの内容を tar コマンドによりbackup.tar
ファイルとして生成します。
$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
コマンドが正常終了すると、コンテナーは停止されます。
そしてdbdata
ボリュームのバックアップを得ることができます。
バックアップからのボリュームの復元
上で生成したバックアップを使えば、同一コンテナー内にこれを復元することができます。 あるいはまったく別のところに作り出している別コンテナーでもかまいません。
たとえばdbstore2
という名の新たなコンテナーを生成します。
$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
そして新たなコンテナーのデータボリューム内に、バックアップファイルを untar します。
$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
上で見てきた作業については、好みのツールを利用して、バックアップ、復元、移行を自動化してテストを行うようにすることができます。
ボリュームの削除
Docker データボリュームは、コンテナーが削除された後も残り続けます。 ボリュームには 2 つのタイプがあるので考慮しておくことが必要です。
- 名前つきボリューム には、コンテナー外部から得られた所定の名称があります。
たとえば
awesome:/bar
と表わされます。 - 匿名ボリューム には名称がありません。 したがってコンテナーが削除されたときには Docker Engine に対して、匿名ボリュームの削除を指示する必要があります。
匿名ボリュームの削除
匿名ボリュームを自動的に削除するには--rm
オプションを利用します。
たとえば以下のコマンドは、匿名の/foo
というボリュームを生成します。
コンテナーが削除されると Docker Engine は/foo
を削除します。
ただしawesome
ボリュームは削除しません。
$ docker run --rm -v /foo -v awesome:/bar busybox top
メモ
別のコンテナーが、
--volumes-from
を使ってボリュームをマウントしている場合は、そのボリューム定義が コピー されます。 そして、初めのコンテナーが削除されても、匿名ボリュームも残り続けます。
全ボリュームの削除
未使用のボリュームを削除して容量を開放するには、以下のコマンドを実行します。
$ docker volume prune
次のステップ
- バインドマウント について学ぶ。
- tmpfs マウント について学ぶ。
- ストレージドライバー について学ぶ。
- サードパーティー製のボリュームドライバープラグイン について学ぶ。