Azure Managed Grafana + Prometheus no AKS: alertas, multi-cluster e troubleshooting - Parte 2

Azure Managed Grafana + Prometheus no AKS: alertas, multi-cluster e troubleshooting - Parte 2

22 de junho de 2026

Na Parte 1, provisionamos toda a infraestrutura de observabilidade — Azure Monitor workspace, Managed Grafana e Prometheus no AKS — além de configurar scrape de métricas customizadas e construir dashboards avançados com PromQL. Agora, vamos avançar para Recording Rules, Alerting Rules, monitoramento multi-cluster, dashboards como código, instrumentação de aplicações e troubleshooting dos problemas mais comuns em produção.

Recording Rules e Alerting Rules

Recording rules pré-computam queries complexas para performance, e alerting rules disparam notificações quando condições são atendidas. Ambas usam PromQL.

Recording Rules

Recording rules são executadas periodicamente e salvam o resultado como uma nova série temporal. Útil para queries caras que seriam lentas em dashboards.

# prometheus-rules.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ama-metrics-prometheus-config
  namespace: kube-system
data:
  recording-rules: |
    groups:
    - name: kubernetes_resource_usage
      interval: 60s
      rules:
      - record: namespace:container_cpu_usage:sum_rate5m
        expr: |
          sum by (namespace) (
            rate(container_cpu_usage_seconds_total{container!="", container!="POD"}[5m])
          )

      - record: namespace:container_memory_working_set:sum
        expr: |
          sum by (namespace) (
            container_memory_working_set_bytes{container!="", container!="POD"}
          )

      - record: node:cpu_utilization:ratio
        expr: |
          1 - avg by (instance) (
            rate(node_cpu_seconds_total{mode="idle"}[5m])
          )

    - name: api_slis
      interval: 30s
      rules:
      - record: api:http_request_error_rate:ratio_rate5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          /
          sum(rate(http_requests_total[5m]))

      - record: api:http_request_latency_p99:seconds
        expr: |
          histogram_quantile(0.99,
            sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
          )

Alerting Rules com Azure Managed Prometheus

Alerting rules no Azure Managed Prometheus são configuradas como Prometheus Rule Groups via Azure Resource Manager, não via ConfigMap. Isso é diferente do Prometheus self-hosted e pega muita gente de surpresa.

# Criar regra de alerta via CLI
az monitor account rule-group create \
  --name "kubernetes-critical-alerts" \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --cluster-name $AKS_CLUSTER \
  --scope $MONITOR_WORKSPACE_ID \
  --interval PT1M \
  --rules '[
    {
      "alert": "PodCrashLooping",
      "expression": "increase(kube_pod_container_status_restarts_total[1h]) > 5",
      "for": "PT10M",
      "severity": 2,
      "labels": {
        "team": "sre",
        "severity": "critical"
      },
      "annotations": {
        "summary": "Pod {{ $labels.namespace }}/{{ $labels.pod }} está em crash loop",
        "description": "O pod {{ $labels.pod }} no namespace {{ $labels.namespace }} reiniciou mais de 5 vezes na última hora."
      }
    },
    {
      "alert": "NodeMemoryPressure",
      "expression": "100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 90",
      "for": "PT5M",
      "severity": 1,
      "labels": {
        "team": "infra",
        "severity": "critical"
      },
      "annotations": {
        "summary": "Node {{ $labels.instance }} com memória acima de 90%",
        "description": "O node {{ $labels.instance }} está usando {{ $value }}% de memória há mais de 5 minutos."
      }
    },
    {
      "alert": "HighErrorRate",
      "expression": "api:http_request_error_rate:ratio_rate5m > 0.05",
      "for": "PT5M",
      "severity": 2,
      "labels": {
        "team": "backend",
        "severity": "warning"
      },
      "annotations": {
        "summary": "Taxa de erros HTTP acima de 5%",
        "description": "A taxa de erros HTTP 5xx está em {{ $value | humanizePercentage }} nos últimos 5 minutos."
      }
    }
  ]'

Integrando alertas Prometheus com Action Groups

