Slide 1

Slide 1 text

Cluster Autoscaler Kubernetes Meetup Tokyo #49(2022/03/29) @bells17

Slide 2

Slide 2 text

▶ @bells17 ▶ Software Engineer ▶ 普段やってること: + Kubernetes 関連コンポーネントの開発 + Kubernetes as a Service開発 ▶ Kubernetes SIG-Docs Japanese localization reviewer ▶ Kubernetes Internal Organizer ▶ #kubenews ▶ @bells17_

Slide 3

Slide 3 text

#kubenews ほぼ毎週⾦曜22:00~YouTubeで配信中 Kubernetes/Cloud Native関連のニュースを中⼼に技術雑談してます

Slide 4

Slide 4 text

今⽇話すこと ▶ Cluster Autoscalerとは? ▶ Cluster Autoscalerのアーキテクチャ ▶ Karpenterとの⽐較

Slide 5

Slide 5 text

このセッションでわかること ▶ Cluster Autoscalerがどんなものかなんとなくわかる ▶ Cluster AutoscalerどういうロジックでScaleUp/Downを⾏うのか?の雰囲気が掴める

Slide 6

Slide 6 text

注意点 ▶ Cluster Autoscaler v1.20.2 ベースでのお話になります(諸事情で若⼲古いです) + https://github.com/kubernetes/autoscaler/tree/cluster-autoscaler-1.20.2 ▶ あくまでCluster Autoscalerの実装を追った結果での理解の説明になるので、 間違いが含まれている可能性があります ▶ ぶっちゃけロジックが複雑過ぎるので、全体像を掴むのに問題無いと思う レベルで所々ある程度説明を端折ってたりまるめてます

Slide 7

Slide 7 text

Cluster Autoscalerとは?

Slide 8

Slide 8 text

Cluster Autoscaler ▶ Kubernetesクラスター上で動作するPodの要求リソースに応じて Nodeの台数を⾃動で増減させるコンポーネント ▶ Podの要求リソース(resource request)とNodeのキャパシティを⽐較する ことでNode台数の増減をコントロールする ▶ Node台数の調整を⾏うために、Kubernetesクラスター実⾏環境に応じた Cloud Providerの選択もしくは実装~組み込みが必要となる ▶ #sig-autoscalingによって管理 ▶ Kubernetes v1.8のタイミングでVersion 1.0 (GA)がリリースされた ▶ ScaleUp/ScaleDownを判定するためのロジックが本当に複雑…

Slide 9

Slide 9 text

公式でサポートされるCloud Provider ▶ AliCloud ▶ AWS ▶ Azure ▶ BaiduCloud ▶ CloudStack ▶ Cluster API ▶ DigitalOcean ▶ Exoscale ▶ GCP ▶ Huawei Cloud ▶ Ionos Cloud Managed Kubernetes ▶ kubemark ▶ OpenStack Magnum ▶ Packet この中に利用したい環境のCloud Providerが無い場合は自作する必要あり

Slide 10

Slide 10 text

Miroに書いたメモはこれくらいの量になった..

Slide 11

Slide 11 text

動作イメージ(ScaleUp)

Slide 12

Slide 12 text

ここに2台のWorker Nodeのあるk8sクラスターがあるとします LVCFDUMHFUOPEFTFMFDUPSOPEFSPMFLVCFSOFUFTJPDPOUSPMQMBOFPHP UFNQMBUF\\aO^^\\SBOHFJ JUFNJUFNT^^\\/P^^\\J^^\\OBNF^^ \\JUFNNFUBEBUBOBNF^^\\DQV^^\\JUFNTUBUVTDBQBDJUZDQV^^\\NFNPSZ^^ \\JUFNTUBUVTDBQBDJUZNFNPSZ^^\\aO^^\\FOE^^ /POBNFDOMMOQEQGDDTBOECPYXPSLFSDQVNFNPSZ,J /POBNFDOMMOQEQGDDTBOECPYXPSLFSDQVNFNPSZ,J

Slide 13

Slide 13 text

このクラスターでは下記のようにCPU=3のリソースを消費する Podが2台既に⽴ち上がっています LVCFDUMHFUQPEPHPUFNQMBUF\\aO^^\\SBOHFJ JUFNJUFNT^^\\/P^^\\J^^\\ OBNF^^\\JUFNNFUBEBUBOBNF^^\\DQV^^\\ JOEFYJUFNTQFDDPOUBJOFST SFTPVSDFTSFRVFTUTDQV^^\\aO^^\\FOE^^ /POBNFOHJOYDQV /POBNFOHJOYDQV

Slide 14

Slide 14 text

ここに更にCPU=3のリソースを消費する1台のPodを追加します DBUQPEZBNM BQJ7FSTJPOW LJOE1PE NFUBEBUB OBNFOHJOY OBNFTQBDFEFGBVMU TQFD DPOUBJOFST JNBHFOHJOY OBNFOHJOY SFTPVSDFT SFRVFTUT DQV LVCFDUMBQQMZGQPEZBNM QPEOHJOYDSFBUFE

Slide 15

Slide 15 text

新たに⽴ち上げようとしたPodはリソース不⾜でpendingステータスに なってしまいます LVCFDUMHFUQPE /".&3&"%:45"5643&45"354"(& OHJOY3VOOJOHE OHJOY3VOOJOHE OHJOY1FOEJOHT LVCFDUMEFTDSJCFQPEOHJOY தུ &WFOUT 5ZQF3FBTPO"HF'SPN.FTTBHF 8BSOJOH'BJMFE4DIFEVMJOHTEFGBVMUTDIFEVMFSOPEFTBSFBWBJMBCMFOPEF T IBEUBJOU\OPEFSPMFLVCFSOFUFTJPDPOUSPMQMBOFUSVF^ UIBUUIFQPEEJEOUUPMFSBUF *OTV⒏DJFOUDQV

Slide 16

Slide 16 text

しかしCluster Autoscalerがいると⾜りないリソースの分の Nodeをいつのまにか⾃動で追加してくれます LVCFDUMHFUOPEFTFMFDUPSOPEFSPMFLVCFSOFUFTJPDPOUSPMQMBOFPHP UFNQMBUF\\aO^^\\SBOHFJ JUFNJUFNT^^\\/P^^\\J^^\\OBNF^^ \\JUFNNFUBEBUBOBNF^^\\DQV^^\\JUFNTUBUVTDBQBDJUZDQV^^\\NFNPSZ^^ \\JUFNTUBUVTDBQBDJUZNFNPSZ^^\\aO^^\\FOE^^ /POBNFDOMMOQEQGDDTBOECPYXPSLFSDQVNFNPSZ,J /POBNFDOMMOQEQGDDTBOECPYXPSLFSDQVNFNPSZ,J /POBNFDOMMOQEQGDDTBOECPYXPSLFSDQVNFNPSZ,J

Slide 17

Slide 17 text

