Home / Azure

AzureCheat sheetAdvanced

Azure Monitor managed Prometheus for AKS - PromQL and cardinality cheat sheet for operators

Query an Azure Monitor workspace from the CLI, find the metrics and labels that inflate active series, and keep PromQL for saturation, throttling and restarts fast at cluster scale.

Deepak Bhardwaj6 min read

Applies toAKS with the Azure Monitor metrics add-on (managed Prometheus, ama-metrics), Azure Monitor workspace, Azure Managed Grafana, Azure CLI 2.x

Who this is for

You already have the metrics add-on running and dashboards in Grafana. Now the bill or the workspace limits are climbing, queries time out on large clusters, and a recording rule you need is missing. These are the commands and queries we keep at hand.

Wiring and access

# Is the add-on enabled, and which workspace does it write to?
az aks show -g rg-aks -n aks-prod --query azureMonitorProfile -o json

# Enable it against an existing Azure Monitor workspace
az aks update -g rg-aks -n aks-prod --enable-azure-monitor-metrics \
  --azure-monitor-workspace-resource-id "$AMW_ID"

# Workspace query endpoint
AMW_QUERY=$(az monitor account show -g rg-monitor -n amw-prod \
  --query metrics.prometheusQueryEndpoint -o tsv)

# Collector pods
kubectl -n kube-system get pods -l rsName=ama-metrics -o wide
kubectl -n kube-system get pods | grep ama-metrics

Querying from the CLI needs the Monitoring Data Reader role on the workspace and a token for the Prometheus audience:

TOKEN=$(az account get-access-token --resource https://prometheus.monitor.azure.com \
  --query accessToken -o tsv)

promq() {
  curl -s -G "$AMW_QUERY/api/v1/query" \
    -H "Authorization: Bearer $TOKEN" --data-urlencode "query=$1" | jq '.data.result'
}

promq 'up{job="kube-state-metrics"}'

The endpoint speaks the standard Prometheus HTTP API, so /api/v1/query_range, /api/v1/series and /api/v1/labels work the same way.

Cardinality: find what is inflating active series

Active time series drive both cost and the workspace limits. Check the workspace's ingestion and active-series utilization metrics in the portal, or list them with:

az monitor metrics list-definitions --resource "$AMW_ID" -o table

Then find the source with PromQL. Run these against a short window; on a large workspace they are expensive.

Question PromQL
Top 20 metric names by series topk(20, count by (__name__)({__name__=~".+"}))
Series per scrape job sort_desc(count by (job)({__name__=~".+"}))
Series per namespace topk(20, count by (namespace)({__name__=~".+"}))
Series one metric produces, by label count by (le)(http_request_duration_seconds_bucket)
Samples per scrape per target topk(20, scrape_samples_post_metric_relabeling)
Targets that grew after a deploy topk(20, delta(scrape_series_added[1h]))

Usual offenders:

Offender Why it explodes Fix
Histograms with many buckets × many routes buckets × label combinations Fewer buckets; drop _bucket where you only need _sum/_count
Labels holding IDs (user_id, request_id, full URL path) Unbounded values Fix instrumentation, or drop the label with relabeling
cAdvisor per-container metrics on dense nodes pods × containers × interfaces/devices Keep only the metrics you chart
Short-lived pods (jobs, CI runners) New pod label value every run Aggregate in recording rules; drop raw series
Pod annotation scraping on everything Every app's debug metrics ingested Scrape only namespaces that opt in

Controlling what gets scraped

The add-on is configured through ConfigMaps in kube-system. The settings ConfigMap (ama-metrics-settings-configmap) turns default targets on or off and holds per-target keep-lists; custom scrape jobs go in ama-metrics-prometheus-config. Neither exists until you create it, and the settings schema has changed between versions (key names such as default-scrape-settings-enabled versus default-targets-scrape-enabled), so start from the template in the current Microsoft documentation for your add-on version.

kubectl -n kube-system get configmap | grep ama-metrics
kubectl -n kube-system get configmap ama-metrics-settings-configmap -o yaml

