Upgrade to Pro — share decks privately, control downloads, hide ads and more …

自作して学ぶKubernetes Scheduler入門

sanposhiho
November 05, 2021
870

自作して学ぶ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

    View full-size slide

  2. 自己紹介

    中田 健誠 / Kyoto Uni (4th year)

    普段はバックエンドエンジニアをしてます


    Twitter: さんぽし (@sanpo_shiho)

    GitHub: @sanposhiho


    Google Summer of Code 2021 @ Kubernetes

    member of Kubernetes / Kubernetes SIGs


    View full-size slide

  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

    View full-size slide

  4. このトークの目指すところ

    内部実装の概要をつかむことを目標とします

    → Schedulerが実際に内部的に行っていることを、Schedulerが自作されていく様子を見
    ながら、機能ごとに段階的に学んでいきます


    View full-size slide

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

    5

    View full-size slide

  6. Kubernetes scheduler

    PodをどのNodeに割り当てるかを決定するコンポーネント


    様々なリソースの状況からPodに最適なNodeを決定する


    6

    View full-size slide

  7. Scheduling Framework

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

    7

    View full-size slide

  8. Scheduling Framework

    Scheduling Framework (の Scheduling Cycle) のうち、

    ● Filter (+ PreFilter)

    ● Score (+ PreScore + Normalized Score )

    の結果が主にNodeの決定に影響している 

    ※ Extenderについては今回は割愛して説明します。 


    8

    View full-size slide

  9. Scheduling Framework

    Filter

    Podを実行できない(したくない)Nodeを候補から除外する


    例えば…

    ・リソース不足でPodが実行できないNode

    ・nodeSelectorの条件に一致しないNode

    を除外する

    9

    View full-size slide

  10. Scheduling Framework

    Score

    残った候補のNodeをスコアリングする。


    例えば…

    ・全体のNodeのリソースの使用量のバランスがちょうど良くなるNodeを優先

    ・Podを実行するコンテナイメージをすでに持っているNodeを優先

    10

    View full-size slide

  11. 12
    ×

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


    View full-size slide

  12. 13
    ×

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

    ×

    ×

    ×

    ×

    もしこの段階で候補のNodeが全滅した場合 

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

    → 諦める (Unscheduled) 


    View full-size slide

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


    View full-size slide

  14. 15
    Score B プラグインの
    Weightが1

    → 10

    → 50

    Score A プラグインの
    Weightが10

    → 300

    → 500


    View full-size slide

  15. 16
    → 10

    → 50

    Node1 が最終的に選ばれる 

    → 300

    → 500

    350

    510


    View full-size slide

  16. Scheduling Framework

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

    17

    View full-size slide

  17. 仕組みざっくりまとめ

    Scheduler は Scheduling Framework の流れに沿って動作

    ● Filter で実行できない/したくないNodeを弾き、

    ● ScoreでNodeをスコアリングし、

    ● 最後にPlugin Weightを加味して、

    ● どのNodeでPodを実行するか決定する

    ● その後Binding CycleでPodをそのNodeにBindする

    18

    View full-size slide

  18. 今回の自作Schedulerに関して

    19

    View full-size slide

  19. 自作Schedulerに関して

    Kubernetes Schedulerのversion:v1.22.0


    リポジトリを公開しています

    → https://github.com/sanposhiho/mini-kube-scheduler/

    この発表の流れに沿ってブランチを切っています


    View full-size slide

  20. 自作Schedulerに関して

    kubernetes-sigs/kube-scheduler-simulator の仕組みを流用しており、

    コマンドライン上からSchedulerの振る舞いを確認することができます。




    View full-size slide

  21. 自作Schedulerに関して

    scenario という形で試したいシナリオを定義することができます。


    シナリオを変更したい場合は、

    /sched.go のscenario関数の中身を変更してください


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


    View full-size slide

  22. 自作Schedulerに関して


    View full-size slide

  23. 自作Schedulerに関して

    基本的には発表を見ていただくだけでなんとなく要点の実装がわかるように解説してい
    きます。


    時間に余裕がある方は、適宜一時停止しながら、

    実際のコードを見たり、変更してみたり、実際に手元で動作させてみたりしてみると理解
    が深まると思います。


    View full-size slide

  24. 適当なスケジュールをするSchedulerを作る

    25
    ブランチ: initial-random-scheduler


    View full-size slide

  25. 適当なScheduler

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


    View full-size slide

  26. やらなければいけない事

    1. ScheduleされていないPodを見つける

    2. そのPodのScheduleを始める

    3. 全てのNodeを取得する

    4. ランダムにNodeを一つ選ぶ

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

    6. 1に戻る


    View full-size slide

  27. ScheduleされていないPodを見つける


    
