Slide 1

Slide 1 text

自作して学ぶKubernetes scheduler入門
 Kensei Nakada @sanposhiho
 1

Slide 2

Slide 2 text

自己紹介
 中田 健誠 / Kyoto Uni (4th year)
 普段はバックエンドエンジニアをしてます
 
 Twitter: さんぽし (@sanpo_shiho)
 GitHub: @sanposhiho
 
 Google Summer of Code 2021 @ Kubernetes
 member of Kubernetes / Kubernetes SIGs


Slide 3

Slide 3 text

もくじ 
 0. Kubernetes schedulerの簡単な概要を知る
 1. 今回の自作に関して
 2. 適当なスケジュールをするSchedulerを作る 🌟
 3. Filter/Score Pluginを実装する 🌟
 4. CycleStateについて知る
 5. Binding Cycle の並列化について知る
 6. Scheduling Cache について知る
 7. Scheduling Queue をざっくり実装する 🌟
 8. Event Handlerについて再度考える 🌟          🌟: 実装する章
 
 3

Slide 4

Slide 4 text

このトークの目指すところ
 内部実装の概要をつかむことを目標とします
 → Schedulerが実際に内部的に行っていることを、Schedulerが自作されていく様子を見 ながら、機能ごとに段階的に学んでいきます


Slide 5

Slide 5 text

Kubernetes schedulerの簡単な概要を知る
 5

Slide 6

Slide 6 text

Kubernetes scheduler
 PodをどのNodeに割り当てるかを決定するコンポーネント
 
 様々なリソースの状況からPodに最適なNodeを決定する
 
 6

Slide 7

Slide 7 text

Scheduling Framework
 公式Doc: 「Scheduling Framework | Kubernetes」より引用
 7

Slide 8

Slide 8 text

Scheduling Framework
 Scheduling Framework (の Scheduling Cycle) のうち、
 ● Filter (+ PreFilter)
 ● Score (+ PreScore + Normalized Score )
 の結果が主にNodeの決定に影響している 
 ※ Extenderについては今回は割愛して説明します。 
 
 8

Slide 9

Slide 9 text

Scheduling Framework
 Filter
 Podを実行できない(したくない)Nodeを候補から除外する
 
 例えば…
 ・リソース不足でPodが実行できないNode
 ・nodeSelectorの条件に一致しないNode
 を除外する
 9

Slide 10

Slide 10 text

Scheduling Framework
 Score
 残った候補のNodeをスコアリングする。
 
 例えば…
 ・全体のNodeのリソースの使用量のバランスがちょうど良くなるNodeを優先
 ・Podを実行するコンテナイメージをすでに持っているNodeを優先
 10

Slide 11

Slide 11 text

11

Slide 12

Slide 12 text

12 ×
 ×
 何かしらの観点でNodeを フィルタリングする


Slide 13

Slide 13 text

13 ×
 ×
 何かしらの観点でNodeを フィルタリングする
 ×
 ×
 ×
 ×
 もしこの段階で候補のNodeが全滅した場合 
 → Preemption(Podの追い出し) を試みる
 → 諦める (Unscheduled) 


Slide 14

Slide 14 text

14 何かしらの観点でNodeを スコアリングする


Slide 15

Slide 15 text

15 Score B プラグインの Weightが1
 → 10
 → 50
 Score A プラグインの Weightが10
 → 300
 → 500


Slide 16

Slide 16 text

16 → 10
 → 50
 Node1 が最終的に選ばれる 
 → 300
 → 500
 350
 510


Slide 17

Slide 17 text

Scheduling Framework
 公式Doc: 「Scheduling Framework | Kubernetes」より引用
 17

Slide 18

Slide 18 text

仕組みざっくりまとめ
 Scheduler は Scheduling Framework の流れに沿って動作
 ● Filter で実行できない/したくないNodeを弾き、
 ● ScoreでNodeをスコアリングし、
 ● 最後にPlugin Weightを加味して、
 ● どのNodeでPodを実行するか決定する
 ● その後Binding CycleでPodをそのNodeにBindする
 18

Slide 19

Slide 19 text

今回の自作Schedulerに関して
 19

Slide 20

Slide 20 text

