cluster autoscalerがNodeGroup情報をキャッシュしていないか、コードを読んで確認
前提条件
クラスタ : EKS(v1.21.1)
NodeGroup : ASG
なぜ、キャッシュしているのではという仮定にいたったのか
- 特定条件の時、ASGがスケールアウトしない
ASGの起動台数が0の時、ephemeral-storage
をrequestsで要求しているpodのためにcluster autoscalerがASGをスケールアップしてくれない事象に遭遇しました。
ASGの起動台数が1以上の時はスケールできていました。起動台数を1から0にした場合も、スケールできました。
issueも出ていたので、自分の環境が原因というわけではなさそうです。
reason: Insufficient ephemeral-storage
というエラーログが出力されます。
- cluster autoscalerがNodeGroupをキャッシュしている?
一度起動したnodeからNodeGroup情報を取得していて、次回以降どのNodeGroupをスケールさせるかの条件に 利用しているのではと推測しました。
実際に、推測通りの挙動をしているかコードを読んで確認してみました。
今回は、キャッシュしているかどうかの部分に焦点を当てているので、その他の処理については詳しく追っていません。
コードリーディング
実際にスケールアップを実行している関数
nodeGroupの情報は nodeInfosForGroups
で関数に渡していそう
scaleUpStatus, typedErr = ScaleUp(autoscalingContext, a.processors, a.clusterStateRegistry, unschedulablePodsToHelp, readyNodes, daemonsets, nodeInfosForGroups, a.ignoredTaints)
nodeInfosForGroups変数を作成している箇所
nodeInfosForGroups, autoscalerError := a.processors.TemplateNodeInfoProvider.Process(autoscalingContext, readyNodes, daemonsets, a.ignoredTaints)
Processメソッドを見てみましょう。
ちなみに、作成したあとに別のProcessメソッドを呼び出していますが、ここでは特に何もしていませんでした。
nodeInfosForGroups, err = a.processors.NodeInfoProcessor.Process(autoscalingContext, nodeInfosForGroups) | V // processors/nodeinfos/node_info_processor.go func (p *NoOpNodeInfoProcessor) Process(ctx *context.AutoscalingContext, nodeInfosForNodeGroups map[string]*schedulerframework.NodeInfo) (map[string]*schedulerframework.NodeInfo, error) { return nodeInfosForNodeGroups, nil }
Processの処理
変数nodesはkubectl get nodesで出力されたnodeのうち、正常に起動しているnode一覧と考えていただいて問題ないです。
resultを初期化しています。こちらは、nodeInfoのmapです。
nodesの要素ごとに、条件を満たした際に、nodeInfoCacheにnodeInfoをセットしています。
added == true
は、processNode関数を確認すると、nodeが所属するASGがまだresultに追加されていないときに満たされることがわかります。
p.nodeInfoCache != nil
は、NewMixedTemplateNodeInfoProvider関数を実行して、MixedTemplateNodeInfoProviderを作成している場合は条件にマッチします。
func (p *MixedTemplateNodeInfoProvider) Process(ctx *context.AutoscalingContext, nodes []*apiv1.Node, daemonsets []*appsv1.DaemonSet, ignoredTaints taints.TaintKeySet) (map[string]*schedulerframework.NodeInfo, errors.AutoscalerError) { ... result := make(map[string]*schedulerframework.NodeInfo) ... for _, node := range nodes { ... added, id, typedErr := processNode(node) ... if added && p.nodeInfoCache != nil { if nodeInfoCopy, err := utils.DeepCopyNodeInfo(result[id]); err == nil { p.nodeInfoCache[id] = nodeInfoCopy } } }
nodeInfoCacheにnodeInfoをセットしたあとに、NodeGroupsごとに処理が実行されます。
nodeInfoCacheのキーにnodeGroupのidがあれば、nodeInfoCacheの値をresultにセットします。
nodeInfoCacheになければ、NodeGroupの情報を元にnodeInfoが作成され、resultにセットされるようです。
なので、providerとしてAWSを利用している場合、 kubectl get nodeが取得されたnode情報のキャッシュ → (キャッシュがないASGの場合は、)ASGのデータ(タグなど)が
nodeInfoとして利用される認識で間違いないと思います。
for _, nodeGroup := range ctx.CloudProvider.NodeGroups() { id := nodeGroup.Id() ... // No good template, check cache of previously running nodes. if p.nodeInfoCache != nil { if nodeInfo, found := p.nodeInfoCache[id]; found { if nodeInfoCopy, err := utils.DeepCopyNodeInfo(nodeInfo); err == nil { result[id] = nodeInfoCopy continue } } } // No good template, trying to generate one. This is called only if there are no // working nodes in the node groups. By default CA tries to use a real-world example. nodeInfo, err := utils.GetNodeInfoFromTemplate(nodeGroup, daemonsets, ctx.PredicateChecker, ignoredTaints) ... result[id] = nodeInfo }
MixedTemplateNodeInfoProviderはcluster autoscalerの起動のときに初期化されるので(intervalのたびに作成されない)、podが起動している限りはキャッシュを持ち続けるようです。
感想
実際にキャッシュとしてnode情報を保持していることがわかりました。
なので、すでに存在しているASGに ephemeral-storage
タグを付与したとしても、cluster autoscalerのpodを再作成しないと、podのresource.requestsにephemeral-storage: ~
と書いても期待通りNodeGroupがスケールしてくれなそうです。
誤った解釈がありましたら、ご指摘いただけると非常に助かります。