Para que os alertas disparem notificações reais (email, Teams, PagerDuty, webhook), você precisa conectar os Prometheus alerts com Azure Monitor Action Groups:

# Criar Action Group
az monitor action-group create \
  --name "ag-sre-oncall" \
  --resource-group $RESOURCE_GROUP \
  --short-name "SRE" \
  --email-receiver name="sre-lead" email-address="sre@empresa.com" \
  --webhook-receiver name="pagerduty" uri="https://events.pagerduty.com/integration/XXXX/enqueue"

# Criar Alert Processing Rule que conecta alertas Prometheus ao Action Group
az monitor alert-processing-rule create \
  --name "route-prometheus-alerts" \
  --resource-group $RESOURCE_GROUP \
  --rule-type AddActionGroups \
  --scopes $MONITOR_WORKSPACE_ID \
  --action-groups "/subscriptions/{sub-id}/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Insights/actionGroups/ag-sre-oncall"

Cenário avançado: Monitoramento multi-cluster

Um dos maiores benefícios do Azure Managed Prometheus sobre o self-hosted é o monitoramento multi-cluster nativo. Com Prometheus self-hosted, você precisaria de Thanos ou Cortex para agregar métricas de vários clusters. Com Azure Managed Prometheus, basta apontar todos os clusters para o mesmo workspace.

Arquitetura multi-cluster

Configurando um segundo cluster

# Segundo cluster — mesmo workspace, mesmo Grafana
az aks update \
  --name "aks-staging-brazil" \
  --resource-group "rg-staging" \
  --enable-azure-monitor-metrics \
  --azure-monitor-workspace-resource-id $MONITOR_WORKSPACE_ID \
  --grafana-resource-id $GRAFANA_ID

Queries multi-cluster em PromQL

Cada cluster adiciona um label cluster às métricas. Isso permite queries que comparam ou agregam entre clusters:

Uso de CPU por cluster:

