← voltar ao índice

· 27 min de leitura · por Tiago

Alertas sem fadiga com Prometheus e Alertmanager

Introdução

Nos artigos anteriores desta série, construímos uma plataforma de observabilidade em etapas.

Instrumentamos aplicações .NET com OpenTelemetry.

Criamos uma arquitetura de OpenTelemetry Collector para Kubernetes.

Definimos SLIs, SLOs e Error Budgets.

Depois organizamos dashboards de Grafana para investigar incidentes usando RED, USE, Golden Signals, exemplars, traces e logs.

Agora chegamos a uma pergunta inevitável:

quando alguém precisa ser chamado?

Parece simples.

Se CPU passar de 80%, alerta.

Se memória passar de 90%, alerta.

Se um pod reiniciar, alerta.

Se aparecer HTTP 500, alerta.

Se a fila crescer, alerta.

Depois de algumas semanas, o time recebe centenas de notificações.

Boa parte delas desaparece sozinha.

Algumas não exigem nenhuma ação.

Outras são consequências do mesmo incidente.

Quando finalmente acontece um problema realmente grave, o alerta chega misturado ao ruído.

Esse fenômeno é conhecido como alert fatigue, ou fadiga de alertas.

O problema não é apenas incômodo.

É um problema de confiabilidade.

Um sistema de alertas que envia notificações demais ensina as pessoas a ignorá-lo.

Cada falso positivo reduz confiança.

Cada alerta sem ação clara aumenta a chance de o próximo ser silenciado mentalmente.

Em algum momento, alguém cria uma regra automática no e-mail.

Outro silencia um canal.

Outro assume que "esse alerta sempre dispara".

Nesse momento, o sistema de monitoramento continua tecnicamente funcionando, mas operacionalmente falhou.

A solução não é simplesmente reduzir thresholds.

Também não é remover todos os alertas.

Precisamos mudar a pergunta.

Em vez de:

Que condições técnicas podemos detectar?

devemos perguntar:

Que situações exigem uma ação humana?

Essa distinção é o centro de uma boa estratégia de alertas.

Neste artigo, vamos construir uma arquitetura de alerting com Prometheus e Alertmanager orientada a impacto e ação.

Vamos entender a diferença entre alerting rules e notificações.

Vamos criar alertas baseados em sintomas e SLOs.

Vamos usar burn rate para detectar consumo perigoso de Error Budget.

Vamos separar page, ticket e informação.

Vamos entender for, keep_firing_for, agrupamento, deduplicação, inibição e silêncios.

Também vamos criar uma configuração prática do Alertmanager com roteamento por severidade, ambiente e time.

O objetivo não é criar mais alertas.

É criar menos alertas, mas fazer com que cada um importe.

Prometheus detecta, Alertmanager decide como notificar

Uma distinção importante precisa ficar clara desde o início.

Prometheus e Alertmanager possuem responsabilidades diferentes.

O Prometheus avalia condições.

Por exemplo:

error_rate > 0.05

Se a condição permanece verdadeira conforme a regra configurada, um alerta entra em estado ativo.

O Alertmanager recebe esses alertas e decide:

Quais devem ser agrupados?

Para onde devem ser enviados?

Quais devem ser suprimidos?

Existe um silêncio ativo?

Já enviamos uma notificação equivalente?

Quando devemos repetir a notificação?

O fluxo conceitual é:

Métricas
   |
   v
Prometheus
   |
   | alerting rules
   v
Alertmanager
   |
   +--> Pager
   |
   +--> Chat
   |
   +--> E-mail
   |
   +--> Webhook

Essa separação é poderosa.

A regra que detecta:

OrdersAvailabilityBudgetBurnFast

não deveria conhecer diretamente Slack, PagerDuty, e-mail ou qualquer outra ferramenta.

Ela adiciona labels como:

severity=page

team=commerce

environment=production

O Alertmanager usa esses labels para decidir o destino.

Isso desacopla detecção de entrega.

Podemos mudar a ferramenta de on-call sem reescrever todas as consultas PromQL.

O primeiro princípio: não alerte sobre tudo que é anormal

Um valor anormal não é automaticamente um incidente.

Considere:

CPU = 92%

Isso pode significar:

aplicação saturada

ou:

aplicação usando eficientemente os recursos disponíveis

Agora considere:

CPU = 25%

HTTP 500 = 30%

checkout indisponível

O segundo cenário é claramente mais importante para o usuário.

Ainda assim, muitas plataformas possuem dezenas de alertas sobre CPU e apenas poucos sobre experiência real.

Uma boa regra é:

pagine por sintomas de impacto, use métricas de causa para diagnóstico.

Isso não significa ignorar capacidade.

Capacidade pode justificar ticket ou ação preventiva.

Mas existe uma grande diferença entre:

Algo exige atenção nesta semana.

e:

Alguém precisa acordar agora.

Misturar as duas categorias é uma das maiores fontes de fadiga.

Três níveis de resposta

Uma estratégia simples é separar alertas em três classes.

Page

Significa:

Existe impacto significativo
ou risco imediato.

Uma pessoa precisa agir agora.

Exemplos:

Error Budget queimando rapidamente

Checkout com alta taxa de falha

Autenticação indisponível

