Kubernetes 1.24がリリースされましたね ![]()
今回もKubernetes v1.24のCHANGELOGをベースにSIG Schedulingに関する機能について紹介していきます。
過去の変更内容:
- Kubernetes 1.23: SIG Scheduling の変更内容
- Kubernetes 1.22: SIG Scheduling の変更内容
- Kubernetes 1.21: SIG Scheduling の変更内容
1.24変更点の所感
- 多くのBeta FeatureがGAになりました:
DefaultPodTopologySpread,NonPreemptingPriority,PodOverhead,PreferNominatedNode,PodAffinityNamespaceSelector - 1.24での機能変更/修正は、利用が増えてきているからか、Pod Topology Spread関連が目立ちます。コミュニティからのフィードバックを受けて成長してきている印象です。
-
nodeAffinity/nodeSeletorの扱いの変更(Urgent Upgrade Notes) -
topologySpreadConstraint.minDomainの導入(API Changes)
-
- それ以外は小さな修正にとどまっています。
下記
がついた文章は、CHANGELOGの公式内容ではなく筆者の補足です。
What's new! (新情報)
Storage Capacity and Volume Expansion Are Generally Available
Kubernetes 1.24: SIG Storageの変更内容を参照ください。
NonPreemptingPriority to Stable
NonPreempting Priority Classの機能がStableになりました。
バッチ処理やデータサイエンスワークロードなどPreemptionされたくないワークロードがある場合に、Preemptionする側のPriorityClassに preemptionPolicy: Never を設定することでPreemptionを行わないPriorityClassを作成することができる機能です(default値はPreemptLowerPriority)。ただしscheduler内のQueueは優先度に応じて並ぶので低優先度のPodよりスケジュールされやすくなるため、バッチワークロードを低優先度に割り当てておくことで、バッチはリソースが空いているときにだけスケジュールされ、プリエンプションされない。が、実現出来ます。
NoPreempting Priority Classの例
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "このpriority classはpodをpreemptしない"
Preemptされる側でPreemptされるされないを制御できる
Preemption Toleration Scheduler Pluginというのもあります![]()
Urgent Upgrade Notes(必ず一読してからアップグレードしなければならない事項)
- Pod Topology Spreadでskew1を計算する際にスケジュールしようとしているPodのnode affinity/selectorにマッチしないノードで稼働しているPodを除外するようになりました。この変更によって、もしtopology spread対象のlabelにマッチするpodが、node affinty/selectorにマッチしないノードで稼働している場合、これまでscheduleされていたpodがunschedulableになる可能性があります。その場合はこのシナリオが発生しないようにnode affinityもしくはpod selectorを調整してください。(#107009, @kerthcet)
インパクトが有るのは、topology spread constraintとnode affinity/selectorの両方を使っている場合のみですのであまりケースは多くないかもしれません。が、どういうふうに治ったのか、説明してますので、興味ある方は折りたたみを開いてください。
おさらい
spec:
topologySpreadConstraints:
# 複数指定したらAND-ed
- topologyKey: zone # zone単位で散らしたい(node label keyを指定できる)
maxSkew: 1 # 各zoneに居るlabelSelectorにマッチする
labelSelector: # pod数の偏りは1以内にしたい
matchLabels:
foo: bar
whenUnsatisfiable: DoNotSchedule # maxSkewを満たせない場合はscheduleしない
# 'ScheduleAnyway'だと無視してスケジュールする
# 今回はこのnodeAffinity/nodeSelectorを一緒に使っている時の挙動が変わります
affinity:
nodeAffinity:
...
nodeSelector:
...
何が問題だったのか
4ノードが2つのゾーンに存在していて、zone1に2 pod, zone2に3 pod稼働しているとします。(label selectorは話を簡単にするために、全部マッチするとします。)
| zone | zone1 | zone2 | ||
|---|---|---|---|---|
| node | worker | worker2 | worker3 | worker4 |
| existingPods | pod1 | pod2 | pod3, pod4 | pod5 |
| vacancy | 空き無し | 空き無し | 空き有り | 空き有り |
ここに、zoneに対するpod topology spreadと worker3に対するanti node-affinity(worker3にはスケジュールされない) をもったtest podが作成されたとします。すると、
- test podはworker3にはscheduleされないはずなのに
- 各zoneのpod数はzone1=2, zone2=3となるので、新しいpodはzone 1にスケジュールされようとしますが、
- nodeに空きがないのでpendingになってしまう
という挙動が有りました。
どう治ったのか
test podのskewを計算するときにnode affinityを考慮するようになりました。なので、
- 各zoneのpod数はzone1=2, zone2=1となり、
- test podはzone2にスケジュールされることになります
副作用
上で説明したシナリオはこの挙動を修正することでスケジュールされやすいケースを意図的に紹介しましたが、当然逆もありえます。この変更によってこれまでscheduleされていたpodがunschedulableになることがあります。
各ゾーンに2nodeずつ、各nodeにはmatching pod(topology constraintにマッチするラベルを持つpod)が1個ずつ動いているとします。各ノードの利用状況はworker3だけ空きがないとします。
| zone | zone1 | zone2 | ||
|---|---|---|---|---|
| node | worker | worker2 | worker3 | worker4 |
| existingPods | pod1 | pod2 | pod3 | pod4 |
| vacancy | 空き有り | 空き有り | 空き無し | 空き有り |
このとき、worker4にanti-node affinityを持つpodが作成されたとします。そうすると
- 以前の挙動: zone1=2, zone2=2なので、どちらのzoneにもスケジュール可能となり、zone1のnodeが空いているので、worker,worker2のどちらかにスケジュールされる
- 修正後の挙動: zone1=2, zone2=1となるので、zone2にのみスケジュール可能となるが、worker3はリソース足りない、worker4はanti node affinityがついている、ので、結果としてunschedulableになってしまうという副作用がおきるので注意が必要です。
こぼれ話
もともと、pod topology constraintとnode affinityを両方指定した時の細かい挙動は議論の余地があり、upstreamでもこれをバグとして修正すべきなのか、backportするほどの修正なのか?が議論されていました。
が、Example: TopologySpreadConstraints with NodeAffinity に、TopologySpreadConstraintsはnodeSelectorをrespectする旨の言及が有り、今回修正されることになったようです。ただし、backportはされないことになりました。
ちなみに1.25ではNodeInclusionPoliciesと呼ばれていた新しいAPIが追加される予定です(KEP-3094,kubernetes/kubernetes#108492)。podTopologyConstraints[*].{nodeTaintsPolicy|nodeAffinityPolicy}というAPIが追加され、Ignore,Honorを指定することでユーザがskewの計算に対してnode affinity, taintをそれぞれ考慮するかどうかが選べるようになります。NodeInclusionPoliciesについての詳細は下記のスライドが詳しいです。
Deprecation(非推奨)
ありません
API Changes(API変更)
-
DefaultPodTopologySpreadがGAになりました(#108278, @kerthcet) -
NonPreemptingPriorityがGAになりました(#107432, @denkensk) -
PodOverheadがGAになりました(#108441,@pacoxu) - Pod affinity namespace selectorとcross-namespace quotaがGAになりました。
PodAffinityNamespaceSelectorfeature gateはlockされ1.26で削除されます。(#108136, @ahg-g)-
Inter-pod affinity and anti-affinity - Namespace selector: podAffinityTerm.namespaceSelectorをつかって他のnamespaceのpodとaffinity,anti-affinityを設定できる機能です -
Cross-namespace Pod Affinity Quota: Namespace selectorを使えるpod数をResourceQuotaで制限できる機能です
-
-
topologySpreadConstraintsにminDomainsフィールドが追加され、topology domain の最小数を制限できるようになりました(#107674, @sanposhiho)-
KEP-3022: Min domains in PodTopologySpread
-
feature gate: MinDomainsInPodTopologySpread, status:alpha -
topology spread constraintにおいて、どのくらいのdomain(zoneとかnode等)に分散させたいか?を調整できるパラメータです。例えば、minDomains:3と指定すると、最低3 domainに散らばるようにpodが配置されるようになります。 -
これまではspreadするdomain数を制御するパラメータが有りませんでした。
-
`minDomains`の挙動の具体例
おさらい
- schedulerは将来にわたってtopology domainが何種類あるか?はわからないので、
- 存在しているnode群からtopology domainを把握し、
- 存在しているdomain間でskewを計算し、
- maxSkewの範囲内でspreadされるようにスケジュールされます。
シナリオ
- 現在zone1, zone2が存在しており、zone3はcluster autoscaler等によって自動的に発生するとします。どのnodeもリソースは潤沢にあるとします。
- pod1, pod2, pod3という3つのpodを、
topology domain=zone, maxSkew=1, whenUnstatifiable=DoNotScheduleのconstraintでscheduleすることを考えます。 - かつ、少なくとも3 domainにspreadしたいと想定します
| zone | zone1 | zone2 | (zone3) |
|---|---|---|---|
| node name | nodeA | nodeK | (nodeX) |
minDomainが無いと何が困るか?
| step | zone1 | zone2 | (zone3) | skew |
|---|---|---|---|---|
| 1 | pod1 | 1 | ||
| 2 | pod1 | pod2 | 0 | |
| 3 | pod1 | pod2,pod3 | 1 |
- 1: pod1がzone1にスケジュールされたとする。
- 2: pod2はskewを小さくしたいのでzone2にスケジュールされる。
- 3: pod3は、zone3は存在しないので、zone1, zone2のどちらにスケジュールしてもskewは1でmaxSkewに反しないのでどちらかにスケジュールされる。
- リソースが潤沢にある場合、どれだけpod作ってもリソースがなくなるまで新しいdomainは出現しない。つまり3 domainにspreadされない
minDomainがあるとどうなるか?
minDomain: 3をつけると、spreadしているtopology domainの個数がそれに満たない場合は各domainのpod数の最大値をskewとします(skewは最大-最小なので、最小を0とするとも言えます)。こうすることでこのシナリオは下記のように変わります。
| step | zone1 | zone2 | zone3 | skew | comment |
|---|---|---|---|---|---|
| 1 | pod1 | 1 | |||
| 2 | pod1 | pod2 | 0 | ||
| 3' | pod1 | pod2,pod3 | 2 | maxSkew違反 | |
| 3 | pod1 | pod2 | pod3 | 0 |
- 1: pod1がzone1にスケジュールされたとする
- 2: pod2はskewを小さくしたいのでzone2にスケジュールされる。
- 3': pod3を作ると、spreadしているdomain数がまだ2なので、zone1, zone2どこにscheduleした仮定としてもと、skewは2になってしまう→つまりmaxSkew制約を満たせない→pod3はpendingになる。
- 3: するとCluster Autoscalerによってzone3が出現する。すると、skew=0となるスケジュールが可能となり、pod3はzone3にスケジュールされる
- 既存のdomainがminDomain未満だと各domainのpodがmaxSkew個になると制約を満たせなくなる。つまりdomainが足りない状態になる。
FEATURE(機能追加)
-
unschedulable queueからactive queueもしくはbackoff queueに強制的にflushする間隔を指定するdeprecatedなコマンドラインflagを追加しました(#108017, @denkensk)
-
もともとはReevaluate flushing unschedulable pods into activeQ kubernetes/kubernetes#87850で議論されているissueを一部カバーする機能です。 -
kubernetesはその分散システムとしての特性上、一度unschedulable判断されたPodが一定期間経過すると強制的にschedule可能かを再評価する仕組みがあります(単純に考えると一旦unschedulableになったpodはクラスタのステートが変化するまで再評価しなくても良い気がしますが、分散システムでは何があるかわからないため)。 -
ただ、この期間は現在までハードコードされ変更出来ない値でした。これが変更出来ない場合、一定期間ごとにactiveQにpodが戻ってくるため、高優先度のpodが大量にある場合、低優先度のpodがscheduling cycleに入れない状態が発生してしまう(kubernetes/kubernetes#86373)ため、設定可能にする修正です。 -
kubernetes/kubernetes#87850ではまだ具体的な解決策は出来ていないですが、この単純なしくみよりもより良い解決策を議論しており、はじめからdeprecatedで将来削除予定のflagとして追加されました。 -
scheduler内部の3つのQueue(active, unschedulable, backoff)については自作して学ぶKubernetes Schedulerが詳しいです。
-
-
CycleStateがwrite once and read many timesなユースケースに対して最適化されました. (#108724, @sanposhiho)-
CycleStateはscheduler pluginが使えるScheduling Cycleをライフサイクルとするcontextのようなものです。もともとsync.RWMutex排他制御されるmap[StateKey]StateDataでしたが、この修正でsyncMapに変更になりました。sync.Mapはwrite once and read many timesに最適化されたデータですが、もともとCycleStateのユースケースはそれに限定されていなかったのですが、これまでで、主なユースケースはPreFilterフェーズで事前計算を行って後続ではそれを参照するのみ、というケースが大半であることから変更されました。
-
-
PreferNominatedNodeがGAになりました。(#106619, @chendave)-
Schedulerのpreemptionは2 scheduling cyle掛けて行われます。
- scheduling cycle
C1: Preemptor PodPがnodeN上のVictim PodVをpreemptします。このcycleでP.status.nominatedNode: Nがセットされます。 - scheduling cycle
C2: Preemptor PodPが実際にスケジュールされる、という挙動をします。
- scheduling cycle
-
cycle C2において、nominatedNodeをまずチェックしてスケジュール可能であればそこにスケジュールするという機能です。通常のFilter→Scoreという処理を行いません。つまり、Node Score的により良いノードがいるかも知れない状況ですが、cycleC1でpreemption下nodeを優先的に選ぶという機能です。
-
- Scheduler Frameworkにおいて
PreFilter拡張点の返り値がStatusからPreFilterResultに変更になりました(#108648, @ahg-g)-
Scheduling Frameworkのメソッドの多くはframework.Statusというデータを返すようになっています。 -
現在のSchedulerのFilterフェーズ(スケジュール可能なノードを探すフェーズ)は、Podのスケジューリングのスループットを上げるためにPercentageOfNodesToScoreで指定した割合のnode数を見つけたらFilterフェーズを早期リターンする用になっているのですが、大規模なクラスタでまだスループットが足りないことが報告されていました。 -
なぜかというと、schedule不可能なPodは、Filterフェーズで全ノードがschanされてしまい、ここでスループットが落ちてしまうとのこと。 -
そこでPreFilterフェーズでFilterフェーズでscanするnodelistを返せるように変更したらどうか?という提案がなされてそれが、PrefilterResultという形で実現されました。 -
最初のユースケースとしてはNodAffinity Pluginが先にnodeAffinityをチェックしてnode対象のNodeの集合をPrefilterすることが想定されている模様。 -
Filterフェーズでは複数のPluginが実行されるので、各nodeを事前にスキップできると不必要なPlugin実行が軽減できスループットの向上が期待されます。
-
- kube-schedulerのインセキュアなコマンドラインフラグ(
--port)が削除されました。代わりに--bind-address,--secure-portを使ってください。(#106865, @jonyhy96) -
PodMaxUnschedulableQDuration(のデフォルト値)が5分にセットされました。(#108761, @denkensk)-
上で導入された値のデフォルトが5分になりました。バグ修正のセクションでも触れられているように現在は、unschedulablePodをactiveQ/backoffQに戻るタイミングをPluginが細かく制御できるため、デフォルト間隔が延長になりました。
-
- Extenderがエラーを返したときに、ログを出力するようになりました。(
--v>5) (#107974, @sanposhiho)
バグ修正
-
Scheduller pluginのevent registrationを修正し、unschedulable podがre-queueされる間隔が長くなってしまう問題を修正しました。(#109442, @ahg-g)
-
各Scheduler PluginはunschedulableなpodをactiveQもしくはbackoffQに戻す必要のあるクラスタイベント(Nodeの追加や更新等)を登録できますが、この登録が正しく行われていませんでした。 -
この修正はv1.21以降のすべてのバージョンにbacckportされています。
-
- Podの
.status.nominatedNodeNameが適切にクリアされずリソースが確保されいる状態になっていた問題を修正しました。(#106816, @Huang-Wei)-
上で説明したように、preemptionは2 scheduling cycleかけて行われますが、一般にC1(低優先度podをpreemptionするcycle)とC2(preeptorをscheduleするcycle)は連続していません。 -
この時、C1とC2の間のcycleでPがNにscheduleできなくなる事象が起きた際に適切にstatus.nominatedNodeが削除されず、scheduler上でP分のリソース利用が予約され続けてしまうバグがありました。 -
例えば、 C1とC2の間のcycleでより高い優先度のPodQがNにスケジュールされてしまってPが入る空きがなくなってしまった場合等です。 -

変更量が多いため1.23にのみbackportされています
-
-
v1beta3configを使った場合、out-of-tree scheduler pluginの想定外の順序で配置されていた問題を修正しました。(#108613, @Huang-Wei)-
default scheduler pluginよりも前にout-of-tree pluginが配置されてしまっていたようです。 -

1.23にbackportされています(v1beta3は1.23から導入されました)
-
- Pod数が少ない場合のPodToplogySpread score計算時の丸め誤差を改善し、より正確にscoreを考慮するようになりました。(#107384, @sanposhiho)
-
int(someFloat64Value)ではなくmath.Roundを使うようになりました。
-
- 実際にscheduleされているnodeへのnomiationを行わないようにしました。(#109245, @alculquicondor)
-
ある特定のシナリオで、scheduler内のcahceでpodが実際にスケジュールされているにも関わらずnominated podにもなってしまっており、リソースを余分に確保している状態になってしまっていた。
-
- Scheduler ExtenderのPreemptを呼んだ際に、後続の
pickOneNodeForPreemptionというfilter stepで必要な、NodeのNumPDBVioloationsを復元するように修正しました。(#105853, @caden2016)-
Scheduler ExtenderのPreemptは、kube-schedulerが選出した各victim候補ノードのvictim Pod群とPDB違反Pod数を入力として、それをfilter(victim候補から外して)して、新たなvictim候補と新たなPDB違反Pod数を返すことが出来ます。 -
SchedulerがExtenerのPreemptメソッドを呼んだ際、PDB違反POD数がリセットされてしまっていました。この値は最終的にvictim候補ノードを1個に絞るpickOneNodeForPreemptionという処理で使われます。 -
影響が大きそうな修正ですが、どのバージョンにもbackportはされていないようです。Scheduler ExtenderのしかもPreemptを使っている事例は殆どないのかもしれません。
-
Other (その他の修正)
-
PriorityClassをkubectl describeする際にPreemptionPolicyが表示されるようになりました。(#108701, @denkensk) - preemptionの詳細がscheduling失敗eventに追加されました。(#107775, @denkensk)
-
FailedSchedulingeventのMessageに↓のような詳細結果が記載されるようになりました。 -
preemption: not eligible due to preemptionPolicy=Never -
preemption: 0/4 nodes are available: 1 Insufficient cpu, 3 Preemption is not helpful for scheduling.
-
-
unschedulableQはunschedulablePodsにrenameされました(#108919, @denkensk)-
Qと名前がついていても実際にはFIFOやLIFOのような構造を持っておらず単なるmapだったので実際に即したrenameです。
-
- Scheduler frameworkの
runAllFiltersオプションが削除されました。(#108829, @kerthcet)-
もう使われていなかったオプションの削除です。
-
-
各topology domain(zoneやnode)にどのぐらい偏ってpodがスケジュールされているかを示す。具体的には、topology domainをzoneだとすると、各zoneにスケジュールされているpod数の最大値-最小値がskewとなります。 ↩