avg by (cluster) (
  1 - rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100

Pods com problema em todos os clusters:

sum by (cluster, namespace) (
  kube_pod_status_phase{phase=~"Failed|Pending|Unknown"}
)

Comparação de latência entre ambientes:

histogram_quantile(0.95,
  sum by (le, cluster) (
    rate(http_request_duration_seconds_bucket{app="api-pedidos"}[5m])
  )
)

Dashboards como código com Terraform

Para times que praticam GitOps, gerenciar dashboards manualmente pelo browser é inaceitável. Dashboards devem viver no repositório, versionados e deployados via pipeline.

Provisionamento com Terraform

# main.tf

resource "azurerm_monitor_workspace" "prometheus" {
  name                = "amw-prometheus-prod"
  resource_group_name = azurerm_resource_group.observability.name
  location            = azurerm_resource_group.observability.location
}

resource "azurerm_dashboard_grafana" "grafana" {
  name                = "grafana-prod"
  resource_group_name = azurerm_resource_group.observability.name
  location            = azurerm_resource_group.observability.location
  sku                 = "Standard"
  zone_redundancy_enabled = true

  azure_monitor_workspace_integrations {
    resource_id = azurerm_monitor_workspace.prometheus.id
  }

  identity {
    type = "SystemAssigned"
  }
}

# Role assignment para o Grafana ler métricas do workspace
resource "azurerm_role_assignment" "grafana_monitoring_reader" {
  scope                = azurerm_monitor_workspace.prometheus.id
  role_definition_name = "Monitoring Reader"
  principal_id         = azurerm_dashboard_grafana.grafana.identity[0].principal_id
}

# Role assignment para SREs acessarem o Grafana
resource "azurerm_role_assignment" "sre_grafana_editor" {
  scope                = azurerm_dashboard_grafana.grafana.id
  role_definition_name = "Grafana Editor"
  principal_id         = data.azuread_group.sre_team.object_id
}

# Habilitar Prometheus metrics no AKS
resource "azurerm_monitor_data_collection_rule" "prometheus" {
  name                = "dcr-prometheus-aks"
  resource_group_name = azurerm_resource_group.observability.name
  location            = azurerm_resource_group.observability.location

  destinations {
    monitor_account {
      monitor_account_id = azurerm_monitor_workspace.prometheus.id
      name               = "MonitoringAccount"
    }
  }

  data_flow {
    streams      = ["Microsoft-PrometheusMetrics"]
    destinations = ["MonitoringAccount"]
  }

  data_sources {
    prometheus_forwarder {
      name    = "PrometheusDataSource"
      streams = ["Microsoft-PrometheusMetrics"]
    }
  }
}

resource "azurerm_monitor_data_collection_rule_association" "aks_prometheus" {
  name                    = "dcra-prometheus-aks"
  target_resource_id      = azurerm_kubernetes_cluster.aks.id
  data_collection_rule_id = azurerm_monitor_data_collection_rule.prometheus.id
}

Exportando e versionando dashboards

# Exportar dashboard existente do Grafana
az grafana dashboard show \
  --name $GRAFANA_NAME \
  --resource-group $RESOURCE_GROUP \
  --dashboard "AKS Cluster Overview" \
  -o json > dashboards/aks-cluster-overview.json

# Importar dashboard a partir de arquivo versionado
az grafana dashboard create \
  --name $GRAFANA_NAME \
  --resource-group $RESOURCE_GROUP \
  --definition @dashboards/aks-cluster-overview.json \
  --overwrite

Instrumentando sua aplicação

Para que métricas de negócio apareçam no Grafana, suas aplicações precisam expor um endpoint /metrics no formato Prometheus. Aqui estão exemplos para as linguagens mais comuns no ecossistema Azure.

Python (FastAPI + prometheus-client)

# metrics.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from fastapi import FastAPI, Request, Response
import time

app = FastAPI()

# Métricas de negócio
REQUEST_COUNT = Counter(
    'http_requests_total',
    'Total de requisições HTTP',
    ['method', 'endpoint', 'status']
)

REQUEST_LATENCY = Histogram(
    'http_request_duration_seconds',
    'Latência de requisições HTTP em segundos',
    ['method', 'endpoint'],
    buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
)

ACTIVE_CONNECTIONS = Gauge(
    'http_active_connections',
    'Conexões HTTP ativas'
)

ORDERS_PROCESSED = Counter(
    'orders_processed_total',
    'Total de pedidos processados',
    ['status']  # success, failed, cancelled
)

@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
    ACTIVE_CONNECTIONS.inc()
    start_time = time.time()

    response = await call_next(request)

    duration = time.time() - start_time
    REQUEST_COUNT.labels(
        method=request.method,
        endpoint=request.url.path,
        status=response.status_code
    ).inc()
    REQUEST_LATENCY.labels(
        method=request.method,
        endpoint=request.url.path
    ).observe(duration)
    ACTIVE_CONNECTIONS.dec()

    return response

@app.get("/metrics")
async def metrics():
    return Response(
        content=generate_latest(),
        media_type="text/plain"
    )

@app.post("/orders")
async def create_order(order: dict):
    try:
        # lógica de negócio
        result = process_order(order)
        ORDERS_PROCESSED.labels(status="success").inc()
        return {"status": "created", "id": result.id}
    except Exception as e:
        ORDERS_PROCESSED.labels(status="failed").inc()
        raise

.NET (ASP.NET Core + prometheus-net)

// Program.cs
using Prometheus;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

var app = builder.Build();

// Middleware para métricas HTTP automáticas
app.UseHttpMetrics(options =>
{
    options.AddCustomLabel("host", context => context.Request.Host.Host);
});

app.MapControllers();

// Endpoint /metrics
app.MapMetrics();

// Métricas customizadas de negócio
var ordersProcessed = Metrics.CreateCounter(
    "orders_processed_total",
    "Total de pedidos processados",
    new CounterConfiguration
    {
        LabelNames = new[] { "status" }
    });

var orderProcessingDuration = Metrics.CreateHistogram(
    "order_processing_duration_seconds",
    "Tempo de processamento de pedidos",
    new HistogramConfiguration
    {
        Buckets = Histogram.ExponentialBuckets(0.01, 2, 10)
    });

app.Run();

Troubleshooting: os problemas mais comuns

Depois de ajudar dezenas de times a implementar essa stack, estes são os problemas que mais consomem tempo e como resolvê-los.

Problema 1: Métricas não aparecem no Grafana

Checklist de diagnóstico:

# 1. Verificar se o agente ama-metrics está rodando
kubectl get pods -n kube-system -l rsName=ama-metrics

# 2. Verificar logs do agente
kubectl logs -n kube-system -l rsName=ama-metrics -c prometheus-collector --tail=50

# 3. Verificar se o agente está conseguindo fazer scrape
kubectl logs -n kube-system -l rsName=ama-metrics -c prometheus-collector | grep "scrape"

# 4. Verificar ConfigMap de configuração
kubectl get configmap ama-metrics-prometheus-config -n kube-system -o yaml

# 5. Verificar se o endpoint /metrics da aplicação está acessível
kubectl port-forward svc/api-pedidos 8080:8080 -n production
curl http://localhost:8080/metrics

Causa mais comum: O namespace da aplicação não está incluído no ConfigMap de scrape. Por padrão, o agente só coleta métricas de kube-system e targets pré-configurados.

Problema 2: High cardinality - custos explodindo

Métricas com muitos labels únicos (alta cardinalidade) inflam o volume de ingestão e os custos. Exemplos perigosos:

# RUIM: user_id como label (milhares de valores)
http_requests_total{user_id="abc123", endpoint="/api/orders"}

# BOM: agregar por endpoint, sem user_id
http_requests_total{endpoint="/api/orders"}

Como identificar métricas de alta cardinalidade:

# Top 10 métricas por número de séries temporais
topk(10, count by (__name__) ({__name__!=""}))

Como limitar no agente:

# ama-metrics-settings-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ama-metrics-settings-configmap
  namespace: kube-system
data:
  default-scrape-settings: |
    [default-scrape-settings]
        # Limitar o número de séries temporais por scrape target
        sample_limit = 5000
        # Limitar o tamanho do label
        label_limit = 20
        label_name_length_limit = 128
        label_value_length_limit = 512

Problema 3: Grafana data source "Prometheus" não mostra dados

Isso acontece quando o data source foi configurado manualmente (em vez de automaticamente pelo --grafana-resource-id). Para verificar e corrigir:

# Listar data sources configurados
az grafana data-source list \
  --name $GRAFANA_NAME \
  --resource-group $RESOURCE_GROUP

# Se o data source Prometheus não existir, criar manualmente
az grafana data-source create \
  --name $GRAFANA_NAME \
  --resource-group $RESOURCE_GROUP \
  --definition '{
    "name": "Azure Managed Prometheus",
    "type": "prometheus",
    "access": "proxy",
    "url": "https://amw-prometheus-prod-xxxxx.eastus2.prometheus.monitor.azure.com",
    "jsonData": {
      "httpMethod": "POST",
      "azureCredentials": {
        "authType": "msi"
      }
    }
  }'

