Dockerコンテナのリソース制限と監視|--memory・--cpusとdocker statsでホストを守る設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Docker > Dockerコンテナのリソース制限と監視|--memory・--cpusとdocker statsでホストを守る設計
「コンテナを複数動かしていたら、ホストのメモリが枯渇してサービスが全落ちした。」
Dockerを本番で使い始めると、こうした事故に一度は直面します。

Dockerはデフォルトでは、コンテナがホストのリソース(メモリ・CPU)を無制限に使用できてしまいます。アプリのバグやメモリリーク、突発的なスパイクで1つのコンテナが暴走すると、同じホスト上の他のコンテナやシステムプロセスが道連れになります。

この記事では、--memory・--cpusオプションによるリソース制限の設計と、docker statsを使ったリアルタイム監視の実践方法を解説します。Docker Composeのdeploy.resources設定、ulimitsによるファイルディスクリプタ制御、cgroupsを使った実体確認まで、本番ホストを守る設計を一通りカバーします。

この記事のポイント

・--memory でコンテナのメモリ上限を設定し、OOM Killerでホストを守る
・--cpus=1.5 でCPU使用量を、--cpu-shares で優先度を制御する
・docker stats --no-stream でリソース使用量をスナップショット確認できる
・Composeではdeploy.resourcesとulimitsをセットで宣言する


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜコンテナにリソース制限が必要か

物理サーバーやVMでも「1つのプロセスがリソースを使い切る」リスクはありますが、Dockerコンテナは起動コストが低い分、複数コンテナを同一ホストに詰め込む運用が一般的です。この「密度の高さ」が、リソース競合リスクを一層高めます。

たとえばRAM 8GBのホストに制限なしでコンテナを10個起動し、そのうち1つがメモリリークを起こした場合、Linuxカーネルの OOM Killer(Out-Of-Memory Killer)がプロセスを強制終了しにかかります。どのプロセスが対象になるかはカーネルのスコアリング次第で、重要なコンテナが殺されることもあります。

OOM Killerが発動すると、/var/log/messages(またはdmesg)に以下のような記録が残ります。

# OOM Killer発動時のカーネルログ出力例 Oct 11 09:23:41 web01 kernel: Out of memory: Kill process 4821 (node) score 854 or sacrifice child Oct 11 09:23:41 web01 kernel: Killed process 4821 (node) total-vm:1048576kB, anon-rss:921600kB Oct 11 09:23:41 web01 kernel: Call Trace: oom_kill_process+0x1d0/0x3d0

「score 854」はOOMスコアを示します。メモリ使用量が多いプロセスほどスコアが高く、先に強制終了されます。コンテナにリソース上限を設けておけば、この強制終了がコンテナ内に閉じ込められ、ホストの他のプロセスを巻き込まずに済みます。

制限を設けることで、1コンテナの暴走を「そのコンテナ内」に封じ込められます。これはマイクロサービス設計における「障害の隔離(Bulkheadパターン)」に相当します。

制限なし運用の典型的な事故シナリオ

・夜間バッチコンテナがメモリを食い尽くす → WebアプリコンテナがOOM Killerで停止
・CPUバウンドな処理がスパイク → レイテンシに敏感な他コンテナのAPIが遅延
・開発者がテスト用コンテナを放置 → 知らぬ間にホストのCPUを90%占有

メモリ制限の設計 --memory・--memory-swap・OOM Killer

1. --memory でメモリ上限を設定する

--memory(短縮形:-m)は、コンテナが使えるメモリの上限(ハードリミット)を設定します。単位はバイト、または k・m・g のサフィックスで指定します。

# メモリ上限を512MBに制限してコンテナを起動 docker run -d --name web-app --memory=512m nginx # 上限をdocker inspectで確認(Bytes単位で出力される) docker inspect web-app | grep '"Memory"' # "Memory": 536870912

コンテナ内のプロセスがこの上限を超えようとすると、Linuxカーネルが OOM Killer を発動してコンテナ内のプロセスを終了させます。コンテナ自体は停止(Exited)し、ホストは守られます。

2. --memory-swap でスワップを制御する

