Kubernetesスケジューリングエラーの解説:解決策とベストプラクティス

Kubernetesスケジューリングをマスターしよう!このガイドでは、Podが「Pending」状態で停止する理由を解明します。`kubectl describe`を使用したエラー診断、CPU/メモリ不足に関連する問題の解決、Node Affinity制限の克服、TaintsとTolerationsの適切な活用による堅牢なワークロード配置について学びます。

Kubernetesスケジューリングエラーの解説:解決策とベストプラクティス

Kubernetesのスケジューリングエラーは、通常、PodがPending状態で停止することで現れます。このステータスは曖昧に感じられるかもしれませんが、特定の意味があります。KubernetesはPodオブジェクトを受け入れましたが、スケジューラーがPodの要件を満たすノードを見つけられなかったということです。コンテナはクラッシュしていません。アプリは起動していません。多くの場合、イメージすらまだプルされていません。

これらの問題を解決する最速の方法は、Podが要求するものとクラスターが提供できるものを比較することです。CPUとメモリの要求、ノードラベル、アフィニティルール、テイント、トレラレーション、永続ボリューム、トポロジースプレッドルール、名前空間クォータが配置をブロックする可能性があります。スケジューラーは必須の制約に厳格です。1つの必須ルールがすべてのノードを除外すると、Podは待機します。

Pending状態のPodの診断:最初のステップ

修正を試みる前に、スケジューラーが失敗している理由を正確に診断する必要があります。この調査の主要なツールはkubectl describe podです。

PodがPendingで停止している場合、describe出力のEventsセクションには、スケジューリングの決定プロセスと拒否理由に関する重要な情報が含まれています。

kubectl describe podの使用

常に問題のあるPodを対象にします:

kubectl describe pod <pod-name> -n <namespace>

出力を調べ、特に下部のEventsセクションに注目します。ここでのメッセージは、通常、スケジューリングを妨げた制約を示します。一般的なメッセージは、Insufficient cpuInsufficient memory、ノードセレクタの不一致、許容されていないテイント、またはボリュームのバインドに関するものです。

一般的なスケジューリングエラーのカテゴリと解決策

スケジューリングの失敗は、主に3つのカテゴリに分類されます:リソース制約、ポリシー制約(アフィニティ/アンチアフィニティ)、およびノード構成(テイント/トレラレーション)です。

1. リソース制約(リソース不足)

これは最も頻繁な原因です。スケジューラーは、Pod仕様で定義された要求を満たすことができるノードを必要とします。十分な割り当て可能なCPUまたはメモリを持つノードがない場合、PodはPendingのままになります。

問題の特定

Eventsセクションには、次のようなメッセージが表示されます:

  • 0/3 nodes are available: 3 Insufficient cpu.
  • 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match node selector.

これらのメッセージは組み合わされることがあります。最初のフレーズだけで止めないでください。3つのノードが3つの異なる理由で失敗した場合、1つの理由だけを修正してもPodはPendingのままになる可能性があります。

リソース不足の解決策

  1. Podの要求を減らす: Podの要求が過剰に高い場合は、PodまたはDeploymentのYAMLでCPUまたはメモリのrequestsを下げてみてください。
  2. クラスター容量を増やす: Kubernetesクラスターにノードを追加します。
  3. 既存のワークロードをクリーンアップする: 重要でないワークロードをスケールダウンし、放棄されたジョブを削除し、既存のデプロイメントの過剰な要求を調整します。ノードメンテナンスにはkubectl drainを使用し、カジュアルなクリーンアップコマンドとしては使用しないでください。
  4. Limit Rangeを使用する: 名前空間に定義されたリソース制限がない場合は、LimitRangeオブジェクトを実装して、単一のPodがリソースを占有するのを防ぎます。

2. ノードセレクタとアフィニティ/アンチアフィニティルール

Kubernetesでは、nodeSelectornodeAffinitypodAffinity/podAntiAffinityを使用して、Podを配置できる場所または配置する必要がある場所を細かく制御できます。

