Skip to content

Commit 3c6dd26

Browse files
authored
Merge pull request #6863 from rrangith/azure-default-sizes
Default min/max sizes for Azure VMSSs
2 parents 069aab7 + 333d438 commit 3c6dd26

File tree

6 files changed

+181
-15
lines changed

6 files changed

+181
-15
lines changed

cluster-autoscaler/FAQ.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@ The following startup parameters are supported for cluster autoscaler:
836836
| `ok-total-unready-count` | Number of allowed unready nodes, irrespective of max-total-unready-percentage | 3
837837
| `max-node-provision-time` | Maximum time CA waits for node to be provisioned | 15 minutes
838838
| `nodes` | sets min,max size and other configuration data for a node group in a format accepted by cloud provider. Can be used multiple times. Format: \<min>:\<max>:<other...> | ""
839-
| `node-group-auto-discovery` | One or more definition(s) of node group auto-discovery.<br>A definition is expressed `<name of discoverer>:[<key>[=<value>]]`<br>The `aws`, `gce`, and `azure` cloud providers are currently supported. AWS matches by ASG tags, e.g. `asg:tag=tagKey,anotherTagKey`<br>GCE matches by IG name prefix, and requires you to specify min and max nodes per IG, e.g. `mig:namePrefix=pfx,min=0,max=10`<br> Azure matches by tags on VMSS, e.g. `label:foo=bar`, and will auto-detect `min` and `max` tags on the VMSS to set scaling limits.<br>Can be used multiple times | ""
839+
| `node-group-auto-discovery` | One or more definition(s) of node group auto-discovery.<br>A definition is expressed `<name of discoverer>:[<key>[=<value>]]`<br>The `aws`, `gce`, and `azure` cloud providers are currently supported. AWS matches by ASG tags, e.g. `asg:tag=tagKey,anotherTagKey`<br>GCE matches by IG name prefix, and requires you to specify min and max nodes per IG, e.g. `mig:namePrefix=pfx,min=0,max=10`<br> Azure matches by VMSS tags, similar to AWS. And you can optionally specify a default min and max size for VMSSs, e.g. `label:tag=tagKey,anotherTagKey=bar,min=0,max=600`.<br>Can be used multiple times | ""
840840
| `emit-per-nodegroup-metrics` | If true, emit per node group metrics. | false
841841
| `estimator` | Type of resource estimator to be used in scale up | binpacking
842842
| `expander` | Type of node group expander to be used in scale up. | random

cluster-autoscaler/cloudprovider/azure/azure_autodiscovery.go

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,31 @@ package azure
1818

1919
import (
2020
"fmt"
21-
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
21+
"strconv"
2222
"strings"
23+
24+
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
2325
)
2426

2527
const (
26-
autoDiscovererTypeLabel = "label"
28+
autoDiscovererTypeLabel = "label"
29+
vmssAutoDiscovererKeyMinNodes = "min"
30+
vmssAutoDiscovererKeyMaxNodes = "max"
2731
)
2832

2933
// A labelAutoDiscoveryConfig specifies how to auto-discover Azure node groups.
3034
type labelAutoDiscoveryConfig struct {
3135
// Key-values to match on.
3236
Selector map[string]string
37+
// MinSize specifies the minimum size for all VMSSs that match Selector.
38+
MinSize *int
39+
// MazSize specifies the maximum size for all VMSSs that match Selector.
40+
MaxSize *int
41+
}
42+
43+
type autoDiscoveryConfigSizes struct {
44+
Min int
45+
Max int
3346
}
3447

