Open12

kubernetes-sigs/cluster-proportional-autoscaler

bells17bells17

controllerは2種類

  • Linear
  • Ladder

うち有効化できるのは1つのみ
ここまで見た限りcluster-proportional-autoscalerは

  • 1種類のターゲットに対してのみ
  • 1つの設定(Linear or Ladderとそのパラメータ)

だけで動かすもののように思える

https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/1.8.5/pkg/autoscaler/autoscaler_server.go#L121-L129

https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/1.8.5/pkg/autoscaler/controller/plugin/plugin.go#L31-L58

それぞれの仕様?はREADMEに詳しめに書いてある

https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/master/README.md

bells17bells17
  • Linear
  • Ladder

それぞれのパラメータは以下

type linearParams struct {
	CoresPerReplica           float64 `json:"coresPerReplica"`
	NodesPerReplica           float64 `json:"nodesPerReplica"`
	Min                       int     `json:"min"`
	Max                       int     `json:"max"`
	PreventSinglePointFailure bool    `json:"preventSinglePointFailure"`
	IncludeUnschedulableNodes bool    `json:"includeUnschedulableNodes"`
}
type ladderParams struct {
	CoresToReplicas paramEntries `json:"coresToReplicas"`
	NodesToReplicas paramEntries `json:"nodesToReplicas"`
}
bells17bells17

clusterstatus生成方法
coreについてはnode.Status.Allocatableから算出
SchedulableNodesは!node.Spec.Unschedulableなnodeの総数

type ClusterStatus struct {
	TotalNodes       int32
	SchedulableNodes int32
	TotalCores       int32
	SchedulableCores int32
}
clusterStatus = &ClusterStatus{}
	clusterStatus.TotalNodes = int32(len(nodes))
	var tc resource.Quantity
	var sc resource.Quantity
	for i := range nodes {
		node, ok := nodes[i].(*v1.Node)
		if !ok {
			glog.Errorf("Unexpected object: %#v", nodes[i])
			continue
		}
		tc.Add(node.Status.Allocatable[v1.ResourceCPU])
		if !node.Spec.Unschedulable {
			clusterStatus.SchedulableNodes++
			sc.Add(node.Status.Allocatable[v1.ResourceCPU])
		}
	}

	clusterStatus.TotalCores = int32(tc.Value())
	clusterStatus.SchedulableCores = int32(sc.Value())

https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/1.8.5/pkg/autoscaler/k8sclient/k8sclient.go#L172-L209

ちなみに引数のnodelabelで対象ノードを絞り込める

https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/1.8.5/pkg/autoscaler/k8sclient/k8sclient.go#L86-L94

bells17bells17

LadderController
READMEの説明で十分じゃない?という気がするけど一応

要約:

data:
  ladder: |-
    {
      "coresToReplicas":
      [
        [ 1, 1 ],
        [ 64, 3 ],
        [ 512, 5 ],
        [ 1024, 7 ],
        [ 2048, 10 ],
        [ 4096, 15 ]
      ],
      "nodesToReplicas":
      [
        [ 1, 1 ],
        [ 2, 2 ]
      ]
    }

のような設定を与えてcores/nodesのサイズを満たしたreplicasを返す
設定データの読み方は

[<cores or nodes>, <replicas>]

なので例えばnodeが2台ならnodesToReplicasの [ 2, 2 ] がマッチしてrelicasが2となる
(実際にはcoresToReplicasの方の条件も考慮されるけど)

以下コード

コアとnodeそれぞれで計算してデカイ方を返す

func (c *LadderController) getExpectedReplicasFromParams(schedulableNodes, schedulableCores int) int {
	replicasFromCore := getExpectedReplicasFromEntries(schedulableCores, c.params.CoresToReplicas)
	replicasFromNode := getExpectedReplicasFromEntries(schedulableNodes, c.params.NodesToReplicas)

	// Returns the results which yields the most replicas
	if replicasFromCore > replicasFromNode {
		return replicasFromCore
	}
	return replicasFromNode
}
  • replicasFromCore
  • replicasFromNode
    それぞれの計算処理はこんなん