ノードセレクタの不一致

利用可能なノードに存在するラベルと一致しないnodeSelectorを定義すると、Podはスケジュールできません。

YAMLスニペットの例(失敗原因):

spec:
  nodeSelector:
    disktype: ssd-fast
  containers: [...] # Podは、disktype=ssd-fastのノードがない場合、Pendingのまま

解決策: nodeSelectorで指定されたラベルが少なくとも1つのノードに存在することを確認し(kubectl get nodes --show-labels)、大文字と小文字が完全に一致していることを確認します。

クラスターに多くのラベルがある場合は、対象を絞ったラベルチェックを使用します:

kubectl get nodes -L disktype,topology.kubernetes.io/zone
kubectl describe node <node-name>

よくある間違いは、以前のノードグループには存在したが、置き換え後のノードグループには存在しないラベルを使用することです。クラスターのアップグレードやオートスケーリンググループの移行後、古い配置ルールが暗黙的に不可能になることがあります。

ノードアフィニティ制約

nodeAffinityは、より柔軟なルール(例:requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution)を提供します。requiredルールが満たされない場合、PodはPendingのままになります。

診断のヒント: 複雑なアフィニティルールを使用する場合、Eventsセクションには多くの場合、node(s) didn't match node selector.と表示されます。

Podアフィニティとアンチアフィニティ

これらのルールは、他のPodに対する相対的な配置を制御します。例えば、アンチアフィニティルールが、特定のサービスをホストするノードでPodを実行しないことを要求しているが、すべてのノードがすでにそのサービスをホストしている場合、スケジューリングは失敗します。

解決策: アフィニティルールのトポロジーキーとセレクタを注意深く確認します。アンチアフィニティルールが制限しすぎている場合は、要件を緩和するか、ルールで選択された対象Podが実際に回避したいノードで実行されていることを確認します。

ルールがハードな要件ではなく優先事項を表す場合は、preferredDuringSchedulingIgnoredDuringExecutionを優先します。必須のアンチアフィニティは、重要なサービスのレプリカを分散させるのに役立ちますが、小規模なクラスターではデプロイメントをブロックする可能性があります。例えば、厳格なゾーンごとに1つのアンチアフィニティを持つ3つのレプリカは、使用可能なゾーンが2つしかないクラスターでは正常にスケジュールできません。

3. テイントとトレラレーション

テイントはノードに直接適用されてPodを排斥し、トレラレーションはPod仕様に追加されて、テイントされたノードへの配置を許可します。

  • テイント: 一致するトレラレーションがない限り、Podを排斥します。
  • トレラレーション: 一致するテイントを持つノードへのPodのスケジューリングを許可します。

テイント拒否の特定

Eventsには、拒否理由が明示的に表示されます:

0/3 nodes are available: 2 node(s) had taint {dedicated: special-workload, effect: NoSchedule}, that the pod didn't tolerate.

テイントとトレラレーションの解決策

主に2つの方法があります:

  1. Podを変更する(アプリケーションPodに推奨): ノードのテイントに一致する必要なtolerationsをPod仕様に追加します。

    トレラレーションの例:

    spec:
      tolerations:
      - key: "dedicated"
        operator: "Equal"
        value: "special-workload"
        effect: "NoSchedule"
      containers: [...] 
    
  2. ノードを変更する(クラスター管理者に推奨): 制限が不要になった場合は、ノードからテイントを削除します。

    # テイントを削除するには
    kubectl taint nodes <node-name> dedicated:special-workload:NoSchedule-
    

ベストプラクティスアラート: 意図的に重要なコントロールプレーンコンポーネントをマスターノードにスケジュールしている場合を除き、アプリケーションPodでグローバルなnode-role.kubernetes.io/master:NoScheduleテイントを許容しないでください。

新しいクラスターでは、コントロールプレーンノードは、古いマスター用語の代わりに、またはそれと併用して、node-role.kubernetes.io/control-planeテイントを一般的に使用します。古いマニフェストからトレラレーションをコピーする前に、実際のテイントを確認します:

