Kubernetes 1.26: SIG-Schedulingの変更内容

Kubernetes 1.26がリリースされましたね :tada:

今回もKubernetes v1.26の[CHANGELOG](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.26.md#v1260)をベースにSIG Scheduling(kube-scheduler)に関する機能について紹介していきます。

<details>
<summary>過去の変更内容</summary>
<ul>
<li><a href="https://qiita.com/everpeace/items/b2bb8a0b02a8b30c9299">Kubernetes 1.25: SIG Scheduling の変更内容</a></li>
<li><a href="https://qiita.com/everpeace/items/3cf879bfe1464947572b">Kubernetes 1.24: SIG Scheduling の変更内容</a></li>
<li><a href="https://qiita.com/everpeace/items/1b6ad68075d81ce315c4">Kubernetes 1.23: SIG Scheduling の変更内容</a></li>
</ul>
</details>

## 1.26変更点の所感

今回の目玉は下記の２つの新機能です。

- [KEP-3521: Pod Scheduling Readiness](https://github.com/kubernetes/enhancements/issues/3521)
- [KEP-3063: Dynamic resource allocation](https://github.com/kubernetes/enhancements/issues/3063)
  - [Kubernetes 1.26: SIG-Node (kubelet) 変更内容
](https://qiita.com/y1r96/items/a217070aca59be39f161#api-change)に解説がありますのでここでは割愛します

その他は細かな修正ばかりです。

## :new: What's new! (新情報)

:pencil: 1.26のリリースノートにはWhat's newのセクション自体がなかったのですが、v1.26のリリースBlogエントリである[Kubernetes v1.26: Electrifying](https://kubernetes.io/blog/2022/12/09/kubernetes-v1-26-release/)から、[Pod Scheduling Readiness](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-scheduling-readiness/)を紹介します。

### :pencil: [Pod Scheduling Readiness](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-scheduling-readiness/)

|KEP | [KEP-3521: Pod Scheduling Readiness](https://github.com/kubernetes/enhancements/issues/3521)|
|---|---|
|feature gate| `PodSchedulingReadiness`|
|feature state| `alpha`|

この機能はPodのAPIに`.spec.schedulingGates`フィールドを追加します。このフィールドは _Podをスケジュールしてよいか？そうでないか？_ を定義します。外部のユーザやコントローラがこのフィールドを制御することで、それぞれのポリシーや要求に応じて、Podがスケジュールされないように制御出来ます。

#### そもそもなんで必要なのか？

現在、Kubernetesクラスタ上のPodは、クラスタ上で実行されているべきワークロードの最小単位として解釈されています。つまりPodが作成された瞬間からそのPodはどこかのノードで動いているべきという解釈です。なので、kube-schedulerは全てのPending Podをnodeに割り当てようとします。

また、kube-schedulerは、一度Podがスケジュール不可能と判断されると次にスケジュールの試行をするまでbackoffするのですが、何らかのクラスター内の状態変化などをきっかけに、その都度、スケジューラーのキューに戻されてスケジュールが試行されます。つまり、スケジュール出来ないと最初からわかっているPodが多いと効率が悪いのです。また、不要なスケジュール試行はPodが実際にnodeにbindされるまでのlatencyを増やしてしまうことにも繋がります。

#### どんなふうに使う？

そこで、`spec.schedulingGates` というフィールドを導入して、そもそも「このPodはスケジュールされているべきかそうでないか？」をAPIレベルで定義できるようにします。また、このAPIによってスケジューラーの無駄な試行を減らして、パフォーマンスの向上にも寄与できます。ただし、少し複雑な更新制約があるので注意してください。

```yaml
apiVersion: v1
kind: Pod
spec:
  # schedulingGatesの要素が空でない限りschedulerのキューには入らない(finalizerの動作に似ている)
  # 注意:一度空になるまでは追加OK、変更NG、削除OK。ただし、一度でも空になったら変更ができなくなります。
  #      なのでまず最初はadmission webhookとかで何かを追加する必要があります。
  schedulingGates:
  - name: foo
  - name: bar
```

#### どんなことに使えそうなのか？

[KEP-3521: Pod Scheduling Readiness](https://github.com/kubernetes/enhancements/issues/3521)には下記の4つのUser Storyが想定されています。

- Dynamic Quota Managerのようなカスタムオーケストレータ(mutating webhookでgateを追加して、schedule readyになったら外す)
- 複数のカスタムオーケストレータによるscheduling readinessの合意(`schedulingGates`が複数になっているのはそのため？？)
- 高度なadmission enqueueロジックの実装
- カスタムコントローラを変更せずに、Pod作成後にschedule制約を変更する(disruptionを防いだり、affinity/anti-affinityを制御したり)

#### 実装はどうなってるのか？
<a href="https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/"><img src="https://d33wubrfki0l68.cloudfront.net/4e9fa4651df31b7810c851b142c793776509e046/61a36/images/docs/scheduling-framework-extensions.png" width="600"/></a>

[Scheduling Framework](https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/)の拡張点に、新しく`PreEnqueue`という拡張点が追加されます。`.spec.schedulingGates`の機能は`PreEnqueue`拡張点を実装する[`SchedulingGates`プラグイン](https://github.com/kubernetes/kubernetes/blob/master/pkg/scheduler/framework/plugins/schedulinggates/scheduling_gates.go)として実装されています。

`PreEnqueue`はkube-schedulerの内部キュー(正確にはactiveQ)にPodを追加される前に常に実行されます。`PreEnqueue`拡張点がSuccessではないステータスを返すと、そのPodは内部キューに追加されず結果としてスケジューリングが試みられません。

`schedulingGates`が一旦空になったら変更不能という制約もこの実装処理から来ています。一旦内部キューに入ってしまうと、Scheduling Cycleでのスケジュール判断と`schedulingGates`の追加がraceを起こしてしまって、`schedulingGates`のセマンティクスが保てなくなってしまうからです。

## :skull_crossbones: Urgent Upgrade Notes（必ず一読してからアップグレードしなければならない事項）

:pencil: ありません

## :wastebasket: Deprecation(非推奨)

:pencil: ありません

## :earth_asia: API Changes（API変更）

- `preEnqueue` という新しい拡張点(extension point)がscheduler component config `v1beta2/v1beta3/v1` に追加されました. ([#113275](https://github.com/kubernetes/kubernetes/pull/113275), [@Huang-Wei](https://github.com/Huang-Wei))
    - :pencil: [Pod Scheduling Readiness](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-scheduling-readiness/) を実現するために導入された拡張点です。
- `ResourceClaim`API(`resource.k8s.io/v1alpha1`内)が追加されました(`DynamicResourceAllocation`feature gateが必要です)。この新しいAPIは、これまでのDevice Pluginに比べ、ノード単位、クラスタ単位などユーザ定義の階層ごとに特別なリソースの種類を定義できるので、より柔軟性があります。([#111023](https://github.com/kubernetes/kubernetes/pull/111023), [@pohly](https://github.com/pohly))
  - :pencil: 解説は[Kubernetes 1.26: SIG-Node (kubelet) 変更内容](https://qiita.com/y1r96/items/a217070aca59be39f161)を参照ください🙇
- `podTopologySpread`プラグインの`NodeInclusionPolicy`がデフォルトで有効になります。([#113500](https://github.com/kubernetes/kubernetes/pull/113500), [@kerthcet](https://github.com/kerthcet))
  - :pencil: [KEP-3094: Take taints/tolerations into consideration when calculating PodTopologySpread skew](https://github.com/kubernetes/enhancements/issues/3094)
  - :pencil: [Kubernetes 1.25: SIG Scheduling の変更内容](https://qiita.com/everpeace/items/b2bb8a0b02a8b30c9299#earth_asia-api-changesapi%E5%A4%89%E6%9B%B4)でも説明しているので参照ください。
- (:pencil: SchedulerConfigの)`v1`にプロファイルレベルのパラメータとして`percentageOfNodesToScore`が追加されました。プロファイルレベルのパラメータが設定されている場合、グローバルレベルの`percentageOfNodesToScore`はオーバーライドされます。([#112521](https://github.com/kubernetes/kubernetes/pull/112521), [@yuanchen8911](https://github.com/yuanchen8911))
  - :pencil: スケジューラはスケジュール先のノードをFilter➔Socreすることで決定します。Scoreはノード毎に並列に計算されますが、実際にはすべてのノードに対して計算されず、`percentageOfNodesToScore`分のノードだけ計算しその中から一番Scoreが高いものが選択されます(並列に計算して十分な数のScoreが集まったらshortcut)
  - :pencil: これまではスケジューラのグローバルなパラメータでしたが、プロファイル毎に選択できるようになります。
  - :pencil: `percentageOfNodesToScore`のデフォルト値は固定ではなく[クラスタのノード数から算出されるようになっています](https://github.com/kubernetes/kubernetes/blob/v1.26.0/pkg/scheduler/schedule_one.go#L550-L578)
    - :pencil: ノード数が100未満だったら100%。100以上の場合は`max(50 - #nodes/125, 5)`% (50%から緩やかに5%まで下がっていく)
    - :pencil: _ただし、対象ノード数が100未満だったら最低100ノードは評価_
- "Retriable and non-retriable pod failures for jobs" 機能がBetaになります。([#113360](https://github.com/kubernetes/kubernetes/pull/113360), [@mimowo](https://github.com/mimowo))
  - :pencil: [KEP-3329: Retriable and non-retriable Pod failures for Jobs](https://github.com/kubernetes/enhancements/issues/3329)
- 新しいPod APIフィールド `.spec.schedulingGates` が導入され、ユーザがPodをスケジュールreadyとマークできるようになりました。([#113274](https://github.com/kubernetes/kubernetes/pull/113274), [@Huang-Wei](https://github.com/Huang-Wei))
    - :pencil: [Pod Scheduling Readiness](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-scheduling-readiness/)

## :sparkle: FEATURE（機能追加）

- `preemption_victims`メトリクスのバケット(:pencil: ゲージ)が`LinearBuckets`から`ExponentialBuckets`に変更されました。([#112939](https://github.com/kubernetes/kubernetes/pull/112939), [@lengrongfu](https://github.com/lengrongfu))
  - :pencil: これまで: `metrics.LinearBuckets(5, 5, 10) // 5から5刻みで10個(50まで)`
  - :pencil: これから: `metrics: ExponentialBuckets(1, 2, 7) // 1から2倍刻みで7個(64まで)`
  - :pencil: これまで最小値が5だったので、それpreemptionされるpodの個数が5未満だと解像度が低かったのが改善されます。
- スケジューラがPod statusを更新する際、`net.ConnectionRefused`エラーだけでなく、`ServiceUnavailable`, `InternalError`エラーでもリトライするようになりました。([#111809](https://github.com/kubernetes/kubernetes/pull/111809), [@Huang-Wei](https://github.com/Huang-Wei))
- `goroutines`メトリクスがスケジューラに追加されました。これは`scheduler_goroutines`メトリクスの代替で、より正確にgoroutine数をカウントします。([#112003](https://github.com/kubernetes/kubernetes/pull/112003), [@sanposhiho](https://github.com/sanposhiho))
  - :pencil: これまで`scheduler`subsystemの中に`scheduler_goroutine`ってメトリクスがありました(なのでprometheusでは`scheduler_scheduler_goroutine`となってしまっていた)。加えて、goroutineのカウントされていない箇所がありました(具体的にはスケジューラ内の`parallelizer`というutil)。
  - :pencil: それを修正しつつ、互換性を加味して、リネームじゃなくて、追加されました。
- `ComponentSLIs` feature gateが有効な時、kube-schedulerで `/metrics/slis` endpointが利用可能になり、health checkメトリクスをscrapeできるようになりました。([#113026](https://github.com/kubernetes/kubernetes/pull/113026), [@Richabanker](https://github.com/Richabanker)) [sig/scheduling]
  - :pencil: [KEP-3466: Kubernetes Component Health SLIs](https://github.com/kubernetes/enhancements/issues/3466)

## :bug: バグ修正

- `podTopologySpread`プラグインでの計算ミスが修正されました。これにより想定外のスケジュール結果を防ぐことが出来ます。([#112507](https://github.com/kubernetes/kubernetes/pull/112507), [@kerthcet](https://github.com/kerthcet))
  - :pencil: 1.25にbackportされています
- `PodTopologySpread` pluginのTaint評価ロジックを`TaintToleration`プラグインのTaint評価ロジックと整合するようにしました。([#112357](https://github.com/kubernetes/kubernetes/pull/112357), [@SataQiu](https://github.com/SataQiu))
  - :pencil: 別々に実装されていた評価ロジックを共通化する変更です。
- Podのスケジュール処理がエラーにった場合に、Pod Conditionのreasonを`Unschedulable`ではなく`ScheduleError`で更新します。([#111999](https://github.com/kubernetes/kubernetes/pull/111999), [@kerthcet](https://github.com/kerthcet))
  - :pencil: v1.22以降のすべてのバージョンにbackportされています。
- `kube-scheduler`, `kube-controller-manager`でpod disruption関係のconditionをセットする際にServer Side Applyを使うようになります。([#113304](https://github.com/kubernetes/kubernetes/pull/113304), [@mimowo](https://github.com/mimowo))
- (:pencil: メトリクスの話です) スケジュール試行の結果が失敗またはスケジュール不能な場合の実行時間に、Unreserveオペレーションが含まれるようになり、長い時間が記録されるようになります。([#113113](https://github.com/kubernetes/kubernetes/pull/113113), [@kerthcet](https://github.com/kerthcet))
  - :pencil: `scheduling_attempt_duration_seconds`メトリクスについての修正です。

## :microscope: Other (その他の修正)

- kube-schedulerのComponent Config `v1beta3` がv1.26で非推奨になり、v1.29で削除予定です。また、`v1beta2`はv1.28で削除予定です。([#112257](https://github.com/kubernetes/kubernetes/pull/112257), [@kerthcet](https://github.com/kerthcet))
- v1.24でGAとなった`DefaultPodTopologySpread`, `NonPreemptingPriority`, `PodAffinityNamespaceSelector`, `PreferNominatedNode` feature gateがv1.26で削除されました。([#112567](https://github.com/kubernetes/kubernetes/pull/112567), [@SataQiu](https://github.com/SataQiu)) \[sig/scheduling\]
- v1.24でGAとなった`PodOverhead` feature gateが削除されました。([#112579](https://github.com/kubernetes/kubernetes/pull/112579), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev))
- スケジューラのdumperが各内部キューのpending pod数をサマリとして出力するようになります。([#111726](https://github.com/kubernetes/kubernetes/pull/111726), [@Huang-Wei](https://github.com/Huang-Wei))
  - :pencil: kube-schedulerは`USR2`シグナルを送ると内部キューの状態をダンプする機能(dumperと呼ばれます)がありますが、これまではPodの情報がすべてダンプされていて、キュー内のPod数が分かりづらい状況でした。
  - :pencil: が、この変更で `activeQ:100; backoffQ:1000; unschedulablePods:1500` のように各内部キューのPod数も一緒にダンプされるようになり少し読みやすくなりました。