Make ScaleDownNonEmptyCandidatesCount a flag.

This commit is contained in:
Beata Skiba 2017-08-30 15:43:39 +02:00
parent 4560cc0a85
commit 576e4105db
4 changed files with 49 additions and 33 deletions

View File

@ -96,6 +96,9 @@ type AutoscalingOptions struct {
ScaleDownDelay time.Duration
// ScaleDownTrialInterval sets how often scale down possibility is check
ScaleDownTrialInterval time.Duration
// ScaleDownNonEmptyCandidatesCount is the maximum number of non empty nodes
// considered at once as candidates for scale down.
ScaleDownNonEmptyCandidatesCount int
// WriteStatusConfigMap tells if the status information should be written to a ConfigMap
WriteStatusConfigMap bool
// BalanceSimilarNodeGroups enables logic that identifies node groups with similar machines and tries to balance node count between them.

View File

@ -58,9 +58,6 @@ const (
ScaleDownNodeDeleteStarted ScaleDownResult = iota
// ScaleDownDisabledKey is the name of annotation marking node as not eligible for scale down.
ScaleDownDisabledKey = "cluster-autoscaler.kubernetes.io/scale-down-disabled"
// ScaleDownNonEmptyCandidatesCount is the maximum number of non empty nodes
// considered at once as candidates for scale down.
ScaleDownNonEmptyCandidatesCount = 30
)
const (
@ -222,8 +219,7 @@ func (sd *ScaleDown) UpdateUnneededNodes(
return sd.markSimulationError(simulatorErr, timestamp)
}
// Check how many candidates we are still missing
additionalCandidatesCount := ScaleDownNonEmptyCandidatesCount - len(nodesToRemove)
additionalCandidatesCount := sd.context.AutoscalingOptions.ScaleDownNonEmptyCandidatesCount - len(nodesToRemove)
if additionalCandidatesCount > len(currentNonCandidates) {
additionalCandidatesCount = len(currentNonCandidates)
}
@ -292,6 +288,11 @@ func (sd *ScaleDown) markSimulationError(simulatorErr errors.AutoscalerError,
// rest. Current candidates are unneeded nodes from the previous run that are
// still in the nodes list.
func (sd *ScaleDown) chooseCandidates(nodes []*apiv1.Node) ([]*apiv1.Node, []*apiv1.Node) {
// Number of candidates should not be capped. We will look for nodes to remove
// from the whole set of nodes.
if sd.context.AutoscalingOptions.ScaleDownNonEmptyCandidatesCount <= 0 {
return nodes, []*apiv1.Node{}
}
currentCandidates := make([]*apiv1.Node, 0, len(sd.unneededNodesList))
currentNonCandidates := make([]*apiv1.Node, 0, len(nodes))
for _, node := range nodes {

View File

@ -144,11 +144,14 @@ func TestFindUnneededMaxCandidates(t *testing.T) {
nodes = append(nodes, n)
}
// shared owner reference
ownerRef := GenerateOwnerReferences("rs", "ReplicaSet", "extensions/v1beta1", "")
pods := make([]*apiv1.Pod, 0, numNodes)
for i := 0; i < numNodes; i++ {
p := BuildTestPod(fmt.Sprintf("p%v", i), 100, 0)
p.Annotations = GetReplicaSetAnnotation()
p.Spec.NodeName = fmt.Sprintf("n%v", i)
p.OwnerReferences = ownerRef
pods = append(pods, p)
}
@ -156,9 +159,12 @@ func TestFindUnneededMaxCandidates(t *testing.T) {
fakeRecorder := kube_util.CreateEventRecorder(fakeClient)
fakeLogRecorder, _ := utils.NewStatusMapRecorder(fakeClient, "kube-system", fakeRecorder, false)
numCandidates := 30
context := AutoscalingContext{
AutoscalingOptions: AutoscalingOptions{
ScaleDownUtilizationThreshold: 0.35,
ScaleDownUtilizationThreshold: 0.35,
ScaleDownNonEmptyCandidatesCount: numCandidates,
},
ClusterStateRegistry: clusterstate.NewClusterStateRegistry(provider, clusterstate.ClusterStateRegistryConfig{}),
PredicateChecker: simulator.NewTestPredicateChecker(),
@ -167,7 +173,7 @@ func TestFindUnneededMaxCandidates(t *testing.T) {
sd := NewScaleDown(&context)
sd.UpdateUnneededNodes(nodes, nodes, pods, time.Now(), nil)
assert.Equal(t, ScaleDownNonEmptyCandidatesCount, len(sd.unneededNodes))
assert.Equal(t, numCandidates, len(sd.unneededNodes))
// Simulate one of the unneeded nodes got deleted
deleted := sd.unneededNodesList[len(sd.unneededNodesList)-1]
for i, node := range nodes {
@ -189,7 +195,7 @@ func TestFindUnneededMaxCandidates(t *testing.T) {
sd.UpdateUnneededNodes(nodes, nodes, pods, time.Now(), nil)
// Check that the deleted node was replaced
assert.Equal(t, ScaleDownNonEmptyCandidatesCount, len(sd.unneededNodes))
assert.Equal(t, numCandidates, len(sd.unneededNodes))
assert.NotContains(t, sd.unneededNodes, deleted)
}

View File

@ -83,6 +83,11 @@ var (
"Node utilization level, defined as sum of requested resources divided by capacity, below which a node can be considered for scale down")
scaleDownTrialInterval = flag.Duration("scale-down-trial-interval", 1*time.Minute,
"How often scale down possiblity is check")
scaleDownNonEmptyCandidatesCount = flag.Int("scale-down-non-empty-candidates-count", 30,
"Maximum number of non empty nodes considered in one iteration as candidates for scale down with drain."+
"Lower value means better CA responsiveness but possible slower scale down latency."+
"Higher value can affect CA performance with big clusters (hundreds of nodes)."+
"Set to non posistive value to turn this heuristic off - CA will not limit the number of nodes it considers.")
scanInterval = flag.Duration("scan-interval", 10*time.Second, "How often cluster is reevaluated for scale up or down")
maxNodesTotal = flag.Int("max-nodes-total", 0, "Maximum number of nodes in all node groups. Cluster autoscaler will not grow the cluster beyond this number.")
cloudProviderFlag = flag.String("cloud-provider", "gce", "Cloud provider type. Allowed values: gce, aws, kubemark")
@ -108,30 +113,31 @@ var (
func createAutoscalerOptions() core.AutoscalerOptions {
autoscalingOpts := core.AutoscalingOptions{
CloudConfig: *cloudConfig,
CloudProviderName: *cloudProviderFlag,
NodeGroupAutoDiscovery: *nodeGroupAutoDiscovery,
MaxTotalUnreadyPercentage: *maxTotalUnreadyPercentage,
OkTotalUnreadyCount: *okTotalUnreadyCount,
EstimatorName: *estimatorFlag,
ExpanderName: *expanderFlag,
MaxEmptyBulkDelete: *maxEmptyBulkDeleteFlag,
MaxGracefulTerminationSec: *maxGracefulTerminationFlag,
MaxNodeProvisionTime: *maxNodeProvisionTime,
MaxNodesTotal: *maxNodesTotal,
NodeGroups: nodeGroupsFlag,
UnregisteredNodeRemovalTime: *unregisteredNodeRemovalTime,
ScaleDownDelay: *scaleDownDelay,
ScaleDownEnabled: *scaleDownEnabled,
ScaleDownTrialInterval: *scaleDownTrialInterval,
ScaleDownUnneededTime: *scaleDownUnneededTime,
ScaleDownUnreadyTime: *scaleDownUnreadyTime,
ScaleDownUtilizationThreshold: *scaleDownUtilizationThreshold,
WriteStatusConfigMap: *writeStatusConfigMapFlag,
BalanceSimilarNodeGroups: *balanceSimilarNodeGroupsFlag,
ConfigNamespace: *namespace,
ClusterName: *clusterName,
NodeAutoprovisioningEnabled: *nodeAutoprovisioningEnabled,
CloudConfig: *cloudConfig,
CloudProviderName: *cloudProviderFlag,
NodeGroupAutoDiscovery: *nodeGroupAutoDiscovery,
MaxTotalUnreadyPercentage: *maxTotalUnreadyPercentage,
OkTotalUnreadyCount: *okTotalUnreadyCount,
EstimatorName: *estimatorFlag,
ExpanderName: *expanderFlag,
MaxEmptyBulkDelete: *maxEmptyBulkDeleteFlag,
MaxGracefulTerminationSec: *maxGracefulTerminationFlag,
MaxNodeProvisionTime: *maxNodeProvisionTime,
MaxNodesTotal: *maxNodesTotal,
NodeGroups: nodeGroupsFlag,
UnregisteredNodeRemovalTime: *unregisteredNodeRemovalTime,
ScaleDownDelay: *scaleDownDelay,
ScaleDownEnabled: *scaleDownEnabled,
ScaleDownTrialInterval: *scaleDownTrialInterval,
ScaleDownUnneededTime: *scaleDownUnneededTime,
ScaleDownUnreadyTime: *scaleDownUnreadyTime,
ScaleDownUtilizationThreshold: *scaleDownUtilizationThreshold,
ScaleDownNonEmptyCandidatesCount: *scaleDownNonEmptyCandidatesCount,
WriteStatusConfigMap: *writeStatusConfigMapFlag,
BalanceSimilarNodeGroups: *balanceSimilarNodeGroupsFlag,
ConfigNamespace: *namespace,
ClusterName: *clusterName,
NodeAutoprovisioningEnabled: *nodeAutoprovisioningEnabled,
}
configFetcherOpts := dynamic.ConfigFetcherOptions{