自作Schedulerに関して
 Kubernetes Schedulerのversion:v1.22.0
 
 リポジトリを公開しています
 → https://github.com/sanposhiho/mini-kube-scheduler/
 この発表の流れに沿ってブランチを切っています


Slide 21

Slide 21 text

自作Schedulerに関して
 kubernetes-sigs/kube-scheduler-simulator の仕組みを流用しており、
 コマンドライン上からSchedulerの振る舞いを確認することができます。
 
 
 


Slide 22

Slide 22 text

自作Schedulerに関して
 scenario という形で試したいシナリオを定義することができます。
 
 シナリオを変更したい場合は、
 /sched.go のscenario関数の中身を変更してください
 
 make start でその時点のSchedulerでシナリオを実行することができます。


Slide 23

Slide 23 text

自作Schedulerに関して


Slide 24

Slide 24 text

自作Schedulerに関して
 基本的には発表を見ていただくだけでなんとなく要点の実装がわかるように解説してい きます。
 
 時間に余裕がある方は、適宜一時停止しながら、
 実際のコードを見たり、変更してみたり、実際に手元で動作させてみたりしてみると理解 が深まると思います。


Slide 25

Slide 25 text

適当なスケジュールをするSchedulerを作る
 25 ブランチ: initial-random-scheduler


Slide 26

Slide 26 text

適当なScheduler
 26 全てのNodeからランダムに選んだNodeに対してPodをBindする


Slide 27

Slide 27 text

やらなければいけない事
 1. ScheduleされていないPodを見つける
 2. そのPodのScheduleを始める
 3. 全てのNodeを取得する
 4. ランダムにNodeを一つ選ぶ
 5. 選んだNodeに対してPodをBindする
 6. 1に戻る


Slide 28

Slide 28 text

ScheduleされていないPodを見つける
 
 
Scheduler は内部のQueueにスケジュールされていないPodを持っている
 ↓
 “ScheduleされていないPodを見つけてQueueに貯める仕組み” が必要


Slide 29

Slide 29 text

ScheduleされていないPodを見つける
 
 
Queue はとりあえず Slice でシンプルに実装
 ● Queue に Pod を追加するメソッドを実装
 ● Queue からPodを取り出すメソッドを実装


Slide 30

Slide 30 text

すごいシンプルなQueue
 /minisched/queue/queue.go 


Slide 31

Slide 31 text

Queue に Pod を追加するメソッド
 Slice にappendしてるだけの関数
 
 
 /minisched/queue/queue.go 


Slide 32

Slide 32 text

Queue の先頭を取り出すメソッド
 /minisched/queue/queue.go 


Slide 33

Slide 33 text

ScheduleされていないPodを見つける
 
 
EventHandler 
 → リソースの変更をトリガーにあらかじめ登録した関数を実行してくれる仕組み
 
 1. EventHandlerを用いて新しいPodの作成を検知する
 2. 新たなPodをQueueに入れる


Slide 34

Slide 34 text

/minisched/eventhandler.go 
 スケジュールされてないPodのみ 
 をFilter
 追加されたときのみ 
 この関数を実行   → 


Slide 35

Slide 35 text

Pod が作られたときに呼ばれるメソッド
 /minisched/eventhandler.go 


Slide 36

Slide 36 text

やらなければいけない事 (再掲)
 1. ScheduleされていないPodを見つける
 2. そのPodのScheduleを始める
 3. 全てのNodeを取得する
 4. ランダムにNodeを一つ選ぶ
 5. 選んだNodeに対してPodをBindする
 6. 1に戻る


Slide 37

Slide 37 text

PodのScheduleを始める
 sched.scheduleOne を実行し続ける
 /minisched/minisched.go 


Slide 38

Slide 38 text

PodのScheduleを始める
 scheduleOne がスケジューラーのメインロジック
 Queue の先頭のPodを取り出す
 /minisched/minisched.go 


Slide 39

Slide 39 text

全てのNodeを取得する
 API からNodeを全て取得
 /minisched/minisched.go 


Slide 40

Slide 40 text

ランダムにNodeを一つ選ぶ
 /minisched/minisched.go 


Slide 41

Slide 41 text

選んだNodeに対してPodをBindする
 
 
 /minisched/minisched.go 


