ボリュームの利用

読む時間の目安: 8 分

ボリュームとは、Docker コンテナーにおいて生成され利用されるデータを、永続的に保持する目的で利用される仕組みです。 バインドマウント はホストマシン OS のディレクトリ構造に依存しますが、ボリュームは完全に Docker によって管理されます。 ボリュームにはバインドマウントに比べて、以下のように優れた点があります。

  • ボリュームはバインドマウントよりも、バックアップや移行が容易です。
  • ボリュームは Docker CLI コマンドや Docker API を利用して管理することができます。
  • ボリュームは Linux と Windows 上のコンテナーにおいて動作します。
  • ボリュームは複数コンテナー間にて安全に共有できます。
  • ボリュームドライバーを用いると、リモートホスト上、あるいはクラウドプロバイダー上のボリュームに保存できるようになります。保存の際にはボリューム内データを暗号化することができ、その他にも種々の機能を利用することができます。
  • ボリュームを新たに生成すると、その内容はコンテナーがあらかじめ用意していた内容になります。
  • Docker Desktop 上のボリュームは、Mac や Windows ホストからのバインドマウントに比べて、より高い性能を実現します。

さらに永続的にデータを保持するならば、コンテナーの書き込みレイヤーを用いるのではなく、ボリュームを用いることの方が適切となる場合が多々あります。 というのもボリュームであれば、これを利用するコンテナーのサイズを増加させることはありません。 ボリューム内のデータは、コンテナーのライフサイクルから離れたところに存在しているからです。

Docker ホスト上のボリューム

コンテナーが一時的な状態を保持するデータを生成しているなら、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には、コンテナー上にてマウントするファイルまたはディレクトリのパスを指定します。 destinationdsttargetといった指定がよく用いられます。
    • オプションreadonlyが指定されると、そのボリュームが コンテナーにおける読み込み専用マウント としてマウントされます。
    • 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

全ボリュームの削除

未使用のボリュームを削除して容量を開放するには、以下のコマンドを実行します。

$ docker volume prune

次のステップ

storage, persistence, data persistence, volumes