Dados críticos não sendo processados
dentro do prazo necessário

Ticket

Significa:

Existe um problema real,
mas não exige interrupção imediata.

Exemplos:

Error Budget sendo consumido lentamente

Capacidade projetada para acabar em alguns dias

Certificado próximo da expiração

Fila crescendo lentamente

Disco aproximando-se de um limite

Informação

Significa:

É útil registrar ou visualizar,
mas não necessariamente notificar alguém.

Exemplos:

Deploy concluído

Pod reiniciado uma vez

Autoscaling aumentou réplicas

Feature flag alterada

Nem todo evento precisa ser um alerta.

Muitos eventos pertencem a dashboards, annotations ou logs.

A primeira pergunta ao criar uma regra deveria ser:

Quem vai receber isso
e o que essa pessoa fará?

Se não existe resposta clara, provavelmente não deveria ser um page.

Um alerta precisa ser acionável

Considere esta notificação:

High CPU

CPU is above 80%.

O que a pessoa deve fazer?

Qual serviço?

Qual ambiente?

Qual impacto?

Desde quando?

Existe runbook?

Qual dashboard abrir?

Talvez CPU alta seja normal.

Agora considere:

Orders API is consuming the availability
error budget 14.4x faster than sustainable.

Environment: production
Service: orders-api
SLO: 99.9%
Long window: 1h
Short window: 5m

Dashboard:
https://grafana.example/...

Runbook:
https://runbooks.example/orders-availability

A segunda notificação carrega contexto operacional.

Um bom alerta deve responder, tanto quanto possível:

O que está acontecendo?

Onde?

Qual o impacto?

Qual a urgência?

Onde investigar?

Existe procedimento conhecido?

O alerta não precisa conter a causa raiz.

Na verdade, muitas vezes ele não deveria tentar adivinhar.

Seu papel é sinalizar um sintoma relevante e facilitar o início da investigação.

Sintoma versus causa

Imagine esta cadeia:

Node com problema de rede

        ↓

Pods perdem conexão com PostgreSQL

        ↓

Queries falham

        ↓

Orders API começa a retornar 500

        ↓

Checkout falha

Podemos criar alertas para:

NodeNetworkErrors

PostgresConnectionErrors

OrdersDatabaseErrors

OrdersHigh5xxRate

CheckoutAvailabilitySLOBurn

Durante o incidente, todos podem disparar.

Se todos gerarem page, uma pessoa recebe cinco notificações sobre o mesmo evento.

Pior ainda, os componentes podem gerar dezenas de instâncias de cada alerta.

Um bom sistema tenta paginar no nível mais próximo do impacto.

Nesse exemplo:

CheckoutAvailabilitySLOBurn

pode ser o page principal.

Os demais sinais continuam disponíveis nos dashboards e podem até gerar alertas de menor severidade.

Essa abordagem reduz duplicação.

O objetivo do page é chamar atenção.

O objetivo da observabilidade é explicar.

Não tente colocar toda investigação dentro do mecanismo de paging.

Uma regra Prometheus básica

Vamos começar por uma regra simples.

groups:
  - name: orders-alerts

    rules:
      - alert: OrdersHighErrorRate

        expr: |
          (
            sum(
              rate(
                orders_requests_failed_total[5m]
              )
            )
            /
            sum(
              rate(
                orders_requests_total[5m]
              )
            )
          ) > 0.05

        for: 10m

        labels:
          severity: page
          team: commerce
          environment: production

        annotations:
          summary: >
            Orders API has a high error rate

          description: >
            More than 5% of valid order requests
            have failed for at least 10 minutes.

          dashboard_url: >
            https://grafana.example.com/d/orders

          runbook_url: >
            https://runbooks.example.com/orders-errors

A expressão calcula:

requisições com falha
/
requisições totais

e dispara quando o resultado passa de 5%.

O for: 10m possui um papel importante.

A condição precisa permanecer verdadeira durante dez minutos antes de o alerta entrar em estado firing.

Isso ajuda a ignorar pequenos blips.

Mas existe um problema.

Por que 5%?

Por que dez minutos?

Se nosso SLO é 99,9%, uma taxa de erro de 5% pode ser catastrófica.

Talvez dez minutos seja tempo demais.

Ao mesmo tempo, 0,2% de erro persistente por dias pode consumir muito Error Budget sem nunca cruzar o threshold de 5%.

Thresholds fixos são fáceis de entender.

Mas frequentemente são uma forma pobre de representar risco.

É por isso que burn rate se torna tão útil.

Alertar pelo Error Budget

No artigo anterior sobre SLI e SLO, definimos:

SLO de disponibilidade:
99,9% em 30 dias

Isso significa:

taxa de erro permitida:
0,1%

ou:

0,001

Burn rate mede quanto mais rápido estamos consumindo esse orçamento.

burn rate = 1

significa ritmo sustentável exatamente igual ao budget disponível.

burn rate = 10

significa consumo dez vezes mais rápido.

Isso nos permite criar alertas baseados em impacto relativo ao objetivo.

Em vez de perguntar:

Erro passou de 5%?

perguntamos:

Estamos consumindo Error Budget
rápido o suficiente para exigir ação agora?

Essa pergunta conecta alerting ao nível de confiabilidade acordado.

