一般的なKubernetesパフォーマンスボトルネックのトラブルシューティング
CPUスロットリング、メモリOOMKill、スケジューリング遅延など、一般的なKubernetesパフォーマンスボトルネックを体系的に診断・解決する方法を学びます。このガイドでは、リソースリクエストのチューニング、HPAスケーリングの最適化、基盤となるクラスター制約の特定に役立つ実践的なコマンドとベストプラクティスを提供し、最適なアプリケーションパフォーマンスを実現します。
一般的なKubernetesパフォーマンスボトルネックのトラブルシューティング
Kubernetesのパフォーマンス問題は、「Kubernetesが遅い」と明確に宣言されることはほとんどありません。ロールアウトが停止する、APIが突然5xxエラーを返す、キューが処理を停止する、Podは正常に見えるのにユーザーがレイテンシを訴える、といった形で現れます。クラスターはその原因の一部に過ぎませんが、一貫したコマンドセットで調査できる部分でもあります。
重要なのは、ノードサイズやレプリカ数にすぐに飛びつかないことです。まず、どのような種類のボトルネックかを特定します。CPUスロットリング、メモリプレッシャー、スケジューリング遅延、スケーリングの遅さ、ネットワークレイテンシ、ストレージレイテンシ、あるいはアプリケーションが単に想定以上の処理を行っている場合などです。
フェーズ1: 症状の特定
特定のコンポーネントに飛び込む前に、観測されたパフォーマンス低下を明確に定義します。一般的な症状は、以下のカテゴリのいずれかに分類されることがよくあります。
- デプロイ/更新の遅延: Podの作成に過度な時間がかかる、またはローリングアップデートが停止する。
- アプリケーションの応答不能: Podは実行中だが、アプリケーションレベルのトラフィックに応答しない(例:高レイテンシ、5xxエラー)。
- 高いリソーススパイク: ノード全体または特定のデプロイメントで、説明不能なCPUまたはメモリ使用率のスパイクが発生する。
- スケジューリング遅延: 新しいPodが
Pending状態のままになる。
フェーズ2: リソース制約(CPUとメモリ)の診断
リソース管理の不備は、Kubernetesパフォーマンス問題の最も頻繁な原因です。リクエストと制限が適切に設定されていないと、スロットリングやOOMKillが発生します。
1. リソース使用率と制限の確認
まず、kubectl describeとkubectl topを使用して、影響を受けるアプリケーションのリソース割り当てを検査します。
実践的な確認: requestsとlimitsを、メトリクスサーバーによって報告される実際の使用量と比較します。
# 名前空間内のすべてのPodのリソース使用量を取得
kubectl top pods -n <namespace>
# 特定のPodのリソースリクエスト/制限を確認
kubectl describe pod <pod-name> -n <namespace>
また、所有するワークロードも検査して、問題が1つのPodに影響しているのか、デプロイメント全体に影響しているのかを理解します。
kubectl get deploy <deployment-name> -n <namespace> -o yaml
kubectl get pods -n <namespace> -l app=<label> -o wide
1つのPodだけが遅く、それが他のPodとは異なるノードにある場合、ノードレベルのプレッシャーが原因である可能性が高くなります。すべてのレプリカが遅い場合、リソース設定、ダウンストリームの依存関係、またはアプリケーションの動作に注目する必要があります。
2. CPUスロットリング
コンテナのCPU使用率が定義された制限に繰り返し達すると、カーネルがスロットリングを実行し、ノード自体に利用可能な容量がある場合でも、深刻なレイテンシスパイクが発生します。これは、一般的なCPU不足と誤認されることがよくあります。
診断のヒント: kubectl topでノードのCPU使用率が100%を示していない場合でも、高レイテンシの応答がないか確認します。スロットリングはコンテナごとに発生します。
より詳細な確認には、メトリクスシステムがコンテナのCPUスロットリングメトリクスを公開している場合は、それを利用します。Prometheusベースのセットアップでは、チームはリクエストレイテンシとともに、スロットリングされたCPU期間などのメトリクスを監視することがよくあります。生のCPU使用率だけでは、コンテナがフルノードコアを使用しているように見える前にスロットリングされる可能性があるため、スロットリングを隠してしまう可能性があります。
解決策:
- ワークロードが正当により多くの処理能力を必要とする場合は、CPU
limitを増やします。 - アプリケーションがビジーウェイトを行っている場合は、単に制限を増やすのではなく、アプリケーションコードを最適化します。
- プラットフォームポリシーに合致する場合は、レイテンシに敏感な一部のサービスに対してCPU制限を削除し、CPUリクエストは維持することを検討します。これにより、ハードなスロットリングを回避しつつ、スケジューラーに有用な配置情報を提供できます。
3. メモリプレッシャーとOOMKill
コンテナがメモリ制限を超えると、KubernetesはOut-Of-Memory(OOM)キルを開始し、コンテナを繰り返し再起動します。
診断: Podのステータスで頻繁な再起動を確認し(kubectl get podsのRESTARTS列を確認)、ログでOOMKilledイベントを調べます。
# OOMKillの最近のイベントを確認
kubectl get events --field-selector involvedObject.name=<pod-name> -n <namespace>
解決策:
- OOMKillが頻繁な場合は、すぐにメモリ
limitを増やします。 - 長期的な修正としては、アプリケーションをプロファイリングしてメモリリークを見つけて修正するか、ヒープサイズを削減します。
メモリはCPUとは動作が異なります。CPUはスロットリングされてもプロセスはゆっくりと動作し続けます。メモリ制限違反は、通常、プロセスが強制終了されることで終わります。そのため、メモリ問題は信頼性インシデントのように見えます。再起動、接続の切断、コールドキャッシュ、進行中のリクエストの失敗などです。
ベストプラクティス: リクエストを賢く設定する。 リソース
requestsは、予想される最小使用量に合理的に近い値に設定します。requestsが低すぎると、スケジューラーがノードをオーバーコミットし、すべてのPodが同時に需要を満たそうとしたときに競合が発生する可能性があります。
フェーズ3: スケジューリングボトルネックの調査
PodがPending状態のままの場合、問題はスケジューラーが適切なノードを見つけられないことにあります。
1. Pending Podの分析
Pending状態のPodに対してkubectl describe podを使用し、Eventsセクションを読みます。このセクションには、スケジュール失敗の明確な説明が含まれていることがよくあります。
一般的なスケジューラーメッセージ:
0/3 nodes are available: 3 Insufficient cpu.(ノード容量の問題)0/3 nodes are available: 3 node(s) had taint {dedicated: infra}, that the pod didn't tolerate.(Taints/Tolerationsの不一致)0/3 nodes are available: 1 node(s) had taint {NoSchedule: true}, that the pod didn't tolerate.(ノードプレッシャーまたはメンテナンス)
2. クラスターリソースの飽和
CPU/メモリ不足によりスケジューリングが遅延している場合、クラスターに十分な総容量が不足しています。
解決策:
- クラスターにノードを追加します。
- リソースリクエストの設定ミスによりノード使用率が人為的に高くなっていないか確認します(フェーズ2参照)。
- クラウドプロバイダーで実行している場合は、Cluster Autoscaler(CA) を使用して、Pending Podが蓄積されたときに動的にノードを追加します。
Cluster Autoscalerが有効になっているのにノードが追加されない場合は、ログを読んでからクラウドプロバイダーが壊れていると決めつけないでください。オートスケーラーは、ノードグループが最大サイズに達した、Pending Podにどのノードグループも満たせない制約がある、またはクォータが新しいインスタンスを妨げているなどの理由で、ノードの追加を拒否する場合があります。
フェーズ4: スケーリングメカニズムのパフォーマンス問題
自動スケーリングは迅速に反応する必要がありますが、Horizontal Pod Autoscaler(HPA)やVertical Pod Autoscaler(VPA)の設定ミスが問題を引き起こす可能性があります。
1. Horizontal Pod Autoscaler(HPA)の遅延
HPAは、Metrics Serverが正確なCPU/メモリ使用率またはカスタムメトリクスを報告することに依存しています。
診断手順:
- Metrics Serverの健全性を確認: Metrics Serverが実行中でアクセス可能であることを確認します。
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" - HPAステータスを確認: HPA設定と最近のイベントを検査します。
メトリクスソースが利用できない、またはスケーリング決定ループが機能していることを示すメッセージがないか確認します。kubectl describe hpa <hpa-name> -n <namespace>
ボトルネック: カスタムメトリクスが使用されている場合、外部メトリクスプロバイダーが正しく機能し、HPAが有用な決定を下すのに十分な頻度でデータを報告していることを確認します。
HPAはリアクティブです。メトリクスが反映しない限り、トラフィックスパイクが来ることを認識しません。突然のバーストが発生するワークロードの場合、より高い最小レプリカ数、より高速なカスタムメトリクス、キューベースのスケーリング、または既知のイベント前の事前スケーリングが必要になる場合があります。
2. Vertical Pod Autoscaler(VPA)の相互作用
VPAはリソースリクエストを自動的に調整しますが、調整フェーズ中にPodを頻繁に再起動またはリサイズすると、特に再起動に耐えられないステートフルアプリケーションの場合、パフォーマンスの不安定性を引き起こす可能性があります。
推奨事項: まずVPAをRecommenderモードで使用するか、updateMode: "Off"を使用して推奨事項のみを観察し、自動適用は行わないことで、不要なリサイズによる混乱を軽減します。
フェーズ5: ネットワークとストレージのパフォーマンス
コンピューティングリソースが問題ないように見える場合、ネットワーキングまたは永続ストレージがボトルネックになっている可能性があります。
1. CNI(Container Network Interface)の問題
Pod間(特にノード間)の通信が遅い、または断続的に失敗する場合、CNIプラグインが過負荷になっているか、設定が間違っている可能性があります。
トラブルシューティング:
- CNIデーモンセットPod(例:Calico、Flannel)のログを確認します。
- 異なるノード上のPod間で
pingまたはcurlを使用して基本的な接続をテストします。
2. Persistent Volume(PV)のレイテンシ
ディスクI/Oに大きく依存するアプリケーション(データベース、ログシステム)は、基盤となるPersistent Volumeのレイテンシが高いとパフォーマンスが低下します。
実践的な確認: プロビジョナーのタイプ(例:AWS EBS gp3 vs. io1)を確認し、ボリュームが必要なIOPS/スループット仕様を満たしていることを確認します。
ストレージに関する警告: 基盤となるディスクのパフォーマンス特性を理解せずに、標準の
hostPathボリュームで高スループットのデータベースを実行しないでください。要求の厳しいワークロードには、マネージドクラウドストレージソリューションまたは高性能ローカルストレージプロビジョナーを使用します。
ノードレベルのボトルネック
時々、ノード上のすべてのPodが同時に遅くなることがあります。それは、1つのデプロイメントに注目するのをやめて、ノードを検査する合図です。
kubectl describe node <node-name>
kubectl top node <node-name>
kubectl get pods --all-namespaces -o wide | grep <node-name>
MemoryPressure、DiskPressure、PIDPressureの状態を確認します。ディスクプレッシャーは見落とされがちです。なぜなら、アプリケーションの症状が、明らかなディスクエラーではなく、起動の遅さ、イメージプルの失敗、エビクションである可能性があるからです。
ノード自体にアクセスできる場合は、以下を確認します。
df -h
iostat -x 1
free -h
journalctl -u kubelet --since "30 minutes ago"
マネージドKubernetesサービスではノードへの直接アクセスが制限される場合がありますが、同じ考え方が適用されます。プロバイダーメトリクス、kubeletイベント、ノード状態を使用して、ノードが共有ボトルネックであるかどうかを判断します。
コントロールプレーンとAPIプレッシャー
ほとんどのアプリケーションレイテンシは、Kubernetes APIサーバーによって引き起こされるわけではありません。Webリクエストは通常、ユーザーリクエストごとにAPIサーバーを呼び出すわけではありません。しかし、コントロールプレーンのプレッシャーは、運用パフォーマンスに悪影響を及ぼす可能性があります。ロールアウトの遅延、スケジューリングの遅延、エンドポイント更新の遅延、コントローラーの遅れなどです。
症状は以下のとおりです。
kubectlコマンドがクラスター全体で遅い。- デプロイメントがPodの作成に通常より時間がかかる。
- コントローラーが望ましい状態に追いつかない。
- イベントにAPIタイムアウトが繰り返し表示される。
問題が通常のアプリケーショントラフィックに影響しているのか、クラスター操作に影響しているのかを確認します。ロールアウトとスケジューリングのみが遅い場合は、コントロールプレーンを管理するクラスターで、APIサーバーの健全性、コントローラーマネージャーの動作、アドミッションウェブフック、etcdの健全性を確認します。
アドミッションウェブフックは特に注意が必要です。ノードに十分な容量がある場合でも、遅いまたは利用できないウェブフックはPodの作成を遅らせる可能性があります。ロールアウトが作成時に停止し、イベントがウェブフック呼び出しに言及している場合は、ノードをリサイズする前にウェブフックサービスを調査します。
実践的なトラブルシューティングの順序
ユーザーから見える症状から始めます。
- HTTPリクエストが遅い: アプリケーションレイテンシ、CPUスロットリング、メモリ再起動、ダウンストリームレイテンシ、ネットワークパスを比較します。
- Podの起動が遅い: イメージプル時間、スケジューリングイベント、ボリュームアタッチ時間、initコンテナを確認します。
- PodがPending: リクエスト、ノード容量、テイント、アフィニティ、クォータ、オートスケーラー制限を確認します。
- 定期的なレイテンシスパイク: CPUスロットリング、ガベージコレクション、ノイジーネイバー、ストレージレイテンシ、HPAスケールタイミングを確認します。
- ランダムな再起動: OOMKilled、livenessプローブ、ノードプレッシャー、以前のコンテナのアプリケーションログを確認します。
次に、一度に1つのレイヤーを証明または排除します。例えば、レイテンシスパイクがCPUスロットリングと正確に一致する場合、有力な手がかりがあります。CPU、メモリ、ネットワーク、ストレージがすべて穏やかな状態でレイテンシスパイクが発生する場合、ボトルネックはアプリケーション内部またはKubernetes外部のダウンストリームサービスにある可能性があります。
推測なしのリクエストと制限のチューニング
不適切なリソース設定は、多くのパフォーマンス問題を引き起こします。
- リクエストが低すぎる: スケジューラーがビジーなPodを同じノードに詰め込みすぎる。
- リクエストが高すぎる: 実際の使用量が控えめでもPodがPendingのままになる。
- CPU制限が低すぎる: レイテンシに敏感なアプリがスロットリングされる。
- メモリ制限が低すぎる: コンテナが遅くなる代わりに強制終了される。
- リクエストがない: スケジューリングの予測可能性が低下し、重要なワークロードがノイジーネイバーと競合する可能性がある。
最近の本番メトリクスを出発点として使用し、通常のスパイクに対応するためのヘッドルームを残します。Java、Node.js、Go、Python、データベースのようなワークロードでは、メモリの動作が大きく異なる可能性があるため、コンテナイメージのサイズが似ているという理由だけで、あるサービスから別のサービスに制限をコピーしないでください。
次のステップ
最高のKubernetesパフォーマンス調査は、良い意味で退屈です。症状を定義し、Podを確認し、ノードを確認し、スケーリングを確認し、次にネットワークとストレージを確認します。kubectl describeとkubectl topは始まりに過ぎませんが、通常はどの方向に進むべきかを教えてくれます。
- 堅牢なResource Quotasを実装して、ノイジーネイバーが重要なアプリケーションを枯渇させるのを防ぎます。
- Podの再起動回数を定期的に確認して、微妙なOOMやアプリケーションの障害を早期に発見します。
- 生の使用率だけでなく、CPUスロットリングメトリクスを具体的に追跡するPrometheus/Grafanaダッシュボードを活用します。