--memory-swapは、メモリとスワップの合計上限を指定します。設定の意味は以下のとおりです。

・--memory=512m --memory-swap=512m:スワップなし(合計512MB = スワップ0)
・--memory=512m --memory-swap=1g:スワップ最大512MB(合計1GB のうち512MBがスワップ)
・--memory=512m --memory-swap=-1:スワップ無制限(本番では非推奨)

スワップを無効化することでレイテンシのスパイクを防げますが、その分 OOM Killer が早く発動します。ユースケースに応じて選択してください。デフォルトでは --memory-swap は --memory の2倍(スワップが同量有効)になるため、本番では明示的に設定することを推奨します。

3. --memory-reservation でソフトリミットを設ける

--memory-reservationは「推奨上限(ソフトリミット)」です。通常はこの値を超えて使えますが、ホストのメモリが逼迫してきたとき、カーネルがこの値まで使用量を引き下げようとします。

# ハードリミット512MB、ソフトリミット256MB、スワップ無効 docker run -d --name api-server \ --memory=512m \ --memory-reservation=256m \ --memory-swap=512m \ myapp:latest

CPU制限の設計 --cpus・--cpu-shares・--cpuset-cpus

1. --cpus でCPUコア数を制限する

--cpusは、コンテナが使えるCPUの最大数(小数点指定可)を設定します。ホストが4コアなら --cpus=1.5 で「最大1.5コア分」に制限できます。

# CPUを1.5コア分に制限 docker run -d --name worker --cpus=1.5 python-worker:latest # 確認(NanoCpus = 1500000000 = 1.5コア) docker inspect worker | grep '"NanoCpus"' # "NanoCpus": 1500000000

2. --cpu-shares で相対的優先度を設定する

--cpu-sharesは、CPUが競合しているときの「優先度ウェイト」を指定します。デフォルト値は1024です。2048を指定すると、デフォルトのコンテナと比べて約2倍のCPU時間が割り当てられます。

ただし、CPUに余裕があるとき(競合がないとき)は制限されません。本番のバッチコンテナに低い値(512など)を設定しておくと、スパイク時に重要なWebコンテナを優先できます。

# Webアプリは高優先度(デフォルト1024の2倍) docker run -d --name web --cpu-shares=2048 myapp:latest # バッチは低優先度 docker run -d --name batch --cpu-shares=512 batch-job:latest

3. --cpuset-cpus でCPUコアを固定する

--cpuset-cpusは、コンテナを特定のCPUコアにピン止めします。NUMAアーキテクチャのサーバーや、レイテンシが厳しいリアルタイム処理で有効です。

# コア0番と1番のみ使用 docker run -d --name realtime-app --cpuset-cpus="0,1" myapp:latest # コア0番から3番を使用(範囲指定) docker run -d --name multi-worker --cpuset-cpus="0-3" worker:latest

docker stats で制限効果をリアルタイム監視する

制限を設定したら、実際に効いているか確認しましょう。docker statsコマンドはコンテナのリソース使用状況をリアルタイムで表示します。

# 全コンテナを継続監視(Ctrl+Cで終了) docker stats # スナップショットとして1回だけ出力(スクリプト連携に便利) docker stats --no-stream # 特定コンテナのみ監視 docker stats web-app redis-cache # 出力フォーマットをカスタマイズ docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"

実際の出力例です(ステージング環境で取得したサンプル):

CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 3f2a8b91c4d2 web-app 0.12% 145.3MiB / 512MiB 28.37% 1.2kB / 856B 0B / 8.19kB 8 e7d4f23a1b0c redis-cache 0.08% 52.7MiB / 256MiB 20.59% 234B / 0B 0B / 0B 5 a1b9c8d7e2f0 batch-job 87.43% 493.1MiB / 512MiB 96.31% 0B / 0B 0B / 102MB 12

MEM USAGE / LIMIT列を見ると、web-app は 512MiB の制限に対して 145.3MiB(28%)、batch-job は 493MiB(96%)まで使っていることがわかります。batch-job は制限に近づいているため、このまま上昇すると OOM Killer が発動します。

