Docker におけるデータ管理

読む時間の目安: 2 分

コンテナー内に生成されたファイルは、デフォルトではすべて書き込み可能なコンテナーレイヤーに保存されます。 これは以下を意味します。

  • コンテナーが存在しなくなると、データを保持しておくことができなくなります。 そのデータをコンテナーの外部から取得したいと思っても、外部プロセスがこれを行うことは極めて困難になります。
  • コンテナーの書き込み可能レイヤーは、コンテナーが稼動しているホストマシンに強く結び付けられています。 したがってその中のデータをどこかに移動させることは容易ではありません。
  • コンテナーの書き込み可能レイヤーにデータを書き込むためには、ファイルシステムを管理する ストレージドライバー が必要になります。 このストレージドライバーは、Linux カーネルを利用してユニオンファイルシステム(union filesystem)を提供します。 この特別な抽象ファイルシステムは データボリューム に比べると性能が劣ります。 データボリュームであれば、ホストのファイルシステムに直接データを書き込むことができます。

Docker コンテナーにおけるファイルをホストマシン上に保存する方法は 2 つあります。 これを行えば、コンテナーが停止した後にもデータを維持していくことができます。 その 2 つの方法とは ボリュームバインドマウント です。 Linux 上において Docker を稼動していれば、さらに tmpfs マウント を用いることもできます。 Windows 上において Docker を稼動していれば、名前つきパイプ(named pipe)を用いることもできます。

データを保持するためのこの 2 つの方法について、さらに具体的に読み進めてください。

適切なマウント方法の選定

どの種類のマウント方法を選んだとしても、コンテナー内部からのデータの見え方は同じです。 そのデータはコンテナー内のファイルシステム上において、ディレクトリとして見えるか、あるいは個別のファイルとして見えます。

ボリューム、バインドマウント、tmpfsマウントにどのような違いがあるのかは、そのデータが Docker ホスト上のどこに保存されるかを考えてみると、わかりやすくなります。

マウントの種類と Docker ホスト上でのデータ保存場所

  • ボリューム はホストのファイルシステムの一部としてデータが保存されます。 そしてこれは Docker によって管理されます。 (Linux であれば/var/lib/docker/volumes/に保存されます。) Docker 以外のプロセスは、このファイルシステム上の保存場所に変更を行ってはなりません。 ボリュームは Docker においてデータを維持するための最良の方法です。

  • バインドマウント はホストシステム上の どこにでも 保存することができます。 それが重要なシステムファイルやディレクトリであってもかまいません。 Docker ホスト上の Docker に無関係なプロセスや Docker コンテナーであったとしても、これを修正することがいつでもできます。

  • tmpfsマウント はホストシステムのメモリ上にのみ保存されます。 そしてホストシステムのファイルシステムに向けての書き込みを行うことはできません。

マウントタイプに関する詳細

  • ボリューム: Docker によって生成され管理されるものです。 ボリュームはdocker volume createコマンドによって明示的に生成します。 あるいは Docker が、コンテナーやサービスの生成時にボリュームを生成します。

    ボリュームを生成した際には、Docker ホスト上のディレクトリにボリュームが保存されます。 このボリュームをコンテナーにマウントすると、そのディレクトリがコンテナー内にマウントされるものとなります。 このことは、バインドマウントが動作する様子と同様です。 ただしボリュームは Docker によって管理されるものであって、ホストマシンの主要な機能からは切り離されています。

    ボリュームは複数のコンテナーに対して同時にマウントすることができます。 たとえそのボリュームを利用するコンテナーが 1 つも実行されていなくても、ボリュームは Docker が利用できる状態にあって、自動的に削除されることはありません。 未使用のボリュームはdocker volume pruneによって削除することができます。

    ボリュームのマウントは、名前つき(named)か 匿名(anonymous)のいずれかにより行われます。 匿名ボリュームがコンテナーにマウントされた初めての段階においては、明示的な名称がありません。 そこで Docker が、Docker ホスト内で必ず固有となるランダムな名称を与えます。 名前を持たないというだけで、名前つきと匿名の各ボリュームは同等に機能します。

    ボリュームでは ボリュームドライバー の利用がサポートされます。 これによって、いろいろな利用の仕方が可能となります。 たとえば手元のデータを、リモートホストやクラウドプロバイダーに保存することができるようになります。

  • バインドマウント: Docker の初期の段階から利用可能。 バインドマウントにはボリュームに比べて機能が制限されています。 バインドマウントを利用する際には、ホストマシン 上のファイルやディレクトリがコンテナーにマウントされます。 そのファイルやディレクトリは、ホストマシン上のフルパスによって参照されます。 ファイルやディレクトリが、Docker ホスト上からなくなっていても問題ありません。 存在していないときは、処理実行時に生成されます。 バインドマウントは非常に効率的なものですが、ホストマシン上のファイルシステムに依存し、利用可能な所定のディレクトリ構造に従って動作します。 Docker アプリケーションを新規開発する際には、これではなく名前つきボリュームを利用することを考えてみてください。 バインドマウントを直接管理するような Docker CLI コマンドはありません。

    バインドマウントは機密ファイルへのアクセスも行います。

    バインドマウントを利用する際の副作用として、これが良いことか悪いことかはわかりませんが、コンテナー 内に動作するプロセスを通じて ホスト のファイルシステムに変更がかけられるということです。 たとえばシステムにとって重要なファイル、ディレクトリを生成、編集、削除ができてしまいます。 セキュリティに影響を及ぼしかねない強力な能力がここにあって、ホストシステム上の Docker 以外のプロセスへも影響します。

  • tmpfs マウント: tmpfsマウントとは、Docker ホスト上もコンテナー上も、ディスクに長らく保持されるものではありません。 これはコンテナーが起動している間のみ、コンテナーが利用するものであって、一時的な状態や機密情報などを保存します。 たとえば Docker 内部においては、Swarm サービスがtmpfsマウントを利用して、サービスコンテナー内に secrets をマウントしています。

  • 名前つきパイプ(named pipe): npipeマウントは Docker ホストとコンテナーの間での通信のために利用されます。 よく行われる利用例としては、コンテナー内部にサードパーティー製のツールを実行させて、名前つきパイプにより Docker Engine API への接続を行うような場合です。

