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になりました。
PodAffinityNamespaceSelector
feature 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で制限できる機能です
-
Inter-pod affinity and anti-affinity - Namespace selector:
-
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のpreemptionは2 scheduling cyle掛けて行われます。
- 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されています
-
上で説明したように、preemptionは2 scheduling cycleかけて行われますが、一般に
-
v1beta3
configを使った場合、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)
-
FailedScheduling
eventの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となります。 ↩