Docker・インフラ構築

Dockerコンテナのログ肥大化対策:daemon.jsonによる容量制限

Dockerコンテナのログファイルが、いつの間にかディスク容量の大半を食い潰していた——この経験がないエンジニアは少ないはずである。アプリケーションは正常に動作している。コンテナも停止していない。それでもホストのディスク使用率だけが上がり続け、ある時点でデータベースやリバースプロキシまで巻き込んで停止する。…

Dockerコンテナのログ肥大化対策:daemon.jsonによる容量制限

/var/lib/docker/containers/ 配下に蓄積されるログは、デフォルト設定のままでは上限が存在しない。一般的な構成で使われる json-file ドライバーでは、ログローテーションを明示的に設定しない限り、コンテナが出力した内容がファイルへ追記され続ける。

本稿では、daemon.json によるDockerコンテナのログ容量制限を中心に、全体設定の適用方法、既存コンテナへの影響、ディスク枯渇時の応急処置、local ドライバーの使い分け、さらにDocker Composeでサービス単位の設定を上書きする方法まで整理する。

デフォルト設定が抱えるディスク枯渇のリスク

Dockerのデフォルトロギングドライバーは json-file である。このドライバーの max-size はデフォルトで -1、つまり無制限として扱われる。ログローテーションも自動では実行されないため、コンテナが出力するログは基本的に一つのJSONログファイルへ蓄積されていく。

ログファイルの保存先は、標準的な構成であれば次のパスになる。

/var/lib/docker/containers/<コンテナID>/<コンテナID>-json.log

docker logs で表示される内容は、主にこのファイルをもとに読み出される。アプリケーションの標準出力と標準エラー出力をDockerが収集し、そのままログドライバーへ渡す仕組みである。

ここで注意したいのは、コンテナ内部のログファイルだけを見ていても問題の全体像を把握できないことだ。たとえば、アプリケーションがコンテナ内の /var/log にログを書いていなくても、標準出力へアクセスログやデバッグログを大量に出していれば、ホスト側のDocker管理領域が膨らむ。Laravelの例外ログを標準出力へ流している構成や、Webサーバーのアクセスログをコンテナログへ集約している構成では、特に増加が速い。

ログが増えやすいコンテナには、次のような傾向がある。

  • アクセスログをリクエスト単位で出力するWebサーバー
  • デバッグモードが有効になっているアプリケーション
  • 失敗した処理を短い間隔で繰り返すワーカー
  • 外部サービスとの接続エラーを何度も出力するバッチ
  • ヘルスチェックや監視処理の結果を標準出力へ出し続けるサービス

ログファイルが数日で数ギガバイトに達することもあるが、増加量はワークロードに左右される。アクセスの少ない検証環境では問題にならなくても、本番公開後にアクセスログの量が増え、同じ設定のままディスクを圧迫することがある。

さらに厄介なのは、Dockerがログの肥大化を理由に分かりやすい警告を出してくれるとは限らない点である。コンテナ自体は稼働しているため、アプリケーション監視だけでは異常を検知しにくい。ホストのディスク使用率を確認した時に初めて、Dockerのログが原因だったと分かるケースもある。

Dockerのデフォルトログ設定は「出力し続ける」ことだけを前提にしており、「どこまで許容するか」という観点が完全に欠落している。

まず確認するべき場所

原因を調べるときは、いきなりログファイルを削除するのではなく、どのコンテナが容量を消費しているかを確認する。ホスト全体の使用量は df -h、ディレクトリ単位の使用量は du で確認できる。

