docker『Cannot Allocate Memory』の原因と対処法

docker『Cannot Allocate Memory』の原因と対処法

「Cannot allocate memory」は、コンテナやホストが新たなメモリを確保できずに処理が失敗している状態を示す。ホストOSの空きメモリ不足・cgroupによるコンテナ単位のメモリ制限・swapの設定・Docker Desktop/WSL2など仮想レイヤでの制約が重なって発生することが多い。どこでメモリ制限に引っかかっているかを特定し、コンテナ設定・アプリコード・インフラ設計の順で手当てしていく。

エラーの意味と典型的な発生条件

「Cannot allocate memory」は次のような状況で出やすい。

・コンテナ内でプロセスをfork/execしようとしたときに、OSが必要なメモリを確保できない。

・巨大な配列/バッファを確保しようとして、cgroupの上限に達する。

・ホスト側が既にメモリ/スワップともに逼迫し、システム全体で新しいメモリ割り当てができない。

・Docker DesktopやWSL2で、VMに割り当てたメモリ上限を超えた。

このエラーと OOMKilled(ExitCode=137)を区別しながら調査していくと原因特定がしやすい。

まずホスト側リソースとコンテナ状況を確認する

# ホストのメモリ・スワップ状況
free -h
vmstat 1 5

# プロセスごとのメモリ使用量(上位)
ps aux --sort=-%mem | head -n 15

# コンテナごとの使用量
docker stats --no-stream

# 特定コンテナの制限値を確認
docker inspect <container> --format '{{json .HostConfig.Memory}} {{json .HostConfig.MemorySwap}}'

ここで「ホスト全体が足りていないか」「あるコンテナだけ異常に使っているか」をざっくり把握しておく。

コンテナのメモリ制限(-m / –memory / –memory-swap)の調整

コンテナはデフォルトでホストと同じ上限を持つが、-m オプションや compose の設定で厳しめに制限されていると、簡単に「Cannot allocate memory」に当たる。

# 例: メモリ512MiB / swap512MiB に制限
docker run --name app \
  -m 512m --memory-swap 1g \
  myorg/app:1.0

# もう少し余裕を持たせる
docker run --name app \
  -m 1g --memory-swap 2g \
  myorg/app:1.0

swap をメモリと同じ値にしていると「実質swapなし」扱いになる実装もあるため、意図した挙動になっているかを確認する。

OOMKilled との違いと dmesg / イベントログでの確認

「Cannot allocate memory」と似た文脈で OOMKilled が発生することも多いので、状態を切り分ける。

# コンテナ状態から OOMKilled を確認
docker inspect <container> --format '{{json .State}}' | jq '.OOMKilled,.ExitCode'

# カーネルログにOOMが残っていないか
dmesg | grep -i -E 'out of memory|oom' | tail -n 20

・OOMKilled= true / ExitCode=137 → 完全にメモリ不足でカーネルがプロセスを殺している。

・OOMKilled= false だがアプリログに「Cannot allocate memory」 → プロセス自体が確保に失敗して例外で落ちている。

両方が同時に出るケースもあるため、ログと合わせて見る。

アプリ側のメモリ使用量を抑える(サンプルコード付き)

コンテナ設定だけでなく、アプリのメモリ使用パターンを見直すと根本改善につながる。

例えば Node.js で巨大配列を一気に読む処理は危険になる。

// NG例: 全件をメモリに保持してしまう
const fs = require('fs');

const data = JSON.parse(fs.readFileSync('big.json', 'utf8'));
console.log(data.length);

// OK例: ストリームで処理する / chunk単位で処理
const readline = require('readline');

async function processFile(path) {
  const stream = fs.createReadStream(path, { encoding: 'utf8' });
  const rl = readline.createInterface({ input: stream });
  for await (const line of rl) {
    // 1行ごとに処理して破棄
  }
}


Javaの場合はヒープサイズの上限を設定しておかないと、コンテナの制限とズレて予期しないエラーを招く。

# Javaの起動時にコンテナ内メモリに合わせる
java -Xms512m -Xmx512m -jar app.jar

Pythonでも、pandasで巨大DataFrameを扱う処理などは chunking・サンプリング・集約などを検討する。

Docker Desktop / WSL2 / macOS / Windows 環境のメモリ制限

