エラーの内容
コンテナが突然停止します。docker ps を実行するとコンテナが消えており、状態を確認すると以下が表示されます:
Container exited with code 137 (OOMKilled: true)
終了コード 137 は、プロセスがシグナル 9(SIGKILL)を受信したことを意味します。OOMKilled: true フラグは、クラッシュやバグではなく、Linux カーネルの OOM killer がメモリ不足を理由にコンテナを意図的に終了させたことを示しています。
発生原因
Linux カーネルは cgroup(コントロールグループ)単位でメモリ使用量を追跡します。コンテナがメモリ制限を超えた場合、またはホスト自体のメモリが枯渇した場合、OOM killer は終了させるプロセスを選択します。コンテナはその対象になりやすいです。
このエラーは主に 2 つのシナリオで発生します:
- コンテナがハードメモリ制限に達した:
--memoryを設定していたが、コンテナがその上限を超えた。 - **ホストのメモリが枯渇した:**制限が設定されておらず、コンテナがホストのメモリをすべて使い切った。
どちらの場合も対処法は同じです。強制終了を確認し、使用量を確認し、制限を設定または引き上げ、メモリを消費している原因を特定します。
手順 1:OOMKill であることを確認する
コンテナの状態を直接確認します:
docker inspect <container_name_or_id> --format='{{.State.OOMKilled}}'
true が返ってきた場合、OOM killer が原因です。次に終了コードを確認します:
docker inspect <container_name_or_id> --format='{{.State.ExitCode}}'
137 が返るはずです。ホストのカーネルログを確認して、どのプロセスが対象になったかを確認します:
dmesg | grep -i 'oom\|killed'
# systemd 環境の場合:
journalctl -k | grep -i oom
Out of memory: Kill process 1234 (node) score 900 or sacrifice child のような行を探します。プロセス名と OOM スコアから、どのコンテナが対象になり、なぜカーネルがそれを選んだかがわかります。
手順 2:現在のメモリ使用量を確認する
実行中のコンテナのメモリ使用量を確認します:
docker stats --no-stream
各コンテナの現在の使用量と設定された制限値が表示されます。制限の 80% を超えているコンテナは、トラフィックが急増すると強制終了される可能性があります。
強制終了されたコンテナに設定されていた制限を確認します:
docker inspect <container_name_or_id> --format='{{.HostConfig.Memory}}'
0 が返った場合、制限が設定されていません。コンテナはホストの使用可能なメモリをすべて消費でき、カーネルが対処するしかない状況になります。
手順 3:メモリ制限を設定または引き上げる
docker run で起動するコンテナにはメモリフラグを追加します:
docker run -d \
--memory="512m" \
--memory-swap="512m" \
your-image
--memory-swap を --memory と同じ値に設定すると、コンテナのスワップが無効になります。スワップを許可する場合は大きな値を設定し、省略するとデフォルトでメモリ制限の 2 倍になります。
Docker Compose(deploy を使用した v3、Swarm または --compatibility フラグと併用)の場合:
services:
app:
image: your-image
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
標準の Docker Compose(v2 構文、Swarm なし)の場合:
services:
app:
image: your-image
mem_limit: 512m
memswap_limit: 512m
更新後にコンテナを再作成します:
docker compose up -d --force-recreate app
手順 4:メモリを消費している原因を特定する
制限を引き上げることで時間を稼げますが、メモリリークは修正されません。使用量が増え続ける場合、コンテナは新しい上限値に達した時点で再び OOMKill されます。再起動の間隔が長くなるだけです。
実行中のコンテナ内でメモリ統計を確認します:
docker exec -it <container_name> sh -c 'cat /proc/meminfo'
docker exec -it <container_name> top
Java アプリの場合:コンテナ対応の設定がないと、JVM はコンテナの制限ではなくホストの RAM を基準にヒープサイズを決定します。512MB のコンテナ内で 16GB のホスト上で動作する JVM は、最大 4GB のヒープを目標とする場合があります。アプリが 512MB を超えると、カーネルが終了させます。ヒープサイズを明示的に設定して修正します:
docker run -d \
-e JAVA_OPTS="-Xmx256m -Xms128m" \
your-java-image
Java 8u191 以降または Java 10 以降では、代わりに -XX:+UseContainerSupport を使用します。JVM が cgroup の制限を直接読み取り、適切なサイズに自動調整されます:
docker run -d \
-e JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" \
your-java-image
Node.js アプリの場合:V8 もコンテナのメモリ制限を認識しません。ヒープサイズを明示的に制限します:
docker run -d your-node-image node --max-old-space-size=256 app.js
全般的なメモリ増加の場合:ヒープダンプを取得し、ランタイムのプロファイリングツールを使ってリークを特定します。実際の負荷に近い状態でプロファイリングを早期に行うほど、原因を素早く発見できます。
手順 5:根本原因を修正する間の再起動ポリシーを設定する
根本原因を修正している間、OOMKill 後もコンテナを稼働し続けるための設定です:
docker run -d \
--restart=on-failure:5 \
--memory="512m" \
your-image
Compose の場合:
services:
app:
image: your-image
restart: on-failure
mem_limit: 512m
これは OOMKill を防ぐものではありません。根本原因を調査している間、サービスを稼働し続けるための措置です。
修正の確認
コンテナを再起動し、メモリをリアルタイムで監視します:
docker stats <container_name>
通常の負荷で 10〜15 分後、メモリ使用量が設定した制限の 70〜80% 未満で安定することを確認します。OOMKilled フラグがクリアされていることも確認します:
docker inspect <container_name> --format='{{.State.OOMKilled}}'
# 期待される出力: false
バックグラウンドでコンテナが静かに再起動していないことも確認します:
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.RestartCount}}"
# RestartCount が増加している場合、まだ強制終了されています
本番環境での OOMKilled を防ぐためのヒント
- **必ずメモリ制限を設定する。**共有ホスト上の無制限のコンテナは、そのホスト上で動作するすべてのサービスをダウンさせる可能性があります。制限の設定をデプロイの必須要件にしましょう。
- **制限と合わせてリザベーションも設定する。**Compose でリザベーションを設定することで、スケジューラがコンテナを配置する前に必要な最小リソースを把握できます。
- **制限に達する前にアラートを設定する。**Docker のメトリクスを Prometheus + cAdvisor に連携し、メモリ使用量が 80% に達した時点でアラートを発報します。カーネルが対処する前に問題を修正できます。
- **本番環境と同じ制限でローカルテストを行う。**本番と同じ
--memory値でコンテナを起動し、負荷テストを実施します。メモリ問題をリリース前に発見できます。 - **ログバッファのサイズに注意する。**大量のログをメモリ内バッファに書き込むアプリは、メモリ使用量を静かに増大させる可能性があります。ロギングドライバの設定を確認してください。
- **スワップの扱いには慎重に。**スワップを許可する(
--memory-swap>--memory)と OOMKill を防げますが、高負荷時のパフォーマンスが低下します。これは根本的な解決策ではなく、あくまで応急処置です。

