問題点:Podが「保留状態(Limbo)」で動かなくなったとき
デプロイを実行したり、サービスをスケーリングしたりした直後に、新しいPodが起動しないことがあります。それどころか、いつまでも Pending 状態のままになってしまいます。describe コマンドでログを詳しく調べると、スケジューラからの苛立たしいメッセージが表示されることがあります:
$ kubectl describe pod my-app-7f45b6d-abcde
...
Events:
Type Warning
Reason FailedScheduling
Message 0/3 nodes are available: 1 Insufficient cpu, 2 Insufficient memory. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.
これは、Kubernetesのスケジューラがクラスター内のすべてのノードを検査した結果、空きスペースがまったくないことを伝えています。優先度の低いPodを追い出してスペースを作る「プリエンプション(preemption)」も試みましたが、それでも十分な空きを確保できませんでした。本質的に、クラスターが「論理上の」容量限界に達している状態です。
ロジック:リクエスト(Requests)と実態
ここに落とし穴があります。Kubernetesのスケジューラは、リアルタイムの使用量ではなく、**リクエスト(Requests)**に基づいて判断を下します。Grafanaのダッシュボードを見て、ノードのCPU使用率が15%でアイドル状態にあるように見えても、スケジューラは依然として「Insufficient cpu(CPU不足)」と報告することがあります。
Podを定義するとき、resources.requests を指定します。スケジューラは、ノード上ですでに動作しているすべてのPodのリクエストを合算します。残りの「割り当て可能(Allocatable)」な容量が新しいPodのリクエストよりも小さい場合、そのノードは不適格とみなされます。例えば、ノードのCPUが8000mで、既存のPodがすでに7500mをリクエストしている場合、実際のCPU使用率がほぼゼロであっても、1000mをリクエストする新しいPodのスケジューリングは失敗します。
エラーメッセージを分解すると以下のようになります:
- 1 Insufficient cpu: 1つのノードで、CPU容量のほとんどが他のPodによって予約されています。
- 2 Insufficient memory: 2つのノードで、Podの最小要件を満たすための未予約のRAMが不足していました。
- No preemption victims found: Podの
PriorityClassが、既存のワークロードを追い出すほど高くありません。
ステップ1:クラスター容量の診断
設定を変更する前に、ノードが自身の状況をどう認識しているか確認しましょう。次のコマンドを実行して、割り当て済みのリソースと総容量を比較します:
kubectl describe nodes | grep -A 7 "Allocated resources"
Requests 列に注目してください。パーセンテージが95%や100%に近い場合は、ボトルネックに達しています。kubectl top nodes はリアルタイムのメトリクスを表示しますが、スケジューラはそれらの数値を無視し、予約されたリクエスト値を優先します。
クイックフィックス:リソースリクエストの適正化
多くのチームが「念のため」と高いリクエスト値を設定しますが、これが大規模なリソースの断片化を招きます。Podの起動に実際には1コア丸ごと必要ない場合、リクエストを下げることでスケジューリングの問題を即座に解決できることがあります。
以下のような一般的なデプロイメントのスニペットを考えてみましょう:
resources:
requests:
cpu: "1000m" # 1 フルコア - 起動時に本当にこれが必要ですか?
memory: "2Gi"
limits:
cpu: "2000m"
memory: "4Gi"
limits は高く保ったまま、requests を減らしてみてください。これにより、スケジューラに柔軟性が生まれ、必要に応じてPodがリソースをバーストできるようになります:
resources:
requests:
cpu: "250m"
memory: "512Mi"
恒久的な対策A:Cluster Autoscalerの有効化
リクエストがすでに正確で、ノードが本当に満杯である場合は、より多くのハードウェアが必要です。EKS、GKE、AKSなどのマネージドサービスを使用している場合は、Cluster Autoscalerを有効にする必要があります。
リソースの制約によりPodが Unschedulable(スケジューリング不能)であることをオートスケーラーが検知すると、新しいノードのプロビジョニングを開始します。新しいインスタンスがクラスターに参加すると、スケジューラは保留中のPodを自動的にそこに配置します。これには、クラウドプロバイダーにもよりますが、通常2〜5分かかります。
恒久的な対策B:優先度(Priority)とプリエンプションの利用
本番環境のAPIが、優先度の低いバックグラウンドジョブによってブロックされていませんか?PriorityClasses を使用して、どのPodが最も重要かをKubernetesに伝えることができます。これにより、スケジューラは重要なPodのためにスペースを空けるべく、重要度の低いPodを退去させることができます。
- PriorityClassを定義する:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: critical-api
value: 1000000
globalDefault: false
description: "コアとなる本番サービスに使用します。"
- Podのスペックに適用する:
spec:
priorityClassName: critical-api
containers:
...
Node SelectorとTaintの確認
問題は総容量ではなく、制約が厳しすぎることにある場合もあります。nodeSelector、affinity、tolerations を使用している場合、スケジューラはノードのごく一部しか見ていない可能性があります。例えば、Podを instance-type: m5.large に固定しており、その特定のノードが満杯である場合、空の t3.medium ノードが10個あってもPodは保留状態のままになります。
検証:修正の確認
クラスターをスケーリングしたりリクエストを減らしたりした後は、Podのイベントを監視しましょう。Scheduled イベントが成功しているか確認します:
$ kubectl get events --watch
...
Normal Scheduled 10s default-scheduler Successfully assigned default/my-app-abcde to ip-10-0-1-52.ec2.internal
最後に1つ注意点があります。Podが起動してもすぐに OOMKilled ステータスでクラッシュする場合、メモリのリクエストを削りすぎた可能性があります。Podがようやく Running 状態に遷移した後も、数分間はその挙動を監視するようにしてください。