Docker DesktopやWSL2では、コンテナはVMの中で動いているため「ホストPC全体のメモリ」ではなく「VMに割り当てたメモリ」の制限を受ける。

・Docker Desktop(macOS/Windows):Settings → Resources → Memory の値までしか使えない。

・WSL2:各ディストリごとのメモリ上限(.wslconfig)や、自動割り当てによりOOMになりやすい構成がある。

# WSL2のメモリ使用状況を確認(Linux側)
free -h
cat /proc/meminfo | head

# Windows側での設定例 (~/.wslconfig)
[wsl2]
memory=8GB   # WSL全体の上限

ホストに十分なメモリがあっても、Docker Desktop/WSLの上限が低いと「Cannot allocate memory」が発生するので、必要に応じて増やしておく。

docker-compose / Swarm / Kubernetes でのメモリ制御パターン

コンテナオーケストレーション環境では、composeやマニフェストに書いたリソース制限が直接影響する。

docker-compose の例。

services:
  app:
    image: myorg/app:1.0
    deploy:
      resources:
        limits:
          memory: 1g
        reservations:
          memory: 512m

Kubernetes の例。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: app
          image: myorg/app:1.0
          resources:
            requests:
              memory: "512Mi"
            limits:
              memory: "1Gi"

limits に対してアプリがメモリを取り過ぎると、kubelet側でOOMKillや確保失敗が発生する。requests/limitsのバランスと実際の使用量を合わせることが大事になる。

メモリリークや無限バッファの痕跡を探す

長時間動かしているコンテナで徐々に「Cannot allocate memory」が出る場合、アプリのメモリリークが疑われる。

# コンテナ内プロセスのメモリ使用量の推移を確認(簡易)
docker exec -it <container> sh -lc "
  ps aux --sort=-%mem | head -n 5
"

# ホスト側から一定間隔で記録
while true; do
  date
  docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}'
  sleep 60
done

使用量が時間とともに単調増加しているなら、ログバッファ・キャッシュ・コレクションの取り扱いを中心にコードの見直しが必要になる。

ビルドやCIでの「Cannot allocate memory」対策

イメージビルド時のnpm install / docker buildx / コンパイルなどでメモリが足りなくなるケースも多い。

# ビルド用のコンテナにだけ多めのメモリを割り当てる
docker run --rm -m 4g --memory-swap 6g \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$PWD":/workspace -w /workspace \
  docker:stable docker build -t myorg/app:build .

# npm / yarn の並列数を下げる
npm install --maxsockets=5

CI環境では、同時ジョブ数と1ジョブあたりのメモリ上限を調整して総量をコントロールする。

NG→OK早見表(よくあるケースと修正例)

# NG: コンテナに過剰に小さいメモリ制限
docker run -m 128m myorg/app:1.0
# OK: 実測に合わせて現実的な値に
docker run -m 1g --memory-swap 2g myorg/app:1.0

# NG: Node/Javaがデフォルト設定で巨大メモリを取りに行く
node server.js
java -jar app.jar
# OK: コンテナのlimitsに合わせて上限を指定
node --max-old-space-size=512 server.js
java -Xms512m -Xmx512m -jar app.jar

# NG: Docker Desktopでメモリ2GBのまま重たいコンテナを多数起動
# OK: Settings → Resources → Memory を増やす or コンテナ側のメモリ節約

# NG: コンテナ内で全件をメモリに読み込むバッチ処理
# OK: ストリーム処理 / chunk処理に書き換える

チェックリスト(上から順に潰す)

1) ホストの free -h / vmstat の結果を確認し、総メモリ・スワップが足りているか
2) docker stats で、どのコンテナがどれくらい使っているかを把握したか
3) 該当コンテナの Memory / MemorySwap 設定を docker inspect で確認したか
4) アプリのログに「Cannot allocate memory」「OutOfMemory」などが出ていないか
5) アプリ側で巨大配列・無制限キャッシュ・ログバッファなどを持っていないか
6) Docker Desktop / WSL2 のVMメモリ上限を確認し、必要なら増やしたか
7) compose / Kubernetes の memory limits が実際の使用量に見合っているか
8) 時系列でメモリ使用量を記録し、リークや単調増加パターンを確認したか