Dockerコンテナの最適化:パフォーマンスボトルネックのトラブルシューティング

Dockerコンテナの動作が遅い場合、この必須ガイドでは、コンテナ化されたアプリケーションにおける一般的なパフォーマンスボトルネックを特定し解決する方法を詳しく説明します。`docker stats`などのDocker監視ツールを効果的に使用し、高いCPU/メモリ使用率を診断し、ストレージドライバの知識を通じてI/Oパフォーマンスを最適化し、マルチステージビルドなどのベストプラクティスを適用して、より高速で効率的な運用を実現する方法を学びます。

Dockerコンテナの最適化:パフォーマンスボトルネックのトラブルシューティング

Dockerコンテナが遅い場合、コンテナ自体が原因であることはほとんどありません。問題は通常、1つ下の層にあります:CPUスロットリング、メモリプレッシャー、遅いディスク書き込み、DNS遅延、ホスト上のノイジーネイバー、またはコンテナ化される前から非効率だったアプリケーションです。

時間を無駄にする最も速い方法は、どのリソースが制約されているかを知る前にDockerフラグを変更し始めることです。証拠から始め、1つのボトルネックを特定し、1つのことを変更し、再度測定します。

チューニングではなくトリアージから始める

docker statsは、迅速なライブビューを提供します:

docker stats
docker stats --no-stream

これを使用して基本的な質問に答えます:

  • CPUは高く持続していますか?
  • メモリは設定された制限に近づいていますか?
  • 遅いリクエスト中にブロックI/Oが上昇していますか?
  • プロセス数が予想外に多いですか?
  • ネットワークI/Oはワークロードと一致していますか?

次に、コンテナの状態とログを確認します:

docker logs --tail 100 <コンテナ名またはID>
docker inspect <コンテナ名またはID> --format 'OOM={{.State.OOMKilled}} Exit={{.State.ExitCode}} Restarting={{.State.Restarting}}'

また、ホストも確認します。ホストがスワップしているか、ディスクが飽和している場合、コンテナは無害に見えることがあります:

top
free -m
vmstat 1
iostat -xz 1

iostatにはsysstatパッケージが必要な場合があります。Docker Desktopを使用している場合、ホストOSとLinuxコンテナの間にVMがあり、ファイルとネットワークの動作が変わることを覚えておいてください。

CPUボトルネック

高いCPUは、アプリケーションがビジー状態であるか、過小プロビジョニングされているか、スロットリングされていることを意味する可能性があります。これらは異なる問題です。

設定されたCPU設定を確認します:

docker inspect <コンテナ> --format '{{json .HostConfig.NanoCpus}} {{json .HostConfig.CpuQuota}} {{json .HostConfig.CpuPeriod}}'

コンテナが厳しすぎる制限でキャップされている場合、ホストにまだアイドルCPUがあるにもかかわらず、リクエストがキューイングされる可能性があります。制御された増加を試みます:

docker run -d --name api --cpus="2" my-api:latest

制限を上げてもCPUが高いままの場合、アプリケーションをプロファイリングします。例えば、NodeサービスがJSONシリアル化で詰まっている、PythonワーカーがGILの下でCPUバウンドである、Javaサービスがガベージコレクションに時間を費やしている可能性があります。Dockerだけではこれを修正できません。

メモリプレッシャーとOOMキル

メモリの問題は、再起動、レイテンシのスパイク、または負荷がかかった状態でのプロセスの消失として現れることがよくあります。

DockerがOOMキルを検出したかどうかを確認します:

docker inspect <コンテナ> --format '{{.State.OOMKilled}}'

メモリがゆっくりと上昇し、決して低下しない場合、リークまたは無制限のキャッシュを探します。特定のリクエスト中にメモリがスパイクする場合、そのパスを負荷下で再現します。言語ランタイムに独自のヒープ制限がある場合、それをコンテナに合わせます。実際のコンテナ予算を理解していないJVMは、動作が悪くなる可能性があります。最新のJVMはコンテナ対応ですが、ヒープ設定は依然として見直す価値があります。

メモリ制限は余裕を残す必要があります。通常のトラフィック中に1GBの制限のうち950MBを使用しているコンテナは健全ではありません。ガベージコレクション、一時バッファ、TLS、圧縮、リクエストバーストにはすべてスペースが必要です。