Recording rules para simplificar SLO alerting

PromQL de SLO pode ficar repetitivo.

Podemos criar recording rules.

groups:
  - name: orders-slo-recording

    interval: 30s

    rules:
      - record: orders:sli_error_ratio:rate5m

        expr: |
          1
          -
          (
            sum(
              rate(
                orders_requests_success_total[5m]
              )
            )
            /
            sum(
              rate(
                orders_requests_total[5m]
              )
            )
          )

      - record: orders:sli_error_ratio:rate30m

        expr: |
          1
          -
          (
            sum(
              rate(
                orders_requests_success_total[30m]
              )
            )
            /
            sum(
              rate(
                orders_requests_total[30m]
              )
            )
          )

      - record: orders:sli_error_ratio:rate1h

        expr: |
          1
          -
          (
            sum(
              rate(
                orders_requests_success_total[1h]
              )
            )
            /
            sum(
              rate(
                orders_requests_total[1h]
              )
            )
          )

      - record: orders:sli_error_ratio:rate6h

        expr: |
          1
          -
          (
            sum(
              rate(
                orders_requests_success_total[6h]
              )
            )
            /
            sum(
              rate(
                orders_requests_total[6h]
              )
            )
          )

Agora as regras de alerta ficam menores e mais legíveis.

Também podemos reutilizar esses indicadores em dashboards.

Essa abordagem possui outra vantagem.

A lógica do SLI fica centralizada.

Se a definição de "evento bom" mudar, podemos revisar as recording rules em um único lugar.

Naturalmente, essas regras viram parte crítica da plataforma.

Elas precisam de versionamento, revisão e testes.

Fast burn alert

Para um SLO de 99,9%, a taxa de erro permitida é:

0,001

Uma estratégia conhecida é paginar quando aproximadamente 2% do Error Budget pode ser consumido em uma hora.

Isso corresponde a burn rate:

14,4

Podemos criar:

groups:
  - name: orders-slo-alerts

    rules:
      - alert: OrdersAvailabilityBudgetBurnFast

        expr: |
          (
            orders:sli_error_ratio:rate1h
              > (14.4 * 0.001)
          )
          and
          (
            orders:sli_error_ratio:rate5m
              > (14.4 * 0.001)
          )

        for: 2m

        labels:
          severity: page
          team: commerce
          environment: production
          slo: availability

        annotations:
          summary: >
            Orders availability error budget
            is burning quickly

          description: >
            The service is consuming the 99.9%
            availability error budget at a fast rate.

          dashboard_url: >
            https://grafana.example.com/d/orders-slo

          runbook_url: >
            https://runbooks.example.com/orders-slo

Existem duas janelas.

1 hora

5 minutos

A janela longa confirma que o problema possui peso suficiente.

A janela curta confirma que o problema ainda está acontecendo.

Sem a janela curta, um incidente já resolvido pode continuar influenciando a média longa e manter a notificação ativa por tempo excessivo.

Sem a janela longa, pequenos picos podem gerar falsos positivos.

Essa combinação melhora precisão.

Slow burn alert

Nem todo consumo perigoso acontece rapidamente.

Podemos ter uma degradação menor que permanece durante horas.

Uma segunda regra pode usar:

6 horas

30 minutos

burn rate 6
      - alert: OrdersAvailabilityBudgetBurnSlow

        expr: |
          (
            orders:sli_error_ratio:rate6h
              > (6 * 0.001)
          )
          and
          (
            orders:sli_error_ratio:rate30m
              > (6 * 0.001)
          )

        for: 5m

        labels:
          severity: page
          team: commerce
          environment: production
          slo: availability

        annotations:
          summary: >
            Orders availability error budget
            has sustained elevated burn

          description: >
            The service is consuming the 99.9%
            availability error budget at a sustained rate.

          dashboard_url: >
            https://grafana.example.com/d/orders-slo

          runbook_url: >
            https://runbooks.example.com/orders-slo

Agora temos dois detectores.

Um encontra incidentes muito agressivos.

Outro encontra degradações menos intensas, mas persistentes.

Não precisamos criar thresholds arbitrários diferentes para cada serviço.

A lógica está ligada ao SLO.

Page e ticket não devem ser a mesma coisa

Podemos adicionar um slow burn ainda mais longo.

Por exemplo:

10% do budget em 3 dias

Esse cenário pode exigir investigação, mas talvez não justifique acordar alguém.

Uma regra pode usar:

      - alert: OrdersAvailabilityBudgetBurnLongTerm

        expr: |
          orders:sli_error_ratio:rate3d
            > (1 * 0.001)

        for: 30m

        labels:
          severity: ticket
          team: commerce
          environment: production
          slo: availability

        annotations:
          summary: >
            Orders availability budget
            is being consumed over the long term

          description: >
            Reliability is degrading slowly enough
            to be handled as planned work,
            but the error budget is at risk.

          dashboard_url: >
            https://grafana.example.com/d/orders-slo

O Alertmanager pode enviar:

severity=page

para a plataforma de on-call.

E:

severity=ticket

para um webhook que cria issue ou notificação assíncrona.

Assim o mesmo modelo de confiabilidade produz respostas diferentes conforme urgência.

for não é apenas um debounce