By default the add-on uses a minimal ingestion profile that keeps a curated set of metrics per default target. Turning that profile off can multiply ingestion for that target; add the specific metrics you need to the keep-list instead.

In custom scrape jobs, drop at ingestion with standard relabeling:

scrape_configs:
  - job_name: app-metrics
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: "true"
    metric_relabel_configs:
      - source_labels: [__name__]
        action: drop
        regex: "http_request_duration_seconds_bucket|go_gc_.*"
      - action: labeldrop
        regex: "request_id|user_id"

After applying, the ama-metrics pods restart and pick up the change within a few minutes. Validate with the collector logs:

kubectl -n kube-system logs -l rsName=ama-metrics -c prometheus-collector --tail=100 | grep -iE "error|invalid|config"

Container and label names in the add-on can vary by version; check kubectl describe pod if the selector returns nothing.

Warning

A typo in metric_relabel_configs with action: keep keeps only what matches, which can silently drop every metric for that job. Compare count by (job) before and after each change.

PromQL operators actually use

Saturation and throttling

Need PromQL
CPU throttling ratio per container sum by (namespace,pod,container)(rate(container_cpu_cfs_throttled_periods_total[5m])) / sum by (namespace,pod,container)(rate(container_cpu_cfs_periods_total[5m]))
CPU used vs request sum by (namespace,pod)(rate(container_cpu_usage_seconds_total{container!=""}[5m])) / sum by (namespace,pod)(kube_pod_container_resource_requests{resource="cpu"})
Memory working set vs limit sum by (namespace,pod,container)(container_memory_working_set_bytes{container!=""}) / sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="memory"})
Node allocatable CPU requested sum by (node)(kube_pod_container_resource_requests{resource="cpu"}) / sum by (node)(kube_node_status_allocatable{resource="cpu"})

Health

Need PromQL
Restarts in the last hour topk(20, increase(kube_pod_container_status_restarts_total[1h]))
OOMKilled containers kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
Pods not ready sum by (namespace)(kube_pod_status_ready{condition="false"})
Deployments below desired kube_deployment_spec_replicas - kube_deployment_status_replicas_available > 0
Nodes not Ready kube_node_status_condition{condition="Ready",status="true"} == 0
Scrape targets down up == 0

Metric availability depends on the keep-lists above: with the minimal profile some cAdvisor and kube-state-metrics series are not ingested. If a query returns nothing, check the keep-list before assuming the workload is healthy.

Keep queries fast at scale

  • Aggregate early. sum by (namespace)(rate(x[5m])) scans the same data as rate(x[5m]) but returns far fewer series to Grafana.
  • Use recording rules for anything on a shared dashboard or an alert. Managed Prometheus rule groups are Azure resources:
az alerts-management prometheus-rule-group create -g rg-monitor -n aks-prod-recording \
  -l westeurope --scopes "$AMW_ID" --cluster-name aks-prod --interval PT1M \
  --rules '[{"record":"namespace:container_cpu_usage:rate5m","expression":"sum by (namespace)(rate(container_cpu_usage_seconds_total{container!=\"\"}[5m]))","enabled":true}]'

That command lives in a CLI extension (az extension add --name alertsmanagement); check az alerts-management prometheus-rule-group create --help for the flags in your version, or deploy rule groups with Bicep (Microsoft.AlertsManagement/prometheusRuleGroups).

  • Match rate() windows to the scrape interval. A window at least four times the scrape interval avoids gaps; with 30-second scrapes, [2m] is the practical minimum.
  • Limit time ranges on dashboards that use high-cardinality metrics. Query limits on the workspace apply per query; check the current Azure Monitor workspace limits page rather than guessing.

Prevent it next time

  • Add a weekly "top 20 metrics by series" check and compare against last week.
  • Require new scrape jobs to come with a metric_relabel_configs block and an expected series count.
  • Alert on the workspace's active-series utilization before it reaches its limit.
  • Put recording rules and scrape config in the same repo as the cluster IaC so changes are reviewed together.