CPUとメモリ制限でDockerコンテナのパフォーマンスを最適化する

CPUとメモリ制限を設定してDockerコンテナのパフォーマンスを最適化する方法を学びます。このガイドでは、CPUシェア、クォータ、メモリ制限、スワップなどの重要な設定オプションをカバーします。`docker stats`を使用してコンテナのリソース使用状況を監視し、リソース不足を防ぎ、アプリケーションの安定性を向上させ、システム全体の効率を高めるベストプラクティスを実装する方法を紹介します。

CPUとメモリ制限でDockerコンテナのパフォーマンスを最適化する

Dockerコンテナは、固定リソースを持つ小さな仮想マシンのように自動的に動作するわけではありません。Dockerに指示しない限り、コンテナは他のプロセスと同様にホストのCPUとメモリを競合します。これはラップトップでは便利ですが、共有サーバーではリスクがあります。

CPUとメモリの制限は魔法のようなパフォーマンス向上策ではありません。制限が低すぎるとアプリケーションが遅くなったり不安定になったりします。制限が高すぎるとホストを保護できません。目標は、各コンテナに通常時とピーク時の作業に十分な余裕を与えつつ、1つの不良プロセスがマシン上の他のすべてをダウンさせるのを防ぐことです。

制限が重要な理由

よくある障害モードはおなじみです。1つのコンテナが暴走ジョブを開始し、メモリが上昇し、ホストがスワップを始め、無関係なサービスが遅くなります。または、CPU負荷の高いバッチタスクがすべてのコアを使用し、隣のAPIコンテナがレイテンシ目標を達成できなくなります。

制限は3つの実用的な問題に役立ちます:

  • 単一のコンテナがすべてのメモリを使用するのを防ぎます。
  • コンテナが本番環境に近い制約で実行されるため、パフォーマンステストがより正確になります。
  • アプリケーションが隣からリソースを静かに借用するのではなく、スケーリングやチューニングが必要なときに気づかせます。

ランダムな数値から始めないでください。負荷下でサービスを実行し、実際の使用状況を監視し、余裕を持って制限を設定してください。小さなバックグラウンドワーカーとJVMサービスでは、非常に異なる扱いが必要です。

CPU制御

DockerはいくつかのCPU制御を提供します。日常的に最も便利なのは--cpusです。

コンテナを約1.5 CPUに制限する:

docker run -d --name api --cpus="1.5" nginx

これはCFSクォータと期間を手動で設定するよりも読みやすいです。内部では、DockerはLinuxのCPUスケジューリング制御を使用しています。

--cpu-sharesは異なります。これはハードキャップではなく、CPU競合時の相対的な重みです:

docker run -d --name important-api --cpu-shares 2048 nginx
docker run -d --name batch-worker --cpu-shares 512 worker-image

ホストにアイドルCPUがある場合、両方のコンテナがより多く使用できます。競合する場合、最初のコンテナがより多くのスケジューリング重みを持ちます。これは優先順位付けに役立ちますが、コンテナが余剰CPUを使用するのを止めるわけではありません。

正確なCFS設定が必要な場合、--cpu-period--cpu-quotaも利用可能です:

docker run -d --name limited-api \
  --cpu-period 100000 \
  --cpu-quota 50000 \
  nginx

これにより、コンテナは各100,000マイクロ秒の期間に50,000マイクロ秒のCPU時間を得られ、おおよそ0.5 CPUに相当します。ほとんどのチームにとって、--cpus="0.5"は同じ意図をより明確に伝えます。

メモリ制御

メモリ制限はCPU制限よりも危険です。超過するとプロセスが強制終了される可能性があるからです。意図的に設定し、ピーク時の動作をテストしてください。

基本オプションは--memoryです:

docker run -d --name web --memory 512m nginx

コンテナが制限を超えると、カーネルがコンテナ内のプロセスを強制終了する可能性があります。Dockerの出力では、これはOOMキルとして表示されることがよくあります:

docker inspect web --format '{{.State.OOMKilled}}'

