kubernetes

Kubernetes: スケジューラの動作

Kubernetes で Pod をノードに割り当てるスケジューラ kube-scheduler の動作についてまとめてみました。動作の検証やソースコードの参照は kubernetes v1.9.2 のものを使っています1

スケジューラのお仕事

Kubernetes のスケジューラの仕事は、新規に Pod が作成されたときに最適なノードを選択して割り当てることです。スケジューラで重要なのは最適なノードをどう選択をするかで、様々なフィルタと優先度付けが実装されています。

スケジューラは kube-scheduler という独立したバイナリになっています。Policy ファイルにより柔軟なカスタマイズが可能で、フィルタや優先度付けの選択や、優先度の重み付け、WebHook による拡張などが可能です。

大雑把な動作は以下のようになります。

  • 新規 Pod が作成される
    • スケジューラは API サーバー を監視(watch) してこのイベントを検知します
  • スケジューラが最適なノードを選択してノードに割り当てる
    • Pod の .spec.NodeName にノード名が設定されます
  • ノード上の kubelet が自ノードへ割り当てられた Pod を検知して作成します

処理の流れ

スケジューラは起動後にリーダー選出(--leader-elect を指定した場合) や Pod の監視 (非同期) などの準備を行った後、Scheduler.scheduleOne() というメソッドをメインループとして呼び出します。

ソースコード: main() -> Options.Run()-> SchedulerServer.Run() -> Scheduler.Run() -> Scheduler.scheduleOne()

scheduler.go
// Scheduler.Run() のメインループの設定
func (sched *Scheduler) Run() {
    // ... 省略
    // StopEverything チャネルが閉じられるまで shced.ScheduleOne を繰り返す
    go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)

Scheduler. scheduleOne() では下記のように一つずつ未割り当ての Pod をキューから取り出して最適なノードに対して紐付けを行っていきます。割り当てる Pod がない場合はキューから取り出す NextPod() でブロックされます。Pod のキューへの追加は InformerFramework を利用して Pod の変更を検知し非同期に行われます(参考: podInformer の イベントハンドラ)。

**

最適なノードの選択

Pod に対しての最適なノードの選択は、下記の図のように predicates と呼ばれる条件によるノードのフィルタと、 priorities という優先度付けによって行われます。Policy ファイルにより柔軟にカスタマイズが可能です。

  • predicates: ノードをフィルタする条件。例えば nodeSelector によるノードの選択やrequest に対するリソースの空き状況によるフィルタがある
  • priorities: ノードの優先度付け。例えば Pod を出来る限りノード、ゾーンを分散させるような優先度付けがある。

なお、Pod / Node Affinity のように、必須 (requiredDuringSchedulingIgnoredDuringExecution)、
と優先(preferredDuringSchedulingIgnoredDuringExecution)の両方があるものは、predicates, priorities の両方で実装されています。

ソースコード: Scheduler.scheduleOne() -> Scheduler.schedule() -> genericScheduler.Schedule()

Predicates (ノードのフィルタ)

Predicates はノードをフィルタする条件です。各 Predicate の処理はそのノードが条件を満たすかを bool で返す、下記の FitPredicate インタフェースを実装した関数となります。高速化のため predicate のフィルタはノードごとに並列に評価されます。

// FitPredicate is a function that indicates if a pod fits into an existing node.
// The failure information is given by the error.
type FitPredicate func(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []PredicateFailureReason, error)
Predicates 説明
PodFitsResources ノードの空きリソースがPod の要求するリソースの request 値に合うか。参考: How Pods with resource requests are scheduled
PodFitsHostPorts Pod が hostPort を利用する場合に、ノードのポートが空いているか
HostName PodSpec の nodeName の指定が合った場合、ノードのホスト名と一致するか
MatchNodeSelector nodeSelector で指定したラベルとノードが合うか
NoDiskConflict Pod がマウントするディスクが既に他でマウントされていないかチェックする
NoVolumeZoneConflict Pod が要求するボリュームが同じゾーン内にあるか
MatchInterPodAffinityPred Pod 間の affinity に合っているかチェックする
CheckNodeMemoryPressure メモリの空き容量が逼迫している場合そのノードを避ける (NodeCondition の MemoryPressure を見ている)
CheckNodeDiskPressure ディスクの空き容量が逼迫している場合そのノードを避ける (NodeCondition の DiskPressure を見ている)
MaxGCEPDVolumeCount, MaxAzureDiskVolumeCount, MaxEBSVolumeCount 各 Persistent Volume のアタッチ数制限のチェック

Priorities (ノードの優先度付け)

Priorities はノードの優先度を付ける処理です。各 Priorities の処理はノードに対して 0 ~ 10 の優先度を設定します。各 Priorities には重み付けが設定でき、全ての Priorities の処理が終わると下記のように Node のスコアが計算されます。

NodeA のスコア = (prioritiesA のスコア * 重み付け) + (prioritiesB のスコア * 重み付け) ....

Priorities も高速化のためノードごとに並列に評価されます。Predicates と違って、ノードごとに独立して計算できないものもあるため、下記のように Map / Reduce の2つの関数で構成されています。(参考: Change interface of priority functions for easier "framework-based" parallelization #24246)

// PriorityMapFunction is a function that computes per-node results for a given node.
// TODO: Figure out the exact API of this method.
// TODO: Change interface{} to a specific type.
type PriorityMapFunction func(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error)

// PriorityReduceFunction is a function that aggregated per-node results and computes
// final scores for all nodes.
// TODO: Figure out the exact API of this method.
// TODO: Change interface{} to a specific type.
type PriorityReduceFunction func(pod *v1.Pod, meta interface{}, nodeNameToInfo map[string]*schedulercache.NodeInfo, result schedulerapi.HostPriorityList) error

重み付けのデフォルトは NodePreferAvoidPodsPriority を除いて全て 1 です。

Priority 説明
SelectorSpreadPriority 同じ種類の Pod が出来る限りノードやゾーンに対して分散するようにする
InterPodAffinityPriority Pod 間の affinity に合っているかチェックする
LeastRequestedPriority リソースの request 値の合計が少ないノードを優先する
BalancedResourceAllocation CPU とメモリの使用率のバランスを取る
NodePreferAvoidPodsPriority preferAvoidPods という Node のアノテーションの考慮。他に優先されるよう、これだけ優先度が 10000 に設定されています。
NodeAffinityPriority Node Affinity による優先度
TaintTolerationPriority TaintPreferNoSchedule の考慮

まとめ

Kubernetes のスケジューラは、様々なフィルタ (predicates) と優先度 (priorities) を使って Pod を最適なノードに割り当てます。ノードやゾーンに対して Pod を分散させる SelectorSpreadPriority のように、耐障害性について重要な優先度もあるので興味があればスケジューラのソースコードを覗いてみてください。

参考


  1. 記載時点の直近にソースコードの移動 plugin/pkg/scheduler -> pkg/scheduler があったので、将来のバージョンではディレクトリが異なっていると思われます。