Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/azure-cli/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Release History
* `az aks nodepool rollback`: Show an accurate warning when only the node OS upgrade channel is enabled (#33854)
* Implement enable/disable flags for user-defined scheduler configuration (#33934)
* `az aks update`: Fix Azure Container Storage configuration detection for lowercase and boolean extension settings (#33938)
* `az aks create`, `az aks update`: Add `--outbound-type-sku` to select the managed NAT gateway SKU (`Standard` or `StandardV2`) with `--outbound-type managedNATGateway`, the GA shape of NAT Gateway V2. `StandardV2` also supports IPv6 (`--nat-gateway-managed-outbound-ipv6-count`), user-provided public IPs (`--nat-gateway-outbound-ips`), and user-provided IP prefixes (`--nat-gateway-outbound-ip-prefixes`).

**App Config**

Expand Down
4 changes: 4 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acs/_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@
CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY = "userAssignedNATGateway"
CONST_OUTBOUND_TYPE_NONE = "none"

# managed NAT gateway SKU
CONST_NAT_GATEWAY_SKU_STANDARD = "Standard"
CONST_NAT_GATEWAY_SKU_STANDARD_V2 = "StandardV2"

# load balancer backend pool type
CONST_LOAD_BALANCER_BACKEND_POOL_TYPE_NODE_IP = "nodeIP"
CONST_LOAD_BALANCER_BACKEND_POOL_TYPE_NODE_IP_CONFIGURATION = "nodeIPConfiguration"
Expand Down
8 changes: 8 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acs/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@
type: int
short-summary: NAT gateway idle timeout in minutes.
long-summary: Desired idle timeout for NAT gateway outbound flows, default is 4 minutes. Please specify a value in the range of [4, 120]. Valid for Standard SKU load balancer cluster with managedNATGateway outbound type only.
- name: --outbound-type-sku
type: string
short-summary: SKU of the managed NAT Gateway (Standard or StandardV2).
long-summary: Only valid with --outbound-type managedNATGateway. Omit to default to StandardV2 where the region supports it, otherwise Standard. StandardV2 adds zone resiliency, IPv6 support, and higher bandwidth.
Comment thread
christine33-creator marked this conversation as resolved.
- name: --outbound-type
type: string
short-summary: How outbound traffic will be configured for a cluster.
Expand Down Expand Up @@ -867,6 +871,10 @@
type: int
short-summary: NAT gateway idle timeout in minutes.
long-summary: Desired idle timeout for NAT gateway outbound flows, default is 4 minutes. Please specify a value in the range of [4, 120]. Valid for Standard SKU load balancer cluster with managedNATGateway outbound type only.
- name: --outbound-type-sku
type: string
short-summary: SKU of the managed NAT Gateway (Standard or StandardV2).
long-summary: Only valid with --outbound-type managedNATGateway. Migrate an existing Standard (V1) cluster to StandardV2 by passing StandardV2. StandardV2 adds zone resiliency, IPv6 support, and higher bandwidth. Downgrade from StandardV2 to Standard is not supported.
- name: --outbound-type
type: string
short-summary: How outbound traffic will be configured for a cluster.
Expand Down
106 changes: 93 additions & 13 deletions src/azure-cli/azure/cli/command_modules/acs/_natgateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,117 @@
from types import SimpleNamespace


def create_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, models: SimpleNamespace):
def create_nat_gateway_profile(
managed_outbound_ip_count,
idle_timeout,
models: SimpleNamespace,
managed_outbound_ipv6_count=None,
outbound_ip_ids=None,
outbound_ip_prefix_ids=None,
nat_gateway_sku=None,
):
"""parse and build NAT gateway profile"""
if not is_nat_gateway_profile_provided(managed_outbound_ip_count, idle_timeout):
if not is_nat_gateway_profile_provided(
managed_outbound_ip_count, idle_timeout,
managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids,
nat_gateway_sku,
):
return None

profile = models.ManagedClusterNATGatewayProfile()
return configure_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models)
return configure_nat_gateway_profile(
managed_outbound_ip_count, idle_timeout, profile, models,
managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids,
nat_gateway_sku,
)


def update_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models: SimpleNamespace):
def update_nat_gateway_profile(
managed_outbound_ip_count,
idle_timeout,
profile,
models: SimpleNamespace,
managed_outbound_ipv6_count=None,
outbound_ip_ids=None,
outbound_ip_prefix_ids=None,
nat_gateway_sku=None,
):
"""parse and update an existing NAT gateway profile"""
if not is_nat_gateway_profile_provided(managed_outbound_ip_count, idle_timeout):
if not is_nat_gateway_profile_provided(
managed_outbound_ip_count, idle_timeout,
managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids,
nat_gateway_sku,
):
return profile
if not profile:
profile = models.ManagedClusterNATGatewayProfile()
return configure_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models)
return configure_nat_gateway_profile(
managed_outbound_ip_count, idle_timeout, profile, models,
managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids,
nat_gateway_sku,
)