Slide 42

Slide 42 text

選んだNodeに対してPodをBindする
 
 
Bind しているメソッド本体
 /minisched/minisched.go 


Slide 43

Slide 43 text

適当な Scheduler おさらい
 1. EventHandlerを用いて新しいPodの作成を検知する
 2. 新たなPodが作成されたら、それをQueueに入れる


Slide 44

Slide 44 text

適当な Scheduler おさらい
 1. Queue のPodを取り出す
 2. そのPodのScheduleを始める
 3. 全てのNodeを取得する
 4. ランダムにNodeを一つ選ぶ
 5. 選んだNodeに対してPodをBindする
 6. 1に戻る


Slide 45

Slide 45 text

動くか試そう
 node0 ~ node9 を作った後に pod1を作成
 ↓動いた 👌 


Slide 46

Slide 46 text

Filter/Score Pluginを作る
 46 ブランチ: filter-plugin, score-plugin


Slide 47

Slide 47 text

Scheduling Framework
 公式Doc: 「Scheduling Framework | Kubernetes」より引用
 47

Slide 48

Slide 48 text

適当な Scheduler += Filter/Score Plugin
 1. Queue のPodを取り出す
 2. そのPodのScheduleを始める
 3. 全てのNodeを取得する
 4. Filter Plugin で Node の候補を絞る
 5. Score Plugin で 残った候補をスコアリングする
 6. 残った候補の中からスコアが高いNodeを選ぶ
 7. 選んだNodeに対してPodをBindする
 8. 1に戻る


Slide 49

Slide 49 text

例として使用するFilter Plugin
 NodeUnschedulable Plugin
 Nodeの.Spec.Unschedulable を見て、trueのNodeを候補から外す
 (実際にSchedulerに標準で搭載されているPluginです)


Slide 50

Slide 50 text

Filter Pluginの実行
 RunFilterPlugins というメソッドでFilter Pluginを実行。
 エラーが返った場合はスケジュールをその時点で諦める
 /minisched/minisched.go 


Slide 51

Slide 51 text

/minisched/minisched.go 


Slide 52

Slide 52 text

ループでFilter PluginsをNode一つ一 つに対して実行
 全てのFilter PluginをpassしたNodeの みがfeasibleNodeとして 
 返される
 /minisched/minisched.go 


Slide 53

Slide 53 text

Filter Pluginの実行
 /minisched/minisched.go 
 ひとつも候補がない場合はerrorを返 す


Slide 54

Slide 54 text

動くか試そう
 Unschedulable: true のNodeを10個(node0 ~ node9)作成


Slide 55

Slide 55 text

動くか試そう
 Unschedulableの指定がないNodeを1個(node10)作成


Slide 56

Slide 56 text

動くか試そう
 ↓動いた 👌 
 Unschedulableの指定がないnode10にbindされている


Slide 57

Slide 57 text

例として使用するScore Plugin
 NodeNumber Plugin
 Nodeの名前の最後の数字がPodの名前の最後の数字と一致するNodeに高い点数をつ ける
 
 例: pod1と言うPodのスケジュール時にはnode1に高いスコアをつける


Slide 58

Slide 58 text

NodeNumber Pluginを実装する
 interfaceを満たす構造体を作成することでPluginを実装できる
 
 
 
 https://github.com/kubernetes/kubernetes/blob/master/pkg/scheduler/framework/interface.go#L413 


Slide 59

Slide 59 text

/minisched/plugins/score/nodenumber/nodenumber.go 
 Pod の名前の最後の数字を取得 
 例: pod1 → 1
 Node の名前の最後の数字を取得 
 例: node1 → 1
 一致していた場合10点を返す 


Slide 60

Slide 60 text

Score Pluginの実行
 
 RunScorePlugins というメソッドで実行
 /minisched/minisched.go 


Slide 61

Slide 61 text

Score Pluginの実行
 
 /minisched/minisched.go 
 ループでScore PluginsをNode一つ一つ に対して実行
 別の形に加工して返却 


Slide 62

Slide 62 text

動くか試そう
 node0 ~ node9 を作った後に pod1 と pod3 を作成
 (Nodeを作る際、先程のspec.Unschedulableの設定は消してます) 
 ↓動いた 👌 (pod1はnode1へ、pod3はnode3へ)
 