リソースが追加されたことでpendingだったPodが起動されました LVCFDUMEFTDSJCFQPEOHJOY 4UBUVT3VOOJOH தུ &WFOUT 5ZQF3FBTPO"HF'SPN.FTTBHF 8BSOJOH'BJMFE4DIFEVMJOHNTEFGBVMUTDIFEVMFSOPEFTBSFBWBJMBCMFOPEF T IBEUBJOU\OPEFSPMFLVCFSOFUFTJP DPOUSPMQMBOFUSVF^ UIBUUIFQPEEJEOUUPMFSBUF *OTV⒏DJFOUDQV 8BSOJOH'BJMFE4DIFEVMJOHNT YPWFSNT EFGBVMUTDIFEVMFSOPEFTBSFBWBJMBCMFOPEF T IBEUBJOU\OPEFSPMFLVCFSOFUFTJP DPOUSPMQMBOFUSVF^ UIBUUIFQPEEJEOUUPMFSBUF *OTV⒏DJFOUDQV 8BSOJOH'BJMFE4DIFEVMJOHNTEFGBVMUTDIFEVMFSOPEFTBSFBWBJMBCMFOPEF T IBEUBJOU\OPEFSPMFLVCFSOFUFTJP DPOUSPMQMBOFUSVF^ UIBUUIFQPEEJEOUUPMFSBUF OPEF T IBEUBJOU\OPEFLVCFSOFUFTJPOPUSFBEZ^ UIBUUIFQPEEJEOUUPMFSBUF *OTV⒏DJFOUDQV /PSNBM4DIFEVMFENTEFGBVMUTDIFEVMFS4VDDFTTGVMMZBTTJHOFEEFGBVMUOHJOYUPDOMMOQEQGDDTBOECPYXPSLFS /PSNBM5SJHHFSFE4DBMF6QNTDMVTUFSBVUPTDBMFSQPEUSJHHFSFETDBMFVQ<\TPNFOPEFHSPVQ NBY ^> /PSNBM1VMMJOHNTLVCFMFU1VMMJOHJNBHFOHJOY /PSNBM1VMMFENTLVCFMFU4VDDFTTGVMMZQVMMFEJNBHFOHJOYJOT /PSNBM$SFBUFENTLVCFMFU$SFBUFEDPOUBJOFSOHJOY /PSNBM4UBSUFENTLVCFMFU4UBSUFEDPOUBJOFSOHJOY

Slide 18

Slide 18 text

ScaleUp時のCluster Autoscalerのログはこんな感じ *TUBUJD@BVUPTDBMFSHP>4UBSUJOHNBJOMPPQ *LMPHYHP>1PEEFGBVMUOHJOYJTVOTDIFEVMBCMF *TDBMF@VQHP>6QDPNJOHOPEFT *TDBMF@VQHP>#FTUPQUJPOUPSFTJ[FTPNFOPEFHSPVQ *TDBMF@VQHP>&TUJNBUFEOPEFTOFFEFEJOTPNFOPEFHSPVQ *TDBMF@VQHP>'JOBMTDBMFVQQMBO<\TPNFOPEFHSPVQ NBY ^> *TDBMF@VQHP>4DBMFVQTFUUJOHHSPVQTPNFOPEFHSPVQTJ[FUP *FWFOU@TJOL@MPHHJOH@XSBQQFSHP>&WFOU W0CKFDU3FGFSFODF\,JOE$POpH.BQ /BNFTQBDFDMVTUFS BVUPTDBMFSSBODIFS /BNFDMVTUFSBVUPTDBMFSTUBUVT 6*%DEGCFGEDGCCD "1*7FSTJPOW 3FTPVSDF7FSTJPO 'JFME1BUI^ UZQF/PSNBMSFBTPO4DBMFE6Q(SPVQ4DBMFVQTFUUJOHHSPVQTPNFOPEFHSPVQTJ[FUP *FWFOU@TJOL@MPHHJOH@XSBQQFSHP>&WFOU W0CKFDU3FGFSFODF\,JOE$POpH.BQ /BNFTQBDFDMVTUFS BVUPTDBMFSSBODIFS /BNFDMVTUFSBVUPTDBMFSTUBUVT 6*%DEGCFGEDGCCD "1*7FSTJPOW 3FTPVSDF7FSTJPO 'JFME1BUI^ UZQF/PSNBMSFBTPO4DBMFE6Q(SPVQ4DBMFVQTFUUJOHHSPVQTPNFOPEFHSPVQTJ[FUP *FWFOU@TJOL@MPHHJOH@XSBQQFSHP>&WFOU W0CKFDU3FGFSFODF\,JOE1PE /BNFTQBDFEFGBVMU /BNFOHJOY 6*%EEFDEDDCGGFB "1*7FSTJPOW 3FTPVSDF7FSTJPO 'JFME1BUI^ UZQF /PSNBMSFBTPO5SJHHFSFE4DBMF6QQPEUSJHHFSFETDBMFVQ<\TPNFOPEFHSPVQ NBY ^>

Slide 19

Slide 19 text

動作イメージ(ScaleDown)

Slide 20

Slide 20 text

逆に先程⽴ち上げたPodを削除します LVCFDUMEFMFUFGQPEZBNM QPEOHJOYEFMFUFE

Slide 21

Slide 21 text

削除して少しするとNodeにTaintが付与されます LVCFDUMEFTDSJCFOPEFDOMMOQEQGDDTBOECPYXPSLFS /BNFDOMMOQEQGDDTBOECPYXPSLFS தུ 5BJOUT %FMFUJPO$BOEJEBUF0G$MVTUFS"VUPTDBMFS1SFGFS/P4DIFEVMF

Slide 22

Slide 22 text

