Skip to content

Commit 5e7f7a1

Browse files
authored
Merge pull request #8543 from lxuan94-pp/xualiliu/oci-maxNodeStartupTime
Make maxNodeStartupTime configurable
2 parents 82018c9 + 83cd97c commit 5e7f7a1

File tree

12 files changed

+238
-156
lines changed

12 files changed

+238
-156
lines changed

cluster-autoscaler/FAQ.md

Lines changed: 149 additions & 148 deletions
Large diffs are not rendered by default.

cluster-autoscaler/cloudprovider/clusterapi/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ metadata:
326326
cluster.x-k8s.io/autoscaling-options-scaledownunreadytime: "20m0s"
327327
# overrides --max-node-provision-time global value for that specific MachineDeployment
328328
cluster.x-k8s.io/autoscaling-options-maxnodeprovisiontime: "20m0s"
329+
# overrides --max-node-startup-time global value for that specific MachineDeployment
330+
cluster.x-k8s.io/autoscaling-options-maxnodestartuptime: "20m0s"
329331
```
330332

331333
#### CPU Architecture awareness for single-arch clusters

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_nodegroup.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ func (ng *nodegroup) GetOptions(defaults config.NodeGroupAutoscalingOptions) (*c
470470
if opt, ok := getDurationOption(options, ng.Id(), config.DefaultMaxNodeProvisionTimeKey); ok {
471471
defaults.MaxNodeProvisionTime = opt
472472
}
473+
if opt, ok := getDurationOption(options, ng.Id(), config.DefaultMaxNodeStartupTimeKey); ok {
474+
defaults.MaxNodeStartupTime = opt
475+
}
473476

474477
return &defaults, nil
475478
}

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_nodegroup_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,7 @@ func TestNodeGroupGetOptions(t *testing.T) {
17681768
ScaleDownUnneededTime: time.Second,
17691769
ScaleDownUnreadyTime: time.Minute,
17701770
MaxNodeProvisionTime: 15 * time.Minute,
1771+
MaxNodeStartupTime: 35 * time.Minute,
17711772
}
17721773

17731774
cases := []struct {
@@ -1788,13 +1789,15 @@ func TestNodeGroupGetOptions(t *testing.T) {
17881789
config.DefaultScaleDownUnneededTimeKey: "1h",
17891790
config.DefaultScaleDownUnreadyTimeKey: "30m",
17901791
config.DefaultMaxNodeProvisionTimeKey: "60m",
1792+
config.DefaultMaxNodeStartupTimeKey: "35m",
17911793
},
17921794
expected: &config.NodeGroupAutoscalingOptions{
17931795
ScaleDownGpuUtilizationThreshold: 0.6,
17941796
ScaleDownUtilizationThreshold: 0.7,
17951797
ScaleDownUnneededTime: time.Hour,
17961798
ScaleDownUnreadyTime: 30 * time.Minute,
17971799
MaxNodeProvisionTime: 60 * time.Minute,
1800+
MaxNodeStartupTime: 35 * time.Minute,
17981801
},
17991802
},
18001803
{
@@ -1809,6 +1812,7 @@ func TestNodeGroupGetOptions(t *testing.T) {
18091812
ScaleDownUnneededTime: time.Minute,
18101813
ScaleDownUnreadyTime: defaultOptions.ScaleDownUnreadyTime,
18111814
MaxNodeProvisionTime: 15 * time.Minute,
1815+
MaxNodeStartupTime: 35 * time.Minute,
18121816
},
18131817
},
18141818
{

cluster-autoscaler/clusterstate/clusterstate.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,14 +624,21 @@ type Readiness struct {
624624
func (csr *ClusterStateRegistry) updateReadinessStats(currentTime time.Time) {
625625
perNodeGroup := make(map[string]Readiness)
626626
total := Readiness{Time: currentTime}
627-
627+
maxNodeStartupTime := MaxNodeStartupTime
628628
update := func(current Readiness, node *apiv1.Node, nr kube_util.NodeReadiness) Readiness {
629+
nodeGroup, errNg := csr.cloudProvider.NodeGroupForNode(node)
630+
if errNg == nil && nodeGroup != nil {
631+
if startupTime, err := csr.nodeGroupConfigProcessor.GetMaxNodeStartupTime(nodeGroup); err == nil {
632+
maxNodeStartupTime = startupTime
633+
}
634+
}
635+
klog.V(1).Infof("Node %s: using maxNodeStartupTime = %v", node.Name, maxNodeStartupTime)
629636
current.Registered = append(current.Registered, node.Name)
630637
if _, isDeleted := csr.deletedNodes[node.Name]; isDeleted {
631638
current.Deleted = append(current.Deleted, node.Name)
632639
} else if nr.Ready {
633640
current.Ready = append(current.Ready, node.Name)
634-
} else if node.CreationTimestamp.Time.Add(MaxNodeStartupTime).After(currentTime) {
641+
} else if node.CreationTimestamp.Time.Add(maxNodeStartupTime).After(currentTime) {
635642
current.NotStarted = append(current.NotStarted, node.Name)
636643
} else {
637644
current.Unready = append(current.Unready, node.Name)

cluster-autoscaler/clusterstate/clusterstate_test.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ func TestTooManyUnready(t *testing.T) {
423423
clusterstate := NewClusterStateRegistry(provider, ClusterStateRegistryConfig{
424424
MaxTotalUnreadyPercentage: 10,
425425
OkTotalUnreadyCount: 1,
426-
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
426+
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute, MaxNodeStartupTime: 35 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
427427
err := clusterstate.UpdateNodes([]*apiv1.Node{ng1_1, ng2_1}, nil, now)
428428
assert.NoError(t, err)
429429
assert.False(t, clusterstate.IsClusterHealthy())
@@ -462,6 +462,37 @@ func TestUnreadyLongAfterCreation(t *testing.T) {
462462
assert.Empty(t, upcomingRegistered["ng1"])
463463
}
464464

465+
func TestUnreadyAfterCreationWithIncreasedStartupTime(t *testing.T) {
466+
now := time.Now()
467+
468+
ng1_1 := BuildTestNode("ng1-1", 1000, 1000)
469+
SetNodeReadyState(ng1_1, true, now.Add(-time.Minute))
470+
ng2_1 := BuildTestNode("ng2-1", 1000, 1000)
471+
SetNodeReadyState(ng2_1, false, now.Add(-time.Minute))
472+
ng2_1.CreationTimestamp = metav1.Time{Time: now.Add(-30 * time.Minute)}
473+
474+
provider := testprovider.NewTestCloudProviderBuilder().Build()
475+
provider.AddNodeGroup("ng1", 1, 10, 1)
476+
provider.AddNodeGroup("ng2", 1, 10, 1)
477+
provider.AddNode("ng1", ng1_1)
478+
provider.AddNode("ng2", ng2_1)
479+
480+
assert.NotNil(t, provider)
481+
fakeClient := &fake.Clientset{}
482+
fakeLogRecorder, _ := utils.NewStatusMapRecorder(fakeClient, "kube-system", kube_record.NewFakeRecorder(5), false, "some-map")
483+
clusterstate := NewClusterStateRegistry(provider, ClusterStateRegistryConfig{
484+
MaxTotalUnreadyPercentage: 10,
485+
OkTotalUnreadyCount: 1,
486+
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute, MaxNodeStartupTime: 35 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
487+
err := clusterstate.UpdateNodes([]*apiv1.Node{ng1_1, ng2_1}, nil, now)
488+
assert.NoError(t, err)
489+
assert.Equal(t, 0, len(clusterstate.GetClusterReadiness().Unready))
490+
assert.Equal(t, 1, len(clusterstate.GetClusterReadiness().NotStarted))
491+
upcoming, upcomingRegistered := clusterstate.GetUpcomingNodes()
492+
assert.Equal(t, 0, upcoming["ng1"])
493+
assert.Empty(t, upcomingRegistered["ng1"])
494+
}
495+
465496
func TestNotStarted(t *testing.T) {
466497
now := time.Now()
467498

@@ -484,7 +515,7 @@ func TestNotStarted(t *testing.T) {
484515
clusterstate := NewClusterStateRegistry(provider, ClusterStateRegistryConfig{
485516
MaxTotalUnreadyPercentage: 10,
486517
OkTotalUnreadyCount: 1,
487-
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
518+
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute, MaxNodeStartupTime: 35 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
488519
err := clusterstate.UpdateNodes([]*apiv1.Node{ng1_1, ng2_1}, nil, now)
489520
assert.NoError(t, err)
490521
assert.Equal(t, 1, len(clusterstate.GetClusterReadiness().NotStarted))
@@ -546,7 +577,7 @@ func TestRegisterScaleDown(t *testing.T) {
546577
clusterstate := NewClusterStateRegistry(provider, ClusterStateRegistryConfig{
547578
MaxTotalUnreadyPercentage: 10,
548579
OkTotalUnreadyCount: 1,
549-
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
580+
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute, MaxNodeStartupTime: 35 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
550581
now := time.Now()
551582
clusterstate.RegisterScaleDown(provider.GetNodeGroup("ng1"), "ng1-1", now.Add(time.Minute), now)
552583
assert.Equal(t, 1, len(clusterstate.scaleDownRequests))
@@ -639,7 +670,7 @@ func TestUpcomingNodes(t *testing.T) {
639670
clusterstate := NewClusterStateRegistry(provider, ClusterStateRegistryConfig{
640671
MaxTotalUnreadyPercentage: 10,
641672
OkTotalUnreadyCount: 1,
642-
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
673+
}, fakeLogRecorder, newBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute, MaxNodeStartupTime: 15 * time.Minute}), asyncnodegroups.NewDefaultAsyncNodeGroupStateChecker())
643674
err := clusterstate.UpdateNodes([]*apiv1.Node{ng1_1, ng2_1, ng3_1, ng4_1, ng5_1, ng5_2}, nil, now)
644675
assert.NoError(t, err)
645676
assert.Empty(t, clusterstate.GetScaleUpFailures())

cluster-autoscaler/config/autoscaling_options.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type NodeGroupAutoscalingOptions struct {
5050
ScaleDownUnreadyTime time.Duration
5151
// Maximum time CA waits for node to be provisioned
5252
MaxNodeProvisionTime time.Duration
53+
// Maximum time CA waits for node to be ready from registered
54+
MaxNodeStartupTime time.Duration
5355
// ZeroOrMaxNodeScaling means that a node group should be scaled up to maximum size or down to zero nodes all at once instead of one-by-one.
5456
ZeroOrMaxNodeScaling bool
5557
// AllowNonAtomicScaleUpToMax indicates that partially failing scale-ups of ZeroOrMaxNodeScaling node groups should not be cancelled

cluster-autoscaler/config/const.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ const (
3838
DefaultScaleDownUnreadyTimeKey = "scaledownunreadytime"
3939
// DefaultMaxNodeProvisionTimeKey identifies MaxNodeProvisionTime autoscaling option
4040
DefaultMaxNodeProvisionTimeKey = "maxnodeprovisiontime"
41+
// DefaultMaxNodeStartupTimeKey identifies MaxNodeProvisionTime autoscaling option
42+
DefaultMaxNodeStartupTimeKey = "maxnodestartuptime"
4143
// DefaultIgnoreDaemonSetsUtilizationKey identifies IgnoreDaemonSetsUtilization autoscaling option
4244
DefaultIgnoreDaemonSetsUtilizationKey = "ignoredaemonsetsutilization"
43-
4445
// DefaultScaleDownUnneededTime is the default time duration for which CA waits before deleting an unneeded node
4546
DefaultScaleDownUnneededTime = 10 * time.Minute
4647
// DefaultScaleDownUnreadyTime identifies ScaleDownUnreadyTime autoscaling option

cluster-autoscaler/config/flags/flags.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ var (
124124
scaleUpFromZero = flag.Bool("scale-up-from-zero", true, "Should CA scale up when there are 0 ready nodes.")
125125
parallelScaleUp = flag.Bool("parallel-scale-up", false, "Whether to allow parallel node groups scale up. Experimental: may not work on some cloud providers, enable at your own risk.")
126126
maxNodeProvisionTime = flag.Duration("max-node-provision-time", 15*time.Minute, "The default maximum time CA waits for node to be provisioned - the value can be overridden per node group")
127+
maxNodeStartupTime = flag.Duration("max-node-start-up-time", 15*time.Minute, "The maximum time from the moment the node is registered to the time the node is ready - the value can be overridden per node group")
127128
maxPodEvictionTime = flag.Duration("max-pod-eviction-time", 2*time.Minute, "Maximum time CA tries to evict a pod before giving up")
128129
nodeGroupsFlag = multiStringFlag(
129130
"nodes",
@@ -300,6 +301,7 @@ func createAutoscalingOptions() config.AutoscalingOptions {
300301
ScaleDownUnreadyTime: *scaleDownUnreadyTime,
301302
IgnoreDaemonSetsUtilization: *ignoreDaemonSetsUtilization,
302303
MaxNodeProvisionTime: *maxNodeProvisionTime,
304+
MaxNodeStartupTime: *maxNodeStartupTime,
303305
},
304306
CloudConfig: *cloudConfig,
305307
CloudProviderName: *cloudProviderFlag,

cluster-autoscaler/core/static_autoscaler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2474,7 +2474,7 @@ func TestStaticAutoscalerUpcomingScaleDownCandidates(t *testing.T) {
24742474

24752475
// Create CSR with unhealthy cluster protection effectively disabled, to guarantee we reach the tested logic.
24762476
csrConfig := clusterstate.ClusterStateRegistryConfig{OkTotalUnreadyCount: nodeGroupCount * unreadyNodesCount}
2477-
csr := clusterstate.NewClusterStateRegistry(provider, csrConfig, autoscalingCtx.LogRecorder, NewBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute}), processors.AsyncNodeGroupStateChecker)
2477+
csr := clusterstate.NewClusterStateRegistry(provider, csrConfig, autoscalingCtx.LogRecorder, NewBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute, MaxNodeStartupTime: 15 * time.Minute}), processors.AsyncNodeGroupStateChecker)
24782478

24792479
// Setting the Actuator is necessary for testing any scale-down logic, it shouldn't have anything to do in this test.
24802480
actuator := actuation.NewActuator(&autoscalingCtx, csr, deletiontracker.NewNodeDeletionTracker(0*time.Second), options.NodeDeleteOptions{}, nil, processorstest.NewTestProcessors(&autoscalingCtx).NodeGroupConfigProcessor)

0 commit comments

Comments
 (0)