3548
// ParseLabelAutoDiscoverySpecs returns any provided NodeGroupAutoDiscoverySpecs
@@ -70,34 +83,67 @@ func parseLabelAutoDiscoverySpec(spec string) (labelAutoDiscoveryConfig, error)
7083
if k == "" || v == "" {
7184
return cfg, fmt.Errorf("empty value not allowed in key=value tag pairs")
7285
}
73-
cfg.Selector[k] = v
86+
87+
switch k {
88+
case vmssAutoDiscovererKeyMinNodes:
89+
minSize, err := strconv.Atoi(v)
90+
if err != nil || minSize < 0 {
91+
return cfg, fmt.Errorf("invalid minimum nodes: %s", v)
92+
}
93+
cfg.MinSize = &minSize
94+
case vmssAutoDiscovererKeyMaxNodes:
95+
maxSize, err := strconv.Atoi(v)
96+
if err != nil || maxSize < 0 {
97+
return cfg, fmt.Errorf("invalid maximum nodes: %s", v)
98+
}
99+
cfg.MaxSize = &maxSize
100+
default:
101+
cfg.Selector[k] = v
102+
}
103+
}
104+
if cfg.MaxSize != nil && cfg.MinSize != nil && *cfg.MaxSize < *cfg.MinSize {
105+
return cfg, fmt.Errorf("maximum size %d must be greater than or equal to minimum size %d", *cfg.MaxSize, *cfg.MinSize)
74106
}
75107
return cfg, nil
76108
}
77109