Problema 4: Prometheus rules não disparam alertas

Causas comuns:

  1. Rule group não associado ao cluster: O --cluster-name precisa corresponder exatamente ao nome do cluster AKS.
  2. Métrica referenciada não existe: A regra usa uma métrica que não está sendo coletada. Verifique no Grafana Explore:
# Verificar se a métrica existe
up{job="api-pedidos"}

# Listar todas as métricas disponíveis de um job
{job="api-pedidos"}
  1. Período de avaliação longo demais: Se o for é PT30M mas a métrica só fica anormal por 10 minutos, o alerta nunca vai disparar.

Problema 5: Latência alta nas queries do Grafana

Queries lentas geralmente indicam que você está consultando muitas séries temporais ou usando ranges muito longos.

Otimizações:

ProblemaSolução
Query lenta com rate() em range grandeUsar recording rules para pré-computar
Dashboard carrega devagarReduzir o time range padrão (1h em vez de 24h)
Muitas séries no mesmo painelAdicionar filtros por namespace ou label
topk() em milhões de sériesFiltrar antes do topk() com label matchers

Custos e otimização

Entender o modelo de custo é essencial para evitar surpresas na fatura.

Modelo de precificação

ComponenteModelo de cobrançaFaixa de preço (referência)
Azure Monitor workspace (Prometheus)Por milhão de amostras ingeridas~$0.30 por milhão de amostras (primeiros 50B)
Managed Grafana StandardInstância fixa mensal~$150/mês por instância
Retenção18 meses incluídosSem custo adicional para retenção padrão
QueriesIncluídasSem custo por query PromQL