def is_nat_gateway_profile_provided(managed_outbound_ip_count, idle_timeout):
return any([managed_outbound_ip_count is not None, idle_timeout])
def is_nat_gateway_profile_provided(
managed_outbound_ip_count,
idle_timeout,
managed_outbound_ipv6_count=None,
outbound_ip_ids=None,
outbound_ip_prefix_ids=None,
nat_gateway_sku=None,
):
return any([
managed_outbound_ip_count is not None,
idle_timeout,
managed_outbound_ipv6_count is not None,
outbound_ip_ids is not None,
outbound_ip_prefix_ids is not None,
nat_gateway_sku is not None,
])


def configure_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models: SimpleNamespace):
def configure_nat_gateway_profile(
managed_outbound_ip_count,
idle_timeout,
profile,
models: SimpleNamespace,
managed_outbound_ipv6_count=None,
outbound_ip_ids=None,
outbound_ip_prefix_ids=None,
nat_gateway_sku=None,
):
"""configure a NAT Gateway with customer supplied values"""
if managed_outbound_ip_count is not None:
if managed_outbound_ip_count is not None or managed_outbound_ipv6_count is not None:
ManagedClusterManagedOutboundIPProfile = models.ManagedClusterManagedOutboundIPProfile
profile.managed_outbound_ip_profile = ManagedClusterManagedOutboundIPProfile(
count=managed_outbound_ip_count
)
if not profile.managed_outbound_ip_profile:
profile.managed_outbound_ip_profile = ManagedClusterManagedOutboundIPProfile()
if managed_outbound_ip_count is not None:
profile.managed_outbound_ip_profile.count = managed_outbound_ip_count
elif profile.managed_outbound_ip_profile.count is None:
# SDK 41.6 no longer materializes the IPv4 default; keep the documented default of 1.
profile.managed_outbound_ip_profile.count = 1
if managed_outbound_ipv6_count is not None:
profile.managed_outbound_ip_profile.count_ipv6 = managed_outbound_ipv6_count

if idle_timeout:
profile.idle_timeout_in_minutes = idle_timeout

if outbound_ip_ids is not None:
ManagedClusterNATGatewayProfileOutboundIPs = models.ManagedClusterNATGatewayProfileOutboundIPs
if ManagedClusterNATGatewayProfileOutboundIPs is None:
raise ValueError("The selected AKS API profile/SDK does not support setting NAT gateway outbound IPs.")
ip_id_list = [x.strip() for x in outbound_ip_ids.split(',') if x.strip()]
profile.outbound_i_ps = ManagedClusterNATGatewayProfileOutboundIPs(public_i_ps=ip_id_list)

if outbound_ip_prefix_ids is not None:
ManagedClusterNATGatewayProfileOutboundIpPrefixes = models.ManagedClusterNATGatewayProfileOutboundIpPrefixes
prefix_id_list = [x.strip() for x in outbound_ip_prefix_ids.split(',') if x.strip()]
profile.outbound_ip_prefixes = ManagedClusterNATGatewayProfileOutboundIpPrefixes(
public_ip_prefixes=prefix_id_list
)

if nat_gateway_sku is not None:
# GA shape: V2 is expressed as outboundType=managedNATGateway + natGatewayProfile.sku.
profile.sku = nat_gateway_sku