なお、LIMIT列が「0B」と表示される場合はリソース制限が設定されていない(無制限)サインです。本番コンテナでこの表示が出たら、速やかに制限を追加してください。

docker statsの主要カラム説明

カラム名 内容
CPU % ホストのCPU総使用率に対する比率(全コア合算)
MEM USAGE / LIMIT 実際のメモリ使用量 / 設定した制限値(制限なしはホスト総量)
MEM % 制限値に対するメモリ使用率
NET I/O ネットワーク送受信量(コンテナ起動からの累積)
BLOCK I/O ブロックデバイス読み書き量(ディスクI/O)
PIDS コンテナ内で動作中のプロセス数

docker inspectで設定値を数値で確認する

docker statsは動的な使用量の確認に向いていますが、設定値(制限値そのもの)を数値で取得したい場合はdocker inspectのGoテンプレートが便利です。スクリプトから設定値を検証する際に活用できます。

# メモリ上限(バイト単位)を確認 docker inspect web-app --format='{{.HostConfig.Memory}}' # 536870912 ← 536870912 / 1024 / 1024 = 512 MiB # CPUクォータを確認(100000 = 1コア相当) docker inspect web-app --format='{{.HostConfig.CpuQuota}}' # 100000 # OOM Killedで落ちたか確認(true = OOM Killerで終了) docker inspect web-app --format='{{.State.OOMKilled}}' # true または false

--formatオプションにGoテンプレートを渡すことで、JSONの特定フィールドだけを素のテキストとして取得できます。grepと組み合わせるより読みやすく、スクリプトへの組み込みにも向いています。

Dockerコンテナの実践的な活用をもっと体系的に学びたい方には、Dockerコンテナ実践ガイド(docker.linuxmaster.jp)もあわせてご参照ください。入門から本番設計まで体系化した解説を提供しています。

ulimitsでファイルディスクリプタ数を制御する

メモリとCPUだけがリソース制限の対象ではありません。Nginx・Node.js・Redisなどの高トラフィックサービスでは、「too many open files」エラーによる障害がよく起こります。コンテナのデフォルトのファイルディスクリプタ数(nofile)は、多くの環境でDockerデーモンのデフォルト値(1024)のままになっています。

--ulimitオプション(docker run)またはComposeのulimitsブロックで、コンテナのファイルディスクリプタ上限を制御できます。

1. docker run でulimitsを設定する

# nofile(ファイルディスクリプタ数)を65536に設定 # 書式: --ulimit nofile=soft:hard docker run -d --name nginx \ --ulimit nofile=65536:65536 \ nginx:latest

2. Composeのulimitsブロックで設定する

# docker-compose.yml(Compose V2) services: nginx: image: nginx:1.27-alpine ulimits: nofile: soft: 65536 hard: 65536 deploy: resources: limits: cpus: '0.50' memory: 256M

・soft:プロセスが自分で変更できる現在の上限
・hard:rootでないと引き上げられない絶対上限

softとhardを同じ値にしておくのが安全な設定です。サービスが実行時に自動でsoft値を引き上げて想定外の挙動を起こすリスクを排除できます。Webサーバーの標準的な推奨値は65536(64Ki)です。

3. 設定が反映されているか確認する

# コンテナ内でulimitを確認 docker exec -it nginx sh -c 'ulimit -n' # 65536 ← 正しく反映されている # /proc経由でも確認できる(コンテナID: 3f2a8b91c4d2 の場合) cat /proc/$(docker inspect nginx --format='{{.State.Pid}}')/limits | grep open # Max open files 65536 65536 files

Composeでリソース制限を設定するdeploy.resources

複数コンテナをDocker Composeで管理する場合、各サービスにリソース制限を宣言しておくことが推奨されます。Composeファイルに書くことで、チーム全員が同じ制限値で運用できます。

# docker-compose.yml(Compose V2以降) services: web: image: myapp:latest deploy: resources: limits: cpus: '1.0' memory: 512M reservations: cpus: '0.25' memory: 128M batch: image: batch-job:latest deploy: resources: limits: cpus: '0.5' memory: 256M reservations: cpus: '0.1' memory: 64M

limitsはハードリミット、reservationsはソフトリミット(最低保証)に相当します。