Slide 63

Slide 63 text

動くか試そう
 ↓score pluginの実行後のスコアをlogに出してみると…
 pod3のスケジュール時はちゃんとnode3が一番スコアが高くなっている


Slide 64

Slide 64 text

適当な Scheduler += Filter/Score Plugin
 1. Queue のPodを取り出す
 2. そのPodのScheduleを始める
 3. 全てのNodeを取得する
 4. Filter Plugin で Node の候補を絞る
 5. Score Plugin で 残った候補をスコアリングする
 6. (残った候補の中から) ランダムにNodeを一つ選ぶ
 7. 選んだNodeに対してPodをBindする
 8. 1に戻る


Slide 65

Slide 65 text

CycleStateについて知る
 65

Slide 66

Slide 66 text

CycleStateについて
 一つの Pod の スケジュール でのデータを保持している。
 → Podのスケジュール開始時に毎回新しく作成される
 
 Plugin(等)はCycleStateにデータを読み書きすることができる。
 
 


Slide 67

Slide 67 text

PreFilter/PreScore Pluginについて
 Filter/Score pluginの実行の準備をできる拡張点
 
 
 PreFilter / PreScore において事前計算を行い、CycleStateに結果を保存。Filter / Score ではその結果を使用し、実行パフォーマンスをあげる
 


Slide 68

Slide 68 text

先程のScorePluginのinterface
 interfaceを満たす構造体を作成することでPluginを実装できる
 
 
 
 https://github.com/kubernetes/kubernetes/blob/master/pkg/scheduler/framework/interface.go#L413 


Slide 69

Slide 69 text

CycleState を使ってみる
 NodeNumber pluginにPreScoreを実装してCycleStateを使ってみる実装があります
 → ブランチ: prescore-plugin


Slide 70

Slide 70 text

Binding Cycle の並列化について知る
 70

Slide 71

Slide 71 text

Scheduling Framework (おさらい)
 71 ## Scheduling Cycle
 PodをどのNodeで実行するかを決定する
 ## Binding Cycle
 Scheduling Cycle での決定を実際にクラスターに適応する


Slide 72

Slide 72 text

Scheduling Cycle
 
 
 72 他のPodのスケジュール結果が確定していないと正しくスケジュールできない
 → 並列に実行できず、Podを一つ一つスケジュールしていくしかない
 
 
 並列に実行するとダメな例: 
 2つのPodが同時にスケジュールされ、”1つのPodしかリソース的に実行できないNode” に2つのPodがBindされる
 


Slide 73

Slide 73 text

Binding Cycle
 
 
 73 スケジュールの結果は確定しているのであとはBindするだけ
 → 並列に実行できる!!効率化!!


Slide 74

Slide 74 text

Binding Cycle の並列化に関係する仕組み
 ● Node を予約する仕組み
 ● Bindのタイミングを管理できる仕組み
 ○ Permit Plugin / wait on permit 
 ○ 実装済(発表時間が足りず削った) → ブランチ: permit-plugin 


Slide 75

Slide 75 text

Scheduling Cache について知る
 75

Slide 76

Slide 76 text

全てのNodeを取得する (おさらい)
 API からNodeを全て取得
 /minisched/minisched.go 


Slide 77

Slide 77 text

Scheduling Cache について知る
 Schedulerは実際にはAPI からNodeを取得しているわけではない
 → Scheduling Cache + snapshot の仕組み
 
 Scheduling Cache に全てのNodeの最新の状態をおいておく


Slide 78

Slide 78 text

Scheduling Cache について知る
 EventHandlerでNodeの作成/更新/削除 を検知してCacheを更新する
 
 
 
 (おさらい) EventHandler 
 → リソースの変更をトリガーにあらかじめ登録した関数を実行してくれる仕組み


Slide 79

Slide 79 text

Scheduling Cache によるメリット
 ● 毎回APIで問い合わせないので効率が良い 
 ● snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変 化しない
 ● Schedulerに都合が良い形でNodeのデータを加工できる


Slide 80

Slide 80 text