Dockerのデータ領域全体を確認するなら、sudo du -sh /var/lib/docker が分かりやすい。ログファイルだけを大きい順に確認する場合は、sudo du -sh /var/lib/docker/containers/*/*.log | sort -rh | head -10 のようなコマンドが使える。

ただし、Dockerのデータルートを標準パスから変更している環境では、このパスをそのまま使えない。docker infoDocker Root Dir で実際の保存先を確認してから調査する必要がある。クラウドの初期構築スクリプトや、専用ディスクへDocker領域を移しているサーバーでは、ここを見落としやすい。

コンテナIDとサービス名の対応は、docker ps --no-truncdocker inspect で確認する。Composeを使っている場合は、プロジェクト名やサービス名とコンテナIDを対応させながら、削除対象を間違えないようにする。ログの大きさだけを見て、データベースやキューのコンテナを不用意に再作成するのは避けたい。

daemon.jsonによるログローテーションの全体適用

Dockerデーモン全体のデフォルトログ設定を変更するには、通常 /etc/docker/daemon.json を編集する。ファイルが存在しない場合は新規作成する。

json-file を使い続けながらログローテーションを有効にする設定は、次のようになる。

{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

読みやすさを優先するなら、実際のファイルではJSONを複数行に分けて記述してもよい。重要なのは、JSONとして正しい形式になっていることと、ログオプションの値を文字列で指定することである。

max-size は一つのログファイルをどこまで大きくするかを指定する。10m なら、おおむね10メガバイトを目安にローテーションされる。max-file は保持するファイル数で、3 なら現在のファイルを含めて複数世代を保持する設定になる。

この設定では、1コンテナあたりのログ保存量をおおよそ30メガバイト程度に抑える考え方になる。ただし、ファイルサイズは厳密なディスク使用量の上限ではない。ログの書き込みタイミングやドライバーの処理、別のログ保存先などによって、実際の使用量が完全に一致するとは限らない。容量制限を設計するときは、設定値を絶対的な安全ラインではなく、運用上の目安として扱うのが現実的である。

設定変更後は、JSONの構文とDockerデーモンの状態を確認する。sudo systemctl reload docker で再読み込みできる環境もあるが、ログ設定を確実に反映させるためにDockerデーモンの再起動が必要になる場合もある。

本番環境で実施する場合は、次の点を先に確認しておく。

  • Dockerデーモンの再起動でコンテナが停止、または再起動する可能性
  • restart: always など、サービスの再起動ポリシー
  • Composeやオーケストレーターによる復旧動作
  • SSH接続やリバースプロキシなど、管理経路への影響
  • 変更前の daemon.json のバックアップ
  • 設定反映後に新規作成したコンテナで確認する手順

設定ファイルの文法ミスは、ログローテーション以前にDockerデーモンの起動失敗につながる。特に max-file の値を数値として書いてしまうミスは起こりやすい。ログオプションでは "max-file": "3" のように文字列として指定する。"max-file": 3 ではなく、クォートを付ける点に注意したい。

また、すでに daemon.json に別の設定が入っている場合、ファイル全体を置き換えてはいけない。レジストリ設定やデータルート、実験的機能などを同じファイルで管理している環境では、既存のキーを残したままJSONを編集する必要がある。

既存コンテナへの影響と再作成の必要性

daemon.json のログ設定を変更し、Dockerデーモンを再読み込みしたとしても、すでに作成済みのコンテナへ自動的に適用されるわけではない。ここはDockerコンテナのログ容量制限で最も誤解されやすい部分である。

デーモンの設定は、基本的に変更後に作成されるコンテナのデフォルト値として使われる。既存コンテナは作成時のログドライバーとログオプションを保持しているため、デーモンの設定を変えただけでは、過去のコンテナのログローテーションは始まらない。

現在のコンテナがどのログドライバーを使っているかは、docker inspect --format '{{.HostConfig.LogConfig.Type}}' <コンテナ名またはID> で確認できる。オプションまで確認するなら、docker inspect <コンテナ名またはID> の出力に含まれる HostConfigLogConfig を見る。

既存コンテナへ新しい設定を適用するには、コンテナを再作成する。Docker Composeで管理している場合は、設定ファイルへ logging を記述したうえで docker compose up -d --force-recreate を実行する方法がある。--force-recreate はコンテナを作り直すため、設定を確実に反映させたい場合に有効である。

ただし、再作成で消えるのはコンテナの書き込み可能レイヤーであり、名前付きボリュームやホスト bind mount のデータまで自動的に消えるわけではない。だからといって無条件に安全という意味ではない。Composeファイルのボリューム定義を確認し、データがどこへ保存されているかを把握してから実施する必要がある。

特に確認したいのは次の項目である。

  • データベースのデータディレクトリがボリュームへ保存されているか
  • アップロードファイルや生成画像がコンテナ内部に残っていないか
  • 設定ファイルをbind mountしているか
  • 再作成後に必要な環境変数が引き継がれるか
  • ネットワーク名や依存サービスとの接続が変わらないか
  • コンテナ停止中に処理中のジョブが失われないか

既存ログについても注意が必要である。新しいコンテナを作り直せば、通常は新しいコンテナIDに紐づくログファイルが作成される。しかし、再作成前のコンテナログがホスト上からすぐ消えるとは限らない。不要な古いコンテナやログが残っていないかを確認し、必要な監査情報や障害調査用のログを保存してから整理する。

daemon.json の変更は、既存コンテナを管理している設定の書き換えではない。新しいコンテナを作る時に使われるデフォルト値を変える操作である。

再作成と再起動は別の操作

docker restartdocker compose restart は、既存コンテナを停止して再起動するだけである。コンテナの作成時設定は変わらないため、ログドライバーや max-size を変更した後の反映手段としては不十分である。

一方、docker compose up -d はComposeファイルの差分を検知して必要なコンテナを作り直すことがあるが、確実性を重視するなら変更内容と環境に応じて --force-recreate を使う。サービスを止められない環境では、段階的に対象を切り替える運用や、ログ設定を個別に検証する手順を用意しておく。

緊急時の安全なログ削除:truncateコマンドの活用

ディスク容量が枯渇し、サービスに影響が出ている緊急事態では、まず空き容量を確保しなければならない。原因がDockerのログであることを確認できたら、ログファイルを安全に縮小する。

ここで、稼働中のコンテナに対して rm でログファイルを削除するのは避けたい。Dockerデーモンはログファイルを開いたまま保持しているため、ファイル名を削除しても、プロセスが開いているファイル記述子からはデータが参照され続ける場合がある。見かけ上ファイルが消えても、期待したほど空き容量が増えないことがある。

この状態は、lsof の出力に削除済みファイルとして現れることがある。ログを消したはずなのにディスク使用量が戻らない場合は、開いたままの削除済みファイルを確認する。ただし、Dockerデーモンを不用意に停止するのは、サービス全体へ影響を与えるため最後の手段にしたい。

稼働中のコンテナが使用しているログファイルを空にする応急処置としては、truncate が使える。

sudo truncate -s 0 /var/lib/docker/containers/<コンテナID>/<コンテナID>-json.log

truncate はファイルそのものを削除せず、サイズをゼロへ切り詰める。Dockerデーモンが保持しているファイル記述子も維持されるため、以後のログ出力を継続できる。ディスクの空き容量を急いで回復させる必要がある場面では、rm より扱いやすい方法である。

対象を間違えると、障害調査に必要なログまで失われる。実行前に、次のような確認を挟むとよい。

1. df -h でホストの空き容量とマウントポイントを確認する。

2. du でDocker領域内の大きなログファイルを特定する。

3. ファイル名からコンテナIDを確認する。

4. docker ps -a --no-trunc でコンテナ名やサービス名と照合する。

5. 必要な範囲だけを対象に truncate -s 0 を実行する。

6. df -h を再度実行し、空き容量が戻ったことを確認する。

7. ログローテーション設定とコンテナ再作成の計画を立てる。

緊急対応では、ファイルを空にしたことで問題が解決したように見える。しかし、設定を直さなければ同じログは再び増え始める。アプリケーションがエラーを繰り返し出力している場合は、ログを消した直後から再び容量を消費するため、根本原因の確認も並行して行う必要がある。

また、truncate はログの保存期間を管理する機能ではない。実行した瞬間までのログを失うため、監査要件や障害解析の要件がある環境では、対象ログを外部へ退避してから切り詰める。緊急時でも、対象ファイルと対象コンテナを一つずつ確認することが重要である。

localドライバーによる自動圧縮と運用効率化

json-file 以外のロギングドライバーとして、Dockerには local が用意されている。local ドライバーは、ログを効率的に保存することを目的としたドライバーで、デフォルトでローテーションと圧縮が行われる。

標準的な local ドライバーの設定には、次のような特徴がある。

パラメータ目安となる設定説明
max-size20MB一つのログファイルの上限
max-file5保持するログファイル数
圧縮有効ローテーションしたログを圧縮して保存

この場合、単純計算では1コンテナあたりおおよそ100MBを一つの目安にできる。圧縮後の実際のディスク使用量はログ内容によって変わるため、これも厳密な上限ではない。テキストの繰り返しが多いログは圧縮されやすく、すでに圧縮された内容やランダム性の高い出力は圧縮効率が低い。

local ドライバーをデーモン全体のデフォルトにする場合は、daemon.json に次のような設定を記述する。

{ "log-driver": "local", "log-opts": { "max-size": "50m", "max-file": "5" } }

ここでも、ログオプションの値は文字列で指定する。設定を変更した後に作成するコンテナから、このドライバーが使われる。

local の利点は、ログローテーションと圧縮を個別に組み合わせなくても、一定のディスク効率を確保しやすい点にある。外部のログ収集基盤を持たない小規模なWebサーバーや、単一ホストで完結する開発環境では、json-file のまま細かく設定するより運用負荷を下げられることがある。

一方で、既存のログ収集ツールや運用手順との互換性は確認しなければならない。docker logs による参照はできるが、保存形式や連携方法が json-file を前提にしているツールでは、そのまま置き換えられない場合がある。外部へログを送りたい場合は、Dockerのロギングドライバーだけに任せず、Fluentdやsyslogなど、採用している収集基盤との接続方法を確認する必要がある。

主要なドライバーの違いを整理すると、次のようになる。

ドライバーローテーション圧縮外部連携向いている環境
json-file設定すれば有効基本なし既存ツールとの互換性を取りやすい既存構成を維持したい場合
localデフォルトで有効自動連携方法の確認が必要単体運用、ディスク効率重視
syslog外部側で管理外部側で管理syslogサーバー既存のsyslog基盤がある環境
fluentd外部側で管理外部側で管理Fluentd集中ログ管理基盤と接続する環境

どのドライバーが優れているかは、ログをどこで保管し、どの期間参照し、誰が調査するかで変わる。単純に圧縮できるからという理由だけで local へ切り替えると、障害対応時に必要な検索や転送ができなくなる可能性がある。

逆に、ホスト上で docker logs を確認できれば十分で、ログの長期保存を別途行っていないのであれば、local は有力な選択肢になる。ログの保持量を決めるときは、ドライバーの機能だけでなく、アプリケーションのエラーログやアクセスログをどの経路で保存するかまで含めて考えたい。

個別コンテナ単位でのログ設定オーバーライド

すべてのコンテナに同じログ容量制限を適用できるとは限らない。短時間で大量のログを出すWebサーバーと、障害発生時だけログが増えるバッチでは、必要な保持量が違う。全体設定を基本としつつ、必要なサービスだけ個別に上書きする方法が現実的である。

docker run で指定する場合は、--log-opt を使う。

docker run --log-opt max-size=10m --log-opt max-file=3 nginx

ログドライバーも明示するなら、--log-driver=json-file を追加する。全体設定で local を指定している環境では、個別に json-file を指定しない限り、コンテナには全体のデフォルトが適用される。

Docker Composeでは、サービス定義の logging に記述する。

services:

app:

image: nginx

logging:

driver: json-file

options:

max-size: "10m"

max-file: "3"

実際のComposeファイルでは、インデントを崩さずYAMLとして記述する必要がある。max-sizemax-file は文字列として扱うため、値をクォートしておく。Composeのバージョンや利用している実装によって書式の扱いが異なる場合があるため、適用前に設定ファイルの検証も行う。

個別設定は、Dockerデーモンの全体設定より優先される。たとえば全体では local を使い、一部のサービスだけ json-file と外部収集の組み合わせを使うこともできる。ただし、設定が複数の場所に分散すると、後から見たときに実際の挙動を判断しにくくなる。

個別設定を採用するなら、Composeファイルやデプロイ手順に理由を残しておきたい。単に max-file: "10" と書くだけでは、なぜそのサービスだけ保持量が多いのか分からなくなる。障害調査で過去ログが必要なのか、外部収集が一時的に使えないのか、あるいはログ出力量が特に大きいのか。設定の背景が分かれば、将来の見直しもしやすい。

ログ容量の決め方

ログの上限を決める際は、まず必要な調査期間を考える。直近の障害だけを確認できればよいサービスと、数日分の業務処理を追跡したいサービスでは、保持すべき量が違う。

次に、ログの出力量を見る。アクセス数が変動するサービスでは、平常時だけでなく、キャンペーンや障害発生時の増加も考慮する必要がある。とはいえ、最大値に合わせて無制限に保持すると、ログがディスクを圧迫するという本来の問題に戻ってしまう。

実務上は、次のように決めると調整しやすい。

  • まず全体に控えめな max-sizemax-file を設定する
  • 監視でホストのディスク使用率を追跡する
  • docker system df やホスト側のディスク使用量を定期的に確認する
  • 障害発生時に必要なログが不足していないかを見る
  • 必要なサービスだけ保持世代や外部保存を見直す

ログ容量の設定は、一度決めたら終わりではない。アプリケーションのリリースで出力内容が変わることもあれば、アクセス量の増加で同じ設定が不足することもある。ログローテーションが設定されているかだけでなく、実際にローテーションが起きているかを確認することが大切である。

設定変更後に確認するポイント

ログ設定を変更したら、設定ファイルを書き換えて終わりにしない。新しく作成したコンテナが意図したドライバーとオプションを使っているかを確認する。

確認には docker inspect が使える。HostConfig.LogConfigTypejson-filelocal が表示され、Config に設定したオプションが反映されていることを確認する。Composeを使っている場合は、サービス定義と実際のコンテナ設定が一致しているかを見る。

確認しておきたい項目は次の通りである。

  • 新規コンテナのログドライバーが想定通りか
  • max-sizemax-file が設定されているか
  • 既存コンテナを再作成したか
  • ローテーション後の古いログが保持世代を超えて残っていないか
  • ホストのディスク使用率に異常な増加がないか
  • 外部ログ収集を使う場合、収集側でもログを受け取れているか
  • アプリケーション側のログ設定と二重保存になっていないか

Dockerのログドライバーによる制限は、コンテナの標準出力と標準エラー出力が対象になる。アプリケーションがコンテナ内のファイルへ直接書き込んでいるログは、別途管理しなければならない。Dockerのログローテーションを設定したからといって、ボリューム上のアプリケーションログやホスト上のログまで自動的に整理されるわけではない。

この違いを把握せずにいると、Dockerのログは小さくなったのにディスク使用量が減らないという状況が起こる。その場合は、/var/lib/docker だけでなく、アプリケーションの保存先、Webサーバーのログディレクトリ、バックアップや一時ファイルの領域も確認する。

ログ管理はコンテナを作る前に決めておく

Dockerコンテナのログ容量制限は、ディスクが危なくなってから追加する応急処置ではない。コンテナをデプロイする前に、どのドライバーを使い、どの程度の期間を保持し、どこへ転送するかを決めておくべき基本設計である。

小規模な単体運用で、ホスト上の docker logs を中心に確認するなら、local ドライバーで自動ローテーションと圧縮を使う構成が扱いやすい。既存のログ収集基盤やツールとの互換性を重視するなら、json-filemax-sizemax-file を設定する方法が分かりやすい。大量のログを長期間保存する必要がある場合は、コンテナのローカルログだけに頼らず、外部のログ基盤へ送る設計を検討する。

最初に押さえるべきなのは、次の三点である。

  • デフォルトの json-file は、設定しなければログが無制限に増える
  • daemon.json の変更だけでは、既存コンテナのログ設定は変わらない
  • 緊急時に truncate -s 0 で容量を戻せても、再発防止にはならない

ログ管理は地味な運用タスクであり、システムが正常に稼働している限り意識されることは少ない。しかし、一度ディスク枯渇を引き起こせば、影響はログを出していたコンテナだけに留まらない。データベースの書き込み、Webサーバーの一時ファイル、Docker自身の動作まで、ホスト上の複数の処理が同じディスクを奪い合うことになる。

だからこそ、Dockerコンテナを起動する時点でログの上限を決めておく。daemon.json で全体の初期値を設定し、必要なサービスだけCompose側で調整する。既存コンテナには再作成が必要であることを把握し、緊急時にはファイル記述子を壊さない方法で容量を回復する。この流れを運用手順に組み込めば、ログ肥大化は予測不能な事故ではなく、管理できるインフラ上の一項目になる。

Related reading: Docker・インフラ構築をわかりやすく解説.

よくある質問

Dockerのログが増え続ける原因は何ですか?
標準的な構成で使われるjson-fileドライバーでは、max-sizeのデフォルト値が-1で、ログローテーションも自動では実行されません。そのため、コンテナの標準出力と標準エラー出力がログファイルへ蓄積され続けます。
daemon.jsonでDockerのログ容量を制限するにはどうすればよいですか?
通常は/etc/docker/daemon.jsonにjson-fileのlog-driverと、max-sizeおよびmax-fileを文字列で指定します。例えばmax-sizeを10m、max-fileを3にすると、1コンテナあたり約30メガバイトを目安にログを保持する設定になりますが、厳密なディスク使用量の上限ではありません。
daemon.jsonを変更すれば既存コンテナにもログ制限が適用されますか?
いいえ。daemon.jsonの設定は基本的に変更後に作成されるコンテナのデフォルト値であり、既存コンテナには自動適用されません。既存コンテナへ反映するには、コンテナを再作成する必要があります。
Dockerのログを緊急時に削除する方法は?
ログの原因と対象コンテナを確認したうえで、truncate -s 0を使ってログファイルをゼロバイトに切り詰めます。rmで稼働中のコンテナのログファイルを削除すると、Dockerデーモンが開いたファイル記述子を保持し、期待したほど空き容量が増えない場合があります。
Docker Composeでサービスごとにログ設定を変更できますか?
はい。Composeのサービス定義にloggingを記述し、driver、max-size、max-fileなどを指定できます。サービス単位の設定はDockerデーモンの全体設定より優先されます。

参考情報