O campo for é frequentemente usado sem muito raciocínio.

Exemplo:

for: 5m

A interpretação é:

A expressão precisa continuar ativa
durante cinco minutos
antes do alerta virar firing.

Isso ajuda a filtrar condições transitórias.

Mas for também atrasa detecção.

Se um incidente grave precisa ser respondido em dois minutos, colocar:

for: 15m

destrói o objetivo.

O valor deve refletir a semântica do sinal.

Para CPU alta:

for: 10m

pode fazer sentido.

Para:

todas as autenticações estão falhando

talvez não.

Burn-rate alerts já usam janelas temporais na expressão.

Adicionar um for enorme por hábito pode duplicar atraso.

Cada minuto precisa ter justificativa.

keep_firing_for e flapping

Prometheus também permite manter um alerta em firing por algum tempo depois que a condição deixa de ser verdadeira.

Exemplo:

keep_firing_for: 5m

Considere uma condição oscilando:

14:00 firing

14:01 normal

14:02 firing

14:03 normal

14:04 firing

Sem proteção, podemos gerar alternância entre:

FIRING

RESOLVED

FIRING

RESOLVED

Isso é flapping.

keep_firing_for pode suavizar pequenas interrupções do sinal e evitar resoluções prematuras.

Exemplo:

      - alert: CriticalDependencyUnavailable

        expr: |
          dependency_up{
            dependency="payment-provider"
          } == 0

        for: 2m

        keep_firing_for: 5m

        labels:
          severity: page
          team: commerce

        annotations:
          summary: >
            Payment provider dependency is unavailable

Esse mecanismo deve ser usado com cuidado.

Não queremos esconder uma resolução real por períodos enormes.

Seu papel é reduzir oscilações pequenas.

Labels são parte da arquitetura de alerting

Labels não servem apenas para mostrar contexto.

Eles controlam roteamento e agrupamento.

Um padrão útil pode incluir:

alertname

severity

team

environment

service

cluster

slo

Exemplo:

labels:
  severity: page
  team: commerce
  environment: production
  service: orders-api
  slo: availability

O Alertmanager pode então decidir:

production + page
-> on-call

staging + page
-> chat

ticket
-> issue webhook

team=commerce
-> canal do Commerce

team=platform
-> canal da Plataforma

A taxonomia precisa ser consistente.

Se cada equipe inventa:

severity=critical

priority=p1

level=urgent

notification=page

o routing tree se torna difícil de manter.

Defina um contrato de labels.

Annotations carregam contexto humano

Labels devem ser pequenos, estáveis e úteis para identidade e roteamento.

Annotations carregam texto e links.

Exemplo:

annotations:
  summary: >
    Orders availability error budget
    is burning quickly

  description: >
    The 99.9% availability SLO is at risk.
    Investigate errors and latency immediately.

  dashboard_url: >
    https://grafana.example.com/d/orders

  runbook_url: >
    https://runbooks.example.com/orders

  repository_url: >
    https://dev.azure.com/example/orders

Não coloque textos enormes dentro de labels.

Além de semanticamente errado, labels fazem parte da identidade das séries e alertas.

Mudanças frequentes podem afetar deduplicação.

Use annotations para informação humana.

Um alerta sem runbook deveria levantar uma pergunta

Nem todo alerta precisa ter um procedimento detalhado.

Mas todo page deveria ter pelo menos um ponto de partida.

Um runbook pode conter:

Como confirmar impacto

Qual dashboard abrir

Quais dependências verificar

Como identificar deploy recente

Como executar rollback

Quem é owner

Quais ações são perigosas

Como escalar o incidente

O runbook não precisa prever todas as causas.

Ele precisa reduzir o tempo gasto descobrindo passos básicos sob pressão.

Um link vazio para:

TODO

é um sinal de que o alerta talvez tenha sido criado antes de estar operacionalmente pronto.

Como o Alertmanager reduz ruído

Quando Prometheus dispara alertas, o Alertmanager aplica várias funções.

As mais importantes são:

Deduplicação

Grouping

Routing

Silences

Inhibition

Cada uma resolve um tipo diferente de ruído.

Entender essas diferenças evita configurações confusas.

Deduplicação

Imagine que o Prometheus envia repetidamente o mesmo alerta enquanto ele permanece firing.

O Alertmanager não deveria enviar uma nova notificação completa a cada avaliação.

Ele identifica alertas equivalentes por seus labels e gerencia o ciclo de notificações.

Isso é deduplicação.

Existe uma consequência importante.

Labels instáveis podem impedir deduplicação eficiente.

Não faça:

labels:
  current_value: "{{ $value }}"

Se o valor muda a cada avaliação, a identidade do alerta pode mudar.

Valores dinâmicos pertencem em annotations.

annotations:
  current_value: "{{ $value }}"

Labels devem representar identidade e roteamento.

Grouping

Considere um cluster com cinquenta pods.

Um problema de rede faz todos perderem acesso ao banco.

Prometheus pode gerar cinquenta alertas:

DatabaseConnectionFailure
pod=a

DatabaseConnectionFailure
pod=b

DatabaseConnectionFailure
pod=c

...

Queremos cinquenta notificações?

Provavelmente não.

Podemos agrupar por:

