Skip to content

Commit 8849e4c

Browse files
authored
{AKS} az aks create/update: add --enable/--disable-control-plane-metrics (#9931)
1 parent 6a94cd6 commit 8849e4c

9 files changed

Lines changed: 652 additions & 3 deletions

File tree

src/aks-preview/HISTORY.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ To release a new version, please select a new version number (usually plus 1 to
1212
Pending
1313
+++++++
1414

15+
21.0.0b6
16+
++++++++
17+
* `az aks create / update`: Add `--enable-control-plane-metrics` and `az aks update`: `--disable-control-plane-metrics` flags to opt clusters into Azure Monitor managed Prometheus control plane metrics (kube-apiserver, etcd, etc.) via the first-class API property `azureMonitorProfile.metrics.controlPlane.enabled` (replaces the AFEC-gated preview).
18+
1519
21.0.0b5
1620
+++++++
1721
* `az aks prepared-image-specification`: Add new command.

src/aks-preview/azext_aks_preview/_help.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,9 @@
606606
- name: --enable-azure-monitor-metrics
607607
type: bool
608608
short-summary: Enable Azure Monitor Metrics Profile
609+
- name: --enable-control-plane-metrics --enable-cp-metrics
610+
type: bool
611+
short-summary: Enable collection of Azure Monitor managed Prometheus control plane metrics for managed cluster components (controlplane-apiserver and controlplane-etcd targets by default). Requires Azure Monitor metrics to be enabled (already enabled or via --enable-azure-monitor-metrics).
609612
- name: --azure-monitor-workspace-resource-id
610613
type: string
611614
short-summary: Resource ID of the Azure Monitor Workspace
@@ -1332,6 +1335,9 @@
13321335
- name: --enable-azure-monitor-metrics
13331336
type: bool
13341337
short-summary: Enable Azure Monitor Metrics Profile
1338+
- name: --enable-control-plane-metrics --enable-cp-metrics
1339+
type: bool
1340+
short-summary: Enable collection of Azure Monitor managed Prometheus control plane metrics for managed cluster components (controlplane-apiserver and controlplane-etcd targets by default). Requires Azure Monitor metrics to be enabled (already enabled or via --enable-azure-monitor-metrics).
13351341
- name: --azure-monitor-workspace-resource-id
13361342
type: string
13371343
short-summary: Resource ID of the Azure Monitor Workspace
@@ -1353,6 +1359,9 @@
13531359
- name: --disable-azure-monitor-metrics
13541360
type: bool
13551361
short-summary: Disable Azure Monitor Metrics Profile. This will delete all DCRA's associated with the cluster, any linked DCRs with the data stream = prometheus-stream and the recording rule groups created by the addon for this AKS cluster.
1362+
- name: --disable-control-plane-metrics --disable-cp-metrics
1363+
type: bool
1364+
short-summary: Disable collection of Azure Monitor managed Prometheus control plane metrics. Leaves Azure Monitor metrics enabled.
13561365
- name: --enable-azure-monitor-app-monitoring
13571366
type: bool
13581367
short-summary: Enable Azure Monitor Application Monitoring

src/aks-preview/azext_aks_preview/_params.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,17 @@ def load_arguments(self, _):
11311131
c.argument("ksm_metric_annotations_allow_list")
11321132
c.argument("grafana_resource_id", validator=validate_grafanaresourceid)
11331133
c.argument("enable_windows_recording_rules", action="store_true")
1134+
c.argument(
1135+
"enable_control_plane_metrics",
1136+
options_list=["--enable-control-plane-metrics", "--enable-cp-metrics"],
1137+
action="store_true",
1138+
help=(
1139+
"Enable collection of Azure Monitor managed Prometheus control plane metrics for managed "
1140+
"cluster components (controlplane-apiserver and controlplane-etcd targets by default). "
1141+
"Requires Azure Monitor metrics to be enabled "
1142+
"(already enabled or via --enable-azure-monitor-metrics)."
1143+
),
1144+
)
11341145
c.argument("enable_azure_monitor_app_monitoring",
11351146
is_preview=True,
11361147
action="store_true"
@@ -1636,6 +1647,27 @@ def load_arguments(self, _):
16361647
hide=True,
16371648
),
16381649
)
1650+
c.argument("disable_azure_monitor_metrics", action="store_true")
1651+
c.argument(
1652+
"enable_control_plane_metrics",
1653+
options_list=["--enable-control-plane-metrics", "--enable-cp-metrics"],
1654+
action="store_true",
1655+
help=(
1656+
"Enable collection of Azure Monitor managed Prometheus control plane metrics for managed "
1657+
"cluster components (controlplane-apiserver and controlplane-etcd targets by default). "
1658+
"Requires Azure Monitor metrics to be enabled "
1659+
"(already enabled or via --enable-azure-monitor-metrics)."
1660+
),
1661+
)
1662+
c.argument(
1663+
"disable_control_plane_metrics",
1664+
options_list=["--disable-control-plane-metrics", "--disable-cp-metrics"],
1665+
action="store_true",
1666+
help=(
1667+
"Disable collection of Azure Monitor managed Prometheus control plane metrics. "
1668+
"Sets azureMonitorProfile.metrics.controlPlane.enabled=false on the cluster."
1669+
),
1670+
)
16391671
c.argument("enable_azure_monitor_app_monitoring",
16401672
action="store_true",
16411673
is_preview=True

src/aks-preview/azext_aks_preview/azuremonitormetrics/azuremonitorprofile.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,59 @@
1818
check_azuremonitormetrics_profile,
1919
rp_registrations
2020
)
21-
from azure.cli.core.azclierror import InvalidArgumentValueError
21+
from azure.cli.command_modules.acs._client_factory import get_container_service_client
22+
from azure.cli.core.azclierror import InvalidArgumentValueError, UnknownError
2223
from knack.util import CLIError
2324
from knack.log import get_logger
2425

2526
logger = get_logger(__name__)
2627

2728

29+
# pylint: disable=line-too-long
30+
def _addon_put_with_control_plane(cmd, cluster_subscription, cluster_resource_group_name, cluster_name):
31+
"""Sibling of core ``addon_put`` that ALSO flips ``metrics.controlPlane.enabled=True``.
32+
33+
Used by the greenfield ``aks create --enable-control-plane-metrics`` path. The
34+
initial cluster PUT intentionally leaves ``control_plane`` unset so the RP does
35+
not schedule the control-plane-metrics collection (CCP) pod before the DCRA is
36+
created in postprocessing. Once the DCRA exists, we issue this PUT so the CCP
37+
pod is scheduled with its DCRA already in place (race-free).
38+
"""
39+
client = get_container_service_client(cmd.cli_ctx, cluster_subscription).managed_clusters
40+
try:
41+
mc = client.get(cluster_resource_group_name, cluster_name)
42+
except CLIError as e:
43+
raise UnknownError(str(e)) from e
44+
# Enable metrics if present and not already enabled (mirrors core addon_put).
45+
if hasattr(mc, "azure_monitor_profile") and mc.azure_monitor_profile:
46+
if hasattr(mc.azure_monitor_profile, "metrics") and mc.azure_monitor_profile.metrics:
47+
if getattr(mc.azure_monitor_profile.metrics, "enabled", None) is False:
48+
mc.azure_monitor_profile.metrics.enabled = True
49+
# Flip control plane now that DCRA exists.
50+
try:
51+
from azure.mgmt.containerservice.models import (
52+
ManagedClusterAzureMonitorProfileMetricsControlPlane,
53+
)
54+
mc.azure_monitor_profile.metrics.control_plane = (
55+
ManagedClusterAzureMonitorProfileMetricsControlPlane(enabled=True)
56+
)
57+
except ImportError:
58+
# Fallback for SDK versions that don't expose the model directly:
59+
# set a dict that the generated client will serialize as the property.
60+
mc.azure_monitor_profile.metrics.control_plane = {"enabled": True}
61+
# Unlike the sibling ``addon_put`` (where ``metrics.enabled`` is already true on the
62+
# cluster from the initial PUT and the postprocessing PUT is just a safety re-affirm),
63+
# this is the ONLY place where ``controlPlane.enabled`` is set during ``aks create``.
64+
# Wait for the LRO so the CP flip is durably persisted before the create command
65+
# returns; otherwise callers / tests that read the cluster immediately could see the
66+
# pre-flip state.
67+
try:
68+
poller = client.begin_create_or_update(cluster_resource_group_name, cluster_name, mc)
69+
poller.result()
70+
except Exception as e:
71+
raise UnknownError(str(e)) from e
72+
73+
2874
# pylint: disable=line-too-long
2975
def link_azure_monitor_profile_artifacts(
3076
cmd,
@@ -49,7 +95,15 @@ def link_azure_monitor_profile_artifacts(
4995
create_rules(cmd, cluster_subscription, cluster_resource_group_name, cluster_name, azure_monitor_workspace_resource_id, azure_monitor_workspace_location, raw_parameters)
5096
# if aks cluster create flow -> do a PUT on the AKS cluster to enable the addon
5197
if create_flow:
52-
addon_put(cmd, cluster_subscription, cluster_resource_group_name, cluster_name)
98+
# If --enable-control-plane-metrics was specified on create, flip
99+
# metrics.controlPlane.enabled HERE (after DCRA creation) instead of on
100+
# the initial cluster PUT. This avoids the CCP pod being scheduled before
101+
# its DCRA exists (which would cause CrashLoopBackOff until reconciliation).
102+
enable_cp = bool(raw_parameters and raw_parameters.get("enable_control_plane_metrics"))
103+
if enable_cp:
104+
_addon_put_with_control_plane(cmd, cluster_subscription, cluster_resource_group_name, cluster_name)
105+
else:
106+
addon_put(cmd, cluster_subscription, cluster_resource_group_name, cluster_name)
53107

54108

55109
# pylint: disable=line-too-long

src/aks-preview/azext_aks_preview/custom.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,7 @@ def aks_create(
11441144
ksm_metric_annotations_allow_list=None,
11451145
grafana_resource_id=None,
11461146
enable_windows_recording_rules=False,
1147+
enable_control_plane_metrics=False,
11471148
# azure monitor profile - app monitoring
11481149
enable_azure_monitor_app_monitoring=False,
11491150
# opentelemetry parameters
@@ -1375,6 +1376,8 @@ def aks_update(
13751376
enable_windows_recording_rules=False,
13761377
disable_azuremonitormetrics=False,
13771378
disable_azure_monitor_metrics=False,
1379+
enable_control_plane_metrics=False,
1380+
disable_control_plane_metrics=False,
13781381
# azure monitor profile - app monitoring
13791382
enable_azure_monitor_app_monitoring=False,
13801383
disable_azure_monitor_app_monitoring=False,

src/aks-preview/azext_aks_preview/managed_cluster_decorator.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2651,6 +2651,109 @@ def get_disable_azure_monitor_metrics(self) -> bool:
26512651
"""
26522652
return self._get_disable_azure_monitor_metrics(enable_validation=True)
26532653

2654+
def validate_control_plane_metrics_params(self) -> None:
2655+
"""Validate the --enable/--disable-control-plane-metrics flag combo and
2656+
its interaction with --enable/--disable-azure-monitor-metrics.
2657+
2658+
Raises MutuallyExclusiveArgumentError or RequiredArgumentMissingError on
2659+
an invalid combination. Returns nothing — use this when you want to
2660+
surface validation errors without consuming a parameter value.
2661+
2662+
Reads raw_param directly so the getters can also delegate here from
2663+
their enable_validation=True path without recursing.
2664+
"""
2665+
enable_cp = self.raw_param.get("enable_control_plane_metrics")
2666+
disable_cp = self.raw_param.get("disable_control_plane_metrics")
2667+
# On create, the property may already be set on the incoming mc object.
2668+
if self.decorator_mode == DecoratorMode.CREATE:
2669+
if (
2670+
self.mc and
2671+
self.mc.azure_monitor_profile and
2672+
self.mc.azure_monitor_profile.metrics and
2673+
self.mc.azure_monitor_profile.metrics.control_plane
2674+
):
2675+
enable_cp = self.mc.azure_monitor_profile.metrics.control_plane.enabled
2676+
2677+
if enable_cp and disable_cp:
2678+
raise MutuallyExclusiveArgumentError(
2679+
"Cannot specify --enable-control-plane-metrics and --disable-control-plane-metrics "
2680+
"at the same time."
2681+
)
2682+
2683+
if enable_cp:
2684+
# Reject combining enable-control-plane-metrics with disable-azure-monitor-metrics
2685+
# in the same command — the resulting payload would be inconsistent.
2686+
if self._get_disable_azure_monitor_metrics(False):
2687+
raise MutuallyExclusiveArgumentError(
2688+
"Cannot specify --enable-control-plane-metrics together with "
2689+
"--disable-azure-monitor-metrics."
2690+
)
2691+
# Must have Azure Monitor metrics enabled (either already or in this command).
2692+
already_enabled = (
2693+
self.mc and
2694+
self.mc.azure_monitor_profile and
2695+
self.mc.azure_monitor_profile.metrics and
2696+
self.mc.azure_monitor_profile.metrics.enabled
2697+
)
2698+
enabling_now = self._get_enable_azure_monitor_metrics(False)
2699+
if not already_enabled and not enabling_now:
2700+
raise RequiredArgumentMissingError(
2701+
"--enable-control-plane-metrics requires Azure Monitor metrics to be enabled. "
2702+
"Specify --enable-azure-monitor-metrics or run on a cluster that already has "
2703+
"Azure Monitor metrics enabled."
2704+
)
2705+
2706+
def _get_enable_control_plane_metrics(self, enable_validation: bool = False) -> bool:
2707+
"""Internal function to obtain the value of enable_control_plane_metrics.
2708+
When enable_validation is True, the flag combinations are validated via
2709+
validate_control_plane_metrics_params before the value is returned.
2710+
2711+
:return: bool
2712+
"""
2713+
# Read the original value passed by the command.
2714+
enable_control_plane_metrics = self.raw_param.get("enable_control_plane_metrics")
2715+
# In create mode, try to read the property value corresponding to the parameter from the `mc` object.
2716+
if self.decorator_mode == DecoratorMode.CREATE:
2717+
if (
2718+
self.mc and
2719+
self.mc.azure_monitor_profile and
2720+
self.mc.azure_monitor_profile.metrics and
2721+
self.mc.azure_monitor_profile.metrics.control_plane
2722+
):
2723+
enable_control_plane_metrics = self.mc.azure_monitor_profile.metrics.control_plane.enabled
2724+
if enable_validation:
2725+
self.validate_control_plane_metrics_params()
2726+
return bool(enable_control_plane_metrics)
2727+
2728+
def get_enable_control_plane_metrics(self) -> bool:
2729+
"""Obtain the value of enable_control_plane_metrics.
2730+
This function will verify the parameter by default. If both enable_control_plane_metrics and
2731+
disable_control_plane_metrics are specified, raise a MutuallyExclusiveArgumentError.
2732+
:return: bool
2733+
"""
2734+
return self._get_enable_control_plane_metrics(enable_validation=True)
2735+
2736+
def _get_disable_control_plane_metrics(self, enable_validation: bool = False) -> bool:
2737+
"""Internal function to obtain the value of disable_control_plane_metrics.
2738+
When enable_validation is True, the flag combinations are validated via
2739+
validate_control_plane_metrics_params before the value is returned.
2740+
2741+
:return: bool
2742+
"""
2743+
# Read the original value passed by the command.
2744+
disable_control_plane_metrics = self.raw_param.get("disable_control_plane_metrics")
2745+
if enable_validation:
2746+
self.validate_control_plane_metrics_params()
2747+
return bool(disable_control_plane_metrics)
2748+
2749+
def get_disable_control_plane_metrics(self) -> bool:
2750+
"""Obtain the value of disable_control_plane_metrics.
2751+
This function will verify the parameter by default. If both enable_control_plane_metrics and
2752+
disable_control_plane_metrics are specified, raise a MutuallyExclusiveArgumentError.
2753+
:return: bool
2754+
"""
2755+
return self._get_disable_control_plane_metrics(enable_validation=True)
2756+
26542757
def _get_enable_azure_monitor_app_monitoring(self, enable_validation=True) -> bool:
26552758
"""Internal function to obtain the value of enable_azure_monitor_app_monitoring.
26562759
This function supports the option of enable_validation. When enabled, if both
@@ -4693,6 +4796,18 @@ def _setup_azure_monitor_metrics(self, mc: ManagedCluster) -> None:
46934796
metric_annotations_allow_list=str(ksm_metric_annotations_allow_list)
46944797
))
46954798
mc.azure_monitor_profile.metrics.kube_state_metrics = kube_state_metrics
4799+
4800+
# NOTE: control_plane.enabled is intentionally NOT set here on the create flow.
4801+
# If we set it on this initial PUT, the RP would schedule the control-plane-metrics
4802+
# collection pod (CCP) before the DCRA (Data Collection Rule Association) has been
4803+
# created in postprocessing. The CCP would then crash-loop with "DCRA not found"
4804+
# until the next reconciliation.
4805+
# Instead, we defer the flip to the addon_put step inside
4806+
# link_azure_monitor_profile_artifacts (postprocessing_after_mc_created), which
4807+
# runs *after* DCRA creation. The validator still runs here to surface flag
4808+
# combination errors early.
4809+
self.context.validate_control_plane_metrics_params()
4810+
46964811
self.context.set_intermediate("azuremonitormetrics_addon_enabled", True, overwrite_exists=True)
46974812

46984813
def _setup_azure_monitor_app_monitoring(self, mc: ManagedCluster) -> None:
@@ -4812,6 +4927,12 @@ def set_up_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster:
48124927
"""
48134928
self._ensure_mc(mc)
48144929

4930+
# Surface control-plane-metrics flag combination errors even when the
4931+
# parent metrics flag was not specified, so users get a clear error
4932+
# instead of a silent ignore when they pass --enable-control-plane-metrics
4933+
# on its own.
4934+
self.context.validate_control_plane_metrics_params()
4935+
48154936
if self.context.get_enable_azure_monitor_metrics():
48164937
self._setup_azure_monitor_metrics(mc)
48174938

@@ -7006,6 +7127,30 @@ def update_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster:
70067127
if self.context.get_disable_azure_monitor_metrics():
70077128
self._disable_azure_monitor_metrics(mc)
70087129

7130+
# Handle enable / disable of control plane metrics independently of the parent metrics flag,
7131+
# so users can toggle control plane metrics on a cluster that already has metrics enabled.
7132+
if self.context.get_enable_control_plane_metrics():
7133+
if mc.azure_monitor_profile is None:
7134+
mc.azure_monitor_profile = self.models.ManagedClusterAzureMonitorProfile() # pylint: disable=no-member
7135+
if mc.azure_monitor_profile.metrics is None:
7136+
# Should not normally happen — validation requires metrics to be enabled — but guard
7137+
# against partially-populated profiles to avoid AttributeError.
7138+
mc.azure_monitor_profile.metrics = (
7139+
self.models.ManagedClusterAzureMonitorProfileMetrics(enabled=True) # pylint: disable=no-member
7140+
)
7141+
mc.azure_monitor_profile.metrics.control_plane = (
7142+
self.models.ManagedClusterAzureMonitorProfileMetricsControlPlane(enabled=True) # pylint: disable=no-member
7143+
)
7144+
7145+
if self.context.get_disable_control_plane_metrics():
7146+
if (
7147+
mc.azure_monitor_profile and
7148+
mc.azure_monitor_profile.metrics
7149+
):
7150+
mc.azure_monitor_profile.metrics.control_plane = (
7151+
self.models.ManagedClusterAzureMonitorProfileMetricsControlPlane(enabled=False) # pylint: disable=no-member
7152+
)
7153+
70097154
if self.context.get_enable_azure_monitor_app_monitoring():
70107155
if mc.azure_monitor_profile is None:
70117156
mc.azure_monitor_profile = self.models.ManagedClusterAzureMonitorProfile()

0 commit comments

Comments
 (0)