⚙️

「Kubernetes v1.22でスケジューラーのevent handlerを動的に設定できるようになるよ」って話をふわっと理解しよう

5 min read

この記事は、「Kubernetesなんとなくわかるくらいの人が、 "スケジューラーのevent handlerを動的に設定できる様にすることの必要性" と "具体的に何ができるか" をふわっと理解する」を目指して書いた記事です。(副次的にスケジューラの動作の一部もふわっと理解できるかもしれません。)

Kubernetes v1.22で動的にスケジューラ内のevent handlerを登録できる様になります。

https://github.com/kubernetes/kubernetes/pull/101394

(この記事で挙げるコードなどはcommit hash 1b3a124ba6049f91175ac2f2b141720af1601ffc時点のものを参照しています。まだ、v1.22はリリースされていないので何か変更が生まれる可能性があります)

順に説明しているので、自分はここまではわかるぞってところまで飛ばしてください。

TL;DR

  • スケジューラでは"自身のキャッシュを更新するため"、"Podの再スケジュールをトリガーするため"にevent handlerを使用している
  • custom resourceなどの変更をトリガーにPodの再スケジュールをトリガーするために、v1.22ではevent handlerを動的に設定できるようになる

そもそもスケジューラって何

公式の説明が最もわかりやすいです。

https://kubernetes.io/ja/docs/concepts/scheduling-eviction/kube-scheduler/

一文で超簡単にいうとKubernetesのcomponentの内の「PodをどのNodeに配置するかを決めてくれるやつ」です。

そもそもevent handlerって何

CNCFのブログがわかりやすいです。

https://www.cncf.io/blog/2019/10/15/extend-kubernetes-via-a-shared-informer/

To stay informed about when these events get triggered you can use a primitive exposed by Kubernetes and the client-go called SharedInformer, inside the cache package.

こちらも一文で超簡単にいうと「リソースの変更をトリガーにあらかじめ登録した関数を実行してくれる仕組み」です。

スケジューラにおけるevent handlerの使用のされ方

スケジューラーではevent handlerを「PodやNodeなどのリソースの変更を監視し、スケジューラに関係するリソースの変更があった場合それに応じた処理を行う」ということをするために使用しています。

例としてNodeに関する部分を見てみましょう

https://github.com/kubernetes/kubernetes/blob/1b3a124ba6049f91175ac2f2b141720af1601ffc/pkg/scheduler/eventhandlers.go#L326-L332
informerFactory.Core().V1().Nodes().Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			AddFunc:    sched.addNodeToCache,
			UpdateFunc: sched.updateNodeInCache,
			DeleteFunc: sched.deleteNodeFromCache,
		},
	)

たとえば新たにNodeを作成した場合、addNodeToCacheという関数が呼ばれることがなんとなくわかると思います。

https://github.com/kubernetes/kubernetes/blob/1b3a124ba6049f91175ac2f2b141720af1601ffc/pkg/scheduler/eventhandlers.go#L63
func (sched *Scheduler) addNodeToCache(obj interface{}) {
	node, ok := obj.(*v1.Node)
	if !ok {
		klog.ErrorS(nil, "Cannot convert to *v1.Node", "obj", obj)
		return
	}

	nodeInfo := sched.SchedulerCache.AddNode(node)
	klog.V(3).InfoS("Add event for node", "node", klog.KObj(node))
	sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(queue.NodeAdd, preCheckForNode(nodeInfo))
}

addNodeToCacheは二つのことをしています。

  1. scheduler cacheに新たなNodeの情報を追加
  2. 全てのunschedulable Oueueに入っているPodをActiveQueueもしくはBackoffQueueに移動

スケジューラでは毎回リソースの情報を取ってきたりするのは面倒なので、cacheを内部的に持っていて、そこでNodeやPodの情報を管理しています。
1ではそのcacheに新しいNodeの情報を追加します。