func getExpectedReplicasFromEntries(schedulableResources int, entries []paramEntry) int {
	if len(entries) == 0 {
		return 0
	}
	// Binary search for the corresponding replicas number
	pos := sort.Search(
		len(entries),
		func(i int) bool {
			return schedulableResources < entries[i][0]
		})
	if pos > 0 {
		pos = pos - 1
	}
	return entries[pos][1]
}

sort.Searchはこちら
https://pkg.go.dev/sort#Search

↑でやってる二分探索(バイナリサーチ)はこちら
https://ja.wikipedia.org/wiki/二分探索

この引数で渡されるパラメータはこの場所でソート済

sort.Sort(params.CoresToReplicas)
sort.Sort(params.NodesToReplicas)

https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/1.8.5/pkg/autoscaler/controller/laddercontroller/ladder_controller.go#L71-L83

entriesは

  • node or core
  • replica

というデータ形式なので、例えば
schedulableNode: 3
に対して

bells17bells17

LinearController

要約:

data:
  linear: |-
    {
      "coresPerReplica": 2,
      "nodesPerReplica": 1,
      "min": 1,
      "max": 100,
      "preventSinglePointFailure": true,
      "includeUnschedulableNodes": true
    }

のような設定値を元に動作させる
要は

  • coresPerReplica: コア数あたりでreplica数を決める(2に対して実際のcpuが6ならreplicaは3になる)
  • nodesPerReplica: node数あたりでreplica数を決める(↑に同じく)
  • min: coresPerReplica/nodesPerReplicaでreplica数を決める際の最低数
  • max: coresPerReplica/nodesPerReplicaでreplica数を決める際の最大数
  • preventSinglePointFailure: trueにするとnode数が2以上の時に、replica数の最低値を自動的に2にする(preventSinglePointFailureの名前の通り複数台にすることで単一障害点にしないように、ということだと思う)
  • includeUnschedulableNodes: trueにするとunschedulableなnodeも計算の際のコア数/node数に含める

という設定値を元に動作して、これらを元に

  • コア数
  • node数

それぞれでreplica数を計算して、サイズの大きい方を返す

例えば↑の設定値に対して

  • コア数: 16
  • node数: 4

だとしたら

  • replica数(コア): 8(16/2)
  • replica数(node): 4(4/1)

でコア数で算出したreplica数の方が多くなるから最終的なreplica数は8になる

コード

includeUnschedulableNodesによるcores/nodes設定箇所

        nodes := schedulableNodes
	cores := schedulableCores
	if c.params.IncludeUnschedulableNodes {
		nodes = totalNodes
		cores = totalCores
	}

replicasFromCore/replicasFromNode算出

replicasFromCore := c.getExpectedReplicasFromParam(cores, c.params.CoresPerReplica)
replicasFromNode := c.getExpectedReplicasFromParam(nodes, c.params.NodesPerReplica)
func (c *LinearController) getExpectedReplicasFromParam(schedulableResources int, resourcesPerReplica float64) int {
	if resourcesPerReplica == 0 {
		return 1
	}
	res := math.Ceil(float64(schedulableResources) / resourcesPerReplica)
	if c.params.Max != 0 {
		res = math.Min(float64(c.params.Max), res)
	}
	return int(math.Max(float64(c.params.Min), res))
}

PreventSinglePointFailureによるreplica数調整

        // Prevent single point of failure by having at least 2 replicas when
	// there are more than one node.
	if c.params.PreventSinglePointFailure &&
		nodes > 1 &&
		replicasFromNode < 2 {
		replicasFromNode = 2
	}

最終的に大きい方を返す処理

        // Returns the results which yields the most replicas
	if replicasFromCore > replicasFromNode {
		return replicasFromCore
	}
	return replicasFromNode

https://github.com/kubernetes-sigs/cluster-proportional-autoscaler/blob/1.8.5/pkg/autoscaler/controller/linearcontroller/linear_controller.go#L102-L146