Kubernetes 1.25: SIG Scheduling の変更内容

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


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

過去の変更内容:
- [Kubernetes 1.24: SIG Scheduling の変更内容](https://qiita.com/everpeace/items/3cf879bfe1464947572b)
- [Kubernetes 1.23: SIG Scheduling の変更内容](https://qiita.com/everpeace/items/1b6ad68075d81ce315c4)
- [Kubernetes 1.22: SIG Scheduling の変更内容](https://qiita.com/everpeace/items/c7ab1c366dd777dce14a)

## 1.25変更点の所感

- v1.24に引き続きPod Topology Spread関連の機能追加が目玉となっています。
  - `NodeInclusionPolicy`
  - `EnableMatchLabelKeysInPodTopologySpread`
- kube-schedulerのComponentConfigがGAし`kubescheduler.config.k8s.io/v1`が利用可能になりました。
- それ以外は軽微なものばかりです。

> 下記 :pencil: がついた文章は、CHANGELOGの公式内容ではなく筆者の補足です。

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

:pencil: ありません

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

:pencil: ありません

# :wastebasket: Deprecation(非推奨)

:pencil: ありません

# :earth_asia: API Changes（API変更）

- Add `NodeInclusionPolicy` to `TopologySpreadConstraints` in PodSpec. ([#108492](https://github.com/kubernetes/kubernetes/pull/108492), [@kerthcet](https://github.com/kerthcet))
  - :pencil: [KEP-3094: take toleration/taints into considering when computing skew](https://github.com/kubernetes/enhancements/pull/3105)
  - :pencil: feature gate: `NodeInclusionPolicyInPodTopologySpread`(v1.25ではalpha)
  - :pencil: [Pod Topology Spreadの超最前線 MinDomains、NodeInclusionPoliciesについて](https://speakerdeck.com/sanposhiho/pod-topology-spreadfalsechao-zui-qian-xian-mindomains-nodeinclusionpoliciesnituite?slide=27)が詳しく解説しています
  - :pencil: `PodSpec.TopologySpreadConstraint.{NodeAffinityPolicy|NodeTaintsPolicy}={Ignore|Honor}` が設定できるようになります
    - :pencil: デフォルトがそれぞれ違うので注意です。それぞれのデフォルト値は `NodeAffinityPolicy=Honor`, `NodeTaintsPolicy=Ignore`です。[公式ドキュメント](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#interaction-with-node-affinity-and-node-selectors)では、nodeSelector/nodeAffinityはrespectするという表記がありますが、tolerationについては記載がなく、これまでrespectされていなかったためこの挙動になっていると思われます。
<details><summary>:pencil: <code>NodeAffinityPolicy=Ignore, Honor</code>の挙動の違い</summary><div>

<br />

**おさらい**

```yaml
spec:
  topologySpreadConstraints:
    # 複数指定したらAND-ed
  - topologyKey: zone # zone単位で散らしたい(node label keyを指定できる. domainと呼びます)
    maxSkew: 1        # 各zoneに居るlabelSelectorにマッチする
    labelSelector:    # このselectorにマッチするpod数の偏りは1以内にしたい
      matchLabels:
        foo: bar
    whenUnsatisfiable: DoNotSchedule # maxSkewを満たせない場合はscheduleしない
                                     # 'ScheduleAnyway'だと無視してスケジュールする
    # 今回追加されたのは下２つ
    nodeAffinityPolicy: Ignore|Honor # skewを計算する対象のnodeにpodのnode affinity/selectorを考慮するかどうか(default=Honor)
    nodeTaintsPolicy: Ignore|Honor   # skewを計算する時に対象のnodeにpodのtoleraitonを考慮するかどうか(default=Ignore)
```

**シナリオ**

4ノードが2つのゾーンに存在していて、zone1に2 pod, zone2に3 pod稼働しているとします。(label selectorは話を簡単にするために、全部マッチするとします。)。ここに、zoneに対するpod topology spreadと **worker3に対するanti node-affinity(worker3にはスケジュールされない)** をもった`test-pod`が作成されたとします。

<table>
  <thead>
    <tr>
      <th>zone</th>
      <th>zone1</th>
      <th></th>
      <th>zone2</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>node</td>
      <td>worker</td>
      <td>worker2</td>
      <td>worker3</td>
      <td>worker4</td>
    </tr>
    <tr>
      <td>existingPods</td>
      <td>pod1</td>
      <td>pod2</td>
      <td>pod3, pod4</td>
      <td>pod5</td>
    </tr>
    <tr>
      <td>vacancy</td>
      <td>空き無し</td>
      <td>空き無し</td>
      <td>空き有り</td>
      <td>空き有り</td>
    </tr>
    <tr>
      <td><code>test-pod</code>の<code>affinity</code></td>
      <td>○</td>
      <td>○</td>
      <td><strong>✗</strong></td>
      <td>○</td>
    </tr>
  </tbody>
</table>

**`NodeAffinityPolicy=Honor(default)`の挙動**

各nodeの対象pod数の計算の際、Podに指定されたnode-affinityにマッチするnodeだけが対象になります。すると、下記のようになるため、domain間の`skew`は　`2(zone1)-1(zone2)=1`　となり、`zone2`に`test-pod`をscheduleすれば、skewが下がるため、`zone2`にscheduleされようとします。`test-pod`は`worker3`へanti node-affinityを持っているため、結果として`worker4`にスケジュールされます。

<table>
  <thead>
    <tr>
      <th>zone</th>
      <th>zone1</th>
      <th></th>
      <th>zone2</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>node</td>
      <td>worker</td>
      <td>worker2</td>
      <td>worker3</td>
      <td>worker4</td>
    </tr>
    <tr>
      <td>existingPods</td>
      <td>pod1</td>
      <td>pod2</td>
      <td>pod3, pod4</td>
      <td>pod5</td>
    </tr>
    <tr>
      <td>vacancy</td>
      <td>空き無し</td>
      <td>空き無し</td>
      <td>空き有り</td>
      <td>空き有り</td>
    </tr>
    <tr>
      <td><code>test-pod</code>の<code>affinity</code></td>
      <td>○</td>
      <td>○</td>
      <td><strong>✗</strong></td>
      <td>○</td>
    </tr>
    <tr>
      <td>skew計算対象Pod数</td>
      <td>1</td>
      <td>1</td>
      <td><strong>対象外</strong></td>
      <td>1</td>
    </tr>
    <tr>
      <td>zone毎Pod数</td>
      <td colspan="2">2</td>
      <td colspan="2"><strong>1</strong></td>
    </tr>
    <tr>
      <td>TopologySpreadの<br/>zone選択</td>
      <td colspan="2">✗</td>
      <td colspan="2"><strong>○</strong></td>
    </tr>
    <tr>
      <td><code>test-pod</code>の<br/>最終的なschedule先</td>
      <td>✗</td>
      <td>✗</td>
      <td>✗</td>
      <td><strong>○</strong></td>
    </tr>
  </tbody>
</table>


**`NodeAffinityPolicy=Ignore`の挙動**

各nodeの対象pod数の計算は下記のようになるため、domain間の`skew`は　`3(zone2)-2(zone1)=1`　となり、`zone1`に`test-pod`をscheduleすれば、skewが下がるため、`zone1`にscheduleされようとしますが、`zone1`には空いているノードが存在しないため、`test-pod`は`Pending`になります。
<table>
  <thead>
    <tr>
      <th>zone</th>
      <th>zone1</th>
      <th></th>
      <th>zone2</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>node</td>
      <td>worker</td>
      <td>worker2</td>
      <td>worker3</td>
      <td>worker4</td>
    </tr>
    <tr>
      <td>existingPods</td>
      <td>pod1</td>
      <td>pod2</td>
      <td>pod3, pod4</td>
      <td>pod5</td>
    </tr>
    <tr>
      <td>vacancy</td>
      <td>空き無し</td>
      <td>空き無し</td>
      <td>空き有り</td>
      <td>空き有り</td>
    </tr>
    <tr>
      <td><code>test-pod</code>の<code>affinity</code></td>
      <td>○</td>
      <td>○</td>
      <td><strong>✗</strong></td>
      <td>○</td>
    </tr>
    <tr>
      <td>skew計算対象Pod数</td>
      <td>1</td>
      <td>1</td>
      <td><strong>2</strong></td>
      <td>1</td>
    </tr>
    <tr>
      <td>zone毎Pod数</td>
      <td colspan="2">2</td>
      <td colspan="2"><strong>3</strong></td>
    </tr>
    <tr>
      <td>TopologySpreadの<br/>zone選択</td>
      <td align="center" colspan="2"><strong>○</strong></td>
      <td align="center" colspan="2">✗</td>
    </tr>
    <tr>
      <td><code>test-pod</code>の<br/>最終的なschedule先</td>
      <td colspan="4"><strong>なし</strong></td>
    </tr>
  </tbody>
</table>

</div></details>

- v1.25はGolang 1.19を使うようになります。このPRではGAがまだ利用できないため1.19rc2を使っています。([#111254](https://github.com/kubernetes/kubernetes/pull/111254), [@dims](https://github.com/dims))
- Pod障害Policyに応じてPod障害を処理するようになりました ([#111113](https://github.com/kubernetes/kubernetes/pull/111113), [@mimowo](https://github.com/mimowo))
  - :pencil: [Kubernetes 1.25: SIG-API Machineryの変更内容](https://qiita.com/Ladicle/items/52ab4ecee5c8b1672fb5#:~:text=Pod%E9%9A%9C%E5%AE%B3Policy%E3%81%AB%E5%BF%9C%E3%81%98%E3%81%A6Pod%E9%9A%9C%E5%AE%B3%E3%82%92%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82)参照
- `DisruptionTarget` Pod Condition Typeが導入されます. `reason` フィールドはpod停止の理由を示します:
  - `PreemptionByKubeScheduler`: kube-schedulerによるPodのpreempt
  - `DeletionByTaintManager`: `NoExecute`taintに伴うtaint managerによるPodのdelete
  - `EvictionByEvictionAPI`: Eviction APIによるPodのevict
  - `DeletionByPodGC`: PodGCによるorphaned Podの削除
  - ([#110959](https://github.com/kubernetes/kubernetes/pull/110959), [@mimowo](https://github.com/mimowo))
  - :pencil: [KEP-3329: Retriable and non-retriable Pod failures for Jobs](https://github.com/kubernetes/enhancements/tree/master/keps/sig-apps/3329-retriable-and-non-retriable-failures)
  - :pencil: feature gate: `PodDisruptionConditions` (v1.25ではalpha)
  - :pencil: JobのPodが失敗した際に、この[`DisruptionTarget`typeのConditionを使ってpodが削除された際の失敗処理が定義できます](https://github.com/kubernetes/enhancements/tree/master/keps/sig-apps/3329-retriable-and-non-retriable-failures#story-2)
- kube-schedulerのComponentConfigがGAし、`kubescheduler.config.k8s.io/v1`が利用可能です。 `v1`では`SelectorSpread`Pluginは削除されています。([#110534](https://github.com/kubernetes/kubernetes/pull/110534), [@kerthcet](https://github.com/kerthcet))
  - :pencil: [v1.24でデフォルトでoffになっているPluginで](https://v1-24.docs.kubernetes.io/docs/reference/scheduling/config/#:~:text=enabled%20by%20default%3A-,SelectorSpread,-%3A%20Favors%20spreading%20across)、今後は`PodToplogySpread`を使うことが[推奨されています](https://github.com/kubernetes/enhancements/tree/master/keps/sig-scheduling/785-scheduler-component-config-api#proposal:~:text=Remove%20Plugin%20SelectorSpread%20(in%20favor%20of%20PodTopologySpread))。
- Local Storage Capacity Isolationが1.25でGAになります。 root file systemをチェックできないシステム(rootless)では、 kubeletの`--local-storage-capacity-isolation=false`を使って無効化してください。無効化されるとpodでlocal ephemeral storage request/limitやemptyDir sizeLimitが利用できなくなります. ([#111513](https://github.com/kubernetes/kubernetes/pull/111513), [@jingxu97](https://github.com/jingxu97))
- `NodeResourcesFit` pluginのエラーメッセージにおいて、許可されている`ScoringStrategyType`を表示するようになりました。([#111206](https://github.com/kubernetes/kubernetes/pull/111206), [@SataQiu](https://github.com/SataQiu))
  - :pencil: plugin初期化時(kube-scheduler起動時)のエラーメッセージの改善です。
  - :pencil: `E0718 07:13:34.754796    9974 run.go:74] "command failed" err="initializing profiles: creating profile for scheduler name default-scheduler: initializing plugin \"NodeResourcesFit\": scoring strategy XXXX is not supported, only [LeastAllocated MostAllocated RequestedToCapacityRatio] is accepted"` と出るようになります
- logging設定向けのGo APIが`k8s.io/component-base`から`k8s.io/component-base/logs/api/v1`に移動しました。 設定ファイルのフォーマットはコマンドラインフラグはこれまでと同じです。([#105797](https://github.com/kubernetes/kubernetes/pull/105797), [@pohly](https://github.com/pohly))
- `PodTopologySpread`がrolling upgrade時に尊重されるようになります。 ([#111441](https://github.com/kubernetes/kubernetes/pull/111441), [@denkensk](https://github.com/denkensk))
  - :pencil: [KEP-3243: respect pod topology spread after rolling upgrades](https://github.com/kubernetes/enhancements/pull/3244)
  - :pencil: feature gate: `EnableMatchLabelKeysInPodTopologySpread` (v1.25ではalpha)
  - :pencil: 解決したい現象
    - Deploymentをsurgeありでrolling updateすると、Topology SpreadがmaxSkewを越えてしまうことがありました。
    - 先に新しいReplicaSet Podが作成され、後から旧ReplicaSet Podが削除されますが、Pod Topology Spreadはscheduling時にしか有効じゃないので、古いReplicaSet Podが消されるときにmaxSkewを超えてしまう場合がありました。
    - ただ、通常Deployment配下のPodの`PodSpec.ToplogySpread.LabelSelector`は新旧ReplicaSetを跨って同じになるので、ReplicaSet　Pod毎にskewを計算するのは今のSpecだとできませんでした。
  - :pencil: 解決法
    - [KEP-3243: respect pod topology spread after rolling upgrades](https://github.com/kubernetes/enhancements/pull/3244)で、「skewを計算するPodの集合」を定義する、`PodSpec.TopologySpreadConstraint.MatchLabelKeys`というAPIが導入されました。このフィールドに定義されているlabelの値が等しいものをskew計算のグループとして捉えるようになります。
    - **通常Deploymentで使う場合は `matchLabelKeys: ["pod-template-hash"]` で良いです。**
    - こうするとReplicaSetそれぞれでskewが計算され、古いreplicasetは関係なくなります。

# :sparkle: FEATURE（機能追加）

- Feature gate `CSIMigration`がenabledに固定され、`CSIMigration`はGAになりました。このfeature gateは`v1.27`で削除予定です. ([#110410](https://github.com/kubernetes/kubernetes/pull/110410), [@Jiawei0227](https://github.com/Jiawei0227))
- `CSIMigrationAWS`がGAに昇格し、trueに固定されました。 ([#111479](https://github.com/kubernetes/kubernetes/pull/111479), [@wongma7](https://github.com/wongma7))
- `CSIMigrationGCE`がGAに昇格し、trueに固定されました。([#111301](https://github.com/kubernetes/kubernetes/pull/111301), [@mattcary](https://github.com/mattcary))

# :bug: バグ修正

- Preemption(PostFilter)中にPluginメトリクスが記録されなかったバグを修正しました。 ([#108727](https://github.com/kubernetes/kubernetes/pull/108727), [@sanposhiho](https://github.com/sanposhiho))
  - :pencil: 過去バージョンへはbackportされていません。
- Jobコントローラで`activeDeadlineSeconds`の設定が有効にならないバグを修正しました。 ([#110294](https://github.com/kubernetes/kubernetes/pull/110294), [@harshanarayana](https://github.com/harshanarayana))
  - :pencil: `JobReadyPods`feature gateが有効(v1.2４以降でデフォルトで有効)な場合に`activeDeadlineSeconds`を超えてもJob Podが削除されずに残るバグがありました。
  - :pencil: v1.22以降すべてのバージョンにbackportされています。
- CSI migrationでinline volumesがattach limitとしてカウントされないバグを修正しました。([#107787](https://github.com/kubernetes/kubernetes/pull/107787), [@Jiawei0227](https://github.com/Jiawei0227))
  - :pencil: CSI driverは`NodeGetInfoResponse`を使って`max_volumes_per_node`を定義できます(参照:[Volume Limits - Kubernetes CSI Developer Documentation](https://kubernetes-csi.github.io/docs/volume-limits.html))
  - :pencil: しかし、CSI Migrationされたpod spec内のvolumeがvolume limitsにカウントされていませんでした。
- scheduler plugin開発者向けの変更: Scheduler Frameworkが提供するShared PodInformerが空のIndexerで初期化されるようになりました。これによってpluginはカスタムIndexerを追加できるようになります。 Indexerの追加はコンフリクトしない限りにおいて追加可能です。([#110663](https://github.com/kubernetes/kubernetes/pull/110663), [@fromanirh](https://github.com/fromanirh))
  - :pencil: これまではIndexerが`nil`だったので、Indexerを使いたいためだけに、カスタムScheduling Pluginで自分でInformerを初期化していたのでとても便利になります。
  - :pencil: ただし、schedulerのShared Informerは[non-terminal podのみが対象](https://github.com/kubernetes/kubernetes/blob/v1.25.0/pkg/scheduler/scheduler.go#L454-L462)なので注意してください。

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

- kube-scheduler ComponentConfig `v1beta2`はv1.25でdeprecatedになりました。 ([#111547](https://github.com/kubernetes/kubernetes/pull/111547), [@kerthcet](https://github.com/kerthcet))