入出力(I/O)パフォーマンスの問題の解決

遅いディスクアクセスは、データベース、キュー、検索エンジン、キャッシュウォームアップ、およびチャットの多いロギングに影響を与えます。まず、書き込みがコンテナの書き込み可能レイヤー、名前付きボリューム、またはバインドマウントのいずれに行われているかを確認します。

docker inspect <コンテナ> --format '{{json .Mounts}}'
docker info --format 'StorageDriver={{.Driver}}'

最新のLinux Dockerインストールでは、overlay2が一般的なデフォルトのストレージドライバです。通常は良い選択ですが、大量の可変データをコンテナレイヤーに書き込むことは依然として悪いパターンです。

永続的なアプリケーションデータには名前付きボリュームを使用します:

docker volume create app-data
docker run -d --name app -v app-data:/var/lib/app my-image

特定のホストパスが必要な場合はバインドマウントを使用しますが、テストしてください。macOSおよびWindows上のDocker Desktopのバインドマウントは、ファイル操作が仮想化境界を越えるため、ネイティブのLinuxファイルシステムアクセスよりもはるかに遅くなる可能性があります。

一時的な高速ファイルには、/dev/shmが役立ちますが、メモリバッキングされており制限があります:

docker run --shm-size=512m my-image

これは、ブラウザ、テストランナー、および共有メモリを必要とするアプリケーションで一般的です。実際のストレージの代わりにはなりません。

イメージサイズとビルドパフォーマンスの最適化

イメージサイズは主に、ビルド、プル、スキャン、デプロイ時間に影響します。コンテナが実行されると、通常はリクエスト処理を高速化しませんが、運用上は依然として重要です。

マルチステージビルドを使用して、コンパイラ、パッケージキャッシュ、テストツールがランタイムイメージに含まれないようにします:

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app

FROM alpine:3.20
COPY --from=builder /app/app /app
CMD ["/app"]

キャッシュ再利用のためにDockerfileの命令を順序付けます:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

ソースコードを変更しても、依存関係ファイルが変更されない限り、依存関係のダウンロードを強制すべきではありません。

ネットワークパフォーマンスの考慮事項

ネットワークの遅延は、しばしばアプリケーションの遅延のように見えます。コンテナ内からテストします:

docker exec -it <コンテナ> sh
time getent hosts api.example.com
time wget -qO- https://api.example.com/health

DNS解決が遅いか不安定な場合、コンテナ内の/etc/resolv.confを調べ、ホストと比較します。実行時にDNSサーバーを指定できます:

docker run -d --name web --dns 1.1.1.1 my-image

これは、診断またはポリシーの選択として行い、ランダムな修正として行わないでください。企業ネットワークでは、内部DNSが必要になる場合があります。

Dockerのデフォルトのブリッジネットワークは、NATとiptables処理を追加します。ほとんどのWebアプリケーションでは、オーバーヘッドは許容範囲です。ホストネットワーキングは、Linux上のオーバーヘッドを削減できます:

docker run --network host my-image

これにより、ネットワーク名前空間の分離が削除され、ポートの処理が変更されます。実際のニーズを測定した場合に使用します。

フィールドチェックリスト

コンテナが遅い場合、このリストを順に確認します:

  1. 特定のリクエスト、ジョブ、またはワークロードで遅延を再現します。
  2. docker stats --no-stream、ログ、およびコンテナインスペクト出力をキャプチャします。
  3. ホストのCPU、メモリ、スワップ、ディスクI/O、およびネットワークを確認します。
  4. 制限を変更する前に、制約されているリソースを特定します。
  5. 永続的または大量の書き込みをボリュームに移動します。
  6. ローカル開発が唯一の遅い場所である場合、LinuxとDocker Desktopでのバインドマウントの動作を比較します。
  7. コンテナメトリクスが遅延を説明しない場合、アプリケーションをプロファイリングします。
  8. 1つの設定を変更し、再度測定します。

有用な考え方はシンプルです:Dockerは分離とパッケージングを提供しますが、通常のシステム作業を排除するわけではありません。CPU、メモリ、ディスク、およびネットワークが、サービスの応答速度を決定します。