ボリュームによる永続化
コンテナは消すと中のデータも一緒に消えます。データベースのファイルなど残したいものは、ボリュームを使ってコンテナの外に保存します。docker run の -v オプションで、ホスト側のディレクトリやボリュームをコンテナ内のパスに対応づけます。こうしておけばコンテナを作り直してもデータは保持され、別のコンテナから同じデータを参照することもできます。
コンテナを実用に使い始めると、必ずぶつかるのがデータの保存の問題です。ここを知らずにデータベースなどを動かすと、ある日コンテナを作り直したとたんに中のデータがまるごと消えていた、という事故につながります。原因を正しく理解し、対策としてのボリュームの使い方を身につけておくことが、コンテナを安心して使うための土台になります。まずは「なぜ消えるのか」から押さえましょう。
コンテナを消すとデータも消える
前のトピックで触れたとおり、コンテナはイメージという読み取り専用の雛形の上に、書き込み用の薄い層を1枚かぶせて動いています。コンテナの中で作ったファイルや書き換えた内容は、すべてこの最上層に記録されます。ところが、docker rm でコンテナを削除すると、この書き込み層ごと消えてなくなります。つまりコンテナの中身は、原則として「使い捨ての作業スペース」なのです。動作確認の一時データならそれで構いませんが、データベースのファイルや、利用者がアップロードした画像のように消えては困るものは、コンテナの外に逃がして生き残らせる必要があります。これをデータの永続化と呼びます。
ボリュームで外に逃がす
永続化の手段が、コンテナの外にデータを保存するボリュームの仕組みです。docker run の -v オプションを使い、「ホスト側のどこか」と「コンテナ内のどこか」を対応づけて、データの実体をコンテナの外に置きます。書式は -v ホスト側:コンテナ側 で、区切りはコロンです。たとえば docker run -d --name web -v /home/user01/data:/usr/share/nginx/html nginx とすると、ホストの /home/user01/data がコンテナ内の /usr/share/nginx/html に結びつきます。こうしておけば、ホスト側のフォルダに置いたファイルはコンテナから見え、逆にコンテナがそこへ書いた内容もホスト側に残ります。コンテナを docker rm で消しても、ホスト側のデータはそのまま残るので、また同じ -v を付けてコンテナを作り直せば、続きから使えます。
このように、ホストの特定フォルダを直接コンテナにつなぐ方式はバインドマウントと呼ばれます。これとは別に、docker が管理する専用の保管領域を使う「名前付きボリューム」もあり、docker volume create mydata で作り、docker run -v mydata:/var/lib/mysql … のようにボリューム名を左側に書いて使います。違いはざっくり言えば、バインドマウントは置き場所をホスト上のパスで自分が決める方式、名前付きボリュームは置き場所を docker に任せて名前だけで扱う方式です。設定ファイルやソースをホスト側で編集しながら使いたいときはバインドマウントが、データベースの中身のように docker に任せておけばよいものは名前付きボリュームが向いています。作成済みのボリュームは docker volume ls で一覧できます。
よくある失敗
最も多い失敗が、コロンの左右を逆に書いてしまうことです。-v は必ず ホスト側:コンテナ側 の順で、これを逆にすると意図しないフォルダがマウント先になり、見えるはずのデータが見えなかったり、空のフォルダで上書きされたように見えたりします。「左がホスト、右がコンテナ」を必ず守ってください。もう1つ、相対パスを書くと予期しない場所が使われることがあるため、バインドマウントのホスト側は /home/user01/data のような絶対パスで指定するのが安全です。
RHEL系(MiracleLinux など)で特有の落とし穴として、SELinux が有効な環境では、ホストのフォルダをそのままマウントしてもコンテナ側から「許可がない(Permission denied)」と弾かれることがあります。その場合は、マウント指定の末尾に :Z を足して -v /home/user01/data:/usr/share/nginx/html:Z のように書くと、SELinux のラベルが調整されてアクセスできるようになります。「ボリュームをつないだのに中が読めない・書けない」ときは、まずこの SELinux のラベルを疑うのが、RHEL系での定石です。
共有と確認のしかた
ボリュームのもう1つの利点は、複数のコンテナから同じデータを参照できることです。同じボリュームを複数のコンテナに -v で割り当てれば、片方が書いた内容をもう片方が読む、といった共有ができます。たとえば、データを書き込む処理用のコンテナと、それを読み出して配信するコンテナで、同じ保存領域を共有する、という構成が組めます。マウントが意図どおりにできているかは、docker exec でコンテナに入り、対象パスの中身を ls で確認するのが手早い方法です。読み取り専用で渡したいときは、マウント指定の末尾に :ro を付けて -v /home/user01/conf:/etc/app:ro のようにすると、コンテナ側からは読めるが書き換えられない状態にでき、設定ファイルを誤って書き換えられる事故を防げます。
名前付きボリュームを使った場合、その中身がホストの実際のどこに保存されているかは、docker volume inspect ボリューム名 で確認できます(Mountpoint という項目にパスが出ます)。とはいえ、名前付きボリュームは docker に管理を任せるのが本来の使い方なので、保存先のパスを直接いじるのは避け、データの出し入れはコンテナ経由で行うのが安全です。使わなくなったボリュームはコンテナを消しても自動では消えず残り続けるため、不要なものは docker volume rm ボリューム名 で明示的に削除します。どのボリュームもどのコンテナからも使われていない状態をまとめて掃除したいときは docker volume prune が使えますが、消すと戻せないので、対象をよく確かめてから実行してください。
実務の使いどころ
実務では、コンテナは消えてもいいもの、データは消えてはいけないもの、という線引きをはっきり意識します。アプリのコンテナ自体は気軽に作り直しつつ、データベースの保存先や設定ファイル、アップロードされたファイルといった「残すべきもの」は必ずボリュームで外に出しておく——これが鉄則です。こうしておけば、アプリのバージョンアップでコンテナを作り直しても、データはそのまま引き継げます。開発中は、ホスト側のソースコードのフォルダをバインドマウントしてコンテナにつなぎ、エディタでホスト側を編集すると即座にコンテナへ反映される、という使い方も定番です。「コンテナは使い捨て、大事なデータはボリュームで外へ」と覚えておけば、データ消失の事故はほぼ防げます。