unschedulable Queueとはその名の通り、unschedulableと判定されたPodの情報が入っているQueueになります。そこからPodを一つずつ取り出し、PodごとにActiveQueue(Backoff timerの待機中の場合はBackoffQueue)に入れていきます。
(追記: 全てのPodが移動させられるわけではないです)

これは、新しいNodeが増えたことで今まで「unschedulable = PodをNodeに配置できない」とされていたPodがscheduleできる可能性があるためです。(unschedulable判定されていたPod達をscheduleし直してみようぜということですね)

スケジューラにおけるpluginって何

今回のdynamic event handlers registrationのことを理解するにはスケジューラのpluginという仕組みを知っておく必要があります。

スケジューラはpluginを自分で実装し、schedulingを柔軟に行うことができる様になっています。この辺りの仕組みはScheduling Frameworkと呼ばれています。

公式の情報は以下になります。

https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/

スケジューリングのいずれかのポイント(複数でも良い)において、動作をするようなプラグインを使用することで、スケジュールの結果に影響を与えることができます。

たとえば、custom resourceの状態によってcustom resourceがAという状態であれば、Node-Aにはスケジューリングしない、Bという状態であれば、Node-Bにはスケジューリングしない。といったことが実現できます。

pluginに関してはスケジューラの動作の本質に近いものになり、詳しく解説すると長くなるのでここまでにしておきます。

スケジューラーのevent handlerを動的に設定できるようになるよ

さて、これが本題です。

https://github.com/kubernetes/kubernetes/pull/101394

dynamic event handlers registrationはそのまま訳すと「動的にイベントハンドラーに登録すること」です。

先程、新しいNodeが増えた際に、今までunschedulableだったPod達がscheduleできる可能性があるため再度scheduleするためにActive Queue(もしくはBackoff Queue)に移動されるということを解説しました。

また、先程、pluginを実装し、custom resourceを含む任意のリソースの状態などによってスケジュールの結果を変更する様なことが実現できるということも解説しました。

ここまで聞いて勘が良い方なら気が付いたかもしれませんね、ここで発生する問題は「custom resourceの状態の変更が行われてもPodを再スケジュールできない」というものです。(正確にはcustom resourceを含むデフォルトでスケジューラーがevent handlerに登録しない種類のリソース全てになります。)

先程用いた以下の例のような動作をするpluginが用いられている場合で解説します

custom resourceの状態によってcustom resourceがAという状態であれば、Node-Aにはスケジューリングしない、Bという状態であれば、Node-Bにはスケジューリングしない。といったことが実現できます。

Node-Aのみがリソースの容量的にPodが入る余地がある(裏を返すと、Node-A以外にはリソース的に新たなPodをスケジュールできない)状況を考えます。

ここで新たにPod-Aをユーザーが作成したとしましょう。この時に、custom resourceの状態がAである場合、Node-Aにスケジューリングできないため、Podはunschedulableになります。

そして、その後custom resourceの状態をBに変更したとします。すると現在の状況だけを見るとNode-AにPod-Aはスケジュールすることができる様になります。しかし、custom resourceの状態の変更にスケジューラ内のevent handlerは気がつくことはできないため、この変更をきっかけにしてPodは再度スケジュールされることはありません。もちろん新たなNodeが追加されるなどのevent handlerが気が付くことができる変更が発生した際には再度スケジュールが走りますが、その際にはまたcustom resourceの状態がAに戻ってしまっている可能性もあります。

そこで、この問題に対処するべく、v1.22では動的にスケジューラー内のevent handlerへの登録を行うことができる様になります。という話でした。

その他

「スケジューラの仕組みなんとなく面白そうやん!!」と思った方は、チェシャ猫さんの以下の記事がnext stepになると思います。

https://ccvanishing.hateblo.jp/entry/2020/12/02/181155

[追記]

この話をもう少し突っ込んで話すとこんな感じです

https://twitter.com/sanpo_shiho/status/1413686472654262273

https://twitter.com/sanpo_shiho/status/1413686473941864450

https://twitter.com/sanpo_shiho/status/1413686475065991169

https://twitter.com/sanpo_shiho/status/1413686476232032257