注意点として、Compose V2(docker composeコマンド)ではdeploy.resourcesがそのまま有効ですが、旧来のdocker-compose(ハイフンあり)コマンドではSwarmモード専用の設定として無視される場合がありました。現在のDockerをお使いであればdocker compose(スペースあり)を使用してください。

# Compose V2で起動 docker compose up -d # 起動後のリソース制限を確認 docker compose stats --no-stream

Compose V1の旧構文(mem_limit・cpu_quota)との関係

Docker Compose V1時代(docker-composeコマンド)は、deployブロックを使わずサービス直下にmem_limitを書いていました。

# Compose V1の旧構文(非推奨・参考) services: app: image: myapp:latest mem_limit: 512m cpu_quota: 50000 # 100000 = 1コア相当、50000 = 0.5コア

docker compose(V2)はmem_limitをそのまま受け付けますが、将来的に非推奨になる可能性があります。既存のCompose設定がV1構文を使っている場合は、deploy.resourcesブロックへの移行を検討してください。

cgroupsの実体確認(/sys/fs/cgroup)

Dockerのリソース制限はLinuxカーネルのcgroups(Control Groups)機能を使って実現されています。設定が本当に反映されているかを、cgroupsの実体ファイルで直接確認できます。

現代のDockerはcgroups v2を使用しており、制御ファイルは/sys/fs/cgroup/配下に配置されます。

# コンテナIDを取得 CONTAINER_ID=$(docker inspect --format '{{.Id}}' web-app) # メモリ制限の実体(バイト単位) cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/memory.max # 536870912 ← 512MiB = 512 * 1024 * 1024 # CPU制限の実体(quota period形式) cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/cpu.max # 100000 100000 ← 100ms中100ms使用可 = 1.0コア相当

memory.maxの値(536870912)は512MiBのバイト数であり、--memory=512mが正しく反映されていることが確認できます。cpu.maxの100000 100000は「100msの期間のうち100ms使用可」を意味し、--cpus=1.0の設定値と一致します。

cgroupsはLinuxのファイルシステムとして/sys/fs/cgroupにマウントされています。Linuxのファイルシステムマウントの基本については、mountコマンドの使い方でも解説しています。

本番環境のベストプラクティスと設計指針

1. まずは計測してから制限値を決める

制限値を感覚で決めるのは危険です。本番投入前にステージング環境でdocker statsを使い、実際のリソース使用量のピーク値を計測してください。その値に20~30%のバッファを加えた値を制限値として設定するのが現実的です。

2. --memory-swap を明示的に設定する

デフォルトでは--memory-swapは--memoryの2倍になります(スワップが--memoryと同量有効)。スワップを有効にするとメモリ枯渇時に応答が劇的に遅くなります。本番では--memory-swapを--memoryと同じ値(スワップ実質ゼロ)に設定することを強く推奨します。

3. コンテナの用途でCPU制限戦略を変える

・APIサーバー(レイテンシ重視):--cpusで上限を設けつつ--cpu-sharesを高めに設定
・バッチ処理(スループット重視):--cpusで上限を低く抑え、他のコンテナへの影響を遮断
・複数コンテナ混在:--cpu-sharesで相対優先度を設定し、スパイク時の競合を制御

4. --pids-limit でフォーク爆弾を防ぐ

メモリとCPUだけでなく、プロセス数も制限しましょう。--pids-limitを設定することで、コンテナ内でのフォーク爆弾(無限にプロセスを生成するバグ・攻撃)を防げます。本番では200~500程度が目安です。

# メモリ・CPU・プロセス数をまとめて制限 docker run -d --name web-app \ --memory=512m \ --memory-swap=512m \ --cpus=1.0 \ --pids-limit=200 \ myapp:latest

5. docker stats を定期実行してベースラインを把握する

docker statsはリアルタイム監視に便利ですが、長時間の傾向分析にはPrometheusやcAdvisorとの連携が実務では一般的です。まずはdocker stats --no-streamを定期的にcronで実行してログに残すだけでも、ベースラインの把握に役立ちます。

