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
- https://prometheus.io/docs/practices/alerting/ - Boas práticas oficiais do Prometheus para alertar sobre sintomas, reduzir ruído e definir pages acionáveis.
- https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ - Referência oficial para alerting rules, estados pending e firing,
for,keep_firing_for, labels e annotations. - https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/ - Referência oficial para recording rules, validação com
promtoole organização de regras. - https://prometheus.io/docs/alerting/latest/alertmanager/ - Conceitos oficiais do Alertmanager, incluindo grouping, deduplicação, inhibition, silences e alta disponibilidade.
- https://prometheus.io/docs/alerting/latest/configuration/ - Referência completa da configuração de routing, receivers, matchers, tempos de agrupamento e inhibition rules.
- https://prometheus.io/docs/alerting/latest/high_availability/ - Arquitetura e comportamento do Alertmanager em alta disponibilidade.
- https://prometheus.io/docs/alerting/latest/notifications/ - Referência para templates e estrutura das notificações enviadas pelos receivers.
- https://sre.google/workbook/alerting-on-slos/ - Capítulo do Google SRE Workbook sobre alertas orientados a SLO, burn rate e estratégia multi-window multi-burn-rate.