Docker データボリューム
コンテナ (Container) は、ポータビリティ (Portability) とアイソレーション (Isolation) の面で比類のない利点を提供し、アプリケーションが様々な環境で一貫して実行 (Run) されることを可能にします。前の章では、アプリケーションを Docker イメージ (Image) にパッケージ化 (Package) し、コンテナとしてデプロイ (Deploy) する方法について詳細に検討し、Docker の強力なランタイム (Runtime) 特性を十分に活用しました。
しかし、コンテナにはデフォルト (Default) で極めて重要な特性があります。それは揮発性 (Ephemerality) です。これは、コンテナが削除されると、その書き込み可能なレイヤー (Writable Layer) に書き込まれたすべてのデータが永久に失われることを意味します。
ステートレス (Stateless) なアプリケーションにとって、これは全く問題になりません。しかし、データベース (Database)、ログ (Log) サービス、コンテンツ管理システム (CMS)、あるいは単純なユーザーの好みの設定など、データを保存する必要があるほぼすべての現実世界のアプリケーションにとって、この揮発性は巨大な課題となります。
この問題を解決するために、Docker はデータ永続化 (Data Persistence) のメカニズムを提供しています。その中で最も一般的であり、公式に最も推奨されている方法が、Docker データボリューム (Docker Volumes) の使用です。
1. コンテナデータの揮発性という課題
Docker Volumes の重要性を真に理解するために、まずコンテナがデフォルトでデータをどのように処理するか、そしてなぜこれがステートフル (Stateful) アプリケーションに頻繁に問題を引き起こすのかを振り返る必要があります。
1.1 コンテナのファイルシステムと「読んだら燃やす」
Docker コンテナを実行すると、イメージをベース (Base) に読み取り専用のファイルシステム (File System) レイヤーが起動します。この読み取り専用レイヤーの上に、Docker は薄く書き込み可能なコンテナレイヤーを追加します。実行中のコンテナが行うすべての変更——ソフトウェアのインストール、ファイルの作成、設定 (Configuration) の変更、ログの書き込み、またはユーザーデータの保存——はこの書き込み可能レイヤーで発生します。
この書き込み可能レイヤーの最も致命的な特徴は、その揮発性です。コンテナを停止 (stop) しただけの場合、そのステータス (書き込み可能レイヤー内のすべてのデータを含む) は保持され、後で再起動 (restart) できます。しかし、docker rm コマンドを使用してこのコンテナを削除 (remove) した場合、その書き込み可能レイヤーは完全に消去されます。そのコンテナのファイルシステム内で作成または変更されたすべてのデータは永遠に消え去ります。
この振る舞いは設計上の欠陥ではなく、意図的なものです。これは「イミュータブル・インフラストラクチャ (Immutable Infrastructure)」の理念を推進しています。つまり、コンテナは消耗品であり、内部のステータスを心配することなく簡単に置き換えることができるという考え方です。
1.2 データ消失の壊滅的な結果
この揮発性が様々なタイプのアプリケーションにもたらす結果を考えてみましょう:
- データベース (PostgreSQL, MySQL など): データベースはすべてのデータ(テーブル、インデックス、ユーザーレコード)をファイルシステムに保存します。データベースコンテナが永続化の設定なしに削除された場合、アプリケーションのすべてのデータは取り返しがつかないほど失われます。本番環境 (Production Environment) において、これは絶対に許容できない事故です。
- ユーザーアップロード機能を持つ Web サーバー (Nginx や WordPress を実行する Apache など): ユーザーがコンテナ内で実行されている Web アプリケーションにアバター、画像、またはドキュメントをアップロードした場合、そのコンテナが削除されると、アップロードされたすべてのコンテンツは灰燼に帰します。
- ログおよび監視システム: アプリケーションは通常、ログを生成します。これらのログが直接コンテナのファイルシステムに書き込まれ、コンテナが削除された場合、貴重な診断 (Diagnostic) 情報が失われます。
- 設定ファイル: 設定は通常、起動時にコンテナに提供できますが、アプリケーションが管理者 (Administrator) に UI を通じた設定の変更を許可し、これらの設定をコンテナ内部のファイルに保存する場合、コンテナが削除されるとこれらの変更も失われます。
1.3 想定シナリオ:シンプルなカウンターアプリケーション
単一の整数カウンターを維持するシンプルな Web サービスを想像してください。ユーザーが特定のエンドポイント (Endpoint) にアクセスするたびに、カウンターが1増加し、現在の値を表示します。このカウンターの値は、コンテナ内のファイルに保存されます。
- このサービスを実行するコンテナを起動します。
- ユーザーが操作し、カウンターがたとえば 10 に増加しました。
- その後、コンテナを停止して削除します。
- 同じイメージから新しいコンテナを起動すると、カウンターは初期値(たとえば 0 または 1)にリセットされ、以前のステータスである 10 は完全に失われます。
これは根本的な問題を浮き彫りにしています。ステータスを維持する必要がある、またはその直接のライフサイクルを超えてデータを保存する必要があるアプリケーションにとって、コンテナのデフォルトの揮発性は巨大なハードルとなります。
2. Docker データボリューム (Volumes) とは何か?
Docker Volumes(データボリューム)は、Docker コンテナが生成および使用するデータを永続化するための優先されるメカニズムです。コンテナの瞬く間に消え去る書き込み可能レイヤーとは異なり、Volume はホストマシンのファイルシステム上の独立した特別なディレクトリ (Directory) であり、Docker によって一元管理されます。
2.1 コアとなる特性と利点
- 永続性 (Persistence): Volume は特定のコンテナから独立して存在します。その Volume を使用しているすべてのコンテナを停止、削除 (remove)、または破棄したとしても、Volume とそのデータはホストマシン上に安全に保持されます。後でこの Volume を新しいコンテナにマウント (Mount) すると、データは無傷のまま利用可能になります。
- Docker によって管理されるストレージ: Docker は Volume の作成、マウント、および管理を担当します。Volume データが存在する具体的なホストマシンの物理パスを気にする必要はありません(通常気にするべきではありません)(必要であれば見つけることはできますが)。Docker はこれらの基盤となる詳細を抽象化 (Abstract) しています。これにより、Volume は異なる Docker ホストマシン間で極めて高いポータビリティを持つことになります。これは Bind Mounts(バインドマウント、正確なホストマシンのパスを指定する必要がある)とは異なります。
- データ共有 (Data Sharing): 複数のコンテナが安全に同じ Volume をマウントして共有できます。これは、Web サーバーが静的リソースにアクセスし、独立したバックグラウンドのワーカー (Worker) プロセスがこれらのリソースの処理を担当するなど、異なるサービスが同じデータセット (Dataset) にアクセスする必要があるシナリオで非常に有用です。
- バックアップとリストア (Backup and Restore): Volume は Docker によって管理され、最終的にはホストマシン上に存在するため、そのデータをバックアップするのは非常に簡単です。標準のホストマシンシステムのバックアップツールを使用するか、専用のコンテナを使用して Volume のコンテンツをパッケージ化してバックアップすることもできます。
- パフォーマンス (Performance): Volume は様々なストレージバックエンドに保存でき、通常はコンテナの書き込み可能レイヤーに直接書き込むよりも優れたパフォーマンスを提供します。コンテナの Copy-on-Write (コピーオンライト) ファイルシステムは追加のオーバーヘッドをもたらすためです。
2.2 Docker Volumes の実際のユースケース
- 本番環境のデータベース: これは間違いなく最も重要なユースケースです。Docker コンテナで MySQL、PostgreSQL、MongoDB、または Redis などのデータベースを実行する場合、そのデータディレクトリ全体(MySQL の
/var/lib/mysql、PostgreSQL の/var/lib/postgresql/dataなど)を Docker Volume にマウントしなければなりません。これにより以下が保証されます: - データベースコンテナがクラッシュ (Crash) した場合、またはバージョンを更新する必要がある場合、同じ Volume を使用して新しいコンテナを起動でき、すべてのデータが即座にリストアされます。
- コンテナが意図的に削除されたとしても、データは永続化されたまま存在します。
- データベースは Volume を介してホストマシンのファイルシステムに直接アクセスするため、優れた I/O パフォーマンスが得られます。
# 例: データボリュームを持つ PostgreSQL データベースを実行する
docker volume create pg_data
docker run -d \
--name my_postgres \
-e POSTGRES_PASSWORD=mysecretpassword \
-v pg_data:/var/lib/postgresql/data \
postgres:13この例では、pg_data は Docker によって管理される名前付きボリューム (named volume) であり、/var/lib/postgresql/data は PostgreSQL がコンテナ内部でデータを保存するパスです。
- コンテンツ管理システム (CMS) とユーザー生成コンテンツ: WordPress、Joomla、またはユーザーによるアップロード(画像、ドキュメント、メディアファイル)を許可するカスタム Web アプリケーションなどの場合、これらのファイルは永続化されなければなりません。WordPress の
wp-contentディレクトリ(テーマ、プラグイン、アップロードされたファイルを含む)は Volume にマウントされるべきです。
# 例: コンテンツディレクトリをデータボリュームにマウントした WordPress を実行する
docker volume create wordpress_content
docker run -d \
--name my_wordpress \
-e WORDPRESS_DB_HOST=db_host \
-e WORDPRESS_DB_USER=wordpress \
-e WORDPRESS_DB_PASSWORD=secret \
-e WORDPRESS_DB_NAME=wordpress \
-v wordpress_content:/var/www/html/wp-content \
wordpress:latestここで、wordpress_content ボリュームは、ユーザーが WordPress コンテナインスタンス (Instance) を頻繁に交換したとしても、アップロードされた記事の画像、インストールされたテーマ、プラグインが絶対に失われないことを保証します。
3. Docker Volume のライフサイクルをマスターする
Docker Volumes の作成、検査 (Inspect)、使用、および削除の方法を理解することは、永続化データを管理するための基礎です。
3.1 Volume の作成
docker volume create コマンドを使用して、名前付きボリュームを明示的に作成できます。Docker は、ホストマシン上の適切な場所を見つけて、このボリュームの実際の物理ストレージを管理します。
# 構文: docker volume create <ボリューム名>
docker volume create myapp_dataコンテナの実行時に存在しないボリュームを指定した場合、Docker は自動的にそれを作成してくれます。しかし本番環境では、明示的に事前にボリュームを作成することがベストプラクティスであり、これによりより優れた制御と明確さがもたらされます。
3.2 すべての Volumes のリストアップ
システム上の Docker によって管理されているすべてのボリュームを表示するには、docker volume ls を使用します。
docker volume lsこのコマンドはテーブルを表示し、DRIVER(通常は local)と VOLUME NAME をリストアップします。
DRIVER VOLUME NAME
local myapp_data
local pg_data
local wordpress_content3.3 Volume の詳細の検査
特定のボリュームの詳細情報を取得するには、docker volume inspect <ボリューム名> を使用します。このコマンドは非常に有用で、特にこのボリュームのデータがホストマシンの物理ディスクのどこにあるか知りたい場合に役立ちます。
docker volume inspect myapp_data出力は JSON 配列 (Array) になり、作成時間、ドライバ (Driver) などの詳細が含まれます。その中で最も重要なのは Mountpoint です。Mountpoint は、ボリュームのデータがホストの物理マシン上に存在する絶対パス (Absolute Path) です。
[
{
"CreatedAt": "2023-10-27T10:00:00Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myapp_data/_data",
"Name": "myapp_data",
"Options": {},
"Scope": "local"
}
]3.4 コンテナでの Volume の使用
Volume をコンテナにマウントするには、docker run コマンドで -v または --mount フラグを使用します。名前付きボリュームの基本的な構文は ボリューム名:コンテナ内パス です。
# 構文: docker run -v <ボリューム名>:<コンテナ内部パス> <イメージ名>
docker run -d \
--name my_data_processor \
-v myapp_data:/app/data \
ubuntu:latest sleep infinityこのコマンドにおいて:
myapp_dataは、作成した名前付きボリュームを指します(存在しない場合、Docker は自動的に作成します)。/app/dataは コンテナ内部 のパスであり、myapp_dataの内容がここにマウントされます。my_data_processorコンテナが/app/dataに書き込むすべてのデータは、ホストマシンのmyapp_dataボリュームに安全かつ永続的に保存されます。
3.5 Volume の削除
不要になったボリュームは、docker volume rm <ボリューム名> を使用して削除できます。
極めて危険: ボリュームの削除は不可逆的な破壊的操作です。そのボリューム内に保存されているすべてのデータは永久に失われます。ボリュームを削除する前に、すべての重要なデータがバックアップされていることを必ず確認してください!
# 特定のボリュームを削除する
docker volume rm myapp_data使用されていないすべてのボリューム(つまり、現在どのコンテナにもマウントされていないボリューム)をクリーンアップ (Clean up) するには、docker volume prune を使用できます。これはディスクスペースを解放するための素晴らしいツールですが、同様に使用には細心の注意が必要です。
# すべての未使用のボリュームを削除する
docker volume pruneクリーンアップを実行する前に、Docker は操作の確認を求めます。
4. 総合実践演習
データ消失のペインポイント (Pain point) と、Volume がいかにしてその状況を救うかを直感的に理解するために、2つの具体的な例を見てみましょう。
4.1 実践その一:失われたカウンターアプリケーションを救う
カウンターの値をファイルに保存するシンプルな Python アプリケーションを作成します。データがどのように失われるかを確認するために最初は Volume を使用せずに実行し、その後データ永続化を目の当たりにするために Volume を使用して実行します。
1. コードファイルの準備:
counter_app という名前のディレクトリを作成します。その中に app.py を作成します:
# counter_app/app.py
import os
import time
COUNTER_FILE = "counter.txt" # カウンターを保存するファイル名
def read_counter():
"""ファイルから現在のカウンターの値を読み取る"""
if os.path.exists(COUNTER_FILE):
with open(COUNTER_FILE, 'r') as f:
try:
return int(f.read().strip())
except ValueError:
return 0
return 0
def write_counter(value):
"""カウンターの値をファイルに書き込む"""
with open(COUNTER_FILE, 'w') as f:
f.write(str(value))
def main():
current_counter = read_counter()
print(f"[{time.ctime()}] 起動、現在のカウンターは: {current_counter}")
current_counter += 1
write_counter(current_counter)
print(f"[{time.ctime()}] カウンターに 1 を加算、結果: {current_counter}")
print(f"[{time.ctime()}] カウンターの値が {COUNTER_FILE} に保存されました")
time.sleep(5) # 少しの作業負荷をシミュレートし、コンテナを数秒間存続させる
if __name__ == "__main__":
main()対応する Dockerfile を作成します:
# counter_app/Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]2. イメージのビルド:
ターミナルで counter_app ディレクトリに入り、以下を実行します:
docker build -t persistent-counter-app .3. 悪い例:Volume を使用しない(データが失われるのをただ見ている):
# 最初のコンテナを実行する
docker run --name counter1 persistent-counter-app
# 次の出力が表示されます: 起動、現在のカウンターは: 0 => カウンターに 1 を加算、結果: 1
# コンテナを削除する(サービスのアップグレードや障害による再起動をシミュレート)
docker rm counter1
# 同じイメージから *新しい* コンテナを起動する
docker run --name counter2 persistent-counter-app
# 出力がまた元に戻ったことがわかります: 起動、現在のカウンターは: 0 => カウンターに 1 を加算、結果: 1
# 悲劇が起きました。前のカウンターの値 (1) は、コンテナが削除されたために永遠に消え去りました!4. 成功例:Volume を使用する(永続化の力を目の当たりにする):
まず、Volume を作成します。
docker volume create counter_volume次に、アプリケーションを実行し、counter_volume をコンテナ内の /app ディレクトリにマウントします。これは、Python スクリプト (Script) が /app の下に作成する counter.txt ファイルが、実際には Volume に書き込まれることを意味します。
# 1回目:Volume を指定してコンテナを実行する
docker run --name counter_persistent_1 -v counter_volume:/app persistent-counter-app
# 出力: 起動、現在のカウンターは: 0 => カウンターに 1 を加算、結果: 1
# このコンテナを削除する(心配無用、Volume とその中のデータは無事です!)
docker rm counter_persistent_1
# *新しい* コンテナを起動し、*同じ* Volume をマウントする
docker run --name counter_persistent_2 -v counter_volume:/app persistent-counter-app
# 魔法が現れました!出力には次のように表示されます: 起動、現在のカウンターは: 1 => カウンターに 1 を加算、結果: 2
# 成功です!データはコンテナの生死を乗り越え、生き残りました。
# もう一度やってみましょう
docker rm counter_persistent_2
docker run --name counter_persistent_3 -v counter_volume:/app persistent-counter-app
# 出力: 起動、現在のカウンターは: 2 => カウンターに 1 を加算、結果: 3inspect を使って、このデータがどこに隠されているか見つけることができます:
docker volume inspect counter_volume
# "Mountpoint" フィールドを見つけます。
# 例: /var/lib/docker/volumes/counter_volume/_data
# ホストマシン(Linux/macOS)で cat コマンドを使用して表示します:
# cat /var/lib/docker/volumes/counter_volume/_data/counter.txt
# 中に "3" と明記されているのをその目で確認できます。4.2 実践その二:Nginx アクセスログの永続化
Nginx Web サーバーは、絶え間なくアクセスログ (access.log) とエラーログを生成します。Nginx コンテナが破棄されても、ログが無傷であることを保証しましょう。
1. ログ用の Volume を作成する:
docker volume create nginx_logs2. Volume を指定して Nginx を実行する:
nginx_logs ボリュームを、Nginx コンテナ内で専用にログが保存される /var/log/nginx ディレクトリにマウントします。
docker run -d \
--name my_nginx_server \
-p 8080:80 \
-v nginx_logs:/var/log/nginx \
nginx:latest3. 少しログデータを生成する:
ブラウザを開いて http://localhost:8080 を数回リロードします。Nginx のウェルカムページが表示されれば、アクセスログが生成されたことを意味します。
4. ホストマシンでログを「のぞき見」する:
まず、nginx_logs の物理的なマウントポイント(Mountpoint)を見つけます:
docker volume inspect nginx_logs
# Mountpoint のパスをメモしますホストマシン上で直接ファイルを表示します:
# cd 先ほどメモした Mountpoint のパス
# cat access.log
# あなたが先ほどブラウザでアクセスして残した記録が見えるはずです!5. コンテナを「Kill」し、永続化を検証する:
docker rm -f my_nginx_servermy_nginx_server が灰になっても、nginx_logs ボリュームとその中の access.log は、ホストマシンのハードディスク (Hard Disk) 上で静かに横たわっています。次回新しい Nginx コンテナを起動してこのボリュームをマウントすると、新しいログは古いログの後ろに追記され続けます。