Dicas para controlar custos

  1. Reduza o scrape interval: Mudar de 15s para 30s corta a ingestão pela metade
  2. Filtre métricas desnecessárias: Nem toda métrica do node-exporter é útil
  3. Use keep/drop no relabel_configs: Descarte séries que você nunca vai consultar
  4. Monitore a ingestão: Use as métricas internas do agente para acompanhar volume
# Exemplo: descartar métricas de filesystem de tmpfs e shm
relabel_configs:
- source_labels: [__name__]
  regex: 'node_filesystem_(size_bytes|avail_bytes|free_bytes)'
  action: keep
metric_relabel_configs:
- source_labels: [fstype]
  regex: 'tmpfs|shm'
  action: drop

Comparativo: Managed Prometheus vs Azure Monitor Metrics

Para fechar, uma comparação direta entre usar Prometheus gerenciado e Azure Monitor Metrics nativo. Muitos times se perguntam se precisam escolher um ou outro — a resposta é que podem coexistir.

CritérioAzure Managed PrometheusAzure Monitor Metrics (nativo)
Linguagem de queryPromQLKQL (Kusto)
Formato de métricasPrometheus / OpenMetricsAzure Monitor metrics format
VisualizaçãoGrafana (managed)Azure Workbooks, Portal, Power BI
AlertasPrometheus alert rules (PromQL)Azure Monitor Alert Rules (KQL)
EcossistemaPrometheus exporters, dashboards Grafana.comAzure integrations nativas
Retenção padrão18 meses93 dias (métricas de plataforma)
Multi-clusterNativo (mesmo workspace)Nativo (mesmo Log Analytics)
Curva de aprendizadoFamiliar para quem já usa Prometheus/GrafanaFamiliar para quem já usa Azure
CustoBaseado em ingestão de amostrasBaseado em ingestão de dados + queries

Recomendação: Use Prometheus + Grafana para métricas de infraestrutura Kubernetes e aplicações cloud-native. Use Azure Monitor para métricas de serviços PaaS do Azure (App Service, SQL Database, Storage, etc.) e correlação com logs via KQL. Os dois coexistem naturalmente.

Conclusão

A combinação de Azure Managed Grafana com Azure Monitor managed service for Prometheus representa o melhor dos dois mundos: a familiaridade e o ecossistema rico do stack open-source Prometheus/Grafana com a confiabilidade operacional e a integração nativa do Azure.

Neste artigo, implementamos uma stack completa de observabilidade moderna que vai desde o provisionamento via CLI e Terraform até dashboards avançados com PromQL, alerting rules com integração a Action Groups, monitoramento multi-cluster e instrumentação de aplicações em Python e .NET. Também cobrimos os cinco problemas mais comuns que travam times em produção e como resolvê-los de forma definitiva.

Os próximos passos naturais a partir daqui são:

  1. Integrar com SLOs: Usar as métricas Prometheus como base para SLIs e SLOs usando recording rules para calcular error budgets
  2. Adicionar traces: Complementar métricas com distributed tracing via OpenTelemetry + Azure Monitor Application Insights
  3. Automatizar resposta a incidentes: Conectar alertas Prometheus com runbooks automatizados
  4. Dashboards como código no pipeline: Versionar todos os dashboards em Git e deployar via CI/CD

Se você já tem Prometheus e Grafana self-hosted, a migração para o managed é incremental: comece com um cluster de staging, valide que suas queries PromQL funcionam identicamente e expanda para produção. A compatibilidade é excelente — na maioria dos casos, é literalmente plug and play.

Referências

Confira mais:

Fique por dentro das novidades

Assine nossa newsletter e receba as últimas atualizações e artigos diretamente em seu email.

Assinar gratuitamente