hisamounaのブログ

アウトプットを習慣化するためのブログ

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がスケールしてくれなそうです。

誤った解釈がありましたら、ご指摘いただけると非常に助かります。