バインドマウントとボリュームは-vまたは--volumeフラグを使って、コンテナーへのマウントを行うことができます。 しかしその文法は多少異なります。 tmpfsマウントの場合は--tmpfsフラグを使います。 コンテナーとサービスの双方において、バインドマウント、ボリューム、tmpfsマウントのどれであっても、--mountフラグを利用することが推奨されます。 文法がよりはっきりとしているからです。

ボリュームの適切な利用例

Docker コンテナーやサービスにおいてデータを保持するためには、ボリュームを用いるのが好ましい方法です。 ボリュームの利用例として以下のものがあります。

  • 複数コンテナーを起動し、データ共有を行う場合です。 ボリュームが明示的に生成されていない場合、コンテナーへのマウント時の初回にボリュームは生成されます。 コンテナーが停止されるか削除されたとしても、ボリュームは残ります。 複数コンテナーは同一ボリュームを同時にマウントすることが可能です。 その場合、読み書き可能、あるいは読み込み専用とすることができます。 ボリュームは、明示的に削除を指示したときのみ削除されます。

  • Docker ホストにおいて、指定されたディレクトリまたはファイル構造が保証されていない場合です。 ボリュームを用いることで、Docker ホストの設定を、コンテナーの実行環境から切り離すことができます。

  • コンテナーデータの保存先として、ローカルマシンではなく、リモートホストやクラウドプロバイダーを利用したい場合です。

  • 1 つの Docker ホストのデータをバックアップして、他のホストに復元、移行する必要がある場合です。 こういった場合にボリュームを選びます。 ボリュームを利用しているコンテナーを停止させてから、ボリュームがあるディレクトリのバックアップをとります(ディレクトリは/var/lib/docker/volumes/<ボリューム名>などです)。

  • Docker Desktop においてアプリケーションが高性能な I/O を必要とする場合です。 ボリュームはホスト上ではなく Linux VM 上に保存されます。 このことはつまり、読み書きにおける待ち時間がより少なくなり、スループットが向上します。

  • Docker Desktop 上でのアプリケーションが、完全にネイティブなファイルシステムの動作を必要とする場合です。 たとえばデータベースエンジンでは、トランザクションの耐久性を保証するために、ディスクへの書き込みを細かく制御できることが必要になります。 ボリュームは Linux VM 上に保存され、これらを保証することができます。 一方バインドマウントは macOS や Windows にリモート接続されるものなので、ファイルシステムの動作は若干異なるものになります。

バインドマウントの適切な利用例

一般的には、可能なかぎりボリュームを用いるべきです。 バインドマウントは、以下のような利用例において適切と考えられます。

  • 設定ファイルをホストマシンからコンテナーに共有するような場合です。 デフォルトで Docker はコンテナーに対し DNS 解決機能を提供しますが、それがこの状況に相当します。 この場合、/etc/resolv.confをホストマシンから各コンテナーへマウントすることを行います。

  • ソースコードやビルド結果を、Docker ホスト上の開発環境とコンテナーとの間で共有する場合です。 たとえば Maven のtarget/ディレクトリをコンテナーにマウントします。 Docker ホスト上にて Maven プロジェクトをビルドするたびに、コンテナーは再ビルドされた結果をすぐに利用します。

    Docker をこのようにして開発に利用する場合、本番環境用の Dockerfile には、本番向けにビルドされたバイナリを、直接イメージにコピーするような記述を行うはずです。 そこではもう、バインドマウントに頼ることはありません。

  • Docker ホストのファイルやディレクトリ構造が、コンテナーにとって必要となるバインドマウントと合致することが保証されている場合です。

tmpfs マウントの適切な利用例

tmpfsマウントの一番の使い方は、ホストマシン上にもコンテナー内にも、データを残しておきたくない場合に利用することです。 セキュリティに関する理由もありますが、アプリケーションが書き込むデータが大量にあって、それが保存を必要としないのであれば、コンテナーの性能を保護する目的があります。

バインドマウントとボリュームを使う際のヒント

バインドマウントとボリュームのどちらかを用いる場合には、以下のことを忘れないでください。

  • コンテナー内のディレクトリに 空のボリューム をマウントしようとしていて、そのディレクトリ内にファイルやディレクトリが存在する場合、そのファイルやディレクトリはボリューム内にコピーされます。 コンテナー起動時に指定したボリュームがまだ存在していなかった場合は、空のボリュームが生成されます。 コンテナーの求めに応じて事前にデータを提供しておく方法として用いられます。

  • コンテナー内のディレクトリに バインドマウントか、空ではないボリューム をマウントしようとしていて、そのディレクトリ内にファイルやディレクトリが存在する場合、マウントによってそのファイルやディレクトリは隠れてしまいます。 それはたとえば、Linux マシン上の/mntにファイルを保存した後に、/mntに対して USB ドライブをマウントしたような場合と同じです。 /mntに存在していた内容は USB ドライブの内容によって隠されてしまい、USB ドライブがアンマウントされるまで続きます。 隠されてしまったファイルは、削除されるわけでなく変更もされません。 しかしバインドマウントやボリュームがアンマウントされない限り、アクセスすることはできません。

次のステップ

storage, persistence, data persistence, volumes, mounts, bind mounts