78-
func matchDiscoveryConfig(labels map[string]*string, configs []labelAutoDiscoveryConfig) bool {
110+
// returns an autoDiscoveryConfigSizes struct if the VMSS's tags match the autodiscovery configs
111+
// if the VMSS's tags do not match then return nil
112+
// if there are multiple min/max sizes defined, return the highest min value and the lowest max value
113+
func matchDiscoveryConfig(labels map[string]*string, configs []labelAutoDiscoveryConfig) *autoDiscoveryConfigSizes {
79114
if len(configs) == 0 {
80-
return false
115+
return nil
81116
}
117+
minSize := -1
118+
maxSize := -1
82119

83120
for _, c := range configs {
84121
if len(c.Selector) == 0 {
85-
return false
122+
return nil
86123
}
87124

88125
for k, v := range c.Selector {
89126
value, ok := labels[k]
90127
if !ok {
91-
return false
128+
return nil
92129
}
93130

94131
if len(v) > 0 {
95132
if value == nil || *value != v {
96-
return false
133+
return nil
97134
}
98135
}
99136
}
137+
if c.MinSize != nil && minSize < *c.MinSize {
138+
minSize = *c.MinSize
139+
}
140+
if c.MaxSize != nil && (maxSize == -1 || maxSize > *c.MaxSize) {
141+
maxSize = *c.MaxSize
142+
}
100143
}
101144

102-
return true
145+
return &autoDiscoveryConfigSizes{
146+
Min: minSize,
147+
Max: maxSize,
148+
}
103149
}

cluster-autoscaler/cloudprovider/azure/azure_autodiscovery_test.go

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ limitations under the License.
1717
package azure
1818

1919
import (
20+
"testing"
21+
2022
"github.com/stretchr/testify/assert"
2123
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
22-
"testing"
2324
)
2425

2526
func TestParseLabelAutoDiscoverySpecs(t *testing.T) {
27+
minVal := 1
28+
maxVal := 2
2629
testCases := []struct {
2730
name string
2831
specs []string
@@ -46,7 +49,7 @@ func TestParseLabelAutoDiscoverySpecs(t *testing.T) {
4649
expectedErr: true,
4750
},
4851
{
49-
name: "InvalidAutoDiscoerLabel",
52+
name: "InvalidAutoDiscoverLabel",
5053
specs: []string{"invalid:test-tag=test-value,another-test-tag"},
5154
expectedErr: true,
5255
},
@@ -60,6 +63,70 @@ func TestParseLabelAutoDiscoverySpecs(t *testing.T) {
6063
specs: []string{"label:=test-val"},
6164
expectedErr: true,
6265
},
66+
{
67+
name: "ValidSpecWithSizes",
68+
specs: []string{
69+
"label:cluster-autoscaler-enabled=true,cluster-autoscaler-name=fake-cluster,min=1,max=2",
70+
"label:test-tag=test-value,another-test-tag=another-test-value,min=1,max=2",
71+
},
72+
expected: []labelAutoDiscoveryConfig{
73+
{Selector: map[string]string{"cluster-autoscaler-enabled": "true", "cluster-autoscaler-name": "fake-cluster"}, MinSize: &minVal, MaxSize: &maxVal},
74+
{Selector: map[string]string{"test-tag": "test-value", "another-test-tag": "another-test-value"}, MinSize: &minVal, MaxSize: &maxVal},
75+
},
76+
},
77+
{
78+
name: "ValidSpecWithSizesOnlyMax",
79+
specs: []string{
80+
"label:cluster-autoscaler-enabled=true,max=2",
81+
},
82+
expected: []labelAutoDiscoveryConfig{
83+
{Selector: map[string]string{"cluster-autoscaler-enabled": "true"}, MaxSize: &maxVal},
84+
},
85+
},
86+
{
87+
name: "ValidSpecWithSizesOnlyMin",
88+
specs: []string{
89+
"label:cluster-autoscaler-enabled=true,min=1",
90+
},
91+
expected: []labelAutoDiscoveryConfig{
92+
{Selector: map[string]string{"cluster-autoscaler-enabled": "true"}, MinSize: &minVal},
93+
},
94+
},
95+
{
96+
name: "NonIntegerMin",
97+
specs: []string{
98+
"label:cluster-autoscaler-enabled=true,min=random,max=2",
99+
},
100+
expectedErr: true,
101+
},
102+
{
103+
name: "NegativeMin",
104+
specs: []string{
105+
"label:cluster-autoscaler-enabled=true,min=-5,max=2",
106+
},
107+
expectedErr: true,
108+
},
109+
{
110+
name: "NonIntegerMax",
111+
specs: []string{
112+
"label:cluster-autoscaler-enabled=true,min=1,max=random",
113+
},
114+
expectedErr: true,
115+
},
116+
{
117+
name: "NegativeMax",
118+
specs: []string{
119+
"label:cluster-autoscaler-enabled=true,min=1,max=-5",
120+
},
121+
expectedErr: true,
122+
},
123+
{
124+
name: "LowerMaxThanMin",
125+
specs: []string{
126+
"label:cluster-autoscaler-enabled=true,min=5,max=1",
127+
},
128+
expectedErr: true,
129+
},
63130
}
64131

65132
for _, tc := range testCases {

cluster-autoscaler/cloudprovider/azure/azure_manager.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,12 +309,13 @@ func (m *AzureManager) getFilteredScaleSets(filter []labelAutoDiscoveryConfig) (
309309

310310
var nodeGroups []cloudprovider.NodeGroup
311311
for _, scaleSet := range vmssList {
312+
var cfgSizes *autoDiscoveryConfigSizes
312313
if len(filter) > 0 {
313314
if scaleSet.Tags == nil || len(scaleSet.Tags) == 0 {
314315
continue
315316
}
316317

317-
if !matchDiscoveryConfig(scaleSet.Tags, filter) {
318+
if cfgSizes = matchDiscoveryConfig(scaleSet.Tags, filter); cfgSizes == nil {
318319
continue
319320
}
320321
}
@@ -332,6 +333,8 @@ func (m *AzureManager) getFilteredScaleSets(filter []labelAutoDiscoveryConfig) (
332333
klog.Warningf("ignoring vmss %q because of invalid minimum size specified for vmss: %s", *scaleSet.Name, err)
333334
continue
334335
}
336+
} else if cfgSizes.Min >= 0 {
337+
spec.MinSize = cfgSizes.Min
335338
} else {
336339
klog.Warningf("ignoring vmss %q because of no minimum size specified for vmss", *scaleSet.Name)
337340
continue
@@ -347,12 +350,14 @@ func (m *AzureManager) getFilteredScaleSets(filter []labelAutoDiscoveryConfig) (
347350
klog.Warningf("ignoring vmss %q because of invalid maximum size specified for vmss: %s", *scaleSet.Name, err)
348351
continue
349352
}
353+
} else if cfgSizes.Max >= 0 {
354+
spec.MaxSize = cfgSizes.Max
350355
} else {
351356
klog.Warningf("ignoring vmss %q because of no maximum size specified for vmss", *scaleSet.Name)
352357
continue
353358
}
354359
if spec.MaxSize < spec.MinSize {
355-
klog.Warningf("ignoring vmss %q because of maximum size must be greater than minimum size: max=%d < min=%d", *scaleSet.Name, spec.MaxSize, spec.MinSize)
360+
klog.Warningf("ignoring vmss %q because of maximum size must be greater than or equal to minimum size: max=%d < min=%d", *scaleSet.Name, spec.MaxSize, spec.MinSize)
356361
continue
357362
}
358363

cluster-autoscaler/cloudprovider/azure/azure_manager_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,53 @@ func TestGetFilteredAutoscalingGroupsVmss(t *testing.T) {
696696
assert.True(t, assert.ObjectsAreEqualValues(expectedAsgs, asgs), "expected %#v, but found: %#v", expectedAsgs, asgs)
697697
}
698698

699+
func TestGetFilteredAutoscalingGroupsVmssWithConfiguredSizes(t *testing.T) {
700+
ctrl := gomock.NewController(t)
701+
defer ctrl.Finish()
702+
703+
vmssName := "test-vmss"
704+
vmssTag := "fake-tag"
705+
vmssTagValue := "fake-value"
706+
vmssTag2 := "fake-tag2"
707+
vmssTagValue2 := "fake-value2"
708+
minVal := 2
709+
maxVal := 4
710+
711+
ngdo := cloudprovider.NodeGroupDiscoveryOptions{
712+
NodeGroupAutoDiscoverySpecs: []string{
713+
fmt.Sprintf("label:%s=%s,min=2,max=5", vmssTag, vmssTagValue),
714+
fmt.Sprintf("label:%s=%s,min=1,max=4", vmssTag2, vmssTagValue2),
715+
},
716+
}
717+
718+
manager := newTestAzureManager(t)
719+
expectedScaleSets := []compute.VirtualMachineScaleSet{fakeVMSSWithTags(vmssName, map[string]*string{vmssTag: &vmssTagValue, vmssTag2: &vmssTagValue2})}
720+
mockVMSSClient := mockvmssclient.NewMockInterface(ctrl)
721+
mockVMSSClient.EXPECT().List(gomock.Any(), manager.config.ResourceGroup).Return(expectedScaleSets, nil).AnyTimes()
722+
manager.azClient.virtualMachineScaleSetsClient = mockVMSSClient
723+
err := manager.forceRefresh()
724+
assert.NoError(t, err)
725+
726+
specs, err := ParseLabelAutoDiscoverySpecs(ngdo)
727+
assert.NoError(t, err)
728+
729+
asgs, err := manager.getFilteredNodeGroups(specs)
730+
assert.NoError(t, err)
731+
expectedAsgs := []cloudprovider.NodeGroup{&ScaleSet{
732+
azureRef: azureRef{
733+
Name: vmssName,
734+
},
735+
minSize: minVal,
736+
maxSize: maxVal,
737+
manager: manager,
738+
enableForceDelete: manager.config.EnableForceDelete,
739+
curSize: 3,
740+
sizeRefreshPeriod: manager.azureCache.refreshInterval,
741+
instancesRefreshPeriod: defaultVmssInstancesRefreshPeriod,
742+
}}
743+
assert.True(t, assert.ObjectsAreEqualValues(expectedAsgs, asgs), "expected %#v, but found: %#v", expectedAsgs, asgs)
744+
}
745+
699746
func TestGetFilteredAutoscalingGroupsWithInvalidVMType(t *testing.T) {
700747
ctrl := gomock.NewController(t)
701748
defer ctrl.Finish()

cluster-autoscaler/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,9 @@ var (
179179
"node-group-auto-discovery",
180180
"One or more definition(s) of node group auto-discovery. "+
181181
"A definition is expressed `<name of discoverer>:[<key>[=<value>]]`. "+
182-
"The `aws` and `gce` cloud providers are currently supported. AWS matches by ASG tags, e.g. `asg:tag=tagKey,anotherTagKey`. "+
182+
"The `aws`, `gce`, and `azure` cloud providers are currently supported. AWS matches by ASG tags, e.g. `asg:tag=tagKey,anotherTagKey`. "+
183183
"GCE matches by IG name prefix, and requires you to specify min and max nodes per IG, e.g. `mig:namePrefix=pfx,min=0,max=10` "+
184+
"Azure matches by VMSS tags, similar to AWS. And you can optionally specify a default min and max size, e.g. `label:tag=tagKey,anotherTagKey=bar,min=0,max=600`. "+
184185
"Can be used multiple times.")
185186

186187
estimatorFlag = flag.String("estimator", estimator.BinpackingEstimatorName,

0 commit comments

Comments
 (0)