group_by:
  - alertname
  - cluster
  - service

Agora alertas semelhantes formam uma única notificação.

A mensagem pode dizer:

DatabaseConnectionFailure

Service: orders-api
Cluster: production

50 alerts firing

A pessoa recebe uma página.

Não cinquenta.

O detalhe das instâncias continua disponível.

group_wait, group_interval e repeat_interval

Esses três tempos controlam comportamento de notificação.

Uma configuração:

route:
  receiver: default

  group_by:
    - alertname
    - cluster
    - service

  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

group_wait

É o tempo inicial de espera antes de enviar a primeira notificação de um novo grupo.

Por que esperar?

Porque durante uma falha vários alertas relacionados podem chegar em poucos segundos.

Esperar 30 segundos permite agrupá-los.

Mas para páginas extremamente críticas, um group_wait muito alto atrasa resposta.

group_interval

Controla quando mudanças em um grupo existente podem gerar nova notificação.

Por exemplo, novos alertas entram no mesmo grupo.

repeat_interval

Controla quando um alerta ainda firing pode ser lembrado novamente.

Sem repetição, um incidente longo pode desaparecer da atenção.

Com repetição muito frequente, o time recebe spam.

Quatro horas pode ser razoável para alguns pages.

Outros ambientes exigem valores diferentes.

Não copie números sem entender o fluxo de on-call.

Routing tree

O Alertmanager usa uma árvore de rotas.

Todo alerta começa na rota raiz.

Depois pode entrar em rotas filhas conforme matchers.

Um exemplo:

route:
  receiver: default-webhook

  group_by:
    - alertname
    - environment
    - service

  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
    - receiver: production-oncall

      matchers:
        - environment="production"
        - severity="page"

      group_wait: 15s
      repeat_interval: 2h

    - receiver: engineering-tickets

      matchers:
        - severity="ticket"

      group_wait: 5m
      repeat_interval: 24h

    - receiver: nonprod-chat

      matchers:
        - environment=~"dev|staging"

A rota raiz captura tudo que não casar com regras mais específicas.

Isso evita alertas desaparecendo por falta de receiver.

As rotas filhas controlam comportamento por contexto.

Pages de produção podem ter espera menor.

Tickets podem ser agrupados por mais tempo.

Ambientes não produtivos podem ir apenas para chat.

Receivers

Para manter a configuração independente de fornecedor, vamos usar webhooks.

receivers:
  - name: default-webhook

    webhook_configs:
      - url: https://alerts.example.com/default
        send_resolved: true

  - name: production-oncall

    webhook_configs:
      - url: https://alerts.example.com/oncall
        send_resolved: true

  - name: engineering-tickets

    webhook_configs:
      - url: https://alerts.example.com/tickets
        send_resolved: true

  - name: nonprod-chat

    webhook_configs:
      - url: https://alerts.example.com/chat
        send_resolved: true

Na prática, podemos usar integrações específicas disponíveis no Alertmanager ou um gateway interno.

Um gateway possui vantagens.

Ele pode centralizar:

credenciais

formatação

integração com Teams ou Slack

criação de tickets

on-call

auditoria

Mas também vira mais um componente para operar.

Para ambientes simples, integração direta pode ser melhor.

continue e múltiplos destinos

Por padrão, quando uma rota filha casa, o processamento normalmente para naquele ramo conforme a configuração da árvore.

Em alguns cenários queremos continuar avaliando rotas irmãs.

Exemplo:

page de produção

deve ir:

on-call

e também:

canal do time

Podemos usar continue: true cuidadosamente.

routes:
  - receiver: production-oncall

    matchers:
      - environment="production"
      - severity="page"

    continue: true

  - receiver: commerce-chat

    matchers:
      - team="commerce"
      - severity="page"

Agora um alerta pode seguir para mais de um receiver.

Use isso com cuidado.

É fácil criar duplicação sem perceber.

A árvore de routing deve ser testada como código.

Inhibition

Inhibition significa suprimir notificações de certos alertas quando outro alerta mais importante está firing.

Imagine:

ClusterDown

Se o cluster inteiro está indisponível, podemos receber:

PodDown

DeploymentUnavailable

ServiceUnreachable

NodeExporterDown

ApplicationSLOBurn

Alguns desses alertas são consequências previsíveis.

Podemos inibi-los enquanto o alerta principal está ativo.

Exemplo:

inhibit_rules:
  - name: cluster-down-inhibits-instance-alerts

    source_matchers:
      - alertname="ClusterDown"
      - severity="page"

    target_matchers:
      - severity="page"

    equal:
      - cluster

A interpretação é:

Se existe ClusterDown
para determinado cluster,

suprima outros pages
do mesmo cluster.

Esse recurso é poderoso.

Também é perigoso.

Uma regra de inibição muito ampla pode esconder incidentes independentes.

Sempre teste:

Quem é source?

Quem é target?

Quais labels precisam ser iguais?

Quanto mais específica a relação, melhor.

Silences

Silence é diferente de inhibition.

Silence é uma regra temporária explícita.

Exemplo:

Vamos fazer manutenção
no cluster production-eu
das 22h às 23h.

Podemos criar um silence que casa:

cluster=production-eu

durante aquele período.

