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

July 05, 2021

この記事は Zenn でも公開されています

https://zenn.dev/sanpo_shiho/articles/22e57dc25a5b2f


この記事は、「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)に入れていきます。

これは、新しい 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

このエントリーをはてなブックマークに追加