Kubernetesスケジューラの内部(Filter/Score二段階)
PodがどのNodeに載るかは運任せではなく、二段階の決定論で決まる。FilterとScoreの仕組み、preemption、アフィニティとTaint・Tolerationの効き方を原理から押さえ、配置の挙動を予測・制御できるようになります。
- 1.スケジューラは1Podずつ、Filter(実行不能なNodeを除外)→Score(残ったNodeを0〜100点で採点)→最高得点Nodeへバインドという二段階で配置を決める。両段階ともプラグインの集合として実装される。
- 2.Filterで全Nodeが落ちると保留中Podはpreemptionを試み、自分が入るために優先度の低いPodを退去させる。Score段階では複数プラグインの重み付き合計で最良Nodeを選ぶ。
- 3.Node/PodアフィニティはScore(ソフト)かFilter(ハード)に作用し、TaintはNodeが課す排他条件、TolerationはPodが持つ通行許可。両者が揃ってはじめてそのNodeへ配置可能になる。
Podを作っても、それが「どのNodeで動くか」を決めるのは別のコンポーネント——kube-schedulerです。配置は乱数ではなく二段階の決定論的アルゴリズムで決まります。仕組みを知れば、なぜこのPodがこのNodeに載ったのかを説明でき、アフィニティやTaintで配置を意図通りに操れます。コンテナとVMの実行モデルの違い(/devops/container-vs-vm/)を前提に、その上の「配置層」を原理から解きます。
スケジューラの仕事:未バインドPodを1つずつ処理する
kube-schedulerは制御ループです。nodeName がまだ空の(未バインドの)Podをキューから1つ取り出し、最適なNodeを1つ選び、API Serverに Binding オブジェクトを書き込む——これを繰り返します。重要な性質が2つあります。
- 1Podずつ処理する:スケジューリングは同時並行のクラスタ全体最適化ではなく、Pod単位の逐次決定です。グローバル最適は保証されず、到着順に局所最適を積み上げます。
- 配置を決めるだけで起動はしない:スケジューラはNodeを選んでバインドするのみ。実際にコンテナを起動するのは各Nodeのkubeletです。役割の分離がスケジューラを軽量に保ちます。
[キュー] 未バインドPod
│ 1つ取り出す
▼
[Scheduling Cycle] Filter → Score → 最高得点Nodeを選択
│ Binding を API Server に書き込み
▼
[Binding Cycle] そのNodeの kubelet がコンテナを起動
第1段階 Filter:実行不能なNodeを落とす
Filter(旧称 Predicates)は、各Nodeに対して「このPodをそもそも置けるか」を真偽で判定します。条件を1つでも満たさないNodeは候補から除外され、残ったNodeをfeasible(実行可能)Nodeと呼びます。代表的なフィルタは次の通りです。
| フィルタ | 落とす条件 | 見ているもの |
|---|---|---|
| NodeResourcesFit | PodのCPU/メモリ要求(requests)に空きが足りない | Nodeの割り当て可能リソース残量 |
| NodeAffinity | Podが要求するNodeラベル条件に合致しない | required なNodeアフィニティ |
| TaintToleration | NodeのTaint(NoSchedule)をPodが許容しない | Taint と Toleration の対応 |
| NodePorts | PodのhostPortが既に使用中 | Nodeのポート占有状況 |
| VolumeBinding | 要求PVをそのNodeで結び付けられない | ストレージのトポロジ制約 |
Filterは早期除外です。判定は真偽なので順序は結果に影響せず、1つでも不合格なら即座にそのNodeを捨てます。計算量を抑えるため、巨大クラスタではfeasibleなNodeが十分集まった時点で残りのNodeの評価を打ち切る(全Nodeを見ない)最適化も入ります。
Filterが見るのは requests(要求)であって limits(上限)ではありません。スケジューラは「このPodが最低限確保したい量」を予約してNodeに詰めます。limits は起動後にkubeletとcgroupが使用量を抑え込むための値で、配置決定には関与しません。だから requests を低く盛れば過密配置になり、実行時にCPUスロットリングやOOMが起きます。配置の健全性は requests の正確さに支配されます。
第2段階 Score:残ったNodeを採点して最良を選ぶ
Filterを通過したNode群に対し、Score(旧称 Priorities)が各Nodeを0〜100点で採点します。複数のScoreプラグインがそれぞれ採点し、プラグインごとの重み付き合計が最終スコアになります。最高点のNodeが選ばれ、同点なら確率的に分散させます。
最終スコア(Node) = Σ weight_i × score_i(Node)
i
例:
NodeResourcesBalancedAllocation … CPU/メモリの使用率を均す
ImageLocality … 必要イメージを既に持つNodeを優遇
InterPodAffinity … 相性の良いPodの近く/遠くを評価
TopologySpreadConstraints … ゾーン/Node間で均等に散らす
ここがソフトな最適化の場です。Filterが「置けるか」の二択だったのに対し、Scoreは「置けるNodeの中でどこが一番良いか」を連続値で評価します。たとえば NodeResourcesBalancedAllocation はCPUとメモリの使用率が偏らないNodeを高く評価し、リソースの断片化を防ぎます。ImageLocality は必要なコンテナイメージを既に保持するNodeを優遇し、起動時のpull時間を縮めます。
Filterで候補集合を絞り切ってからScoreで採点するのは、計算の節約だけでなく意味の分離でもあります。Filterは「制約充足(satisfiability)」、Scoreは「目的関数の最大化」。前者で実行可能性を保証し、後者で品質を上げる。この分業により、ハードな制約(リソース不足やTaint)を品質スコアと混ぜて誤魔化すことがなくなり、配置の正しさが担保されます。
preemption:高優先度Podのために場所を空ける
Filterで全Nodeが落ちたら、そのPodは保留(Pending)になります。ただしPodに高い priorityClass が付いていれば、スケジューラは**preemption(プリエンプション)**を試みます。これは「自分が入るために、優先度の低い既存Podを退去(evict)させて場所を作る」操作です。
手順はこうです。スケジューラは各Nodeについて「優先度が自分より低いPodをいくつか取り除けば、自分がFilterを通れるか」を仮想的に評価します。最小の犠牲で済むNodeを選び、退去対象Podに猶予期間付きで削除を発行します。退去させられたPodはキューに戻り、再スケジュールされます(行き場がなければPendingのまま)。
preemptionは「優先度が低いPodなら必ず追い出せる」わけではありません。PodDisruptionBudget(最低稼働数の保証)に抵触する退去はできるだけ避けられ、退去には terminationGracePeriod の猶予が要ります。さらに退去で空けても、別のPodに横取りされて自分が入れない競合も起こり得ます。優先度は「順番待ちの強さ」であって即時の場所確保を保証する仕組みではない、と捉えるのが正確です。
アフィニティ:required はFilter、preferred はScore
Pod/Nodeアフィニティは「どのNodeを好む/避けるか」を宣言する仕組みですが、強度によって作用する段階が変わるのが核心です。
requiredDuringSchedulingIgnoredDuringExecution(ハード):条件を満たさないNodeをFilterで除外する。満たすNodeが無ければPodはPendingになる。preferredDuringSchedulingIgnoredDuringExecution(ソフト):満たすNodeをScoreで加点する。満たせなくても配置はされ、単に優先度が下がるだけ。
たとえばPod間アフィニティで「キャッシュPodと同じNodeに載りたい」をハードで書くと、相手がいないNodeは候補から消えます。ソフトで書けば「いれば近くに、いなくても他に置く」になります。Anti-affinity(反アフィニティ)も同様で、ハードなら「同じNodeに同種Podがいたら除外」、ソフトなら「いたら減点」です。可用性のためにレプリカを別Nodeへ散らす設計は、このanti-affinityか後述のTopologySpreadで表現します。配置を絞りすぎてPendingを生まないよう、強制が必要な制約だけをハードにするのが定石です。
Taint・Toleration:Node側の排他とPod側の通行許可
アフィニティがPod側からNodeを引き寄せる仕組みなのに対し、Taint・TolerationはNode側からPodを弾く仕組みで、向きが逆です。
- Taint:Nodeに付ける「印」。
key=value:effectの形を取り、effectがNoScheduleなら、対応するTolerationを持たないPodはそのNodeに配置されない(Filterで除外)。NoExecuteなら既に動いているPodも退去させる。 - Toleration:Podに付ける「通行許可」。NodeのTaintに合致するTolerationを持つPodだけが、そのNodeに配置され得る。
Node: taint = gpu=true:NoSchedule ← GPUノードを汎用Podから守る
Pod-A: toleration なし → Filterで除外(載れない)
Pod-B: toleration { gpu=true } → Filterを通過(載れる候補になる)
ここで誤解しやすい点があります。Tolerationは「載る権利」を保証せず、「Taintで弾かれない」だけです。Pod-BがそのNodeに載るには、Tolerationに加えてリソースなど他のFilterもすべて通る必要があります。逆にTolerationを持っていても、他のNodeの方がScoreが高ければそちらに載ります。GPUノードに確実に呼び込みたければ、Taint(汎用Podを排除)と Nodeアフィニティ(GPUノードを要求)を両方併用するのが正攻法です。
(1)向き:アフィニティはPodがNodeを選び、TaintはNodeがPodを拒む。(2)既定の効果:アフィニティを書かないPodはどこにでも載れるが、Taintの付いたNodeはToleration無きPodを全員拒む(排他がデフォルト)。(3)段階:ハードアフィニティと NoSchedule のTaintはFilter、ソフトアフィニティはScoreに効く。この3点を押さえれば配置の挙動はほぼ説明できます。
まとめ
- kube-schedulerは未バインドPodを1つずつ処理し、Filter(実行不能Nodeの除外)→ Score(残ったNodeを0〜100点で採点)→ 最高得点Nodeへバインドという二段階で配置を決める。両段階ともプラグインの集合。
- Filterが見るのは
limitsではなくrequests。Scoreは複数プラグインの重み付き合計で最良Nodeを選ぶソフト最適化。制約充足と目的関数最大化を分業させる設計。 - preemptionは、全Nodeが落ちた高優先度Podが優先度の低いPodを退去させて場所を空ける機構。ただし
PodDisruptionBudgetや猶予期間に縛られ、即時確保は保証されない。 - アフィニティは required ならFilter/preferred ならScoreに作用し、TaintはNode側の排他・TolerationはPod側の通行許可で向きが逆。GPUノードの確保のように、確実な誘導には両者の併用が要る。配置を予測したいなら、まず「どの制約がFilterで、どれがScoreか」を仕分けるのが出発点です。サービス間の依存配置やゾーン分散の話は、より広い分散システムの設計(/devops/service-mesh/)や輻輳・背圧の議論(/devops/congestion-collapse-backpressure/)にも繋がります。
DevOps/インフラ Article
Kubernetesスケジューラの内部(Filter/Score二段階)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Kubernetes
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 5
導入後に効く点
Filterで全Nodeが落ちると保留中Podはpreemptionを試み、自分が入るために優先度の低いPodを退去させる。Score段階では複数プラグインの重み付き合計で最良Nodeを選ぶ。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「Kubernetes / スケジューラ」に近いか確認する。
- 強みである「スケジューラは1Podずつ、Filter(実行不能なNodeを除外)→Score(残ったNodeを0〜100点で採点)→最高得点Nodeへバインドという二段階で配置を決める。両段階ともプラグインの集合として実装される。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。