return profile
38 changes: 35 additions & 3 deletions src/azure-cli/azure/cli/command_modules/acs/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
CONST_OS_SKU_CBLMARINER, CONST_OS_SKU_MARINER,
CONST_OS_SKU_UBUNTU, CONST_OS_SKU_UBUNTU2204, CONST_OS_SKU_UBUNTU2404,
CONST_OS_SKU_WINDOWS2019, CONST_OS_SKU_WINDOWS2022, CONST_OS_SKU_WINDOWS2025,
CONST_NAT_GATEWAY_SKU_STANDARD, CONST_NAT_GATEWAY_SKU_STANDARD_V2,
CONST_OUTBOUND_TYPE_LOAD_BALANCER, CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY,
CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY,
CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING, CONST_OUTBOUND_TYPE_NONE,
Expand Down Expand Up @@ -119,7 +120,11 @@
validate_load_balancer_outbound_ips, validate_load_balancer_outbound_ports,
validate_load_balancer_sku, validate_max_surge, validate_max_unavailable,
validate_nat_gateway_idle_timeout,
validate_nat_gateway_managed_outbound_ip_count, validate_network_policy,
validate_nat_gateway_managed_outbound_ip_count,
validate_nat_gateway_managed_outbound_ipv6_count,
validate_nat_gateway_v2_params, validate_nat_gateway_v2_params_for_update,
validate_network_policy,
validate_outbound_type_sku, validate_outbound_type_sku_for_update,
validate_nodepool_id, validate_nodepool_labels, validate_nodepool_name,
validate_nodepool_tags, validate_nodes_count, validate_os_sku,
validate_pod_subnet_id, validate_ppg, validate_priority,
Expand Down Expand Up @@ -197,6 +202,7 @@