Scheduling Cache によるメリット
 ● 毎回APIで問い合わせないので効率が良い 
 ● snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変 化しない
 ● Schedulerに都合が良い形でNodeのデータを加工できる


Slide 81

Slide 81 text

snapshot に関して
 Cacheと同様にNodeの状態を保持している
 ● スケジュール中はNodeを取得する際にsnapshotに問い合わせる
 ○ (= APIから取得しない) 
 ○ (= scheduling cacheからも直接取得しない )
 ● スケジュールの開始時にCacheのデータを用いて更新されるようになっている
 ○ 逆に言うとスケジュールの開始時以外は更新されない 
 


Slide 82

Slide 82 text

snapshot に関して
 snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変化しな い
 → もし、取得するNodeの状態がスケジュール中に変わってしまうと、同一スケジュール中 のプラグインの結果などがブレてしまうことにつながる
 


Slide 83

Slide 83 text

Scheduling Cache によるメリット
 ● 毎回APIで問い合わせないので効率が良い (自明)
 ● snapshotと組み合わせることで一つのCycleを実行中にNodeの状態が変化しない
 ● Schedulerに都合が良い形でNodeのデータを加工できる


Slide 84

Slide 84 text

Node を予約する仕組みが必要な理由
 Binding Cycle を並行に実行
 → 次のPodのスケジュール時に前のPodのBindが終了していない場合におかしなことに
 


Slide 85

Slide 85 text

Node を予約する仕組みが必要な理由
 次のPodのスケジュール時に前のPodのBindが終了していない場合におかしなことに
 → Pod がNodeを予約する仕組みを作り、Binding Cycleの開始前に予約を行う
 → 他のPodのスケジュールの際には、
 予約されているPodもすでにBindされているPodかのようにして扱う
 
 


Slide 86

Slide 86 text

Scheduling Cache によるメリット 
 ● 毎回APIで問い合わせないので効率が良い (自明)
 ● snapshotと組み合わせることで一つのCycleを実行中にNodeの状態が変化しない
 ● Schedulerに都合が良い形でNodeのデータを加工できる
 ← 👀


Slide 87

Slide 87 text

Node を予約する仕組みとCache
 Binding Cycle に進む前に、Node を予約する 
 → Cache を操作し、PodがNodeにBindされていることにする 
 (もし、Binding Cycleが失敗した場合はCacheを元に戻す)


Slide 88

Slide 88 text

Scheduling Queue をざっくり実装する
 88 ブランチ: scheduling-queue


Slide 89

Slide 89 text

すごいシンプルなQueue (再掲)
 /minisched/queue/queue.go 


Slide 90

Slide 90 text

すごいシンプルなQueue
 スケジューリングに失敗した後のPodのことなどを考えていない
 → スケジューリングに失敗したPodはまた後でスケジューリングし直すために
 Queueに戻したい


Slide 91

Slide 91 text

Scheduling Queue をざっくり実装する
 
 
 /minisched/queue/queue.go 


Slide 92

Slide 92 text

Scheduling Queueが持つ3つのQueueの種類
 ● activeQ: Schedule待ちのPodのQueue
 ● podBackoffQ backoff中のPodのQueue。
 ● unschedulableQ 一度スケジュールしようとして失敗したPodのQueue
          Unschedulableとなった原因のPluginなども保存


Slide 93

Slide 93 text

Queue への追加/Queueからの取り出し


Slide 94

Slide 94 text

Queue への追加/Queueからの取り出し
 追加
 ● Add: 新たにPodが作成された時
 ● AddUnschedulableIfNotPresent: スケジュールに失敗した時
 ○ この際にUnschedulableとなった原因のPluginなども保存 
 取り出し
 ● スケジューリング開始時にPopを使用してactiveQからPodが取り出される


Slide 95

Slide 95 text

スケジュールに失敗した時にPodを戻す
 スケジュールに失敗した際にはsched.ErrorFunc を実行する


Slide 96

Slide 96 text

unschedulableQにPodを追加 
 unschedulableの原因となったpluginの記録 


Slide 97

Slide 97 text

backoffQ → activeQ


Slide 98

Slide 98 text

backoffQ → activeQ
 ● backoffの待ちが終了したPodをactiveQに移動


Slide 99

Slide 99 text

