$30 off During Our Annual Pro Sale. View Details »

自作して学ぶKubernetes Scheduler入門

sanposhiho
November 05, 2021
530

自作して学ぶKubernetes Scheduler入門

CloudNative Days Tokyo 2021での登壇資料です。
https://event.cloudnativedays.jp/cndt2021/talks/1184

GitHub repository
https://github.com/sanposhiho/mini-kube-scheduler

sanposhiho

November 05, 2021
Tweet

More Decks by sanposhiho

Transcript

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

  2. 自己紹介
 中田 健誠 / Kyoto Uni (4th year)
 普段はバックエンドエンジニアをしてます
 


    Twitter: さんぽし (@sanpo_shiho)
 GitHub: @sanposhiho
 
 Google Summer of Code 2021 @ Kubernetes
 member of Kubernetes / Kubernetes SIGs

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


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

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

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

  8. Scheduling Framework
 Scheduling Framework (の Scheduling Cycle) のうち、
 • Filter

    (+ PreFilter)
 • Score (+ PreScore + Normalized Score )
 の結果が主にNodeの決定に影響している 
 ※ Extenderについては今回は割愛して説明します。 
 
 8
  9. Scheduling Framework
 Filter
 Podを実行できない(したくない)Nodeを候補から除外する
 
 例えば…
 ・リソース不足でPodが実行できないNode
 ・nodeSelectorの条件に一致しないNode
 を除外する
 9

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

  11. 11

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


  13. 13 ×
 ×
 何かしらの観点でNodeを フィルタリングする
 ×
 ×
 ×
 ×
 もしこの段階で候補のNodeが全滅した場合

    
 → Preemption(Podの追い出し) を試みる
 → 諦める (Unscheduled) 

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


  15. 15 Score B プラグインの Weightが1
 → 10
 → 50
 Score

    A プラグインの Weightが10
 → 300
 → 500

  16. 16 → 10
 → 50
 Node1 が最終的に選ばれる 
 → 300


    → 500
 350
 510

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

  18. 仕組みざっくりまとめ
 Scheduler は Scheduling Framework の流れに沿って動作
 • Filter で実行できない/したくないNodeを弾き、
 •

    ScoreでNodeをスコアリングし、
 • 最後にPlugin Weightを加味して、
 • どのNodeでPodを実行するか決定する
 • その後Binding CycleでPodをそのNodeにBindする
 18
  19. 今回の自作Schedulerに関して
 19

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


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


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

    でその時点のSchedulerでシナリオを実行することができます。

  23. 自作Schedulerに関して


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


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


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


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

    選んだNodeに対してPodをBindする
 6. 1に戻る

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


  29. ScheduleされていないPodを見つける
 
 