Cluster Autocalerのログからunneeded nodeに選ばれTaintが付与されたことがわかります (削除⾃体はunneeded nodeに選ばれてから時間を少し置くのでまだされません) *TUBUJD@BVUPTDBMFSHP>4UBSUJOHNBJOMPPQ *TUBUJD@BVUPTDBMFSHP>$BMDVMBUJOHVOOFFEFEOPEFT *QSF@pMUFSJOH@QSPDFTTPSHP>4LJQQJOHDOMMOQKEGSRTBOECPYNBTUFSOPOPEFHSPVQDPOpH *TDBMF@EPXOHP>/PEFDOMMOQEQGDDTBOECPYXPSLFSJTOPUTVJUBCMFGPSSFNPWBMDQVVUJMJ[BUJPOUPPCJH *TDBMF@EPXOHP>/PEFDOMMOQEQGDDTBOECPYXPSLFSJTOPUTVJUBCMFGPSSFNPWBMDQVVUJMJ[BUJPOUPPCJH *TDBMF@EPXOHP>/PEFDOMMOQEQGDDTBOECPYXPSLFSDQVVUJMJ[BUJPO *TUBUJD@BVUPTDBMFSHP>DOMMOQEQGDDTBOECPYXPSLFSJTVOOFFEFETJODF 65$NEVSBUJPOT *TUBUJD@BVUPTDBMFSHP>4DBMFEPXOTUBUVTVOOFFEFE0OMZGBMTFMBTU4DBMF6Q5JNF 65$NMBTU4DBMF%PXO%FMFUF5JNF65$ NMBTU4DBMF%PXO'BJM5JNF65$NTDBMF%PXO'PSCJEEFOGBMTF JT%FMFUF*O1SPHSFTTGBMTFTDBMF%PXO*O$PPMEPXOGBMTF *TUBUJD@BVUPTDBMFSHP>4UBSUJOHTDBMFEPXO *TDBMF@EPXOHP>DOMMOQEQGDDTBOECPYXPSLFSXBTVOOFFEFEGPST *TDBMF@EPXOHP>/PDBOEJEBUFTGPSTDBMFEPXO *EFMFUFHP>4VDDFTTGVMMZBEEFE%FMFUJPO$BOEJEBUF5BJOUPOOPEFDOMMOQEQGDDTBOECPYXPSLFS

Slide 23

Slide 23 text

しばらくしてNodeを確認すると3台⽬のNodeが削除されているのが確認できます LVCFDUMHFUOPEFTFMFDUPSOPEFSPMFLVCFSOFUFTJPDPOUSPMQMBOFPHP UFNQMBUF\\aO^^\\SBOHFJ JUFNJUFNT^^\\/P^^\\J^^\\OBNF^^ \\JUFNNFUBEBUBOBNF^^\\DQV^^\\JUFNTUBUVTDBQBDJUZDQV^^\\NFNPSZ^^ \\JUFNTUBUVTDBQBDJUZNFNPSZ^^\\aO^^\\FOE^^ /POBNFDOMMOQEQGDDTBOECPYXPSLFSDQVNFNPSZ,J /POBNFDOMMOQEQGDDTBOECPYXPSLFSDQVNFNPSZ,J

Slide 24

Slide 24 text

EventをみるとScaleDownによってnodeが削除されるのが確認できます LVCFDUMHFUFWFOUT -"454&&/5:1&3&"40/0#+&$5.&44"(& NT/PSNBM3FNPWJOH/PEFOPEFDOMMOQEQGDDTBOECPYXPSLFS /PEFDOMMOQEQGDDTBOECPYXPSLFSFWFOU3FNPWJOH/PEFDOMMOQEQGDD TBOECPYXPSLFSGSPN$POUSPMMFS NT/PSNBM4DBMF%PXOOPEFDOMMOQEQGDDTBOECPYXPSLFS OPEFSFNPWFECZDMVTUFSBVUPTDBMFS

Slide 25

Slide 25 text

ScaleDown時のCluster Autoscalerのログはこんな感じ *TUBUJD@BVUPTDBMFSHP>DOMMOQEQGDDTBOECPYXPSLFSJTVOOFFEFETJODF 65$NEVSBUJPONT *TUBUJD@BVUPTDBMFSHP>4DBMFEPXOTUBUVTVOOFFEFE0OMZGBMTFMBTU4DBMF6Q5JNF 65$NMBTU4DBMF%PXO%FMFUF5JNF65$N MBTU4DBMF%PXO'BJM5JNF65$NTDBMF%PXO'PSCJEEFOGBMTFJT%FMFUF*O1SPHSFTTGBMTF TDBMF%PXO*O$PPMEPXOGBMTF *TUBUJD@BVUPTDBMFSHP>4UBSUJOHTDBMFEPXO *TDBMF@EPXOHP>DOMMOQEQGDDTBOECPYXPSLFSXBTVOOFFEFEGPSNT *TDBMF@EPXOHP>4DBMFEPXOSFNPWJOHFNQUZOPEFDOMMOQEQGDDTBOECPYXPSLFS *FWFOU@TJOL@MPHHJOH@XSBQQFSHP>&WFOU W0CKFDU3FGFSFODF\,JOE$POpH.BQ /BNFTQBDFDMVTUFSBVUPTDBMFSSBODIFS /BNFDMVTUFSBVUPTDBMFSTUBUVT 6*%DEGCFGEDGCCD "1*7FSTJPOW 3FTPVSDF7FSTJPO 'JFME1BUI^ UZQF /PSNBMSFBTPO4DBMF%PXO&NQUZ4DBMFEPXOSFNPWJOHFNQUZOPEFDOMMOQEQGDDTBOECPYXPSLFS *FWFOU@TJOL@MPHHJOH@XSBQQFSHP>&WFOU W0CKFDU3FGFSFODF\,JOE$POpH.BQ /BNFTQBDFDMVTUFSBVUPTDBMFSSBODIFS /BNFDMVTUFSBVUPTDBMFSTUBUVT 6*%DEGCFGEDGCCD "1*7FSTJPOW 3FTPVSDF7FSTJPO 'JFME1BUI^ UZQF /PSNBMSFBTPO4DBMF%PXO&NQUZ4DBMFEPXOSFNPWJOHFNQUZOPEFDOMMOQEQGDDTBOECPYXPSLFS *EFMFUFHP>4VDDFTTGVMMZBEEFE5P#F%FMFUFE5BJOUPOOPEFDOMMOQEQGDDTBOECPYXPSLFS *FWFOU@TJOL@MPHHJOH@XSBQQFSHP>&WFOU W0CKFDU3FGFSFODF\,JOE/PEF /BNFTQBDF /BNFDOMMOQEQGDD TBOECPYXPSLFS 6*%GBGCBFFBDFCCDFCGE "1*7FSTJPOW 3FTPVSDF7FSTJPO 'JFME1BUI^ UZQF/PSNBMSFBTPO 4DBMF%PXOOPEFSFNPWFECZDMVTUFSBVUPTDBMFS

Slide 26

Slide 26 text

動作イメージまとめ ▶ 紹介したイメージのようにPodのリソース要求に対してNode側が リソース不⾜に陥った場合、Cluster AutoscalerはNodeの増加を⾏い、 リソースを補填する ▶ 反対にScaleDownを⾏う際は、削除可能なNodeのシミュレートを⾏い、 unneededなNodeが⾒つかった場合は⼀定の時間を置いてから、 uneededなNodeの削除を⾏い台数の削減を⾏う ▶ uneededなNodeが⾒つかった際、基本的には削除前に ”DeletionCandidateOfClusterAutoscaler”というTaintが付与され、 対象NodeにPodがスケジューリングされるのを防⽌する ▶ 削除処理中は”ToBeDeletedTaint”というTaintが付与され、削除処理が 実施中であることが確認できるようになっている

Slide 27

Slide 27 text

Cluster Autoscalerのアーキテクチャ

Slide 28

Slide 28 text

Cluster Autoscaler Overview

Slide 29

Slide 29 text

主なプロセス(go routineなど) ▶ main loop: ScaleUp/ScaleDown処理を担当 ▶ CloudProviderNodeInstancesCache Refresh loop: + 各NodeGroupに所属するInstance(Node)⼀覧のキャッシュを定期的に リフレッシュする ▶ http server: 以下の2つのEndpointを提供するhttp server ▶ /metrics: prometheus形式のメトリクス取得⽤Endpoint ▶ /health-check: ヘルスチェック⽤Endpoint ▶ 起動するhttp serverのデフォルトポートは8085

Slide 30

Slide 30 text

CloudProviderNodeInstancesCache Refresh loop 以下の処理を2分毎に実⾏してるだけ // Refresh refreshes cache. func (cache *CloudProviderNodeInstancesCache) Refresh() { klog.Infof("Start refreshing cloud provider node instances cache") refreshStart := time.Now() nodeGroups := cache.cloudProvider.NodeGroups() cache.removeEntriesForNonExistingNodeGroupsLocked(nodeGroups) for _, nodeGroup := range nodeGroups { nodeGroupInstances, err := nodeGroup.Nodes() if err != nil { klog.Errorf("Failed to get cloud provider node instance for node group %v, error %v", nodeGroup.Id(), err) } cache.updateCacheEntryLocked(nodeGroup, &cloudProviderNodeInstancesCacheEntry{nodeGroupInstances, time.Now()}) } klog.Infof("Refresh cloud provider node instances cache finished, refresh took %v", time.Now().Sub(refreshStart)) } https://github.com/kubernetes/autoscaler/blob/cluster-autoscaler-1.20.2/cluster-autoscaler/clusterstate/utils/node_instances_cache.go#L154-L176

Slide 31

Slide 31 text

ScaleUp/Down おおまかな流れ (簡単に説明するためにめちゃくちゃ省略してます)

Slide 32

Slide 32 text

1. 初期化処理 1. Node/Pod情報の取得 2. CloudProvider情報のRefresh 3. ClusterStateRegistry(Cluster内のNodeGroup/Node/ScaleUp/ScaleDown/etc のデータを ⼊れるオブジェクト)のデータを更新 4. ClusterSnapshot(Schedulingのシュミレーションを⾏うためのNode/Podを詰め込んだオブ ジェクト)のデータを構築

Slide 33

Slide 33 text

2. NodeGroup/Nodeの状態チェック 1. CloudProvider側のInstance情報には存在して、k8s Nodeには存在しない Node(unregisteredNodes)が登録から⼀定時間経過していれば、そのNodeを 削除して処理を中断する + ⻑期間unregisteredNodesである=Nodeのprovisioningに失敗したとみなされたので削除さ れる 2. ClusterStateRegistryにUnreadyであると判断されたNode数が⼀定数を超えた場合はCluster がUnhealthyだと判断されたら処理を中断する 3. Node作成中にCloudProviderからエラーだと判断されたInstanceがあった場合、それらの Instanceを削除 4. Cloud Providerの各NodeGroupのInstance数がNodeGroupの想定サイズ内に収まっていない 場合、Instance数をNodeGroupの想定サイズに収まるように台数削減を⾏い、処理を中断する

Slide 34

Slide 34 text

3. ScaleUp 1. unschedulableなPodが存在しなければScaleUpをSkip(そもそもScaleUpする必要が無い) 2. CloudProviderから取得できるNodeGroupをフィルタリング~unschedulablePodの スケジューリングシュミレーションをパスしたNodeGroupをScaleUp候補にする 3. ScaleUp候補のNodeGroupの中からExpanderを⽤いて最終的にScaleUpを⾏うNodeGroupを 選定する + 選択可能なExpanderはrandom/most-pods/least-waste/price/priorityの5つ 4. 選定されたNodeGroupと類似のNodeGroupをScaleUpを⾏うNodeGroupに追加する + 類似NodeGroupの追加は“--balance-similar-node-groups”オプションをtrueに設定していた 場合のみ 5. ScaleUpを⾏うNodeGroup⼀覧の内、どのNodeGroupを何台増加するかの調整を⾏う + NodeGroupの台数増加は可能な限り均等に⾏われる + トータルで増加するNode台数はスケジューリングシュミレーションの際に決定される 6. 上記の調整内容を元にScaleUpを実⾏ ~ 成功したら処理を終了(ScaleDownプロセスには移⾏しない)

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

4. ScaleDown準備 1. ScaleUpが⾏われなかった場合はScaleDownに関する処理を開始 2. Nodeリソース(GPU or CPU or Mem)の中で最も使⽤率の⾼いリソースの使⽤率が⼀定値以下 であるNodeからScaleDown候補となるuneededNode⼀覧の⽣成を⾏う a. emptyNode: 以下のような条件を満たすPodを持たないNode 1. Mirro Podではない 2. DaemonSet Podではない 3. ReplicaSetやJobなどに紐付いていない 4. etc b. ⾮emptyNodeの中で以下のような条件をNode + 対象Nodeの削除をブロッキングするPodが無い + 対象Nodeで現在動作しているPodの中で再スケジューリングが必要なPodが全て他のNodeに 再スケジューリングできる + 再スケジューリングできるかはScheduling Framework PreFilter/Filterによって検証する

Slide 37

Slide 37 text

5. ScaleDown 1. unneededNodesに追加されてから⼀定時間が経過したNodeを対象にScaleDown処理を開始 2. 対象となるunneededNodesからemptyNodesを抽出 3. emptyNodesが存在すれば全てのemptyNodesの削除を実施 + 削除処理はNode単位でgo routineを利⽤して⾮同期に⾏う + 削除前にNodeに”ToBeDeletedTaint”Taintを付与する 4. 削除開始したemptyNodesが1つ以上あった場合はScaleDown処理を終了する 5. emptyNodesの削除が⾏われなかった場合は、⾮emptyNodesの削除処理を⾏う 6. ⾮emptyNodesの中から1つのNodeを取り出しemptyNodesの際と同様にgo routineで⾮同期 での削除を開始する 7. もし、ScaleDownを開始するNodeが1つもなかった場合はunneededNodesに対して "DeletionCandidateOfClusterAutoscaler"Taintの付与を⾏う

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

補⾜ ▶ 説明したロジックはわかりやすさ重視のためめちゃくちゃ簡略化してます ▶ スライド最後のAppendixにもう少し詳しいフローを書いてますので興味ある⽅はそちらも ⾒てみてください

Slide 40

Slide 40 text

Karpenterとの⽐較

Slide 41

Slide 41 text

Karpenter ▶ AWSが作成した独⾃のCluster Autoscaler ▶ 最新バージョンは現在v0.7.3なのでまだGAしてない? + 今回調査したKarpenterもこのバージョン ▶ controller-runtimeによるOperator形式で実装されている ▶ autoscalingを⾏うための設定をprovisioning Custom Resourceとして定義 することでautoscalingを実現 ▶ Cloud Provider機能によって任意の実⾏環境でKarpenterを動作させること ができるが、現在の実装はAWSのみ ▶ コードの実装がCluster Autoscalerの1000倍くらい簡単でコードもきれい logo: https://github.com/aws/karpenter/blob/v0.7.3/website/static/logo.png

Slide 42

Slide 42 text

KarpenterとCluster Autoscaler ▶ https://karpenter.sh/v0.7.3/concepts/ によるとCluster Autoscalerと ⽐べ以下のような特徴があるとのこと + Group-less node provisioning: + NodeGroupのような概念はなく、直接provisioningするNodeを管理する + またKarpenterによって起動された訳ではないNodeはScaleDownしない + Scheduling enforcement: + kube-schedulerを介さずにbinpackingしたPodを provisioningしたNodeにKarpenterが直接スケジュールする + なので例えばCSIStorageCapacityの情報など、kube-schedulerであればScheduling Frameworkがスケジューリング可能かをチェックするような厳密なチェックを⾏わずに スケジューリングを⾏う + Designed to handle the full flexibility of the cloud: + クラウド側インスタンスタイプの柔軟な選択が可能 logo: https://github.com/aws/karpenter/blob/v0.7.3/website/static/logo.png

Slide 43

Slide 43 text

Karpenter Overview

Slide 44

Slide 44 text

Karpenter ScaleUp

Slide 45

Slide 45 text

Karpenter ScaleDown

Slide 46

Slide 46 text

まとめ

Slide 47

Slide 47 text

感想 ▶ ScaleUp/Downのロジックがめっちゃ複雑だった… ▶ とはいえ計算対象からMirror PodやDaemonSet Podなどを取り除く必要があるなど、 いろんなロジックで計算しなければいけないことを知れたのは⾯⽩かった ▶ また、シミュレーションの際にPreFilter/Filterを⾏ったチェックをしていたり、ScaleUpが 必要なNode数をFirst Fit Decreasing bin-packing approximation algorithmという アルゴリズムでPodの箱詰めを⾏って計算しているというやり⽅も興味深かった ▶ Karpenterのロジックと⽐べるとだいぶ複雑なので、ある程度Karpenterで間に合うレベル であればこのくらいシンプルな⽅が個⼈的には良いかなという気も… + とはいえスケジューリング可能かのチェックがかなり簡易的だしKarpenter側で直接 Nodeにスケジューリングしてる箇所は場合によっては問題を発⽣させる可能性が⾼く なるので難しいところ

Slide 48

Slide 48 text

参考資料 ▶ https://github.com/kubernetes/autoscaler/tree/cluster-autoscaler-1.20.2/cluster-autoscaler ▶ https://github.com/kubernetes/autoscaler/tree/cluster-autoscaler-1.20.2 ▶ https://kubernetes.io/blog/2016/07/autoscaling-in-kubernetes/ ▶ https://docs.oracle.com/ja-jp/iaas/Content/ContEng/Tasks/contengusingclusterautoscaler.htm ▶ https://docs.aws.amazon.com/eks/latest/userguide/autoscaling.html ▶ https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler ▶ https://docs.microsoft.com/ja-jp/azure/aks/cluster-autoscaler ▶ https://en.wikipedia.org/wiki/Bin_packing_problem ▶ https://en.wikipedia.org/wiki/First-fit-decreasing_bin_packing ▶ https://github.com/aws/karpenter/tree/v0.7.3 ▶ https://karpenter.sh/v0.7.3/concepts/ ▶ https://karpenter.sh/v0.7.3/provisioner/ ▶ https://karpenter.sh/v0.7.3/aws/provisioning/ ▶ https://github.com/aws/karpenter/blob/v0.7.3/designs/v1alpha4-api.md ▶ https://github.com/aws/karpenter/blob/v0.7.3/designs/termination.md ▶ https://github.com/aws/karpenter/blob/v0.7.3/designs/limits.md ▶ https://github.com/aws/karpenter/blob/v0.7.3/designs/bin-packing.md

Slide 49

Slide 49 text

Thanks / Question? ▶ @bells17 ▶ Slide: https://speakerdeck.com/bells17 ▶ @bells17_

Slide 50

Slide 50 text

Appendix 本番はここから!

Slide 51

Slide 51 text

インデックス ▶ Cloud Provider実装について ▶ 主なオプション ▶ ClusterSnapshot ▶ PredicateChecker ▶ Estimator ▶ Expander⼀覧 ▶ ClusterStateStrategy ▶ ScaleDown props ▶ ScaleUp/Down ざっくりフロー

Slide 52

Slide 52 text

Cloud Provider実装について

Slide 53

Slide 53 text

Cloud Provider ▶ Cluster Autoscalerに組み込んで実⾏される ▶ Goで定義されたインターフェイスのメソッドを実装して組み込むこと で、独⾃のCloud Provider実装をCluster Autoscalerから利⽤することが できる ▶ Goのインターフェイスであるインターフェイスは↓にある https://github.com/kubernetes/autoscaler/blob/cluster-autoscaler- 1.20.2/cluster-autoscaler/cloudprovider/cloud_provider.go

Slide 54

Slide 54 text

CloudProvider Interface type CloudProvider interface { Name() string NodeGroups() []NodeGroup NodeGroupForNode(*apiv1.Node) (NodeGroup, error) Pricing() (PricingModel, errors.AutoscalerError) GetAvailableMachineTypes() ([]string, error) NewNodeGroup(machineType string, labels map[string]string, systemLabels map[string]string, taints []apiv1.Taint, extraResources map[string]resource.Quantity) (NodeGroup, error) GetResourceLimiter() (*ResourceLimiter, error) GPULabel() string GetAvailableGPUTypes() map[string]struct{} Cleanup() error Refresh() error }

Slide 55

Slide 55 text

NodeGroup Interface type NodeGroup interface { MaxSize() int MinSize() int TargetSize() (int, error) IncreaseSize(delta int) error DeleteNodes([]*apiv1.Node) error DecreaseTargetSize(delta int) error Id() string Debug() string Nodes() ([]Instance, error) TemplateNodeInfo() (*schedulerframework.NodeInfo, error) Exist() bool Create() (NodeGroup, error) Delete() error Autoprovisioned() bool }

Slide 56

Slide 56 text

独⾃のCloud Providerを実装する ▶ CloudProvider/NodeGroupの2つのインターフェイスを満たした実装を⾏う ▶ 上記CloudProviderインスタンスを⽣成するBuilderを実装する ▶ Cluster Autoscalerのmain.goをコピーして、上記Builderを利⽤してCloud Providerを ⽣成できるように書き換える (Cluster Autoscalerはライブラリとして分離されてないためこういった対応が必要) ▶ mainifestsファイルについては各CloudProviderのexampleにあるものが参考になる

Slide 57

Slide 57 text

注意点 ▶ NodeGroup内のNode削除を⾏うDeleteNodesは実際のNodeの削除完了が成功するまで 待機する必要がある ▶ Cloud Provider Interfaceの中には”NewNodeGroup”というメソッドがあり、まるで現在 稼働中のクラスターに新たなNodeGroupを追加できるかのように思えるが、実際にはこの メソッドが利⽤されることは無い点に注意 + これは現在最新のv1.23の実装でも同様 + NewNodeGroupメソッドを利⽤した新たなNodeGroupの追加を⾏いたい場合は、 Cluster Autoscalerの拡張ポイントの1つであるNodeGroupManager processorの独⾃実装 が必要 + 不要なNodeGroupを削除するNodeGroup.Deleteも同様に呼び出されることは無い

Slide 58

Slide 58 text

主なオプション

Slide 59

Slide 59 text

主なオプション オプション名 説明 cloud-provider 利⽤するCloud Provider cloud-config 選択したCloud Providerに渡される設定ファイルのパス Cloud側に接続するためのAPIキーなどを主に設定する scan-interval Cluster Autoscalerのループ間隔を設定する デフォルト: 10秒 namespace Cluster Autoscalerを起動するNamespace Statusを保存するConfigMapとLeaderElectionのResourceLockに指定したNamespaceが利⽤される デフォルト: kube-system node-autoprovisioning-enabled 本来であればtrueにすれば必要に応じて新たなNodeGroupを作成してくれるオプションなはず しかし実装が無いので現実には機能しない scale-down-enabled ScaleDown機能を有効にするかどうか? デフォルト: true balance-similar-node-groups trueにするとScaleUpの際に選ばれたNodeGroupと類似のNodeGroupをScaleUp処理に追加するようになる expander ScaleUpの際に候補となるNodeGroupから実際にScaleUpするNodeGroupを選ぶ際のアルゴリズムを選択する 組み込まれてるexpanderはrandom/most-pods/least-waste/price/priorityの5つ デフォルト: random estimator NodeGroupをScaleUpする際に何台のNode追加すれば対象NodeGroupのNodeにスケジューリング可能なunschedulablePodを全て スケジューリングできるかを計算するアルゴリズムを選択する 組み込まれてるのはbinpacking1つのみ max-nodes-total Cluster AutoscalerがScaleUpする総Node数(このNode数を超えるサイズにはScaleUpされない) デフォルト: 0(無制限) cores-total クラスターの最⼩:最⼤CPU数(この範囲を超えるサイズにはScaleUp/Downを⾏わない) デフォルト: 0: 320000 (単位: コア数 format <最⼩値>:<最⼤値>) memory-total クラスターの最⼩:最⼤メモリ数(この範囲を超えるサイズにはScaleUp/Downを⾏わない) デフォルト: 0: 6400000(単位: GiB format <最⼩値>:<最⼤値>)

Slide 60

Slide 60 text

ClusterSnapshot

Slide 61

Slide 61 text

ClusterSnapshot ▶ ClusterSnapshotはCluster内のNodeと各NodeにスケジューリングされたPodを管理する オブジェクト ▶ 主にPredicateCheckerによる対象Podをスケジューリング可能なNodeを探査するために利⽤ される ▶ PredicateCheckerはClusterSnapshotをScheduling Frameworkのlisterとして振る舞わせるこ とでClusterSnapshotのデータを元にScheduling Frameworkに処理をさせる

Slide 62

Slide 62 text

PredicateChecker

Slide 63

Slide 63 text

PredicateChecker ▶ PredicateCheckerはScaleUp/Downを⾏うための以下のチェックを⾏うためのインターフェイス + 対象のPodを対象のNodeにスケジューリング可能か? + 対象のPodをスケジューリング可能なNodeはどれか? ▶ 実装はScheduling FrameworkのPreFilter/Filterによってチェックを⾏う SchedulerBasedPredicateChecker1つのみ + Scheduling Frameworkに設定はデフォルトでのみ使⽤可 + 設定変更などしたい場合は独⾃実装が必要 ▶ ScaleUpでの主な利⽤箇所: + 対象NodeGroupにスケジューリング可能なPodがあるかの判定に利⽤ + Binpacking AlgorithmによるPodを詰め込めるNodeの検索に利⽤ ▶ ScaleDownでの主な利⽤箇所: + ScaleDown候補のNodeで動かしている再スケジューリングが必要なPodの 再スケジューリング先のNodeを探すために利⽤

Slide 64

Slide 64 text

Estimator

Slide 65

Slide 65 text

Estimator ▶ ScaleUpを⾏う際に対象NodeGroupのNodeを何台増やせばよいかを計算するために利⽤される ▶ First Fit Decreasing bin-packing approximation algorithm(bin-packing algorithm)という アルゴリズムを⽤いて計算される ▶ ⼤まかな⼿順は以下 + Podの要求リソース(CPU+Memory)をスコアリングする + スコアを元にサイズの⼤きなもの順にPodをソート + ソートされた順番で以下のようにPodのスケジューリングをテスト + PredicateCheckerを使⽤してPodをスケジューリング可能なNodeを探す + ⾒つかればClusterSnapshot内の対象NodeにPodを追加 + ⾒つからなければClusterSnapshot内に新規Nodeを作成し、PodをそのNodeに追加する + 上記テストを通して新規追加されたNode数を計算する

Slide 66

Slide 66 text

PredicateChecker Overview

Slide 67

Slide 67 text

Expander⼀覧

Slide 68

Slide 68 text

Expander⼀覧 ▶ random: ランダムにNodeGroupを選択 ▶ most-pods: スケジューリング可能なunschedulablePodの種類が最も多いNodeGroupを選択 ▶ least-waste: 対象NodeGroupにスケジューリング可能なunschedulablePodを全てスケジューリングした 際に、最もNodeに無駄な空き容量が発⽣しないNodeGroupを選択 ▶ price: 最も費⽤効果が⾼くクラスターの優先Nodeサイズと⼀致するNodeGroupを選択 + 利⽤するにはCloudProvider側でPricingメソッドの実装が必要 ▶ priority: ConfigMapで設定した設定ファイルに基づき最も⾼い優先度NodeGroupを選択 # 設定ファイル例 apiVersion: v1 kind: ConfigMap metadata: name: cluster-autoscaler-priority-expander data: priorities: |- 10: - .*t2\.large.* - .*t3\.large.* 50: - .*m4\.4xlarge.*

Slide 69

Slide 69 text

ClusterStateStrategy

Slide 70

Slide 70 text

ClusterStateStrategy ▶ ScaleUp/ScaleDownを⾏うために必要な様々な計算されたデータが⼊ってる ▶ また、ScaleUp/ScaleDownの実⾏状態や失敗時のBackoffデータなども管理している

Slide 71

Slide 71 text

ClusterStateStrategy 1 プロパティ名 説明 nodes 型: []*apiv1.Node ⼀⾔で説明するとobtainNodeListsが返すallNodesがデータの実体 allNodesは⼀部加⼯されることはあってもa.AllNodeLister().List()の取得結果ほぼそのまま=すべてのnode⼀覧 unregisteredNodes 型: map[string]UnregisteredNode csr.cloudProviderNodeInstancesにあってallNodesには無いnode情報 つまり、実際のk8s nodeには存在しないCloudProviderから取得できるinstance情報 nodeInfosForGroups 型: map[string]*schedulerframework.NodeInfo core_utils.GetNodeInfosForGroupsが返すnodeInfosForGroupsがデータ実体 CloudProviderのNodeGroup毎に1つのschedulerframework.NodeInfoを⽣成して設定している このNodeInfoはsanitizeNodeInfo/sanitizeTemplateNodeによって様々なtaintを除去している perNodeGroupReadiness 型:map[string]Readiness 各ステータス別のnode数をカウントしたReadinessをNodeGroup単位で保持している Readinessのデータ: Deleted: "ToBeDeletedByClusterAutoscaler" taintのあるnode数 NotStarted: readyではないけどnode.CreationTimestamp+15分を過ぎたnode数 Registered: allNodesに含まれているnode数 LongUnregistered: unregisteredNodesに含まれるnodeの内、Cluster AutoscalerにUnregisteredNodeだと判断されてから15分を超 過したnode数(オプションのmax-node-provision-timeで何分以上をLongUnregisteredに含めるかを設定できる) Unregistered: unregisteredNodesに含まれるnodeの内LongUnregisteredではないnode数 totalReadiness 型: Readiness perNodeGroupReadinessの(NodeGroup別でない)総合計版

Slide 72

Slide 72 text

ClusterStateStrategy 2 プロパティ名 説明 cloudProviderNodeInstances 型: map[string][]cloudprovider.Instance (map[])[]cloudprovider.Instance) NodeGroup単位でcache.fetchCloudProviderNodeInstancesForNodeGroupがキャッシュしたInstance⼀覧を格納したデータ cloudProviderNodeInstances 型: map[string][]cloudprovider.Instance 前回のループ時のcloudProviderNodeInstancesを保存してる acceptableRanges 型: map[string]AcceptableRange NodeGroupのTargetSizeの直近での変動数の最⼤/最⼩値を管理するデータ AcceptableRangeのデータ: MinNodes: 現在~直近で実際に動くであろうnodeの最⼩数 - scaleup予定のノード数は実際にはまだ稼働していないかもしれない MaxNodes: 現在~直近で実際に動くであろうnodeの最⼤数 - scaledown予定のノード数はまだ稼働している可能性がある CurrentTarget: NodeGroup.TargetSize()の値そのまんま incorrectNodeGroupSizes 型: map[string]IncorrectNodeGroupSize NodeGroup単位で、readiness.Registered(k8s nodeに登録されているnode数)がacceptableRange.MaxNodesと acceptableRange.MinNodesの間に収まってないものがあれば、それらをincorrectNodeGroupSizesとして保存する 想定よりも多くなったnodeを削除するために利⽤される

Slide 73

Slide 73 text

ClusterStateStrategy 3 プロパティ名 説明 scaleUpRequests 型: map[string]*ScaleUpRequest(map[]*ScaleUpRequest) 現在ScaleUpを実⾏中のNodeGroupと追加したnode数(Increase)を管理するデータ ScaleUpRequestのデータ: NodeGroup: NodeGroupオブジェクト Increase: 追加node数 Time: 追加開始時刻 ExpectedAddTime: node追加完了期限 scaleUpFailures 型: map[string][]ScaleUpFailure(map[][]ScaleUpFailure) ScaleUpに失敗したNodeGroupのデータを保存している ScaleUpFailureのデータ: NodeGroup: NodeGroupオブジェクト Reason: 失敗理由 Time: 失敗確認時刻 scaleDownRequests 型: []*ScaleDownRequest 現在のScaleDownの実⾏情報を管理するデータ RegisterScaleDownのデータ: NodeGroup: NodeGroupオブジェクト NodeName: Node名 Time: 削除開始時刻 ExpectedAddTime: node削除完了期限 backoff ScaleUpのbackoff管理オブジェクト 要はpod起動のbackoffみたいに1度scaleupに失敗したら⼀定以上の間隔を開けてリトライして、更に失敗したら更に実⾏間隔を空け て、という制御を⾏うためのプロパティ

Slide 74

Slide 74 text

ScaleDown props

Slide 75

Slide 75 text

ScaleDown props ▶ ScaleDownのプロパティにはunneededNodesを始めとしたScaleDownの実⾏と状態管理に 必要な各種データが保存されている ▶ プロパティの多くはUpdateUnneededNodesメソッドによってアップデートされていく

Slide 76

Slide 76 text

ScaleDown props プロパティ名 説明 unneededNodes 型: map[string]time.Time (map[<初めて追加された時刻>]) ScaleDown候補のNode⼀覧 simulator.FindNodesToRemoveによってnodesToRemove(削除可能node)と判断されたnode⼀覧と各nodeが初めて unneededNodesに追加された時刻が⼊る unneededNodesList 型: []*apiv1.Node simulator.FindNodesToRemoveによってnodesToRemove(削除可能node)と判断されたnode⼀覧が設定される unremovableNodes 型: map[string]time.Time (map[]) 何らかの理由で削除不可だと判断されたnode⼀覧とttlを管理している ttlを過ぎるまでは削除されず、checkNodeUtilizationでsd.unremovableNodesにあるnodeは削除不能状態にあるノードだというエ ラーが返る 要するにttlを設けることで短期間で連続で対象nodeがunremovableであるかどうかを毎回チェックするのを避けるための仕組みだ と考えられる unremovableNodeReasons map[string]*simulator.UnremovableNode 各種チェックによって、何らかの理由で削除不可だと判断されたnodeの⼀覧と各nodeの削除不可だと判断された理由などを管理し ている podLocationHints 型: map[string]string (map[]<findPlaceForによって選ばれた次のスケジューリング候補node名>) FindNodesToRemove/findPlaceForによって対象nodeを削除した際に、そのnode内部で動いていた再スケジューリングが必要な podが動作する次のnode候補はどれか?というがを詰め込まれている nodeUtilizationMap 型: map[string]simulator.UtilizationInfo (map[nodename]UtilizationInfo) node毎にcheckNodeUtilizationで計算したUtilizationInfoが⼊っている

Slide 77

Slide 77 text

ScaleUp/Down ざっくりフロー

Slide 78

Slide 78 text

ざっくりフロー ~ 初期化処理編 1 1. 各種データの初期化 2. Node⼀覧(all/ready)の取得 3. Nodeが空なら処理中断する 4. CloudProviderをRefresh 5. 優先度が設定されていない or ⼀定値以下のスケジュール済Pod⼀覧を詰め込んだ ClusterSnapshotを⽣成(スケジューリングなどのシュミレートに使⽤) 6. ClusterStateRegistryの各種データアップデート 7. CloudProvider側のInstance情報には存在して、k8s Nodeには存在しない Node(unregisteredNodes)が登録から⼀定時間経過していれば、そのNodeを 削除して処理中断する + ⻑期間unregisteredNodesである=Nodeのprovisioningに失敗したとみなされたので削除される

Slide 79

Slide 79 text

ざっくりフロー ~ 初期化処理編 2 8. ClusterStateRegistryにUnreadyであると判断されたNode数が⼀定数を超えた場合はCluster がUnhealthyだと判断された処理中断する 9. Node作成中にCloudProviderからエラーだと判断されたInstanceがあった場合、それらの Instanceを削除 10. Cloud Providerの各NodeGroupのInstance数がNodeGroupの想定サイズ内に収まっていない 場合、Instance数をNodeGroupの想定サイズに収まるように台数削減を⾏い、処理中断する 11. unschedulableなPod⼀覧の中で、preemptionによってNodeから追い出され、次の起動候補 Node(nominatedNode)がされているPodをClusterSnapshot内のnominatedNodeに追加 12. これから起動予定のNode(UpcomingNode)と、そのNode内で起動が決まってる Pod(Mirror PodやDaemonset Podなど)をClusterSnapshotに追加

Slide 80

Slide 80 text

ざっくりフロー ~ ScaleUp実施判定編 1. unschedulableなPodが無ければScaleUpをスキップ 2. readyなNode数が”--max-nodes-total”オプションで設定した数を超過していた場合は ScaleUpをスキップ + ”—max-nodes-total”はNodeの最⼤数なので、これを超過していた場合はScaleUpしない 3. unschedulableなPodがすべて2秒以内に作成されたものであったならScaleUpをスキップ + 恐らくまだSchedulerによってNodeを割り当てられていなかったりするためにスキップして ると思われる 4. 上記チェックをパスした場合はScaleUp処理を開始する

Slide 81

Slide 81 text

ざっくりフロー ~ ScaleUp編 1 1. NodeGroup⼀覧に対して、以下のチェックを⾏いScaleUp候補となるNodeGroupの フィルタリングを⾏う a. NodeGroupがScaleUp可能な状態であるか? b. 現在のNodeGroupのInstance数がNodeGroupの最⼤値に達しているか? c. 1台以上NodeをScaleUpすることで、NodeGroupごとに消費可能なリソース量(GPU/CPU/ Memory)の最⼤値を超過するか? + 消費可能なリソース量というのは”—memory-total”などのオプションで設定される

Slide 82

Slide 82 text

ざっくりフロー ~ ScaleUp編 2 2. フィルタを通過したNodeGroupが作成予定のNodeに対して以下のシュミレートを⾏う a. Scheduling FrameworkのPreFilter/Filterを利⽤して、作成予定Nodeにスケジューリング可 能なunschedulablePod⼀覧のシュミレート b. First Fit Decreasing bin-packing approximation algorithmを⽤いたPodのスケジューリング に必要なNode数のシュミレート 3. シュミレートした結果、1つ以上のPodがスケジューリング可能であれば、ScaleUp対象の NodeGroup候補に加える

Slide 83

Slide 83 text

ざっくりフロー ~ ScaleUp編 3 4. “--expander”オプションにより設定されたアルゴリズムにより、NodeGroup候補より1つの NodeGroupを選定 + 選択可能なexpanderはrandom/most-pods/least-waste/price/priorityの5つ 5. 選ばれたNodeGroupでシュミレートした追加Node数を追加した結果、”--max-nodes-total” オプションで設定した数を超過する場合にはSaleUp処理を中断する 6. “--balance-similar-node-groups”オプションをtrueに設定していた場合、以下の条件を満たす NodeGroupが類似NodeGroupとしてScaleUp対象のNodeGroupに追加される a. 選定されたNodeGroupとリソース容量が類似してる b. (⼀部を除いた)ラベル情報が⼀致してる c. 選定されたNodeGroupにスケジューリング可能なPodをすべてスケジューリングできる d. NodeGroupがScaleUp可能な状態である

Slide 84

Slide 84 text

ざっくりフロー ~ ScaleUp編 4 7. 選定されたNodeGroup群とトータルで追加したいNode台数情報を元にどのNodeGroupにそ れぞれ何台のNodeを追加するのか?を決定する 8. NodeGroup毎にScaleUp処理を実⾏する 9. ScaleUpがすべて成功したら処理終了(ScaleDown判定処理には移⾏しない)

Slide 85

Slide 85 text

ざっくりフロー ~ ScaleDown準備編 1 1. ScaleDown機能が有効であれば、ScaleDown処理を開始する 2. 全てのNodeから以下の条件を満たすScaleDown候補Nodeリストを作成する a. NodeGroupに所属しているNodeであり b. 対象NodeGroupの現在のNode数がNodeGroupの最⼩値に達していないこと 3. ScaleDown候補Nodeリストを更に以下の条件でフィルタリングする a. ”ToBeDeletedTaint”Taintを持っていたら除外 b. “cluster-autoscaler.kubernetes.io/scale-down-disabled”=true annotationを持っていたら 除外 c. Nodeリソース(GPU or CPU or Mem)の中で最も使⽤率の⾼いリソースの使⽤率が⼀定値を超 えていたら除外

Slide 86

Slide 86 text

ざっくりフロー ~ ScaleDown準備編 2 4. フィルタリングしたScaleDown候補から以下の条件を満たすemptyNode⼀覧を⽣成 + emptyNodeとは(おおまかにいうと)以下の条件を満たすpodが1つも無いNodeのこと a. Mirro Podではない b. DaemonSet Podではない c. ReplicaSetやJobなどに紐付いていない d. 削除中のPodではない e. Terminal State Podではない f. HostPathまたはEmptyDirのボリューム(=LocalVolume)を使⽤していない g. Podに紐づくPDBがある場合、紐づくPDBのpdb.Status.DisruptionsAllowedが1以上であること h. etc (複雑過ぎて書ききれない) 5. ScaleDown候補NodeをemptyNodeと⾮emptyNodeに分割

Slide 87

Slide 87 text

ざっくりフロー ~ ScaleDown準備編 3 6. ⾮emptyNodeを更に、前回のScaleDown処理で既にunneededNodesに選ばれたものかどうかで 2つに分割 7. 既にunneededNodesに選ばれていた⾮emptyNodeを対象に以下の条件で更にNodeを絞り込む a. 対象Nodeの削除をブロッキングするPodは無いか? b. 対象Nodeで現在動作しているPodの中で再スケジューリングが必要なPodが全て他のNodeに 再スケジューリングできるか? + 再スケジューリングできるかはScheduling Framework PreFilter/Filterによって検証する 8. まだunneededNodesに選ばれてない⾮emptyNodeの⼀部を対象に上記と同様のNodeの絞り込み をする

Slide 88

Slide 88 text

ざっくりフロー ~ ScaleDown準備編 4 9. 以下の3種類のNodeをunneededNodesに設定する a. emptyNode b. 既にunneededNodesに選ばれていた⾮emptyNodeの中で前ページ条件で絞り込んだもの c. まだunneededNodesに選ばれてない⾮emptyNodeの⼀部で前ページ条件で絞り込んだもの

Slide 89

Slide 89 text

ざっくりフロー ~ ScaleDown実施判定編 1. unschedulableなPodがすべて2秒以内に作成されたものであったならScaleDownをスキップ 2. 前回のScaleUp実施から⼀定時間内であればスキップ 3. 前回のScaleDown失敗から⼀定時間内であればスキップ 4. 前回のScaleDown処理開始から⼀定時間内であればスキップ 5. 現在ScaleDown処理実⾏中であればスキップ 6. それ以外ならScaleDown処理を実施

Slide 90

Slide 90 text

ざっくりフロー ~ ScaleDown編 1 1. unneededNodesを更に以下の条件で絞り込む a. ready statusでかつunneededNodesに追加されてから⼀定時間が経過したNode b. unready statusでかつunneededNodesに追加されてから⼀定時間が経過したNode c. Nodeが所属するNodeGroupの現在の数-対象NodeGroupで現在削除中のNode数が NodeGroupの最⼩値を下回っていないNode 2. 絞り込んだunneededNodesからemptyNodesを⽣成 3. emptyNodesが存在すれば全てのemptyNodesの削除を実施 + 削除処理はNode単位でgo routineを利⽤して⾮同期に⾏う + 削除前にNodeに”ToBeDeletedTaint”Taintを付与する 4. 削除開始したemptyNodesが1つ以上あった場合はScaleDown処理を終了する

Slide 91

Slide 91 text

ざっくりフロー ~ ScaleDown編 2 5. emptyNodesの削除が⾏われなかった場合は、⾮emptyNodesの削除処理を⾏う 6. ⾮emptyNodesの中から1つのNodeを取り出しemptyNodesの際と同様にgo routineで⾮同期 での削除を開始する 7. もし、ScaleDownを開始するNodeが1つもなかった場合はunneededNodesに対して "DeletionCandidateOfClusterAutoscaler"Taintの付与を⾏う

Slide 92

Slide 92 text

補⾜ ▶ ⼀応最低限のわかりやすさを保てる範囲でフローを書いたつもりです ▶ ただ実際にはこの説明の10倍くらい複雑で、⽂章で説明しようとすると 「コード読んだ⽅が正確じゃね?」となってしまうのでこれ以上細かく説明できなさそうでした