Alertas continuam existindo.

Mas notificações correspondentes são silenciadas.

Silences são úteis para:

manutenção planejada

migração

teste de disaster recovery

incidente conhecido sendo tratado

Mas silêncio não deve virar lixeira.

Um silence de:

6 meses

para um alerta ruidoso provavelmente significa que a regra está errada.

Corrija o alerta.

Não esconda o problema permanentemente.

Inhibition, silence ou correção da regra?

Use inhibition quando:

um alerta mais importante
explica outros alertas dependentes.

Use silence quando:

existe uma janela temporária
em que notificações não são desejadas.

Corrija a regra quando:

o alerta dispara frequentemente
sem exigir ação.

Essa distinção simples evita muito abuso operacional.

Não page por um único pod reiniciado

Considere:

PodRestarted

Um pod reinicia uma vez.

Kubernetes o substitui.

Usuários não percebem.

Tudo continua saudável.

A plataforma funcionou como projetado.

Ainda assim, alguém recebe page às 2 da manhã.

Esse alerta ensina uma lição errada:

qualquer evento interno
é emergência.

Reinícios podem ser muito importantes.

Mas precisamos olhar contexto.

Talvez um ticket seja gerado quando:

reinícios crescem continuamente

ou um page quando:

deployment perde disponibilidade

A diferença é entre evento e impacto.

Kubernetes existe justamente para recuperar workloads.

Não alerte porque o mecanismo de recuperação foi usado uma vez.

Alerte quando ele não está conseguindo manter o serviço saudável.

Baixo tráfego exige cuidado

Burn rate funciona muito bem quando existe volume suficiente.

Mas considere um serviço que recebe:

10 requisições por hora.

Uma única falha representa:

10% de erro.

Para um SLO de 99,9%, a burn rate instantânea parece gigantesca.

Paginar por uma única requisição pode não fazer sentido.

Serviços de baixo tráfego exigem estratégias diferentes.

Podemos combinar:

mínimo de eventos

janela maior

blackbox probes

synthetic traffic

SLO adequado ao volume

Exemplo:

(
  error_ratio > threshold
)
and
(
  request_count > 100
)

Mas cuidado para não mascarar jornadas críticas.

Uma operação de pagamento de alto valor pode ter baixo volume e ainda exigir atenção imediata.

A regra precisa refletir risco do negócio.

Ausência de dados também pode ser sinal

Uma query que retorna zero e uma query que não retorna série são coisas diferentes.

Imagine que o exporter parou.

A métrica:

orders_requests_total

simplesmente desaparece.

Uma regra:

orders_requests_total == 0

pode não disparar porque não existe série para comparar.

Prometheus possui funções e padrões para detectar ausência.

Por exemplo:

absent(
  up{
    job="orders-api"
  }
)

Mas antes de criar dezenas de alertas absent(), pense no sintoma.

Talvez seja melhor ter:

blackbox probe falhou

SLO de disponibilidade degradou

target scraping ausente

O objetivo continua sendo evitar duplicação.

Meta-monitoring

Uma plataforma de alertas também pode falhar.

Prometheus pode parar de avaliar regras.

Alertmanager pode ficar indisponível.

Uma integração de notificação pode quebrar.

O pior cenário é:

produção caiu

e o sistema de alertas também.

Ninguém é avisado.

Por isso precisamos monitorar o monitoramento.

Uma estratégia forte combina whitebox e blackbox.

Whitebox:

Prometheus está up?

Alertmanager está up?

Rules estão sendo avaliadas?

Existem falhas de envio?

Blackbox:

Uma notificação sintética
consegue atravessar todo o pipeline?

Conceitualmente:

Synthetic alert
   |
   v
Prometheus
   |
   v
Alertmanager
   |
   v
Notification channel
   |
   v
External verification

Isso testa a cadeia real.

Monitorar apenas:

alertmanager_up = 1

não prova que a integração final está funcionando.

Alertmanager em alta disponibilidade

Para ambientes críticos, uma única instância de Alertmanager é um ponto de falha.

Alertmanager suporta clustering para alta disponibilidade.

Existe um detalhe importante no desenho.

Quando há múltiplos Alertmanagers, o Prometheus deve conhecer as instâncias apropriadas em vez de depender cegamente de um load balancer que esconda toda a topologia.

O mecanismo de alta disponibilidade prioriza não perder notificações.

Em situações de partição, duplicatas podem ser preferíveis a deixar de entregar um page crítico.

Isso mostra um princípio interessante.

Para alerting:

duplicar ocasionalmente

pode ser melhor que:

perder silenciosamente.

A arquitetura deve refletir a criticidade do sistema.

Segurança do Alertmanager

Alertmanager possui poder operacional.

Quem acessa sua interface ou API pode, dependendo das permissões e exposição:

visualizar alertas

criar silences

alterar comportamento operacional

Não exponha o endpoint publicamente sem controles.

Use:

autenticação

TLS

NetworkPolicy

restrição de ingress

RBAC na camada de acesso

Credenciais de receivers também precisam ser protegidas.

Não coloque tokens diretamente em repositórios públicos.

Use Secrets ou mecanismos apropriados da plataforma.

Um silêncio malicioso ou acidental durante um incidente pode ser tão perigoso quanto o próprio incidente.

