コンテナでデータを管理する

これまでは導入として、基本的な Docker の概念 や、Docker イメージ の動作に加え、 コンテナのネットワーク について学びました。このセクションでは、どのようにコンテナ内やコンテナ間でデータを管理できるかを検討します。

データを Docker で管理するための、2つの主な手法を見ていきます。

  • データ・ボリュームと、
  • データ・ボリューム・コンテナです。

データ・ボリューム

データ・ボリューム (data volume) とは、1つまたは複数のコンテナ内で、特別に設計されたディレクトリであり、 ユニオン・ファイルシステム (Union File System) をバイパス(迂回)するものです。データ・ボリュームは、データの保持や共有のために、複数の便利な機能を提供します。

  • ボリュームはコンテナ作成時に初期化されます。コンテナのベース・イメージ上で、特定のマウント・ポイント上のデータが指定されている場合、初期化されたボリューム上に既存のデータをコピーします。
  • データ・ボリュームはコンテナ間で共有・再利用できます。
  • データ・ボリュームに対する変更を直接行えます。
  • イメージを更新しても、データ・ボリューム上には影響ありません。
  • コンテナ自身を削除しても、データ・ボリュームは残り続けます。

データ・ボリュームは、データ保持のために設計されており、コンテナのライフサイクルとは独立しています。そのため、コンテナの削除時、Docker は 決して 自動的にボリュームを消さないだけでなく、コンテナから参照されなくなっても”後片付け”をせず、ボリュームはそのままです。

データ・ボリュームの追加

docker createdocker run コマンドで -v フラグを使うと、コンテナにデータ・ボリュームを追加できます。-v を複数回使うことで、複数のデータ・ボリュームをマウントできます。それでは、ウェブ・アプリケーションのコンテナに対して、ボリュームを1つ割り当ててみましょう。

$ docker run -d -P --name web -v /webapp training/webapp python app.py

これはコンテナの中に /webapp という新しいボリュームを作成しました。

注釈

DockerfileVOLUME 命令を使っても、イメージから作成されるあらゆるコンテナに対し、新しいボリュームを追加可能です。

Docker はボリュームを、標準では読み書き可能な状態でマウントします。あるいは、読み込み専用(read-only) を指定したマウントも可能です。

$ docker run -d -P --name web -v /opt/webapp:ro training/webapp python app.py

ボリュームの場所

docker inspect コマンドを使うことで、ボリュームがホスト上で使っている場所を探せます。

$ docker inspect web

ボリュームに関する情報を含む、コンテナの詳細設定が表示されます。出力は、おそらく次のようなものでしょう:

...
Mounts": [
    {
        "Name": "fac362...80535",
        "Source": "/var/lib/docker/volumes/fac362...80535/_data",
        "Destination": "/webapp",
        "Driver": "local",
        "Mode": "",
        "RW": true
    }
]
...

ホスト上に場所にあがるのが上の ‘Source’ (ソース)であり、コンテナ内のボリューム指定は Destination です。RW の表示は、ボリュームの読み書き可能を意味します。

データ・ボリュームとしてホスト上のディレクトリをマウント

-v フラグを使ってボリュームを作成することに加え、Docker デーモンのホスト上にあるディレクトリも、コンテナにマウント可能です。

$ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py

このコマンドはホスト側のディレクトリ /src/webapp をコンテナ内の /opt/webapp にマウントします。パス /opt/webapp がコンテナ内のイメージに存在している場合でも、/src/webapp を重複マウントします。しかし、既存の内容は削除しません。マウントを解除すると、内容に対して再度アクセス可能となります。これは、通常の mount コマンドと同じような動作をします。

コンテナ内のディレクトリ は、/src/docs のように、常に絶対パスの必要があります。ホスト側のディレクトリ は相対パスでも 名前 でも構いません。ホスト側のディレクトリ に対して絶対パスを指定すると、Docker は指定したパスを拘束・マウント(bind-mount)します。このとき 名前 の値を指定すると、Docker は指定した 名前 のボリュームを作成します。

名前 の値は、アルファベットの文字で開始する必要があります。具体的には、 a-z0-9_ (アンダースコア)、 . (ピリオド)、 - (ハイフン)です。絶対パスの場合は / (スラッシュ)で始めます。

