🍣

QueueingHintについて調べた

2024/06/14に公開

Kubernetesについて初心者ですが少しコードを読む機会があったので、初めて技術的な記事を書いて見ました。
間違い等あったら修正していきます。

QueueingHint概要

初めにKEPが一番詳しいらしいのでそれを読むのが良いかもしれない。具体的なケースなども明記されておりわかりやすい。
QueueingHint (以降QHint) は、Kubernetesスケジューラの効率を向上させるための新しい機能のこと。QHintは、スケジューラの各プラグインを拡張しより詳細な情報を提供することで、キューへの配置を最適化し、必要に応じてバックオフをスキップする。

Kubernetes Scheduler

そもそもKubernetesのスケジューラについての概要を把握する必要がある。QHintの理解には以下のドキュメントや記事が役立ちそう。(理解できたとは言っていない)
プラグインとかの説明とかは自作記事とかを読むと雰囲気わかってよかった。

Schedulerの動作概要

スケジューラーは新しく作成されたPodや、まだNodeに割り当てられていないPodを監視し、最適なNodeに割り当てる役割を担う。kube-schedulerはKubernetesのデフォルトスケジューラーであり、Podのリソース要件(例:必要なCPUやメモリの量)やポリシー制約(例:特定のラベルを持つNodeにのみ配置する、特定のNodeには配置しない)を考慮して適切なNodeを選ぶ。適切なNodeが見つからない場合はPodはスケジュール待ち状態になる。
スケジューリングされるPodはactiveQ, backoffQ, UnschedulableQこの3つのどこかに属することになる。activeQはスケジューリング可能性が高いPodが入るQで、backoffQは一時的にスケジューリングが難しいPodが入るQであり、一定のバックオフ期間を経てから再度スケジューリングを試みる。UnschedulableQはスケジューリングができないPodを一時的に格納するQで、これらのPodはクラスタの状態が変わるまで再スケジューリングされない。

QHint

QHintは、PodをactiveQ, backoffQ, UnschedulableQの間で移動させる際に、各プラグインがより詳細な情報を提供できるように拡張された機能。これにより、Podのキューへの配置を最適化し、必要に応じてバックオフをスキップすることができる。QHintは、QueueingHintFn (以降QHintFn)という関数を通じて、どのキューに移動するべきかを判断する。

コードを読んでいく

QHintはPriorityQueue構造体が持つqueueingHintMapを通じて処理を行う

type PriorityQueue struct {
	...
	// queueingHintMap is keyed with profile name, valued with registered queueing hint functions.
	queueingHintMap QueueingHintMapPerProfile
	...
}

queueingHintMapはスケジューラを作成するNew関数

// New returns a Scheduler
func New(ctx context.Context,
	client clientset.Interface,
	informerFactory informers.SharedInformerFactory,
	dynInformerFactory dynamicinformer.DynamicSharedInformerFactory,
	recorderFactory profile.RecorderFactory,
	opts ...Option) (*Scheduler, error)

の内部の以下の処理で作られたデータを受け渡すことで作成する

	queueingHintsPerProfile := make(internalqueue.QueueingHintMapPerProfile)
	for profileName, profile := range profiles {
		preEnqueuePluginMap[profileName] = profile.PreEnqueuePlugins()
		queueingHintsPerProfile[profileName] = buildQueueingHintMap(profile.EnqueueExtensions())
	}

具体的にQHintはどのようなものが入るかというとこんな感じ。このQHintFnを通じてQの移動を判断する。

// EventsToRegister returns the possible events that may make a Pod
// failed by this plugin schedulable.
func (pl *dynamicResources) EventsToRegister() []framework.ClusterEventWithHint {
	if !pl.enabled {
		return nil
	}

	events := []framework.ClusterEventWithHint{
		// Changes for claim or class parameters creation may make pods
		// schedulable which depend on claims using those parameters.
		{Event: framework.ClusterEvent{Resource: framework.ResourceClaimParameters, ActionType: framework.Add | framework.Update}, QueueingHintFn: pl.isSchedulableAfterClaimParametersChange},
		{Event: framework.ClusterEvent{Resource: framework.ResourceClassParameters, ActionType: framework.Add | framework.Update}, QueueingHintFn: pl.isSchedulableAfterClassParametersChange},

		...
	}
	return events
}

このqueueingHintMapを参照する関数は2箇所ある。
これらの関数がなんのための関数かを見ていきたい。
isPodWorthRequeuing()は実際に登録したQHintFnを利用してどのQに移動するかの判断をする。
isEventOfInterest()に関してはQHintが見るべきイベントかの判断に利用する。
これらの関数は基本的にはPodがQを移動する可能性がある場合に呼ばれてQHintFnを利用してどのように移動するかを決める。

func (p *PriorityQueue) isPodWorthRequeuing(logger klog.Logger, pInfo *framework.QueuedPodInfo, event framework.ClusterEvent, oldObj, newObj interface{}) framework.QueueingHint

func (p *PriorityQueue) isEventOfInterest(logger klog.Logger, event framework.ClusterEvent) bool

この関数を呼び出すmovePodsToActiveOrBackoffQueue() はPodのスケジューリングをリトライしたい時にPodをUnschedulableQからactiveQ・backoffQに移す処理を行う。そしてこの関数の呼び出し元を辿っていくと確かに再スケジューリングしたいタイミングなのでQからPodを動かせるかみたいタイミングでQHintを読んでいることがわかる。

  • flushUnschedulablePodsLeftover(): UnschedulableQに長いこといるPodをactiveQ / backoffQに移動させる
  • AssignedPodAdded(): 新しいPodがNodeにバインドされた時に動作する
  • AssignedPodUpdated(): labelの更新などPodが更新された時に動作する
  • moveAllToActiveOrBackoffQueue():
func (p *PriorityQueue) movePodsToActiveOrBackoffQueue(logger klog.Logger, podInfoList []*framework.QueuedPodInfo, event framework.ClusterEvent, oldObj, newObj interface{}) {

まとめ

QHintは「スケジュールのリトライのタイミング」をプラグインごとに拡張できるようにしたものである。

参考

見つけたQHintを実装してるPRたち

Discussion