Queue はとりあえず Slice でシンプルに実装
 • Queue に Pod

    を追加するメソッドを実装
 • Queue からPodを取り出すメソッドを実装

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


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


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


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

    新たなPodをQueueに入れる

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


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


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


    5. 選んだNodeに対してPodをBindする
 6. 1に戻る

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


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


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


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


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


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


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


  44. 適当な Scheduler おさらい
 1. Queue のPodを取り出す
 2. そのPodのScheduleを始める
 3. 全てのNodeを取得する


    4. ランダムにNodeを一つ選ぶ
 5. 選んだNodeに対してPodをBindする
 6. 1に戻る

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


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


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

  48. 適当な 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に戻る

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


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


  51. /minisched/minisched.go 


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


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


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


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


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


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


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


  59. /minisched/plugins/score/nodenumber/nodenumber.go 
 Pod の名前の最後の数字を取得 
 例: pod1 → 1
 Node の名前の最後の数字を取得

    
 例: node1 → 1
 一致していた場合10点を返す 

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


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


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

    
 ↓動いた 👌 (pod1はnode1へ、pod3はnode3へ)
 

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


  64. 適当な 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に戻る

  65. CycleStateについて知る
 65

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


    
 

  67. PreFilter/PreScore Pluginについて
 Filter/Score pluginの実行の準備をできる拡張点
 
 
 PreFilter / PreScore において事前計算を行い、CycleStateに結果を保存。Filter

    / Score ではその結果を使用し、実行パフォーマンスをあげる
 

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


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


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

  71. Scheduling Framework (おさらい)
 71 ## Scheduling Cycle
 PodをどのNodeで実行するかを決定する
 ## Binding

    Cycle
 Scheduling Cycle での決定を実際にクラスターに適応する

  72. Scheduling Cycle
 
 
 72 他のPodのスケジュール結果が確定していないと正しくスケジュールできない
 → 並列に実行できず、Podを一つ一つスケジュールしていくしかない
 
 


    並列に実行するとダメな例: 
 2つのPodが同時にスケジュールされ、”1つのPodしかリソース的に実行できないNode” に2つのPodがBindされる
 

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


  74. Binding Cycle の並列化に関係する仕組み
 • Node を予約する仕組み
 • Bindのタイミングを管理できる仕組み
 ◦ Permit

    Plugin / wait on permit 
 ◦ 実装済(発表時間が足りず削った) → ブランチ: permit-plugin 

  75. Scheduling Cache について知る
 75

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


  77. Scheduling Cache について知る
 Schedulerは実際にはAPI からNodeを取得しているわけではない
 → Scheduling Cache + snapshot

    の仕組み
 
 Scheduling Cache に全てのNodeの最新の状態をおいておく

  78. Scheduling Cache について知る
 EventHandlerでNodeの作成/更新/削除 を検知してCacheを更新する
 
 
 
 (おさらい) EventHandler

    
 → リソースの変更をトリガーにあらかじめ登録した関数を実行してくれる仕組み

  79. Scheduling Cache によるメリット
 • 毎回APIで問い合わせないので効率が良い 
 • snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変 化しない
 •

    Schedulerに都合が良い形でNodeのデータを加工できる

  80. Scheduling Cache によるメリット
 • 毎回APIで問い合わせないので効率が良い 
 • snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変 化しない
 •

    Schedulerに都合が良い形でNodeのデータを加工できる

  81. snapshot に関して
 Cacheと同様にNodeの状態を保持している
 • スケジュール中はNodeを取得する際にsnapshotに問い合わせる
 ◦ (= APIから取得しない) 
 ◦

    (= scheduling cacheからも直接取得しない )
 • スケジュールの開始時にCacheのデータを用いて更新されるようになっている
 ◦ 逆に言うとスケジュールの開始時以外は更新されない 
 

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


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


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


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


  86. Scheduling Cache によるメリット 
 • 毎回APIで問い合わせないので効率が良い (自明)
 • snapshotと組み合わせることで一つのCycleを実行中にNodeの状態が変化しない
 •

    Schedulerに都合が良い形でNodeのデータを加工できる
 ← 👀

  87. Node を予約する仕組みとCache
 Binding Cycle に進む前に、Node を予約する 
 → Cache を操作し、PodがNodeにBindされていることにする 
 (もし、Binding

    Cycleが失敗した場合はCacheを元に戻す)

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


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


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


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


  92. Scheduling Queueが持つ3つのQueueの種類
 • activeQ: Schedule待ちのPodのQueue
 • podBackoffQ backoff中のPodのQueue。
 • unschedulableQ

    一度スケジュールしようとして失敗したPodのQueue
          Unschedulableとなった原因のPluginなども保存

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


  94. Queue への追加/Queueからの取り出し
 追加
 • Add: 新たにPodが作成された時
 • AddUnschedulableIfNotPresent: スケジュールに失敗した時
 ◦

    この際にUnschedulableとなった原因のPluginなども保存 
 取り出し
 • スケジューリング開始時にPopを使用してactiveQからPodが取り出される

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


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


  97. backoffQ → activeQ


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


  99. unschedulableQ → backoffQ/activeQ


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


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


  102. unschedulableQ → backoffQ/activeQ
 
 特定のイベントの発行時にbackoffQ/activeQへ移動
 unschedulableQはPodがどのPluginのせいでスケジュールできなかった(Unschedulableになった)のか を記録している 
 
 例:

    Filterで候補0となりUnschedulableとなった場合は、Filterで一つでもNodeを除外したPluginが Unschedulableの原因となったPluginとして記録される 

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


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


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


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


    • Cacheの更新
 
 

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


  108. EventHandlerの動的な登録


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


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


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


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


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

    から backoffQ/activeQ へと移動させる

  114. まとめ/終わりに
 114

  115. まとめ
 本日のトークで実装の対象とした機能のみで
 • Filter/Score pluginsによるスケジュール
 • Scheduling QueueによるBindされていないPodの管理
 • EventHandlerによるQueueの操作


    が実装され、最低限のスケジュールを行うことができるようになりました

  116. Next step....
 時間の関係上今回触れなかった機能が多くあります
 • Preemption
 • Permit Plugin / wait

    on permit
 • Reserve/UnReserve Plugin
 • QueueSort Plugin
 • Extender全般
 • PodsToActivate によるCycleStateを通したQueue内の操作
 • その他リソースの変更によるQueue内の操作
 など 

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


    117