kubectl describe node <node-name> | grep -i taints

高度なスケジューリング制約

あまり一般的ではありませんが、重要な制約がスケジューリングをブロックすることもあります:

ストレージボリューム制約

PodがPersistentVolumeClaim(PVC)を要求しているが、それが利用可能なノードにバインドできない場合(例:特定のストレージプロビジョナーの要件やボリュームの利用不可)、PodはPendingのままになる可能性があります。

診断: 最初にPVCのステータスを確認します(kubectl describe pvc <pvc-name>)。PVCがPendingで停止している場合、ボリュームが利用可能になるまでPodのスケジューリングは停止されます。

ストレージは、StorageClassのvolumeBindingMode: WaitForFirstConsumerによって意図的に遅延されることもあります。このモードでは、ボリュームをPodと同じゾーンに作成する必要がある可能性があるため、バインディングはスケジューラーが適切なノードを選択するまで待機します。これは正常ですが、Podとストレージの制約を同時に満たすノードがない場合、PodはPendingのままになります。

DaemonSetとトポロジースプレッド

DaemonSetは、(存在する場合)その選択基準に一致するノードにのみスケジュールされます。クラスターが分割されている場合や、新しいノードがDaemonSetのセレクタに一致しない場合、実行されません。

トポロジースプレッド制約(定義されている場合)は、均等な分散を保証します。現在の分散により、スプレッド制約を尊重しながらどのノードにも配置できない場合、スケジューリングは失敗します。

トポロジースプレッドの失敗は、部分的な障害の後によく発生します。1つのゾーンが利用できず、デプロイメントにゾーン間の厳格なスプレッド制約があるとします。Kubernetesは、スキュールールに違反するため、残りのゾーンに新しいレプリカを配置することを拒否する可能性があります。この動作は分散目標を保護しますが、障害時には容量を回復するために一時的に制約を緩和する必要があるかもしれません。

名前空間クォータとLimitRange

Podは名前空間ポリシーによってブロックされることもあります。ResourceQuotaは名前空間内の集計使用量を制御します。LimitRangeはデフォルト値や最小/最大リソース値を設定できます。

Pod仕様が妥当に見えるが、作成やスケジューリングがまだ失敗する場合に確認します:

kubectl get resourcequota -n <namespace>
kubectl describe resourcequota -n <namespace>
kubectl get limitrange -n <namespace>
kubectl describe limitrange -n <namespace>

クォータの問題は、共有開発クラスターでよく見られます。チームは十分な物理クラスター容量を持っているかもしれませんが、古いプレビュー環境やクリーンアップされなかった完了したジョブによって名前空間クォータが枯渇している可能性があります。

現実的なデバッグ手順

PodがPendingの場合、次の順序で行います:

  1. kubectl describe podを実行し、最新のスケジューリングイベントをコピーします。
  2. kubectl describe nodeを使用して、要求されたCPUとメモリをノードの割り当て可能容量と照合します。
  3. PodがnodeSelector、ノードアフィニティ、またはトポロジーキーを使用している場合は、ノードラベルを確認します。
  4. 候補ノードのテイントとPodのトレラレーションを確認します。
  5. Podが永続ストレージをマウントしている場合は、PVCとStorageClassを確認します。
  6. 名前空間クォータとLimitRangeを確認します。
  7. Cluster Autoscalerが役立つと予想される場合は、そのログまたはイベントを検査します。

この順序が重要なのは、PendingのPodはアプリケーションの実行時問題ではないからです。基盤となる制約が変更されない限り、デプロイメントを再起動してもほとんど役に立ちません。

スケジューリング成功のためのベストプラクティス