Teste regras antes de colocar em produção

Alerting rules são código.

Precisam de validação.

O promtool pode validar sintaxe de arquivos de regras.

Exemplo:

promtool check rules \n  ./prometheus/orders-alerts.rules.yml

Isso detecta problemas estruturais.

Mas sintaxe correta não significa lógica correta.

Também precisamos testar comportamento.

Perguntas úteis:

A regra dispara com dados de incidente?

Ela permanece silenciosa em operação normal?

O label severity está correto?

O Alertmanager roteia para o destino esperado?

Grouping funciona?

Inhibition funciona?

Resolved notification chega?

Use ambientes de teste.

Use dados históricos.

Use incidentes passados.

Alertas deveriam passar por Pull Request.

Uma mudança em:

severity=ticket

para:

severity=page

pode aumentar drasticamente carga de on-call.

Isso merece review.

Um exemplo completo de Alertmanager

Vamos juntar os principais conceitos.

global:
  resolve_timeout: 5m

route:
  receiver: default-webhook

  group_by:
    - alertname
    - environment
    - service

  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
    - receiver: production-oncall

      matchers:
        - environment="production"
        - severity="page"

      group_wait: 15s
      group_interval: 2m
      repeat_interval: 2h

    - receiver: engineering-tickets

      matchers:
        - severity="ticket"

      group_wait: 10m
      group_interval: 30m
      repeat_interval: 24h

    - receiver: nonprod-chat

      matchers:
        - environment=~"dev|staging"

      group_wait: 1m
      group_interval: 10m
      repeat_interval: 12h

receivers:
  - name: default-webhook

    webhook_configs:
      - url: https://alerts.example.com/default
        send_resolved: true

  - name: production-oncall

    webhook_configs:
      - url: https://alerts.example.com/oncall
        send_resolved: true

  - name: engineering-tickets

    webhook_configs:
      - url: https://alerts.example.com/tickets
        send_resolved: true

  - name: nonprod-chat

    webhook_configs:
      - url: https://alerts.example.com/chat
        send_resolved: true

inhibit_rules:
  - name: cluster-down-inhibits-service-pages

    source_matchers:
      - alertname="ClusterDown"
      - severity="page"

    target_matchers:
      - severity="page"

    equal:
      - environment
      - cluster

Essa configuração estabelece uma política.

Produção e page:

on-call

Ticket:

sistema assíncrono

Dev e staging:

chat

ClusterDown:

inibe pages dependentes
do mesmo cluster

Ainda precisamos adaptar a taxonomia aos serviços reais.

Mas agora existe uma estrutura previsível.

Não agrupe demais

Grouping reduz ruído.

Mas grouping excessivo pode esconder contexto.

Considere:

group_by:
  - severity

Agora todos os pages de produção podem virar uma única notificação.

DatabaseDown

CheckoutSLOBurn

CertificateExpired

KafkaUnavailable

Tudo agrupado porque todos possuem:

severity=page

Isso é ruim.

O grupo deve representar incidentes relacionados.

Normalmente usamos dimensões como:

alertname

service

cluster

environment

Mas também não devemos incluir labels muito específicos.

Se incluirmos:

pod

cada pod pode criar um grupo diferente.

O agrupamento deixa de reduzir ruído.

A configuração precisa refletir a unidade operacional do incidente.

Não repita a mesma página em cada camada

Considere:

Ingress latency high

API latency high

Worker latency high

Database latency high

Se o usuário só percebe latência na API, talvez o page principal deva estar no SLO da API.

As outras métricas ajudam a investigar.

Um princípio útil é alertar alto na pilha quando possível.

Isso evita páginas redundantes por causas que já se manifestam no sintoma.

Exceções existem.

Um banco pode estar corrompendo dados sem gerar erro visível imediatamente.

Um sistema pode estar perdendo dinheiro.

Uma fila pode estar prestes a extrapolar um deadline.

Nesses casos, um page interno pode ser justificado.

A regra não é:

nunca alerte causas

É:

não page causas redundantes
sem uma razão operacional clara.

Como revisar um alerta existente

Pegue qualquer page atual e responda:

1. Qual usuário é afetado?

Se ninguém é afetado agora:

é realmente page?

2. Existe ação imediata?

Se a pessoa só pode dizer:

vamos olhar amanhã

provavelmente é ticket.

3. O alerta representa sintoma ou causa?

Se é causa, existe outro page mais próximo do impacto?

4. Ele dispara sozinho demais?

Talvez precise:

for

janela maior

SLO

mínimo de volume

5. Existem notificações duplicadas?

Talvez precise:

grouping

inhibition

melhor taxonomia

6. Possui owner?

Quem responde?

7. Possui dashboard e runbook?

A investigação começa onde?

8. Já gerou ação útil recentemente?

Se um alerta disparou cinquenta vezes e nunca gerou ação, isso é evidência forte de problema na regra.

Alertas precisam de manutenção.

Métricas para medir a qualidade do alerting

Também podemos medir o próprio processo.

Exemplos:

Pages por plantão

Pages por incidente

Percentual de pages acionáveis

Alertas reconhecidos sem ação

Tempo até acknowledgment

Tempo até resolução

Quantidade de alertas silenciados