スワップ動作は誤解されやすいです。--memoryが設定されている場合、--memory-swapはメモリとスワップの合計許容量であり、スワップ単独の許容量ではありません。

これにより、256 MBのRAMと最大256 MBのスワップが許可され、合計512 MBになります:

docker run -d --name worker --memory 256m --memory-swap 512m alpine

--memory-swap--memoryと等しく設定すると、スワップアカウンティングが利用可能なシステムでそのコンテナの追加スワップが無効になります:

docker run -d --name no-extra-swap --memory 256m --memory-swap 256m alpine

--memory-swap -1を設定すると、ホストの利用可能なスワップまで無制限のスワップが許可されます。これによりプロセスは生存し続けるかもしれませんが、レイテンシがひどくなる可能性があります。

前後の監視

サービスが実際の負荷下にあるときにdocker statsを使用します:

docker stats
docker stats web worker

CPU %MEM USAGE / LIMITBLOCK I/OPIDSを監視します。メモリ制限の100%で動作しているサービスは「効率的」ではなく、トラフィックの急増で強制終了される一歩手前です。常にスロットリングされているCPUバウンドのサービスは、平均CPUは許容範囲でも、ユーザーは遅いリクエストを経験するかもしれません。

簡単なワンショットビューの場合:

docker stats --no-stream

OOMや再起動の手がかりを得るには:

docker ps -a
docker inspect <container> --format 'OOM={{.State.OOMKilled}} Exit={{.State.ExitCode}}'
docker logs --tail 100 <container>

継続的な監視には、コンテナメトリクスをPrometheus、Grafana、クラウド監視サービス、または既存のプラットフォームにエクスポートします。docker statsはトリアージであり、長期的なアラートシステムではありません。

適切なチューニングワークフロー

テスト環境で厳しい制限なしに開始し、アイドル時、通常時、ピーク時の使用状況を取得します。次に、観測されたピークを上回るメモリを設定し、ガベージコレクション、キャッシュ成長、TLSハンドシェイク、短いバーストのための十分な余裕を持たせます。CPUについては、ハードキャップが必要か、相対的な優先順位だけで十分かを判断します。

小さなAPIの例:

docker run -d --name api \
  --cpus="2" \
  --memory 1g \
  --memory-swap 1g \
  -p 8080:80 \
  my-api:latest

これはAPIが最大2 CPU、1 GBのRAM、追加のスワップ許容なしを得ることを意味します。これは普遍的に正しいわけではありません。その範囲でテストされたサービスに対する明確な出発点です。

Docker Composeの場合、ローカルのDockerエンジンは以下のようなリソースオプションをサポートしています:

services:
  api:
    image: my-api:latest
    mem_limit: 1g
    cpus: 2.0

ComposeとSwarm/Kubernetesのリソース設定は同一ではないため、フィールドがどこでも同じ動作をすると思い込む前に、デプロイ先を確認してください。

よくある間違い

メモリを低く設定しすぎるのが最も一般的な間違いです。アプリケーションは起動し、スモークテストに合格しますが、テストでカバーされたよりも多くのメモリを割り当てるリクエストパターンでダウンします。

CPU制限を使用して非効率なコードを隠すのも別の間違いです。サービスがリクエストごとに高コストな作業を行っているために遅い場合、低いCPU制限は症状をより明白にします。コードパスを修正するわけではありません。

言語ランタイムを無視することも問題です。JVM、Node.js、Go、Python、Rubyアプリはメモリプレッシャーに対して異なる反応を示します。一部のランタイムは、内部のメモリアサンプションがコンテナ制限と一致するように明示的なヒープ設定が必要です。

最後に、1つのコンテナだけをチューニングしないでください。ホストは依然としてカーネル、Docker、ロギング、監視エージェント、およびサイドカープロセスのためにCPUとメモリを必要とします。余裕を残してください。

適切な制限は障害を小さくし、パフォーマンスをより予測可能にします。それらは推測ではなく測定から得られるべきです。