# consts for ManagedCluster
load_balancer_skus = [CONST_LOAD_BALANCER_SKU_BASIC, CONST_LOAD_BALANCER_SKU_STANDARD]
nat_gateway_skus = [CONST_NAT_GATEWAY_SKU_STANDARD, CONST_NAT_GATEWAY_SKU_STANDARD_V2]
sku_names = [CONST_MANAGED_CLUSTER_SKU_NAME_BASE, CONST_MANAGED_CLUSTER_SKU_NAME_AUTOMATIC]
sku_tiers = [CONST_MANAGED_CLUSTER_SKU_TIER_FREE, CONST_MANAGED_CLUSTER_SKU_TIER_STANDARD, CONST_MANAGED_CLUSTER_SKU_TIER_PREMIUM]
network_plugins = [CONST_NETWORK_PLUGIN_KUBENET, CONST_NETWORK_PLUGIN_AZURE, CONST_NETWORK_PLUGIN_NONE]
Expand Down Expand Up @@ -428,8 +434,21 @@ def load_arguments(self, _):
c.argument('load_balancer_backend_pool_type', arg_type=get_enum_type(backend_pool_types))
c.argument('nrg_lockdown_restriction_level', arg_type=get_enum_type(nrg_lockdown_restriction_levels))
c.argument('nat_gateway_managed_outbound_ip_count', type=int, validator=validate_nat_gateway_managed_outbound_ip_count)
c.argument('nat_gateway_managed_outbound_ipv6_count',
options_list=['--nat-gateway-managed-outbound-ipv6-count', '--nat-gw-ipv6-count'],
type=int, validator=validate_nat_gateway_managed_outbound_ipv6_count,
help='NAT gateway managed outbound IPv6 count. Only valid with --outbound-type '
'managedNATGateway and --outbound-type-sku StandardV2.')
c.argument('nat_gateway_idle_timeout', type=int, validator=validate_nat_gateway_idle_timeout)
c.argument('outbound_type', arg_type=get_enum_type(outbound_types))
c.argument('nat_gateway_sku', options_list=['--outbound-type-sku'], arg_type=get_enum_type(nat_gateway_skus), validator=validate_outbound_type_sku)
c.argument('nat_gateway_outbound_ip_ids', options_list=['--nat-gateway-outbound-ips', '--nat-gw-ips'],
help='Comma-separated public IP resource IDs for the cluster NAT gateway. '
'Only valid with --outbound-type-sku StandardV2.')
c.argument('nat_gateway_outbound_ip_prefix_ids',
options_list=['--nat-gateway-outbound-ip-prefixes', '--nat-gw-prefixes'],
help='Comma-separated public IP prefix resource IDs for the cluster NAT gateway. '
'Only valid with --outbound-type-sku StandardV2.')
c.argument('outbound_type', arg_type=get_enum_type(outbound_types), validator=validate_nat_gateway_v2_params)
c.argument('network_plugin', arg_type=get_enum_type(network_plugins))
c.argument('network_plugin_mode', arg_type=get_enum_type(network_plugin_modes))
c.argument('network_policy', validator=validate_network_policy)
Expand Down Expand Up @@ -695,11 +714,24 @@ def load_arguments(self, _):
c.argument("load_balancer_sku", arg_type=get_enum_type([CONST_LOAD_BALANCER_SKU_STANDARD]), validator=validate_load_balancer_sku)
c.argument('nrg_lockdown_restriction_level', arg_type=get_enum_type(nrg_lockdown_restriction_levels))
c.argument('nat_gateway_managed_outbound_ip_count', type=int, validator=validate_nat_gateway_managed_outbound_ip_count)
c.argument('nat_gateway_managed_outbound_ipv6_count',
options_list=['--nat-gateway-managed-outbound-ipv6-count', '--nat-gw-ipv6-count'],
type=int, validator=validate_nat_gateway_managed_outbound_ipv6_count,
help='NAT gateway managed outbound IPv6 count. Only valid with --outbound-type '
'managedNATGateway and --outbound-type-sku StandardV2.')
c.argument('nat_gateway_idle_timeout', type=int, validator=validate_nat_gateway_idle_timeout)
c.argument('nat_gateway_sku', options_list=['--outbound-type-sku'], arg_type=get_enum_type(nat_gateway_skus), validator=validate_outbound_type_sku_for_update)
c.argument('nat_gateway_outbound_ip_ids', options_list=['--nat-gateway-outbound-ips', '--nat-gw-ips'],
help='Comma-separated public IP resource IDs for the cluster NAT gateway. '
'Only valid with --outbound-type-sku StandardV2.')
c.argument('nat_gateway_outbound_ip_prefix_ids',
options_list=['--nat-gateway-outbound-ip-prefixes', '--nat-gw-prefixes'],
help='Comma-separated public IP prefix resource IDs for the cluster NAT gateway. '
'Only valid with --outbound-type-sku StandardV2.')
c.argument('network_dataplane', arg_type=get_enum_type(network_dataplanes))
c.argument('network_plugin', arg_type=get_enum_type(network_plugins))
c.argument('network_policy', arg_type=get_enum_type(network_policies))
c.argument('outbound_type', arg_type=get_enum_type(outbound_types))
c.argument('outbound_type', arg_type=get_enum_type(outbound_types), validator=validate_nat_gateway_v2_params_for_update)
c.argument('auto_upgrade_channel', arg_type=get_enum_type(auto_upgrade_channels))
c.argument('cluster_autoscaler_profile', nargs='+', options_list=["--cluster-autoscaler-profile", "--ca-profile"],
help="Comma-separated list of key=value pairs for configuring cluster autoscaler. Pass an empty string to clear the profile.")
Expand Down
93 changes: 93 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acs/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,99 @@ def validate_nat_gateway_idle_timeout(namespace):
raise InvalidArgumentValueError("--nat-gateway-idle-timeout must be in the range [4,120]")


def validate_nat_gateway_managed_outbound_ipv6_count(namespace):
"""validate NAT gateway profile managed outbound IPv6 count (StandardV2, dual-stack only)"""
ipv6_count = namespace.nat_gateway_managed_outbound_ipv6_count
if ipv6_count is not None:
if ipv6_count < 1 or ipv6_count > 16:
raise InvalidArgumentValueError(
"--nat-gateway-managed-outbound-ipv6-count must be in the range [1,16]"
)