例えば、ホスト側ディレクトリ/foo または foo を 指定可能です。/foo 値を指定すると、Docker は(ディレクトリに)拘束したマウントを作成します。foo を指定すると、Docker はその名前のボリュームを作成します。

Mac または Windows 上で Docker Machine を使う場合、Docker デーモンは OS X または Windows ファイルシステム上に限定的なアクセスを行います。Docker Machine は自動的に /Users (OS X) または C:\Users (Windows) ディレクトリのマウントを試みます。つまり、OS X 上で使っているファイルやディレクトリをマウント可能です。

docker run -v /Users/<パス>:/<コンテナ内のパス> ...

Windows 上でも、同様にディレクトリのマウントが使えます。

docker run -v /c/Users/<パス>:/<コンテナ内のパス> ...`

パスには、仮想マシンのファイルシステム上にある全てのパスを指定できます。もし VirtualBox などでフォルダの共有機能を使っているのであれば、追加の設定が必要です。VirtualBox の場合は、ホスト上のフォルダを共有フォルダとして登録する必要があります。それから、Docker の -v フラグを使ってマウントできます。

ホスト上のディレクトリをマウントするのは、テストに便利かもしれません。例えば、ソースコードをコンテナの中にマウントしたとします。次にソースコードに変更を加え、アプリケーションにどのような影響があるのか、リアルタイムで確認できます。ホスト側のディレクトリは絶対パスで指定する必要があります。もしディレクトリが存在しない場合、Docker は自動的にディレクトリを作成します。このホスト・パスの自動生成機能は廃止予定です。

Docker ボリュームは、デフォルトで読み書き可能なモードでマウントしますが、読み込み専用としてのマウントもできます。

$ docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py

ここでは同じ /src/webapp ディレクトリをマウントしていますが、読み込み専用を示す ro オプションを指定しています。

mount機能の制限 により、ホスト側のソース・ディレクトリ内のサブディレクトリに移動すると、コンテナの中からホスト上のファイルシステムに移動できる場合があります。これには悪意を持つユーザがホストにアクセスし、ディレクトリを直接マウントする必要があります。

注釈

ホスト・ディレクトリとは、ホストに依存する性質があります。そのため、ホストディレクトリを Dockerfile でマウント出来ません。なぜなら、イメージの構築はポータブル(どこでも実行可能な状態の意味)であるべきだからです。全てのホスト環境でホスト・ディレクトリを使えるとは限りません。

ボリューム・ラベル

SELinux のようなラベリング・システムでは、コンテナ内にマウントされたボリュームの内容に対しても、適切なラベル付けが行われます。ラベルがなければ、コンテナの中の内容物を使って実行しようとしても、セキュリティ・システムがプロセスの実行を妨げるでしょう。標準では、Docker は OS によって設定されるラベルに対して変更を加えません。

コンテナの内容物に対するラベルを変更するには、ボリュームのマウントにあたり、:z または :Z を末尾に付けられます(接尾辞)。これらの指定をすると、Docker に対して共有ボリュームが再度ラベル付けされたものと伝えます。z オプションは、ボリュームの内容が複数のコンテナによって共有されていると Docker に伝えます。その結果、Docker は共有コンテント・ラベルとして内容をラベル付けします。Z オプションは、内容はプライベートで共有されるべきではない(private unshared)ラベルと Docker に伝えます。現在のコンテナのみが、プライベートに(個別に)ボリュームを利用可能です。

ホスト上のファイルをデータ・ボリュームとしてマウント

-v フラグはホストマシン上のディレクトリ だけ ではなく、単一のファイルに対してもマウント可能です。

$ docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

これは新しいコンテナ内の bash シェルを流し込むものです。コンテナを終了するときに、ホスト上の bash history に対して、コンテナ内で実行したコマンドを履歴として記録します。

注釈

vised --in-place を含む多くのツールによる編集は、結果としてiノードを変更する場合があります。Docker v1.1.0 までは、この影響により “sed: cannot rename ./sedKdJ9Dy: Device or resource busy” (デバイスまたはリソースがビジー) といったエラーが表示されることがありました。マウントしたファイルを編集したい場合、親ディレクトリのマウントが最も簡単です。

データ・ボリューム・コンテナの作成とマウント

データに永続性を持たせたい場合(データを保持し続けたい場合)、たとえばコンテナ間での共有や、データを保持しないコンテナから使うには、名前を付けたデータ・ボリューム・コンテナ(Data Volume Container)を作成し、そこにデータをマウントするのが良い方法です。

ボリュームを持ち、共有するための新しい名前付きコンテナを作成しましょう。training/postgres イメージを再利用し、全てのコンテナから利用可能なレイヤーを作成し、ディスク容量を節約します。

$ docker create -v /dbdata --name dbdata training/postgres /bin/true

次に、--volumes-from フラグを使い、他のコンテナから /dbdata ボリュームをマウント可能です。

$ docker run -d --volumes-from dbdata --name db1 training/postgres

あるいは、他からも。

$ docker run -d --volumes-from dbdata --name db2 training/postgres

この例では、postgres イメージには /dbdata と呼ばれるディレクトリが含まれています。そのため dbdata コンテナからボリュームをマウントする(volumes from)とは、元の postgres イメージから /dbdata が隠された状態です。この結果、dbdata コンテナからファイルを表示しているように見えます。

--volumes-from パラメータは複数回利用できます。複数のコンテナから、複数のデータボリュームを一緒に扱えます。

また、ボリュームのマウントは連鎖(chain)できます。この例では、dbdata コンテナのボリュームは db1 コンテナと db2 コンテナからマウントできるだけとは限りません。

$ docker run -d --name db3 --volumes-from db1 training/postgres

ボリュームをマウントしているコンテナを削除する場合、ここでは始めの dbdata コンテナや、派生した db1db2 コンテナのボリュームは削除されません。ディスクからボリュームを削除したい場合は、最後までボリュームをマウントしていたコンテナで、必ず docker rm -v を実行する必要があります。この機能を使えば、コンテナ間でのデータボリュームの移行や更新を効率的に行えます。

注釈

コンテナ削除時、-v オプションでボリュームを消そうとしなくても、Docker は何ら警告を表示しません。コンテナを -v オプションに使わず削除してしまうと、最終的にボリュームは、どのコンテナからも参照されない “宙づり”(dangling) ボリュームになってしまいます。宙づりボリュームは除去が大変であり、多くのディスク容量を使用する場合もあります。このボリューム管理の改善については、現在 プルリクエスト#14214 において議論中です。

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

ボリュームを使った他の便利な機能に、バックアップや修復、移行があります。これらの作業を使うには、新しいコンテナを作成するときに --volumes-from フラグを使い、次のようにボリュームをマウントします。

$ docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

ここでは新しいコンテナを起動し、dbdata コンテナからボリュームをマウントします。そして、ローカルのホスト上のディレクトリを /backup としてマウントします。最終的に、dbdata ボリュームに含まれる内容をバックアップするため、 tar コマンドを使い /backup ディレクトリの中にあるファイルを backup.tar に通します。コマンドの実行が完了すると、コンテナは停止し、dbdata ボリュームのバックアップが完了します。

これで同じコンテナに修復(リストア)したり、他のコンテナにも移行できます。新しいコンテナを作成してみましょう。

$ docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

それから、新しいコンテナのデータ・ボリュームにバックアップしたファイルを展開します。

$ docker run --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar"

この手法を使うことで、好みのツールを用いた自動バックアップ、移行、修復が行えます。

ボリューム共有時の重要なヒント

複数のコンテナが1つまたは複数のデータ・ボリュームを共有できます。しかしながら、複数のコンテナが1つの共有ボリュームに書き込むことにより、データ破損を引き起こす場合があります。アプリケーションが共有データ・ストアに対する書き込みに対応した設計かどうか、確認してください。

データ・ボリュームは Docker ホストから直接アクセス可能です。これが意味するのは、データ・ボリュームは通常の Linux ツールから読み書き可能です。コンテナとアプリケーションが直接アクセスできることを知らないことにより、データの改竄を引き起こすことは望ましくありません。

次のステップ

これまでは、どのように Docker を使うのか、少々学んできました。次は Docker と Docker Hub で利用可能なサービスを連携し、自動構築(Automated Build)やプライベート・リポジトリ(private repository)について学びます。

Docker Hub の操作 に移動します。