unschedulableQ → backoffQ/activeQ


Slide 100

Slide 100 text

unschedulableQ → backoffQ/activeQ
 
 ● 60秒以上unschedulableQに存在するPodはbackoffQ/activeQに移動
 ● 特定のイベントの発行時にbackoffQ/activeQに移動


Slide 101

Slide 101 text

EventHandler について再度考える
 101 ブランチ: event-handler


Slide 102

Slide 102 text

unschedulableQ → backoffQ/activeQ
 
 特定のイベントの発行時にbackoffQ/activeQへ移動
 unschedulableQはPodがどのPluginのせいでスケジュールできなかった(Unschedulableになった)のか を記録している 
 
 例: Filterで候補0となりUnschedulableとなった場合は、Filterで一つでもNodeを除外したPluginが Unschedulableの原因となったPluginとして記録される 


Slide 103

Slide 103 text

EnqueueExtensions
 EnqueueExtensions を満たすことで「プラグインのスケジュールの結果が変わり得るイベ ント」の定義ができる


Slide 104

Slide 104 text

NodeUnschedulable Plugin(おさらい)
 Nodeの.Spec.Unschedulable を見て、trueのNodeを候補から外す


Slide 105

Slide 105 text

NodeUnschedulable Pluginの例
 ● Nodeの.spec.Unschedulableの変更
 ● 新たなNodeの追加
 → このプラグインのスケジュールの結果が変わり得る


Slide 106

Slide 106 text

EventHandler(おさらい) 
 EventHandler 
 → リソースの変更をトリガーにあらかじめ登録した関数を実行してくれる仕組み
 
 これまでの登場箇所
 ● PodのQueueへの追加
 ● Cacheの更新
 
 


Slide 107

Slide 107 text

EventHandler と Queue の更新
 EnqueueExtensions.EventsToRegister の情報から、
 EventHandlerにイベント+Queueの更新を行う関数を動的に登録する


Slide 108

Slide 108 text

EventHandlerの動的な登録


Slide 109

Slide 109 text

EventHandlerの動的な登録
 /minisched/eventhandler.go 
 細かい…


Slide 110

Slide 110 text

EventHandlerの動的な登録
 新しいPodをQueueに入れるEventHandlerの 登録(前述)
 gvkMap: EventsToRegisterの情報が集められ たmap


Slide 111

Slide 111 text

EventHandlerの動的な登録
 新しいPodをQueueに入れるEventHandlerの 登録(前述)
 要約: 「イベントが実行された時に MoveAllToActiveOrBackoffQueueが実行されるように 登録する関数


Slide 112

Slide 112 text

EventHandlerの動的な登録
 新しいPodをQueueに入れるEventHandlerの 登録(前述)
 登録され得るリソースイベントから動的に設定 


Slide 113

Slide 113 text

Queue の更新
 MoveAllToActiveOrBackoffQueue メソッド(引数: 発生したイベント)
 
 unschedulableQのPodを見て、
 引数のイベントが、unschedulableの原因となったpluginの「スケジュールの結果が変わり うるイベント」だった場合に
 unschedulableQ から backoffQ/activeQ へと移動させる


Slide 114

Slide 114 text

まとめ/終わりに
 114

Slide 115

Slide 115 text

まとめ
 本日のトークで実装の対象とした機能のみで
 ● Filter/Score pluginsによるスケジュール
 ● Scheduling QueueによるBindされていないPodの管理
 ● EventHandlerによるQueueの操作
 が実装され、最低限のスケジュールを行うことができるようになりました


Slide 116

Slide 116 text

Next step....
 時間の関係上今回触れなかった機能が多くあります
 ● Preemption
 ● Permit Plugin / wait on permit
 ● Reserve/UnReserve Plugin
 ● QueueSort Plugin
 ● Extender全般
 ● PodsToActivate によるCycleStateを通したQueue内の操作
 ● その他リソースの変更によるQueue内の操作
 など 


Slide 117

Slide 117 text

ご静聴ありがとうございました 
 質問などはTwitterに投げてくださっても○です↓
 (「いや、これ違くね?」ってところもあれば、是非教えてください )
 
 Twitter: さんぽし(@sanpo_shiho)
 GitHub: @sanposhiho
 117