Skip to content

Commit 2cd7445

Browse files
authored
update node info processors to include unschedulable nodes (#8520)
* pass allNodes to node info provider Process This change passes all the nodes to the mixed node info provider processor that is called from `RunOnce`. The change is to allow unschedulable and unready nodes to be processed as bad canidates during the node info template generation. The Process function has been updated to separate nodes into good and bad candidates to make the filtering match the original intent. * add --scale-from-unschedulable flag This change introduces a flag which will instruct the CA to ignore a node's `.spec.unschedulable` field when creating node template for considering which node group to scale.
1 parent 57e9a05 commit 2cd7445

File tree

7 files changed

+38
-11
lines changed

7 files changed

+38
-11
lines changed

cluster-autoscaler/FAQ.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,7 @@ The following startup parameters are supported for cluster autoscaler:
11041104
| `scale-down-unready-enabled` | Should CA scale down unready nodes of the cluster | true |
11051105
| `scale-down-unready-time` | How long an unready node should be unneeded before it is eligible for scale down | 20m0s |
11061106
| `scale-down-utilization-threshold` | The maximum value between the sum of cpu requests and sum of memory requests of all pods running on the node divided by node's corresponding allocatable resource, below which a node can be considered for scale down | 0.5 |
1107+
| `scale-from-unschedulable` | Should CA ignore a node's .spec.unschedulable field when creating a node template for considering to scale a node group. | false |
11071108
| `scale-up-from-zero` | Should CA scale up when there are 0 ready nodes. | true |
11081109
| `scan-interval` | How often cluster is reevaluated for scale up or down | 10s |
11091110
| `scheduler-config-file` | scheduler-config allows changing configuration of in-tree scheduler plugins acting on PreFilter and Filter extension points | |

cluster-autoscaler/config/autoscaling_options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ type AutoscalingOptions struct {
230230
BalancingLabels []string
231231
// AWSUseStaticInstanceList tells if AWS cloud provider use static instance type list or dynamically fetch from remote APIs.
232232
AWSUseStaticInstanceList bool
233+
// ScaleFromUnschedulable tells the autoscaler to ignore a node's .spec.unschedulable field when creating a node template.
234+
// Specifically, this will cause the autoscaler to set the node template's .spec.unschedulable field to false.
235+
ScaleFromUnschedulable bool
233236
// GCEOptions contain autoscaling options specific to GCE cloud provider.
234237
GCEOptions GCEOptions
235238
// KubeClientOpts specify options for kube client

cluster-autoscaler/config/flags/flags.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ var (
167167
balancingIgnoreLabelsFlag = multiStringFlag("balancing-ignore-label", "Specifies a label to ignore in addition to the basic and cloud-provider set of labels when comparing if two node groups are similar")
168168
balancingLabelsFlag = multiStringFlag("balancing-label", "Specifies a label to use for comparing if two node groups are similar, rather than the built in heuristics. Setting this flag disables all other comparison logic, and cannot be combined with --balancing-ignore-label.")
169169
awsUseStaticInstanceList = flag.Bool("aws-use-static-instance-list", false, "Should CA fetch instance types in runtime or use a static list. AWS only")
170+
scaleFromUnschedulable = flag.Bool("scale-from-unschedulable", false, "Specifies that the CA should ignore a node's .spec.unschedulable field in node templates when considering to scale a node group.")
170171

171172
// GCE specific flags
172173
concurrentGceRefreshes = flag.Int("gce-concurrent-refreshes", 1, "Maximum number of concurrent refreshes per cloud object type.")
@@ -351,6 +352,7 @@ func createAutoscalingOptions() config.AutoscalingOptions {
351352
},
352353
NodeDeletionDelayTimeout: *nodeDeletionDelayTimeout,
353354
AWSUseStaticInstanceList: *awsUseStaticInstanceList,
355+
ScaleFromUnschedulable: *scaleFromUnschedulable,
354356
GCEOptions: config.GCEOptions{
355357
ConcurrentRefreshes: *concurrentGceRefreshes,
356358
MigInstancesMinRefreshWaitTime: *gceMigInstancesMinRefreshWaitTime,

cluster-autoscaler/core/static_autoscaler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ func (a *StaticAutoscaler) RunOnce(currentTime time.Time) caerrors.AutoscalerErr
348348
return typedErr.AddPrefix("failed to initialize RemainingPdbTracker: ")
349349
}
350350

351-
nodeInfosForGroups, autoscalerError := a.processors.TemplateNodeInfoProvider.Process(autoscalingCtx, readyNodes, daemonsets, a.taintConfig, currentTime)
351+
nodeInfosForGroups, autoscalerError := a.processors.TemplateNodeInfoProvider.Process(autoscalingCtx, allNodes, daemonsets, a.taintConfig, currentTime)
352352
if autoscalerError != nil {
353353
klog.Errorf("Failed to get node infos for groups: %v", autoscalerError)
354354
return autoscalerError.AddPrefix("failed to build node infos for node groups: ")

cluster-autoscaler/processors/nodeinfosprovider/mixed_nodeinfos_processor.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ func (p *MixedTemplateNodeInfoProvider) Process(autoscalingCtx *ca_context.Autos
7878
result := make(map[string]*framework.NodeInfo)
7979
seenGroups := make(map[string]bool)
8080

81+
// sort nodes into those good and bad candidates for templates. the bad candidates will be processed
82+
// at the end of this function as a last resort for a node info template.
83+
goodCandidates := make([]*apiv1.Node, 0)
84+
badCandidates := make([]*apiv1.Node, 0)
85+
for _, node := range nodes {
86+
if isNodeGoodTemplateCandidate(node, now) {
87+
goodCandidates = append(goodCandidates, node)
88+
} else {
89+
badCandidates = append(badCandidates, node)
90+
}
91+
}
92+
8193
// processNode returns information whether the nodeTemplate was generated and if there was an error.
8294
processNode := func(node *apiv1.Node) (bool, string, caerror.AutoscalerError) {
8395
nodeGroup, err := autoscalingCtx.CloudProvider.NodeGroupForNode(node)
@@ -103,11 +115,7 @@ func (p *MixedTemplateNodeInfoProvider) Process(autoscalingCtx *ca_context.Autos
103115
return false, "", nil
104116
}
105117

106-
for _, node := range nodes {
107-
// Broken nodes might have some stuff missing. Skipping.
108-
if !isNodeGoodTemplateCandidate(node, now) {
109-
continue
110-
}
118+
for _, node := range goodCandidates {
111119
added, id, typedErr := processNode(node)
112120
if typedErr != nil {
113121
return map[string]*framework.NodeInfo{}, typedErr
@@ -156,11 +164,7 @@ func (p *MixedTemplateNodeInfoProvider) Process(autoscalingCtx *ca_context.Autos
156164
}
157165

158166
// Last resort - unready/unschedulable nodes.
159-
for _, node := range nodes {
160-
// Allowing broken nodes
161-
if isNodeGoodTemplateCandidate(node, now) {
162-
continue
163-
}
167+
for _, node := range badCandidates {
164168
added, _, typedErr := processNode(node)
165169
if typedErr != nil {
166170
return map[string]*framework.NodeInfo{}, typedErr

cluster-autoscaler/simulator/node_info_utils.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ func createSanitizedNode(node *apiv1.Node, newName string, taintConfig *taints.T
115115
}
116116
newNode.Labels[apiv1.LabelHostname] = newName
117117

118+
if taintConfig != nil {
119+
if taintConfig.ShouldScaleFromUnschedulable() {
120+
newNode.Spec.Unschedulable = false
121+
}
122+
}
123+
118124
if taintConfig != nil {
119125
newNode.Spec.Taints = taints.SanitizeTaints(newNode.Spec.Taints, *taintConfig)
120126
}

cluster-autoscaler/utils/taints/taints.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ type TaintConfig struct {
9797
startupTaintPrefixes []string
9898
statusTaintPrefixes []string
9999
explicitlyReportedTaints TaintKeySet
100+
// The scaleFromUnschedulable field helps to inform the CA when
101+
// to ignore .spec.unschedulable for a node. It is being added to this
102+
// struct for convenience as it will be used in similar places that check
103+
// for taints to ignore.
104+
scaleFromUnschedulable bool
100105
}
101106

102107
// NewTaintConfig returns the taint config extracted from options
@@ -128,6 +133,7 @@ func NewTaintConfig(opts config.AutoscalingOptions) TaintConfig {
128133
startupTaintPrefixes: []string{IgnoreTaintPrefix, StartupTaintPrefix},
129134
statusTaintPrefixes: []string{StatusTaintPrefix},
130135
explicitlyReportedTaints: explicitlyReportedTaints,
136+
scaleFromUnschedulable: opts.ScaleFromUnschedulable,
131137
}
132138
}
133139

@@ -147,6 +153,11 @@ func (tc TaintConfig) IsStatusTaint(taint string) bool {
147153
return matchesAnyPrefix(tc.statusTaintPrefixes, taint)
148154
}
149155

156+
// ShouldScaleFromUnschedulable returns whether a node's .spec.unschedulable field should be ignored.
157+
func (tc TaintConfig) ShouldScaleFromUnschedulable() bool {
158+
return tc.scaleFromUnschedulable
159+
}
160+
150161
func (tc TaintConfig) isExplicitlyReportedTaint(taint string) bool {
151162
_, ok := tc.explicitlyReportedTaints[taint]
152163
return ok

0 commit comments

Comments
 (0)