Alertas que flappam

Imagine:

120 pages por semana

e apenas:

8 exigiram ação.

Isso é um incidente de alerting.

A plataforma está desperdiçando atenção humana.

Uma meta operacional pode ser reduzir páginas não acionáveis.

Não existe número universal.

Mas acompanhar tendência cria responsabilidade.

Alert fatigue também é dívida técnica

Times frequentemente aceitam alertas ruins porque:

sempre foi assim.

Cada alerta ruim adiciona uma pequena carga.

Depois de meses, ninguém quer mexer porque existem centenas de regras.

Isso é dívida técnica.

Reserve tempo para:

remover alertas obsoletos

reduzir duplicações

adicionar runbooks

revisar thresholds

migrar para SLO burn rate

melhorar grouping

corrigir routing

Uma boa prática é revisar alertas após incidentes.

Pergunte no postmortem:

O alerta correto disparou?

Disparou cedo demais?

Tarde demais?

Houve ruído?

Faltou contexto?

Recebemos páginas duplicadas?

O runbook ajudou?

O sistema de alerting também aprende com incidentes.

O que não deve virar page

Alguns exemplos comuns.

CPU alta sem impacto

Pode ser ticket de capacidade.

Não necessariamente page.

Um pod reiniciou

Kubernetes recuperou.

Observe tendência.

Disco 70%

Se existe espaço para semanas, talvez seja apenas capacity planning.

Um erro HTTP isolado

Sistemas falham ocasionalmente.

Observe proporção e impacto.

Deploy falhou, mas versão anterior continua saudável

A pipeline deve notificar o time responsável.

Talvez não seja incidente de produção.

Certificado expira em 30 dias

Ticket.

Certificado expira em 30 minutos

Agora a urgência muda.

Contexto temporal define severidade.

Um guia de decisão prático

Use page quando uma pessoa precisa agir imediatamente.

Use ticket quando existe tempo para resposta planejada.

Use dashboards e logs para informação sem ação necessária.

Prefira alertar sintomas percebidos pelo usuário.

Use SLO e burn rate para pages de confiabilidade.

Use múltiplas janelas para equilibrar velocidade e precisão.

Use for para ignorar condições transitórias quando isso não comprometer tempo de resposta.

Use keep_firing_for para reduzir flapping quando pequenas oscilações causam resoluções prematuras.

Use labels estáveis para routing e identidade.

Use annotations para texto, valores dinâmicos e links.

Use grouping para consolidar alertas relacionados.

Use inhibition para esconder consequências previsíveis de um alerta dominante.

Use silence para manutenção temporária.

Não use silence como correção permanente para uma regra ruim.

Versione regras e configurações.

Teste routing e notificações.

Monitore o próprio pipeline de alerting.

O fluxo completo da nossa série

Agora podemos conectar todos os artigos.

Aplicação .NET
    |
    v
OpenTelemetry
    |
    v
Logs, Metrics, Traces
    |
    v
OpenTelemetry Collector
    |
    v
Backends de observabilidade
    |
    v
SLI
    |
    v
SLO
    |
    v
Error Budget
    |
    v
Burn Rate
    |
    v
Prometheus Alerting Rule
    |
    v
Alertmanager
    |
    +--> Grouping
    |
    +--> Deduplication
    |
    +--> Inhibition
    |
    +--> Routing
    |
    v
Page ou Ticket
    |
    v
Grafana Incident Dashboard
    |
    v
Metrics
    |
    v
Trace
    |
    v
Logs
    |
    v
Causa e ação

Observe como cada componente possui uma responsabilidade.

OpenTelemetry produz contexto.

Collector transporta.

Prometheus avalia sinais.

SLO define o que importa.

Burn rate define urgência.

Alertmanager controla notificações.

Grafana orienta investigação.

Traces e logs explicam detalhes.

Essa separação cria uma plataforma mais previsível.

Conclusão

O objetivo de um sistema de alertas não é detectar tudo que acontece.

É chamar a pessoa certa, no momento certo, quando existe algo que realmente exige ação.

Alertas demais não aumentam segurança.

Eles reduzem confiança.

CPU alta, pod reiniciado, erro isolado e fila momentaneamente maior podem ser informações úteis.

Mas isso não significa que alguém precisa ser acordado.

Uma boa estratégia começa pela experiência do usuário e pelos objetivos de confiabilidade.

SLOs e Error Budgets fornecem esse contexto.

Burn rate transforma o consumo do budget em urgência.

Prometheus avalia as condições.

Alertmanager reduz ruído com deduplicação, grouping, routing, inhibition e silences.

Labels criam uma taxonomia operacional.

Annotations fornecem contexto.

Runbooks reduzem tempo de resposta.

E uma política clara separa:

Page

Ticket

Informação

Talvez a melhor pergunta para revisar qualquer alerta seja muito simples:

quando essa notificação chegar às 3 da manhã, existe algo útil que uma pessoa pode e deve fazer imediatamente?

Se a resposta for não, o problema provavelmente não está no engenheiro que ignorou o alerta.

O problema está no alerta.

Uma plataforma de observabilidade madura protege sistemas.

Uma plataforma de alerting madura também protege a atenção das pessoas.

Fontes

← voltar ao índice