Skip to content

Commit a714395

Browse files
authored
aks-preview: Support VMSS agent pool VM size resize via nodepool update (#9732)
1 parent 5b63af1 commit a714395

7 files changed

Lines changed: 159 additions & 9 deletions

File tree

src/aks-preview/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ To release a new version, please select a new version number (usually plus 1 to
1111

1212
Pending
1313
+++++++
14+
* `az aks nodepool update`: Support `--node-vm-size` to resize VM size of an existing VMSS-based agent pool (preview). Requires AFEC registration `Microsoft.ContainerService/AgentPoolVMSSResize`.
1415

1516
20.0.0b3
1617
++++++

src/aks-preview/azext_aks_preview/_help.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2484,7 +2484,7 @@
24842484
short-summary: Set the localDNS Profile for a nodepool with a JSON config file.
24852485
- name: --node-vm-size -s
24862486
type: string
2487-
short-summary: VM size for Kubernetes nodes. Only configurable when updating the autoscale settings of a VirtualMachines node pool.
2487+
short-summary: VM size for Kubernetes nodes. For VMSS pools, changing this triggers a rolling upgrade to replace nodes with the new size (preview). For VirtualMachines pools, only configurable when updating autoscale settings.
24882488
- name: --upgrade-strategy
24892489
type: string
24902490
short-summary: Upgrade strategy for the node pool. Allowed values are "Rolling" or "BlueGreen". Default is "Rolling".
@@ -2519,6 +2519,8 @@
25192519
text: az aks nodepool update --mode System -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster
25202520
- name: Update cluster autoscaler vm size, min-count and max-count for virtual machines node pool
25212521
text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --update-cluster-autoscaler --node-vm-size "Standard_D2s_v3" --min-count 2 --max-count 4
2522+
- name: Resize VM size for a VMSS node pool (preview, requires AFEC registration)
2523+
text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --node-vm-size Standard_D4s_v3
25222524
- name: Update a node pool with blue-green upgrade settings
25232525
text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --drain-batch-size 50% --drain-timeout-bg 5 --batch-soak-duration 10 --final-soak-duration 10
25242526
"""

src/aks-preview/azext_aks_preview/_params.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,6 +2299,7 @@ def load_arguments(self, _):
22992299
"node_vm_size",
23002300
options_list=["--node-vm-size", "-s"],
23012301
completer=get_vm_size_completion_list,
2302+
is_preview=True,
23022303
)
23032304
c.argument(
23042305
"gpu_driver",

src/aks-preview/azext_aks_preview/agentpool_decorator.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1944,6 +1944,30 @@ def update_fips_image(self, agentpool: AgentPool) -> AgentPool:
19441944

19451945
return agentpool
19461946

1947+
def update_vm_size(self, agentpool: AgentPool) -> AgentPool:
1948+
"""Update VM size for the AgentPool object.
1949+
1950+
Allows changing the VM size (SKU) of an existing VMSS-based agent pool.
1951+
The RP will perform a rolling upgrade (surge new nodes, drain old, delete old)
1952+
to replace nodes with the new VM size.
1953+
1954+
Note: This is only for VMSS pools. VMs pools handle VM size changes through
1955+
the autoscaler update path (update_auto_scaler_properties_vms).
1956+
1957+
:return: the AgentPool object
1958+
"""
1959+
self._ensure_agentpool(agentpool)
1960+
1961+
# Skip for VirtualMachines pools - they handle VM size via autoscaler path
1962+
if self.context.get_vm_set_type() == CONST_VIRTUAL_MACHINES:
1963+
return agentpool
1964+
1965+
node_vm_size = self.context.raw_param.get("node_vm_size")
1966+
if node_vm_size:
1967+
agentpool.vm_size = node_vm_size
1968+
1969+
return agentpool
1970+
19471971
def update_localdns_profile(self, agentpool: AgentPool) -> AgentPool:
19481972
"""Update local DNS profile for the AgentPool object if provided via --localdns-config."""
19491973
self._ensure_agentpool(agentpool)
@@ -2006,6 +2030,9 @@ def update_agentpool_profile_preview(self, agentpools: List[AgentPool] = None) -
20062030
# update ssh access
20072031
agentpool = self.update_ssh_access(agentpool)
20082032

2033+
# update vm size for VMSS pools
2034+
agentpool = self.update_vm_size(agentpool)
2035+
20092036
# update local DNS profile
20102037
agentpool = self.update_localdns_profile(agentpool)
20112038

@@ -2039,13 +2066,6 @@ def update_auto_scaler_properties(self, agentpool: AgentPool) -> AgentPool:
20392066
if self.context.get_vm_set_type() == CONST_VIRTUAL_MACHINES:
20402067
return agentpool
20412068

2042-
vm_size = self.context.raw_param.get("node_vm_size")
2043-
if vm_size is not None:
2044-
raise InvalidArgumentValueError(
2045-
"--node-vm-size can only be used with virtual machines agentpools. "
2046-
"Updating VM size is not supported for virtual machine scale set agentpools."
2047-
)
2048-
20492069
(
20502070
update_cluster_autoscaler,
20512071
enable_cluster_autoscaler,

src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,69 @@ def common_update_fips_image(self):
27732773
with self.assertRaises(MutuallyExclusiveArgumentError):
27742774
dec_3.update_fips_image(agentpool_2)
27752775

2776+
def common_update_vm_size(self):
2777+
# Test case 1: No node_vm_size provided (should not change agentpool)
2778+
dec_1 = AKSPreviewAgentPoolUpdateDecorator(
2779+
self.cmd,
2780+
self.client,
2781+
{"node_vm_size": None},
2782+
self.resource_type,
2783+
self.agentpool_decorator_mode,
2784+
)
2785+
# fail on passing the wrong agentpool object
2786+
with self.assertRaises(CLIInternalError):
2787+
dec_1.update_vm_size(None)
2788+
2789+
agentpool_1 = self.create_initialized_agentpool_instance(
2790+
vm_size="Standard_D2s_v3"
2791+
)
2792+
dec_1.context.attach_agentpool(agentpool_1)
2793+
dec_agentpool_1 = dec_1.update_vm_size(agentpool_1)
2794+
ground_truth_agentpool_1 = self.create_initialized_agentpool_instance(
2795+
vm_size="Standard_D2s_v3"
2796+
)
2797+
self.assertEqual(dec_agentpool_1, ground_truth_agentpool_1)
2798+
2799+
# Test case 2: node_vm_size provided (should update agentpool)
2800+
dec_2 = AKSPreviewAgentPoolUpdateDecorator(
2801+
self.cmd,
2802+
self.client,
2803+
{"node_vm_size": "Standard_D4s_v3"},
2804+
self.resource_type,
2805+
self.agentpool_decorator_mode,
2806+
)
2807+
agentpool_2 = self.create_initialized_agentpool_instance(
2808+
vm_size="Standard_D2s_v3"
2809+
)
2810+
dec_2.context.attach_agentpool(agentpool_2)
2811+
dec_agentpool_2 = dec_2.update_vm_size(agentpool_2)
2812+
ground_truth_agentpool_2 = self.create_initialized_agentpool_instance(
2813+
vm_size="Standard_D4s_v3"
2814+
)
2815+
self.assertEqual(dec_agentpool_2, ground_truth_agentpool_2)
2816+
2817+
# Test case 3: VirtualMachines pool with node_vm_size provided (should be no-op)
2818+
dec_3 = AKSPreviewAgentPoolUpdateDecorator(
2819+
self.cmd,
2820+
self.client,
2821+
{"node_vm_size": "Standard_D4s_v3"},
2822+
self.resource_type,
2823+
self.agentpool_decorator_mode,
2824+
)
2825+
agentpool_3 = self.create_initialized_agentpool_instance(
2826+
vm_size="Standard_D2s_v3"
2827+
)
2828+
# Set pool type to VirtualMachines - use the correct attribute based on decorator mode
2829+
from azure.cli.command_modules.acs._consts import AgentPoolDecoratorMode
2830+
if self.agentpool_decorator_mode == AgentPoolDecoratorMode.MANAGED_CLUSTER:
2831+
agentpool_3.type = CONST_VIRTUAL_MACHINES
2832+
else:
2833+
agentpool_3.type_properties_type = CONST_VIRTUAL_MACHINES
2834+
dec_3.context.attach_agentpool(agentpool_3)
2835+
dec_agentpool_3 = dec_3.update_vm_size(agentpool_3)
2836+
# vm_size should remain unchanged for VMs pools
2837+
self.assertEqual(dec_agentpool_3.vm_size, "Standard_D2s_v3")
2838+
27762839
def common_update_upgrade_strategy(self):
27772840
# Test case 1: No upgrade strategy provided (should not change agentpool)
27782841
dec_1 = AKSPreviewAgentPoolUpdateDecorator(
@@ -3113,6 +3176,9 @@ def test_update_vtpm(self):
31133176
def test_update_fips_image(self):
31143177
self.common_update_fips_image()
31153178

3179+
def test_update_vm_size(self):
3180+
self.common_update_vm_size()
3181+
31163182
def test_update_upgrade_strategy(self):
31173183
self.common_update_upgrade_strategy()
31183184

@@ -3209,6 +3275,9 @@ def test_update_vtpm(self):
32093275
def test_update_fips_image(self):
32103276
self.common_update_fips_image()
32113277

3278+
def test_update_vm_size(self):
3279+
self.common_update_vm_size()
3280+
32123281
def test_update_upgrade_strategy(self):
32133282
self.common_update_upgrade_strategy()
32143283

src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23405,3 +23405,51 @@ def test_aks_list_vm_skus(self):
2340523405
(sku.get("locationInfo") or [{}])[0].get("zones") or []
2340623406
)
2340723407
assert len(zones) > 0, f"SKU '{sku['name']}' has no zones despite --zone filter"
23408+
23409+
@live_only()
23410+
@AllowLargeResponse()
23411+
@AKSCustomResourceGroupPreparer(
23412+
random_name_length=17, name_prefix="clitest", location="centraluseuap"
23413+
)
23414+
def test_aks_nodepool_update_vmss_vm_size_resize(
23415+
self, resource_group, resource_group_location
23416+
):
23417+
"""Test VMSS agent pool VM size resize via nodepool update (preview)."""
23418+
aks_name = self.create_random_name("cliakstest", 16)
23419+
nodepool_name = "nodepool1"
23420+
self.kwargs.update(
23421+
{
23422+
"resource_group": resource_group,
23423+
"name": aks_name,
23424+
"nodepool_name": nodepool_name,
23425+
"ssh_key_value": self.generate_ssh_keys(),
23426+
}
23427+
)
23428+
23429+
# Create cluster with Standard_D2s_v3
23430+
create_cmd = (
23431+
"aks create --resource-group={resource_group} --name={name} "
23432+
"--node-count=1 --node-vm-size Standard_D2s_v3 "
23433+
"--ssh-key-value={ssh_key_value}"
23434+
)
23435+
self.cmd(
23436+
create_cmd,
23437+
checks=[
23438+
self.check("provisioningState", "Succeeded"),
23439+
self.check("agentPoolProfiles[0].vmSize", "Standard_D2s_v3"),
23440+
],
23441+
)
23442+
23443+
# Resize nodepool VM size to Standard_D4s_v3
23444+
update_cmd = (
23445+
"aks nodepool update --resource-group={resource_group} "
23446+
"--cluster-name={name} -n {nodepool_name} "
23447+
"--node-vm-size Standard_D4s_v3"
23448+
)
23449+
self.cmd(
23450+
update_cmd,
23451+
checks=[
23452+
self.check("provisioningState", "Succeeded"),
23453+
self.check("vmSize", "Standard_D4s_v3"),
23454+
],
23455+
)

src/aks-preview/azext_aks_preview/tests/latest/test_update_agentpool_profile_preview.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def test_update_agentpool_profile_preview_default_behavior(self):
129129
decorator.update_os_sku = Mock(return_value=agentpool)
130130
decorator.update_fips_image = Mock(return_value=agentpool)
131131
decorator.update_ssh_access = Mock(return_value=agentpool)
132+
decorator.update_vm_size = Mock(return_value=agentpool)
132133
decorator.update_localdns_profile = Mock(return_value=agentpool)
133134
decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool)
134135
decorator.update_upgrade_strategy = Mock(return_value=agentpool)
@@ -154,6 +155,7 @@ def test_update_agentpool_profile_preview_default_behavior(self):
154155
decorator.update_os_sku.assert_called_once_with(agentpool)
155156
decorator.update_fips_image.assert_called_once_with(agentpool)
156157
decorator.update_ssh_access.assert_called_once_with(agentpool)
158+
decorator.update_vm_size.assert_called_once_with(agentpool)
157159
decorator.update_localdns_profile.assert_called_once_with(agentpool)
158160
decorator.update_auto_scaler_properties_vms.assert_called_once_with(agentpool)
159161
decorator.update_upgrade_strategy.assert_called_once_with(agentpool)
@@ -197,6 +199,7 @@ def test_update_agentpool_profile_preview_with_agentpools_parameter(self):
197199
decorator.update_os_sku = Mock(return_value=agentpool)
198200
decorator.update_fips_image = Mock(return_value=agentpool)
199201
decorator.update_ssh_access = Mock(return_value=agentpool)
202+
decorator.update_vm_size = Mock(return_value=agentpool)
200203
decorator.update_localdns_profile = Mock(return_value=agentpool)
201204
decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool)
202205
decorator.update_upgrade_strategy = Mock(return_value=agentpool)
@@ -359,6 +362,7 @@ def test_update_agentpool_profile_preview_system_mode_regular_flow(self):
359362
decorator.update_os_sku = Mock(return_value=agentpool)
360363
decorator.update_fips_image = Mock(return_value=agentpool)
361364
decorator.update_ssh_access = Mock(return_value=agentpool)
365+
decorator.update_vm_size = Mock(return_value=agentpool)
362366
decorator.update_localdns_profile = Mock(return_value=agentpool)
363367
decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool)
364368
decorator.update_upgrade_strategy = Mock(return_value=agentpool)
@@ -382,6 +386,7 @@ def test_update_agentpool_profile_preview_system_mode_regular_flow(self):
382386
decorator.update_os_sku.assert_called_once_with(agentpool)
383387
decorator.update_fips_image.assert_called_once_with(agentpool)
384388
decorator.update_ssh_access.assert_called_once_with(agentpool)
389+
decorator.update_vm_size.assert_called_once_with(agentpool)
385390
decorator.update_localdns_profile.assert_called_once_with(agentpool)
386391
decorator.update_auto_scaler_properties_vms.assert_called_once_with(agentpool)
387392
decorator.update_upgrade_strategy.assert_called_once_with(agentpool)
@@ -430,6 +435,7 @@ def mock_method(pool):
430435
decorator.update_os_sku = create_mock_update_method("update_os_sku")
431436
decorator.update_fips_image = create_mock_update_method("update_fips_image")
432437
decorator.update_ssh_access = create_mock_update_method("update_ssh_access")
438+
decorator.update_vm_size = create_mock_update_method("update_vm_size")
433439
decorator.update_localdns_profile = create_mock_update_method("update_localdns_profile")
434440
decorator.update_auto_scaler_properties_vms = create_mock_update_method("update_auto_scaler_properties_vms")
435441
decorator.update_upgrade_strategy = create_mock_update_method("update_upgrade_strategy")
@@ -450,6 +456,7 @@ def mock_method(pool):
450456
"update_os_sku",
451457
"update_fips_image",
452458
"update_ssh_access",
459+
"update_vm_size",
453460
"update_localdns_profile",
454461
"update_auto_scaler_properties_vms",
455462
"update_upgrade_strategy",
@@ -500,6 +507,7 @@ def track_and_return(pool):
500507
decorator.update_os_sku = create_tracking_mock("update_os_sku")
501508
decorator.update_fips_image = create_tracking_mock("update_fips_image")
502509
decorator.update_ssh_access = create_tracking_mock("update_ssh_access")
510+
decorator.update_vm_size = create_tracking_mock("update_vm_size")
503511
decorator.update_localdns_profile = create_tracking_mock("update_localdns_profile")
504512
decorator.update_auto_scaler_properties_vms = create_tracking_mock("update_auto_scaler_properties_vms")
505513
decorator.update_upgrade_strategy = create_tracking_mock("update_upgrade_strategy")
@@ -567,7 +575,7 @@ def test_update_agentpool_profile_preview_mixed_modes_scenario(self):
567575
update_methods = [
568576
'update_network_profile', 'update_artifact_streaming', 'update_managed_gpu',
569577
'update_secure_boot', 'update_vtpm', 'update_os_sku', 'update_fips_image',
570-
'update_ssh_access', 'update_localdns_profile', 'update_auto_scaler_properties_vms',
578+
'update_ssh_access', 'update_vm_size', 'update_localdns_profile', 'update_auto_scaler_properties_vms',
571579
'update_upgrade_strategy', 'update_blue_green_upgrade_settings', 'update_gpu_profile',
572580
'update_gpu_mig_strategy'
573581
]
@@ -638,6 +646,7 @@ def test_update_agentpool_profile_preview_managed_cluster_mode(self):
638646
decorator.update_os_sku = Mock(return_value=agentpool)
639647
decorator.update_fips_image = Mock(return_value=agentpool)
640648
decorator.update_ssh_access = Mock(return_value=agentpool)
649+
decorator.update_vm_size = Mock(return_value=agentpool)
641650
decorator.update_localdns_profile = Mock(return_value=agentpool)
642651
decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool)
643652
decorator.update_upgrade_strategy = Mock(return_value=agentpool)

0 commit comments

Comments
 (0)