def validate_outbound_type_sku(namespace):
"""Validate --outbound-type-sku on create (managed NAT gateway SKU).

The SKU only applies to the managed NAT gateway outbound type and, on create, drives building a
NAT gateway profile, so --outbound-type must be set explicitly to managedNATGateway; omitting it
defaults the cluster to loadBalancer and produces an incompatible request.
"""
if getattr(namespace, 'nat_gateway_sku', None) is None:
return
if namespace.outbound_type != 'managedNATGateway':
raise InvalidArgumentValueError(
"--outbound-type-sku is only valid with --outbound-type managedNATGateway; "
"specify --outbound-type managedNATGateway explicitly."
)


def validate_outbound_type_sku_for_update(namespace):
"""Validate --outbound-type-sku on update (managed NAT gateway SKU).

Unlike create, --outbound-type may be omitted when the cluster is already managed NAT gateway;
only an explicit non-managed-NAT-gateway outbound type is rejected here. The update decorator
additionally verifies the cluster's existing outbound type.
"""
if getattr(namespace, 'nat_gateway_sku', None) is None:
return
if namespace.outbound_type is not None and namespace.outbound_type != 'managedNATGateway':
raise InvalidArgumentValueError(
"--outbound-type-sku is only valid with --outbound-type managedNATGateway."
)


def validate_nat_gateway_v2_params(namespace):
"""Validate the V2-only NAT gateway params on create.

The V2-only params (managed IPv6 count, BYO outbound IPs / IP prefixes) require the managed NAT
gateway outbound type at the StandardV2 tier; the Standard (V1) SKU cannot carry them. Omitting
--outbound-type-sku is tolerated and defaults to StandardV2 where the region supports it, so only
an explicit Standard SKU is rejected here (region availability is enforced by the RP). On create
--outbound-type must be set explicitly to managedNATGateway.
"""
v2_params = [
getattr(namespace, 'nat_gateway_managed_outbound_ipv6_count', None),
getattr(namespace, 'nat_gateway_outbound_ip_ids', None),
getattr(namespace, 'nat_gateway_outbound_ip_prefix_ids', None),
]
if not any(p is not None for p in v2_params):
return
# Omitting the sku defaults to StandardV2 where supported, so only an explicit Standard is rejected.
if namespace.outbound_type != 'managedNATGateway' or getattr(namespace, 'nat_gateway_sku', None) == 'Standard':
raise InvalidArgumentValueError(
"--nat-gateway-managed-outbound-ipv6-count, --nat-gateway-outbound-ips and "
"--nat-gateway-outbound-ip-prefixes are only valid with --outbound-type managedNATGateway "
"and --outbound-type-sku StandardV2; specify --outbound-type managedNATGateway explicitly."
)


def validate_nat_gateway_v2_params_for_update(namespace):
"""Validate the V2-only NAT gateway params on update.

The V2-only params (managed IPv6 count, BYO outbound IPs / IP prefixes) require the StandardV2
tier. Unlike create, an omitted --outbound-type-sku is not a "default to StandardV2": on update
the RP preserves the cluster's existing SKU (which may be Standard), so the V2-only params
require an explicit --outbound-type-sku StandardV2. --outbound-type may be omitted when the
cluster is already managed NAT gateway; an explicit non-managed-NAT-gateway outbound type is
also rejected.
"""
v2_params = [
getattr(namespace, 'nat_gateway_managed_outbound_ipv6_count', None),
getattr(namespace, 'nat_gateway_outbound_ip_ids', None),
getattr(namespace, 'nat_gateway_outbound_ip_prefix_ids', None),
]
if not any(p is not None for p in v2_params):
return
# On update the RP preserves the existing SKU (possibly Standard), so require an explicit StandardV2.
if (namespace.outbound_type is not None and namespace.outbound_type != 'managedNATGateway') or \
getattr(namespace, 'nat_gateway_sku', None) != 'StandardV2':
raise InvalidArgumentValueError(
"--nat-gateway-managed-outbound-ipv6-count, --nat-gateway-outbound-ips and "
"--nat-gateway-outbound-ip-prefixes are only valid with --outbound-type managedNATGateway "
"and --outbound-type-sku StandardV2."
)


def validate_nodes_count(namespace):
"""Validates that min_count and max_count is set between 0-1000"""
if namespace.min_count is not None:
Expand Down
Loading
Loading