Linuxのプロセス監視ツール(ss・lsof等)との組み合わせについては、Linux ポート確認の全コマンドも参考にしてください。

よくあるトラブルと対処法

OOM Killedでコンテナが突然停止する場合

コンテナがExited (137)で停止しているとき、OOM Killerによる強制終了が疑われます。以下の手順で原因を確認してください。

・ステップ1:OOMKilledフラグと終了コードを確認する

# OOMKilledフラグを確認(Goテンプレート形式) docker inspect web-app --format='{{.State.OOMKilled}}' # true ← OOM Killerで停止 # 終了コードを確認(OOM Killedは通常137 = SIGKILL) docker inspect web-app --format='{{.State.ExitCode}}' # 137 # grepでも確認可能 docker inspect web-app | grep '"OOMKilled"' # "OOMKilled": true

exit code 137はSIGKILL(強制終了シグナル)を意味するため、アプリが自分でクラッシュしたケース(Segfaultなど)と区別できます。OOMKilledがtrueで終了コードが137なら、メモリ上限に達してコンテナが強制終了されています。

・ステップ2:カーネルログで詳細を確認する

# ホストのカーネルログでも確認可能 dmesg | grep -i "oom\|killed process" # [12345.678] Out of memory: Kill process 3142 (java) ...

・ステップ3:制限値の見直し

OOM Killedが頻発する場合は以下の順序で対処します。

1. docker statsで通常時のメモリ使用量のピーク値を計測する
2. --memory(またはdeploy.resources.limits.memory)をピーク使用量の1.5倍~2倍程度に引き上げる
3. それでもOOMが続くなら、アプリのメモリリークを調査する(heapdump等)

制限値を大きくするだけでは根本解決にならないケースがあります。アプリ側のリークを排除した上で、docker statsでベースラインを計測してから適切な上限値を設定してください。

--cpus設定後もCPU %が高く見える場合

--cpus=1.0を設定しているのにdocker statsでCPU %が100%を超えるように見える場合は、docker statsのCPU %がホスト全CPUコア数に対する割合で表示されることに注意してください。4コアのホストで--cpus=1.0であれば、最大25%(1コア/4コア)が上限です。制限は正しく効いています。

本記事のまとめ

Dockerコンテナのリソース制限と監視に関する主要なオプション・コマンドをまとめます。
やりたいこと コマンド / 設定
メモリ上限を設定する docker run --memory=512m コンテナ名
スワップを無効にする docker run --memory=512m --memory-swap=512m コンテナ名
CPU使用量を制限する docker run --cpus=1.5 コンテナ名
CPU相対優先度を設定する docker run --cpu-shares=2048 コンテナ名
特定のCPUコアに固定する docker run --cpuset-cpus="0,1" コンテナ名
プロセス数を制限する docker run --pids-limit=200 コンテナ名
ファイルディスクリプタ上限を設定する docker run --ulimit nofile=65536:65536 コンテナ名
リソース使用量を一覧確認する docker stats --no-stream
制限値を数値で確認する docker inspect --format='{{.HostConfig.Memory}}' コンテナ名
Composeでメモリ制限を宣言する deploy.resources.limits.memory: 512M
Composeでulimitsを設定する ulimits.nofile.soft/hard: 65536
cgroupsで制限値を確認する cat /sys/fs/cgroup/.../memory.max
Dockerのリソース制限はホスト保護の基本です。開発環境では意識されないことが多いですが、本番に上がる前には必ず設定しておきましょう。Composeファイルにdeploy.resourcesとulimitsを定義することで、チーム全員が同じ制限値で運用できます。

本記事で紹介した--memory・--cpus・docker statsの実践的な使い方をさらに深く学びたい方は、Dockerコンテナ実践ガイド(docker.linuxmaster.jp)をご覧ください。Docker入門から本番運用まで体系的に解説しています。

Dockerのリソース制限を「なぜ設定するか」から理解するには、Linuxサーバー構築の「型」が土台になります

--memoryや--cpusはコマンドとして覚えられても、「どの値を設定すべきか」「なぜcgroupsがホストを守れるのか」を説明できるかどうかで現場での信頼が変わります。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

※ 「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。