Scheduler は内部のQueueにスケジュールされていないPodを持っている

    ↓

    “ScheduleされていないPodを見つけてQueueに貯める仕組み” が必要


    View full-size slide

  28. ScheduleされていないPodを見つける


    
Queue はとりあえず Slice でシンプルに実装

    ● Queue に Pod を追加するメソッドを実装

    ● Queue からPodを取り出すメソッドを実装


    View full-size slide

  29. すごいシンプルなQueue

    /minisched/queue/queue.go 


    View full-size slide

  30. Queue に Pod を追加するメソッド

    Slice にappendしてるだけの関数



    /minisched/queue/queue.go 


    View full-size slide

  31. Queue の先頭を取り出すメソッド

    /minisched/queue/queue.go 


    View full-size slide

  32. ScheduleされていないPodを見つける


    
EventHandler 

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


    1. EventHandlerを用いて新しいPodの作成を検知する

    2. 新たなPodをQueueに入れる


    View full-size slide

  33. /minisched/eventhandler.go 

    スケジュールされてないPodのみ 

    をFilter

    追加されたときのみ 

    この関数を実行   → 


    View full-size slide

  34. Pod が作られたときに呼ばれるメソッド

    /minisched/eventhandler.go 


    View full-size slide

  35. やらなければいけない事 (再掲)

    1. ScheduleされていないPodを見つける

    2. そのPodのScheduleを始める

    3. 全てのNodeを取得する

    4. ランダムにNodeを一つ選ぶ

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

    6. 1に戻る


    View full-size slide

  36. PodのScheduleを始める

    sched.scheduleOne を実行し続ける

    /minisched/minisched.go 


    View full-size slide

  37. PodのScheduleを始める

    scheduleOne がスケジューラーのメインロジック

    Queue の先頭のPodを取り出す

    /minisched/minisched.go 


    View full-size slide

  38. 全てのNodeを取得する

    API からNodeを全て取得

    /minisched/minisched.go 


    View full-size slide

  39. ランダムにNodeを一つ選ぶ

    /minisched/minisched.go 


    View full-size slide

  40. 選んだNodeに対してPodをBindする



    /minisched/minisched.go 


    View full-size slide

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


    
Bind しているメソッド本体

    /minisched/minisched.go 


    View full-size slide

  42. 適当な Scheduler おさらい

    1. EventHandlerを用いて新しいPodの作成を検知する

    2. 新たなPodが作成されたら、それをQueueに入れる


    View full-size slide

  43. 適当な Scheduler おさらい

    1. Queue のPodを取り出す

    2. そのPodのScheduleを始める

    3. 全てのNodeを取得する

    4. ランダムにNodeを一つ選ぶ

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

    6. 1に戻る


    View full-size slide

  44. 動くか試そう

    node0 ~ node9 を作った後に pod1を作成

    ↓動いた 👌 


    View full-size slide

  45. Filter/Score Pluginを作る

    46
    ブランチ: filter-plugin, score-plugin


    View full-size slide

  46. Scheduling Framework

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

    47

    View full-size slide

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


    View full-size slide

  48. 例として使用するFilter Plugin

    NodeUnschedulable Plugin

    Nodeの.Spec.Unschedulable を見て、trueのNodeを候補から外す

    (実際にSchedulerに標準で搭載されているPluginです)


    View full-size slide

  49. Filter Pluginの実行

    RunFilterPlugins というメソッドでFilter Pluginを実行。

    エラーが返った場合はスケジュールをその時点で諦める

    /minisched/minisched.go 


    View full-size slide

  50. /minisched/minisched.go 


    View full-size slide

  51. ループでFilter PluginsをNode一つ一
    つに対して実行

    全てのFilter PluginをpassしたNodeの
    みがfeasibleNodeとして 

    返される

    /minisched/minisched.go 


    View full-size slide

  52. Filter Pluginの実行

    /minisched/minisched.go 

    ひとつも候補がない場合はerrorを返
    す


    View full-size slide

  53. 動くか試そう

    Unschedulable: true のNodeを10個(node0 ~ node9)作成


    View full-size slide

  54. 動くか試そう

    Unschedulableの指定がないNodeを1個(node10)作成


    View full-size slide

  55. 動くか試そう

    ↓動いた 👌 

    Unschedulableの指定がないnode10にbindされている


    View full-size slide

  56. 例として使用するScore Plugin

    NodeNumber Plugin

    Nodeの名前の最後の数字がPodの名前の最後の数字と一致するNodeに高い点数をつ
    ける


    例: pod1と言うPodのスケジュール時にはnode1に高いスコアをつける


    View full-size slide

  57. NodeNumber Pluginを実装する

    interfaceを満たす構造体を作成することでPluginを実装できる




    https://github.com/kubernetes/kubernetes/blob/master/pkg/scheduler/framework/interface.go#L413 


    View full-size slide

  58. /minisched/plugins/score/nodenumber/nodenumber.go 

    Pod の名前の最後の数字を取得 

    例: pod1 → 1

    Node の名前の最後の数字を取得 

    例: node1 → 1

    一致していた場合10点を返す 


    View full-size slide

  59. Score Pluginの実行


    RunScorePlugins というメソッドで実行

    /minisched/minisched.go 


    View full-size slide

  60. Score Pluginの実行


    /minisched/minisched.go 

    ループでScore PluginsをNode一つ一つ
    に対して実行

    別の形に加工して返却 


    View full-size slide

  61. 動くか試そう

    node0 ~ node9 を作った後に pod1 と pod3 を作成

    (Nodeを作る際、先程のspec.Unschedulableの設定は消してます) 

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


    View full-size slide

  62. 動くか試そう

    ↓score pluginの実行後のスコアをlogに出してみると…

    pod3のスケジュール時はちゃんとnode3が一番スコアが高くなっている


    View full-size slide

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


    View full-size slide

  64. CycleStateについて知る

    65

    View full-size slide

  65. CycleStateについて

    一つの Pod の スケジュール でのデータを保持している。

    → Podのスケジュール開始時に毎回新しく作成される


    Plugin(等)はCycleStateにデータを読み書きすることができる。



    View full-size slide

  66. PreFilter/PreScore Pluginについて

    Filter/Score pluginの実行の準備をできる拡張点



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


    View full-size slide

  67. 先程のScorePluginのinterface

    interfaceを満たす構造体を作成することでPluginを実装できる




    https://github.com/kubernetes/kubernetes/blob/master/pkg/scheduler/framework/interface.go#L413 


    View full-size slide

  68. CycleState を使ってみる

    NodeNumber pluginにPreScoreを実装してCycleStateを使ってみる実装があります

    → ブランチ: prescore-plugin


    View full-size slide

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

    70

    View full-size slide

  70. Scheduling Framework (おさらい)

    71
    ## Scheduling Cycle

    PodをどのNodeで実行するかを決定する

    ## Binding Cycle

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


    View full-size slide

  71. Scheduling Cycle



    72
    他のPodのスケジュール結果が確定していないと正しくスケジュールできない

    → 並列に実行できず、Podを一つ一つスケジュールしていくしかない



    並列に実行するとダメな例: 

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


    View full-size slide

  72. Binding Cycle



    73
    スケジュールの結果は確定しているのであとはBindするだけ

    → 並列に実行できる!!効率化!!


    View full-size slide

  73. Binding Cycle の並列化に関係する仕組み

    ● Node を予約する仕組み

    ● Bindのタイミングを管理できる仕組み

    ○ Permit Plugin / wait on permit 

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


    View full-size slide

  74. Scheduling Cache について知る

    75

    View full-size slide

  75. 全てのNodeを取得する (おさらい)

    API からNodeを全て取得

    /minisched/minisched.go 


    View full-size slide

  76. Scheduling Cache について知る

    Schedulerは実際にはAPI からNodeを取得しているわけではない

    → Scheduling Cache + snapshot の仕組み


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


    View full-size slide

  77. Scheduling Cache について知る

    EventHandlerでNodeの作成/更新/削除 を検知してCacheを更新する




    (おさらい) EventHandler 

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


    View full-size slide

  78. Scheduling Cache によるメリット

    ● 毎回APIで問い合わせないので効率が良い 

    ● snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変
    化しない

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


    View full-size slide

  79. Scheduling Cache によるメリット

    ● 毎回APIで問い合わせないので効率が良い 

    ● snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変
    化しない

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


    View full-size slide

  80. snapshot に関して

    Cacheと同様にNodeの状態を保持している

    ● スケジュール中はNodeを取得する際にsnapshotに問い合わせる

    ○ (= APIから取得しない) 

    ○ (= scheduling cacheからも直接取得しない )

    ● スケジュールの開始時にCacheのデータを用いて更新されるようになっている

    ○ 逆に言うとスケジュールの開始時以外は更新されない 


    View full-size slide

  81. snapshot に関して

    snapshotと組み合わせることでPodのスケジュール中に参照するNodeの状態が変化しな
    い

    → もし、取得するNodeの状態がスケジュール中に変わってしまうと、同一スケジュール中
    のプラグインの結果などがブレてしまうことにつながる


    View full-size slide

  82. Scheduling Cache によるメリット

    ● 毎回APIで問い合わせないので効率が良い (自明)

    ● snapshotと組み合わせることで一つのCycleを実行中にNodeの状態が変化しない

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


    View full-size slide

  83. Node を予約する仕組みが必要な理由

    Binding Cycle を並行に実行

    → 次のPodのスケジュール時に前のPodのBindが終了していない場合におかしなことに


    View full-size slide

  84. Node を予約する仕組みが必要な理由

    次のPodのスケジュール時に前のPodのBindが終了していない場合におかしなことに

    → Pod がNodeを予約する仕組みを作り、Binding Cycleの開始前に予約を行う

    → 他のPodのスケジュールの際には、

    予約されているPodもすでにBindされているPodかのようにして扱う



    View full-size slide

  85. Scheduling Cache によるメリット 

    ● 毎回APIで問い合わせないので効率が良い (自明)

    ● snapshotと組み合わせることで一つのCycleを実行中にNodeの状態が変化しない

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

    ← 👀


    View full-size slide

  86. Node を予約する仕組みとCache

    Binding Cycle に進む前に、Node を予約する 

    → Cache を操作し、PodがNodeにBindされていることにする 

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


    View full-size slide

  87. Scheduling Queue をざっくり実装する

    88
    ブランチ: scheduling-queue


    View full-size slide

  88. すごいシンプルなQueue (再掲)

    /minisched/queue/queue.go 


    View full-size slide

  89. すごいシンプルなQueue

    スケジューリングに失敗した後のPodのことなどを考えていない

    → スケジューリングに失敗したPodはまた後でスケジューリングし直すために

    Queueに戻したい


    View full-size slide

  90. Scheduling Queue をざっくり実装する



    /minisched/queue/queue.go 


    View full-size slide

  91. Scheduling Queueが持つ3つのQueueの種類

    ● activeQ: Schedule待ちのPodのQueue

    ● podBackoffQ backoff中のPodのQueue。

    ● unschedulableQ 一度スケジュールしようとして失敗したPodのQueue

             Unschedulableとなった原因のPluginなども保存


    View full-size slide

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


    View full-size slide

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

    追加

    ● Add: 新たにPodが作成された時

    ● AddUnschedulableIfNotPresent: スケジュールに失敗した時

    ○ この際にUnschedulableとなった原因のPluginなども保存 

    取り出し

    ● スケジューリング開始時にPopを使用してactiveQからPodが取り出される


    View full-size slide

  94. スケジュールに失敗した時にPodを戻す

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


    View full-size slide

  95. unschedulableQにPodを追加 

    unschedulableの原因となったpluginの記録 


    View full-size slide

  96. backoffQ → activeQ


    View full-size slide

  97. backoffQ → activeQ

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


    View full-size slide

  98. unschedulableQ → backoffQ/activeQ


    View full-size slide

  99. unschedulableQ → backoffQ/activeQ


    ● 60秒以上unschedulableQに存在するPodはbackoffQ/activeQに移動

    ● 特定のイベントの発行時にbackoffQ/activeQに移動


    View full-size slide

  100. EventHandler について再度考える

    101
    ブランチ: event-handler


    View full-size slide

  101. unschedulableQ → backoffQ/activeQ


    特定のイベントの発行時にbackoffQ/activeQへ移動

    unschedulableQはPodがどのPluginのせいでスケジュールできなかった(Unschedulableになった)のか
    を記録している 


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

    View full-size slide

  102. EnqueueExtensions

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


    View full-size slide

  103. NodeUnschedulable Plugin(おさらい)

    Nodeの.Spec.Unschedulable を見て、trueのNodeを候補から外す


    View full-size slide

  104. NodeUnschedulable Pluginの例

    ● Nodeの.spec.Unschedulableの変更

    ● 新たなNodeの追加

    → このプラグインのスケジュールの結果が変わり得る


    View full-size slide

  105. EventHandler(おさらい) 

    EventHandler 

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


    これまでの登場箇所

    ● PodのQueueへの追加

    ● Cacheの更新



    View full-size slide

  106. EventHandler と Queue の更新

    EnqueueExtensions.EventsToRegister の情報から、

    EventHandlerにイベント+Queueの更新を行う関数を動的に登録する


    View full-size slide

  107. EventHandlerの動的な登録


    View full-size slide

  108. EventHandlerの動的な登録

    /minisched/eventhandler.go 

    細かい…


    View full-size slide

  109. EventHandlerの動的な登録

    新しいPodをQueueに入れるEventHandlerの
    登録(前述)

    gvkMap: EventsToRegisterの情報が集められ
    たmap


    View full-size slide

  110. EventHandlerの動的な登録

    新しいPodをQueueに入れるEventHandlerの
    登録(前述)

    要約: 「イベントが実行された時に
    MoveAllToActiveOrBackoffQueueが実行されるように
    登録する関数


    View full-size slide

  111. EventHandlerの動的な登録

    新しいPodをQueueに入れるEventHandlerの
    登録(前述)

    登録され得るリソースイベントから動的に設定 


    View full-size slide

  112. Queue の更新

    MoveAllToActiveOrBackoffQueue メソッド(引数: 発生したイベント)


    unschedulableQのPodを見て、

    引数のイベントが、unschedulableの原因となったpluginの「スケジュールの結果が変わり
    うるイベント」だった場合に

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


    View full-size slide

  113. まとめ/終わりに

    114

    View full-size slide

  114. まとめ

    本日のトークで実装の対象とした機能のみで

    ● Filter/Score pluginsによるスケジュール

    ● Scheduling QueueによるBindされていないPodの管理

    ● EventHandlerによるQueueの操作

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


    View full-size slide

  115. Next step....

    時間の関係上今回触れなかった機能が多くあります

    ● Preemption

    ● Permit Plugin / wait on permit

    ● Reserve/UnReserve Plugin

    ● QueueSort Plugin

    ● Extender全般

    ● PodsToActivate によるCycleStateを通したQueue内の操作

    ● その他リソースの変更によるQueue内の操作

    など 


    View full-size slide

  116. ご静聴ありがとうございました 

    質問などはTwitterに投げてくださっても○です↓

    (「いや、これ違くね?」ってところもあれば、是非教えてください )


    Twitter: さんぽし(@sanpo_shiho)

    GitHub: @sanposhiho

    117

    View full-size slide