ボリュームの利用

読む時間の目安: 8 分

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

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

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

Docker ホスト上のボリューム

コンテナーが一時的な状態を保持するデータを生成しているなら、tmpfs マウント を使うことで、そのデータが永続的に保存されないようにすることを考えてください。 あるいは tmpfs マウント を使うことで、コンテナーの書き込みレイヤーに出力されることも防ぐことができるので、コンテナーの性能を向上させることもできます。

ボリュームはバインドプロパゲーションの 1 つである rprivate を利用します。 ただしボリュームにおいてバインドプロパゲーションを設定することはできません。

-v または --mount フラグの選択

もともと -v フラグや --volume フラグはスタンドアロンコンテナーに対して、また --mount フラグはスウォームサービスに対して用いられてきたものです。 しかし Docker 17.06 からは --mount をスタンドアロンコンテナーに対しても利用できるようになりました。 全般に --mount の方がわかりやすいものですが、記述は増えます。 両者の最大の違いは、-v の文法がオプション指定のすべてを 1 項目にとりまとめるものであるのに対して、--mount の文法はそれを 1 つずつ個別に分けている点です。 以下に両フラグにおける文法を比較します。

はじめて利用する方は、--volume よりも --mount の方が文法は簡単なので、--mount を利用してください。

ボリュームドライバーのオプションを指定する必要がある場合は、--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 が指定されると、そのボリュームが コンテナーにおける読み込み専用マウント としてマウントされます。
    • 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

ボリュームを使ったサービスの起動

サービスを起動してボリュームを定義すると、各サービスそれぞれは固有のローカルボリュームを利用します。 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=10.0.0.10,rw,nfsvers=4,async"' \
    nginx:latest

データボリュームのバックアップ、復元、移行

ボリュームはバックアップ、復元、移行が簡単にできます。 --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