スケジューリングの問題を最小限に抑えるために、次の運用ベストプラクティスを採用します:

  1. リソース要求を明示的に定義する: CPUとメモリに対して常に妥当なrequests(およびオプションのlimits)を設定します。これにより、スケジューラーはノード容量を正確に評価できます。
  2. ゾーニングにノードラベルを使用する: 一貫したノードラベリング(例:hardware=gpuzone=us-east-1a)を実装し、nodeSelectorまたはnodeAffinityを使用してワークロードを適切なハードウェアに誘導します。
  3. テイントとトレラレーションを文書化する: メンテナンスやハードウェア分離のためにノードがテイントされている場合は、これらのテイントを中央で文書化します。テイントされたリソースへのアクセスを必要とするアプリケーションマニフェストに対応するトレラレーションが含まれていることを確認します。
  4. Cluster Autoscalerを監視する(使用する場合): スケーリングソリューションに依存している場合は、それらが機能していることを確認します。スケーリングをトリガーするべき容量不足が、静かに失敗し、PodをPendingのままにしている可能性があります。
  5. スケジューラーログを確認する(上級者向け): 詳細な診断のために、kube-schedulerコンポーネント自体のログを確認します。マネージドクラスターでは、アクセスはプロバイダーによって異なる場合があるため、Podイベントとプロバイダー固有のコントロールプレーンログから始めます。

症状ではなく制約を修正する

正しい修正は、制約が偶発的なものか意図的なものかによって異なります。誰かが本番マニフェストを小さなステージングクラスターにコピーしたためにPodが8 CPUを要求している場合は、その環境の要求を減らします。PodがGPUを必要としていてGPUノードが存在しない場合、トレラレーションを追加しても役に立ちません。クラスターには適切なハードウェアが必要です。テイントがデータベースノードを一般的なワークロードから保護している場合、無関係なPodをスケジュールするためだけにテイントを削除しないでください。

本番環境の変更については、その理由をGitで可視化します。ノードラベル、テイント、アフィニティルール、リソース要求は配置契約です。将来の運用者は、ルールがパフォーマンス、コンプライアンス、ハードウェアアクセス、コスト管理、または単なる歴史的な偶然のいずれのために存在するかを知る必要があります。

誤解を招くクイックフィックスの例

いくつかの一般的な修正は、即座にPendingステータスを消しますが、後でより悪い問題を引き起こします。

CPU要求を下げることは、元の要求が過剰であった場合に役立ちますが、無料の容量ツールではありません。アプリケーションがピークトラフィック時にそのCPUを実際に必要とする場合、Podはスケジュールされても負荷がかかるとパフォーマンスが低下する可能性があります。要求を大幅に削減する前に、使用履歴とレイテンシを確認します。

広範なトレラレーションを追加するとPodをスケジュールできますが、別の目的のために予約されたノードに配置される可能性があります。トレラレーションは「このPodはここで許可されている」と言います。「このPodはここを優先すべき」とは言いません。許可と意図の両方が必要な場合は、トレラレーションをノードアフィニティまたはノードセレクタと組み合わせます。

アンチアフィニティルールを削除すると、レプリカを迅速に復元できますが、すべてのレプリカが1つのノードまたは1つのゾーンに配置される可能性があります。これは障害時には許容される場合もありますが、意識的な一時的な変更であるべきであり、静かな恒久的な漂流であってはなりません。

クラスターを拡張することは多くの場合正しい答えですが、PendingのPodが新しいノードを使用できることを確認した後に限ります。Podがオートスケーリングされたノードグループが持たないラベルを必要とする場合、ノードを追加しても、より多くの不適切なノードが得られるだけです。

最終確認

PendingのPodは、Podとクラスターの間のネゴシエーションの失敗です。Podは、特定のノードに配置するために、リソース、ラベル、ストレージ、トポロジー、および許可を要求します。クラスターは、容量、テイント、ラベル、クォータ、および利用可能なボリュームで応答します。kubectl describe podは、そのネゴシエーションがどこで失敗したかを示します。イベントを注意深く読めば、ほとんどの修正は簡単になります。Podの要件を変更するか、クラスターの利用可能な容量を変更するか、もはや現実と一致しないポリシーを修正します。