Lab 52 — SLO, error budget e alarme que acorda alguém
O problema, e a empresa que o tem
A Cadência cresceu desde o L01: a API de pedidos hoje atende 120 lojas, a equipe de plataforma tem seis pessoas, e depois do L51 cada requisição carrega log, métrica e trace correlacionados pelo mesmo trace id. A instrumentação nunca foi o problema. O problema é o que acontece depois de ela existir.
Ao longo de dezoito meses, toda vez que algo quebrou uma vez, alguém adicionou um alarme para "isso não acontecer de novo": CPU da task, memória, profundidade da fila, conexões do banco, IOPS, espaço em disco. Hoje são 40, todos apontando para o mesmo tópico SNS, que entrega e-mail idêntico para as seis pessoas. Cinco delas já criaram regra de filtro que arquiva automaticamente — não por preguiça, mas porque ler tudo deixou de ser possível.
Na terça-feira passada, seis alarmes dispararam pela manhã por motivo benigno — deploy, backup agendado, um pico de tráfego normal. No meio deles, um sétimo alarme apontava erro real afetando clientes. Ficou sem resposta por seis horas, com a mesma aparência visual dos outros seis. Ninguém fez nada errado individualmente: o desenho é que garantia esse resultado mais cedo ou mais tarde.
O que este laboratório NÃO é
Não é redução arbitrária dos 40 alarmes — cortar pela metade sem critério de sintoma é apostar em qual causa importa, e a equipe descobre o erro exatamente no incidente que cortou. Também não é construção de painel — isso é o L53. E não é chaos engineering: testar se a alta disponibilidade desenhada realmente segura uma falha de AZ é o L57, e ele depende deste módulo porque precisa de um SLO já definido para medir o efeito do experimento.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando na seção de implantação.
- Explicar por que 40 alarmes corretos produzem um resultado pior que zero alarmes.
- Distinguir alarme de sintoma (o que o cliente sente) de alarme de causa (o que está acontecendo internamente).
- Derivar o orçamento de erro a partir da meta declarada de um SLO.
- Calcular burn rate por metric math, e explicar por que ele precisa de duas janelas, não uma.
- Configurar um alarme composto que só entra em ALARM quando janela curta e janela longa concordam.
- Ligar a ação de um alarme diretamente a um plano de resposta do Incident Manager.
- Provar, com carga sintética, que pico isolado não dispara o alarme composto e consumo sustentado dispara.
- Rebaixar um alarme técnico de "notificação" para "diagnóstico" sem apagar o conhecimento que ele carrega.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| SLI, SLO e error budget | DOP-C02 | meta declarada convertida em orçamento tolerável na janela | a diferença entre medir algo e definir um objetivo mensurável sobre ele |
| Burn rate e multi-window alerting | DOP-C02 | duas janelas, mesmo limiar, exigidas em concordância | por que uma janela sozinha confunde pico com tendência |
| Composite alarms | DOP-C02, SOA-C02 | `alarm_rule` com `ALARM()` combinando duas condições | que o rule casa pelo NOME do alarme, e reduz ruído sem apagar o alarme original |
| Metric math em alarme | SOA-C02, DOP-C02 | expressão que transforma duas métricas brutas numa taxa | quando usar `metric_query` com `expression` em vez de `metric_name` direto |
| Ação de alarme para Incident Manager | DOP-C02 | ARN do plano de resposta em `alarm_actions` | que CloudWatch tem ação nativa "Create incident", sem EventBridge no meio |
| Plano de escalonamento | DOP-C02 | estágios com contato e prazo de confirmação | que a garantia de resposta vem do escalonamento, não da primeira tentativa de contato |
| Alarme de sintoma vs de causa | DOP-C02, SAP-C02 | o que acorda gente vs o que só aparece no painel | que um alarme correto pode ser a métrica errada para decidir "quem acorda" |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve um sistema com dezenas de alarmes de recurso e pede por que a equipe não percebeu um incidente real. A resposta esperada não é "faltou alarme" — é "faltou priorização por impacto ao cliente". Quem responde "adicionar mais um alarme" reproduz o próprio erro que o cenário descreve.
Requisitos, e como cada um muda o desenho
A coluna da direita é onde cada requisito deixou marca no Terraform ou na configuração do plano de resposta.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Meta de disponibilidade | 99,9% em 30 dias | define o orçamento de erro (0,1%) que todo o cálculo de burn rate deriva |
| Detecção rápida de consumo agressivo | minutos, não horas | janela curta de 5 min com limiar 14,4x — o par que reage a incidente real rápido |
| Sem alarme por pico isolado | obrigatório | alarme composto com E lógico entre janela curta e janela longa |
| Cobertura de consumo moderado e sustentado | até 6 horas | segundo par de janelas (30 min / 6 h) com limiar 6x, mesmo plano de resposta |
| Plantão de uma pessoa, sem torre de controle 24h | requisito da equipe | plano de escalonamento do Incident Manager, não só um contato único |
| Conhecimento de causa técnica preservado | os 40 alarmes continuam existindo | alarm_actions esvaziado, não o recurso — vira painel de diagnóstico |
| Sintoma medido na borda, não em recurso interno | obrigatório | fonte do SLI é ALB (5xx/total) e contador OTel do L51, nunca CPU ou fila |
Arquitetura mínima: quarenta alarmes e uma caixa de entrada
Este é o desenho que a Cadência tem hoje. Ele é implantável — de fato está implantado — e cada alarme individualmente está correto: mede o que diz medir, com limiar razoável. O defeito não está em nenhum alarme isolado.
- → CPUUtilization, MemoryUtilization por task
- → ApproximateNumberOfMessagesVisible
- → DatabaseConnections, ReadIOPS
- → estado ALARM de qualquer um dos 40
- → e-mail idêntico para os 40, sem distinção
- Compute
- Integração de apps
- Banco de dados
- Gestão e governança
- Fora da AWS
Este desenho é o que a maioria das equipes tem hoje, e ele funciona tecnicamente: toda métrica de recurso tem alarme. O defeito não é a ausência de alarme — é a ausência de prioridade. Percorra os passos e repare que a régua de decisão de "quem acorda" nunca existiu neste desenho: ela foi terceirizada para a caixa de entrada de cada pessoa.
- Cada alarme nasceu de um incidente passado, isolado. Ninguém sentou para desenhar 40 alarmes de uma vez. Cada um foi adicionado depois de um susto específico — "da última vez foi memória, então vamos alarmar memória". A coleção cresceu por acréscimo, nunca por revisão do conjunto.
- Nenhuma métrica aqui mede o que o cliente sente. CPU, memória, profundidade de fila e conexões de banco são causas possíveis de um problema — não são o problema. Um pico de CPU que não afeta latência nem taxa de erro é, do ponto de vista do cliente, evento que não aconteceu.
- Os 40 convergem para o mesmo canal, sem hierarquia. Um alarme de CPU alta por dois minutos e um alarme de banco inacessível chegam com a mesma aparência visual na caixa de entrada. Nada no transporte carrega prioridade.
- A defesa racional contra o ruído é ignorar tudo. Com dezenas de e-mails por semana, a resposta economicamente racional de cada pessoa é parar de ler. A regra de filtro que arquiva automaticamente não é preguiça: é a única forma de manter a caixa de entrada usável.
- O incidente real fica emaranhado entre os que não importam. Numa manhã em que 6 dos 40 dispararam por motivo benigno (deploy, backup agendado), o sétimo — erro real de cliente — teve a mesma chance de ser lido que os outros seis: nenhuma. Ele ficou sem resposta por seis horas.
- Por que a equipe continua adicionando alarme assim. "Vamos criar um alarme para isso não acontecer de novo" é a reação mais barata a um incidente, e parece responsável. O custo — mais um item competindo pela mesma atenção finita — só aparece agregado, meses depois, e ninguém mede.
Antes de mudar qualquer coisa, meça a fadiga. O comando abaixo conta quantas vezes os 40 alarmes mudaram de estado em 30 dias, e quantos e-mails isso gerou — o número torna o problema discutível.
# Rode ANTES de mudar nada. Da conta de estados os ultimos 30 dias.
PROJETO=ffv-lab
for alarme in $(aws cloudwatch describe-alarms --alarm-name-prefix "${PROJETO}-" \
--query "MetricAlarms[].AlarmName" --output text); do
n=$(aws cloudwatch describe-alarm-history --alarm-name "$alarme" \
--history-item-type StateUpdate \
--start-date "$(date -u -v-30d +%FT%TZ)" --end-date "$(date -u +%FT%TZ)" \
--query "length(AlarmHistoryItems)" --output text)
echo "$alarme: $n transicoes"
done
# Na Cadencia: 40 alarmes, 187 transicoes em 30 dias — media de 6 por dia, todas no
# mesmo topico, para as mesmas seis pessoas. Nenhuma delas distinguia gravidade.O incidente que a fadiga escondeu
Na manhã em que seis alarmes benignos dispararam junto com o real, a taxa de erro subiu para clientes por seis horas antes de alguém investigar — não porque ninguém quisesse agir, mas porque nada no desenho separava o alarme que importava dos outros seis. Alertar mais não preveniu o incidente: preveniu que ele fosse notado a tempo.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. A pergunta que o desenho inteiro responde não é "como medir mais coisas" — é "quando o consumo do orçamento justifica acordar uma pessoa".
- → erros e total por período de 5 min
- → contadores de pedido/erro por rota
- → burn rate, janela curta
- → burn rate, janela longa
- → ALARM(janela curta)
- → ALARM(janela longa)
- → estado ALARM confirmado
- → ação: iniciar incidente pelo plano
- → engajamento e escalonamento registrados
- → quem responde consulta ao investigar causa
- Rede e entrega
- Gestão e governança
- Integração de apps
- Fora da AWS
A pergunta deixou de ser "está anormal?" e passou a ser "estou queimando o orçamento rápido demais?". O que muda no desenho não é acrescentar caixas às métricas técnicas — é uma cadeia nova, que nasce em métrica de sintoma e só chega a uma pessoa depois de duas janelas concordarem. Os 40 alarmes técnicos continuam existindo, mas saem deste caminho.
- O sinal nasce onde o cliente está, não onde o servidor está. ALB e OTel medem o que atravessa a borda: requisição respondida com erro, requisição total. Nenhum dos dois é métrica de recurso — os dois são métrica de experiência.
- O orçamento converte a meta do SLO em número que se queima. Um SLO de 99,9% tolera 0,1% de falha na janela. O burn rate divide a taxa de erro observada agora por esse 0,1% — o resultado diz quantas vezes mais rápido que o sustentável o orçamento está sendo gasto.
- Uma janela sozinha mistura pico com tendência. Cinco minutos de erro concentrado podem produzir burn rate de 50x sem significar nada — foi um deploy, um retry em lote. A janela curta detecta rápido; ela não decide sozinha.
- A janela longa é o que torna o alarme confiável o bastante para acordar alguém. Exigir que o MESMO limiar também esteja excedido numa janela de 1 hora filtra o pico transiente: só sobra o consumo que persiste — que é o que de fato ameaça a meta do mês.
- A ação do alarme aponta para um plano, não para uma lista de e-mail. A mesma ação de alarme que hoje aceita ARN de tópico SNS aceita ARN de plano de resposta do Incident Manager — o CloudWatch oferece isso como ação nativa ("Create incident").
- Escalonamento é o que garante resposta quando a primeira pessoa não vê. O plano de resposta engaja o contato de plantão; se não há confirmação dentro do prazo do estágio, o plano de escalonamento avança para o próximo contato — sozinho, sem alguém decidir isso na hora do incidente.
- Os 40 alarmes técnicos não morreram — mudaram de função. Continuam medindo CPU, memória e fila, e continuam úteis — para responder "por que o burn rate subiu" depois que alguém já foi acordado, não para decidir SE alguém deve ser.
O que a segunda janela compra, em uma frase
Sem ela, todo pico de erro de 5 minutos — deploy, retry em lote, um cliente com script quebrado — teria a mesma urgência que uma falha real e sustentada. A janela longa é o preço pequeno (esperar até 1 hora para confirmar) que compra a diferença entre "algo aconteceu" e "algo que precisa de alguém agora".
De um erro isolado a uma pessoa acordada
O tempo entre o erro começar e alguém ser notificado não é zero por desenho: as duas janelas custam até 1 hora de confirmação para o par rápido. É uma troca deliberada — resposta mais lenta em 55 minutos, contra zero incidente perdido em ruído.
O evento que o CloudWatch emite quando um alarme muda de estado é o mesmo formato usado em todo o ciclo de vida de alarme — inclusive o composto. Vale ler com atenção porque é a partir dele que o Incident Manager decide o título e o conteúdo inicial do incidente.
// O que o CloudWatch entrega quando o alarme composto muda de estado. E o payload
// que o modelo do plano de resposta usa para preencher titulo e contexto.
{
"source": "aws.cloudwatch",
"detail-type": "CloudWatch Alarm State Change",
"resources": ["arn:aws:cloudwatch:us-east-1:111122223333:alarm:ffv-lab-slo-disponibilidade-queima-rapida-confirmada"],
"detail": {
"alarmName": "ffv-lab-slo-disponibilidade-queima-rapida-confirmada",
"state": {
"value": "ALARM",
// O motivo cita o alarm_rule inteiro — as DUAS janelas em ALARM, nao uma.
"reason": "arn:...:burn-5m transitioned to ALARM, arn:...:burn-1h transitioned to ALARM"
},
"previousState": { "value": "OK" }
}
}Deduplicação já vem de fábrica
Por padrão, o Incident Manager deduplica incidentes originados do MESMO alarme ou evento — se o composto oscilar entre ALARM e OK várias vezes na mesma janela de instabilidade, isso não abre um incidente novo a cada oscilação. Não é preciso escrever lógica própria de deduplicação para o caso mais comum.
As decisões, e o que se perde em cada uma
📋 Uma equipe de plataforma de seis pessoas, com plantão semanal de uma pessoa só, herdou 40 alarmes de causa técnica acumulados em 18 meses e quer que o alarme que acorda alguém signifique alguma coisa quando dispara.
A equipe não tem gente para triar 40 sinais por semana, então a triagem tem de estar no DESENHO, não na disciplina de quem lê o e-mail. Burn rate com duas janelas resolve o problema central sem apagar conhecimento nenhum: os 40 alarmes continuam existindo e continuam corretos — só deixam de ser o critério de "acordar alguém". O custo é quase todo de configuração; nenhum recurso novo fica ligado o tempo todo.
Alt: CloudWatch Application Signals — SLO e burn rate gerenciados — A AWS oferece um recurso nativo que calcula burn rate automaticamente a partir de um SLO declarado, sem escrever metric math à mão. É um caminho legítimo para quem já usa Application Signals — mas esconde a aritmética que este laboratório existe para ensinar. Vale adotar DEPOIS de entender o mecanismo, não antes.
Alt: Um único alarme de erro 5xx, sem burn rate — Resolve metade do problema — pelo menos mede sintoma — mas não distingue pico transiente de consumo sustentado. Sem a segunda janela, volta a gerar ruído ou perde incidente real, dependendo de para qual lado o limiar for ajustado.
Alt: Reduzir os 40 alarmes cortando pela metade — Parece disciplina e não é: sem critério de sintoma, cortar é apostar em qual causa importa mais — e a equipe descobre que errou exatamente no incidente que cortou.
Alt: Rodízio de plantão lendo tudo com atenção redobrada — Não escala com o número de alarmes nem com o tamanho da equipe; é pedir disciplina para compensar um desenho que gera ruído, e falha do mesmo jeito sob cansaço.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Fonte do SLI de disponibilidade | ALB: 5xx do alvo sobre total | log de aplicação; contador do OTel isoladamente | métrica já existe, sem instrumentação nova, e mede exatamente a borda que o cliente atravessa | não distingue POR QUE deu erro — só que deu; a causa vem do painel de diagnóstico |
| Limiar da janela rápida | 14,4x — prática de SRE consolidada | valor customizado calculado do zero | é um ponto de partida testado pela indústria, e evita reinventar um número sem histórico | pode não caber perfeitamente no perfil de tráfego da Cadência; revisar depois de meses de dado real |
| Duas janelas com E lógico | composite alarm | uma janela só, com evaluation_periods alto | separa pico transiente de consumo sustentado sem esconder o alarme individual | até 1 hora de atraso na confirmação do par rápido — aceito pelo requisito declarado |
| Ação do alarme | ARN do plano de resposta direto | EventBridge + Lambda customizada | é a ação nativa do CloudWatch para Incident Manager; menos peça para manter | menos flexibilidade de lógica condicional do que uma função Lambda ofereceria |
| Destino dos 40 alarmes técnicos | painel de diagnóstico, sem ação | apagar os alarmes; manter ação para o plantão | preserva o conhecimento de causa sem competir pela atenção de quem está sendo acordado | ninguém é notificado se um alarme técnico sozinho indicar problema real ainda não sintomático |
| Rotação de plantão | referenciada por ARN, provisionada fora deste módulo | criar contato e rotação neste Terraform | escalonamento é recurso organizacional, reaproveitado por muitos planos de resposta | este laboratório não ensina a provisionar contato — é decisão de escopo, não lacuna técnica |
A dívida que este módulo cria, e que não paga
O limiar de 14,4x veio direto da prática consolidada da indústria, sem calibração sobre o tráfego real da Cadência. Se o perfil de erro natural (retries de cliente, timeouts esperados) girar em torno de um valor alto, o limiar vai disparar com frequência maior do que o desejável. A correção é medir o burn rate histórico por semanas antes de confiar cegamente no valor — e isso é o próprio L57, que testa a resiliência com hipótese escrita.
Construir: o orçamento, e a janela que detecta rápido
O orçamento nasce de uma única linha — a meta do SLO — e tudo o resto do módulo deriva dela. Mudar a meta de 99,9% para 99,99% multiplica o rigor do limiar por dez sem tocar em nenhuma outra linha.
# slo.tf — o SLO de disponibilidade, e o orcamento derivado dele
locals {
# Meta de atingimento: 99,9% das requisicoes bem-sucedidas, medida numa
# janela de 30 dias. NAO e um valor da documentacao da AWS: e a meta que
# a Cadencia declara para a API de pedidos, e a que o restante do modulo
# deriva.
slo_meta_disponibilidade = 0.999
# Orcamento de erro: quanto de falha o SLO tolera antes de deixar de
# cumprir a meta. Com 99,9%, sao 0,1% das requisicoes na janela.
slo_orcamento_erro = 1 - local.slo_meta_disponibilidade
}
# ── Janela curta: detecta rapido, nao decide sozinha ──────────────────────────
resource "aws_cloudwatch_metric_alarm" "queima_rapida_curta" {
alarm_name = "${var.projeto}-slo-disponibilidade-burn-5m"
alarm_description = "Burn rate do SLO de disponibilidade, janela de 5 minutos."
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
# 14,4x e o limiar classico da pratica de SRE consolidada pela industria
# para a janela rapida: neste ritmo o orcamento de 30 dias esgota em ~2h.
threshold = 14.4
treat_missing_data = "notBreaching" # ausencia de trafego nao e falha
metric_query {
id = "erros"
return_data = false
metric {
namespace = "AWS/ApplicationELB"
metric_name = "HTTPCode_Target_5XX_Count"
stat = "Sum"
period = 300
dimensions = {
LoadBalancer = aws_lb.principal.arn_suffix
TargetGroup = aws_lb_target_group.api.arn_suffix
}
}
}
metric_query {
id = "total"
return_data = false
metric {
namespace = "AWS/ApplicationELB"
metric_name = "RequestCount"
stat = "Sum"
period = 300
dimensions = {
LoadBalancer = aws_lb.principal.arn_suffix
TargetGroup = aws_lb_target_group.api.arn_suffix
}
}
}
metric_query {
id = "queima"
return_data = true
# taxa observada dividida pelo orcamento permitido: quantas vezes mais
# rapido que o sustentavel o erro esta acontecendo agora.
expression = "(erros / total * 100) / (${local.slo_orcamento_erro} * 100)"
label = "Burn rate — janela de 5 min"
}
}
# ── Janela longa: confirma que o consumo persiste ─────────────────────────────
resource "aws_cloudwatch_metric_alarm" "queima_rapida_longa" {
alarm_name = "${var.projeto}-slo-disponibilidade-burn-1h"
alarm_description = "Burn rate do SLO de disponibilidade, janela de 1 hora."
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
threshold = 14.4 # o MESMO limiar da janela curta — o que muda e a duracao da confirmacao
treat_missing_data = "notBreaching"
metric_query {
id = "erros"
return_data = false
metric {
namespace = "AWS/ApplicationELB"
metric_name = "HTTPCode_Target_5XX_Count"
stat = "Sum"
period = 3600
dimensions = {
LoadBalancer = aws_lb.principal.arn_suffix
TargetGroup = aws_lb_target_group.api.arn_suffix
}
}
}
metric_query {
id = "total"
return_data = false
metric {
namespace = "AWS/ApplicationELB"
metric_name = "RequestCount"
stat = "Sum"
period = 3600
dimensions = {
LoadBalancer = aws_lb.principal.arn_suffix
TargetGroup = aws_lb_target_group.api.arn_suffix
}
}
}
metric_query {
id = "queima"
return_data = true
expression = "(erros / total * 100) / (${local.slo_orcamento_erro} * 100)"
label = "Burn rate — janela de 1 h"
}
}
# ── O E logico: so acorda alguem quando as duas concordam ─────────────────────
resource "aws_cloudwatch_composite_alarm" "queima_rapida" {
alarm_name = "${var.projeto}-slo-disponibilidade-queima-rapida-confirmada"
alarm_description = "Consumo sustentado do orcamento de erro, confirmado em duas janelas."
# ALARM() casa pelo NOME do alarme, nao pela ARN. As duas janelas tem de
# estar em ALARM ao mesmo tempo — e' isso que filtra o pico transiente.
alarm_rule = "ALARM(\"${aws_cloudwatch_metric_alarm.queima_rapida_curta.alarm_name}\") AND ALARM(\"${aws_cloudwatch_metric_alarm.queima_rapida_longa.alarm_name}\")"
# Dois destinos, dois papeis: o topico e visibilidade em canal de chat; a
# ARN do plano de resposta e o que de fato abre o incidente e engaja o
# plantao. O CloudWatch oferece isto como acao nativa de alarme ("Create
# incident"), apontando para um plano do Incident Manager.
alarm_actions = [
aws_sns_topic.plantao.arn,
aws_ssmincidents_response_plan.orcamento_de_erro.arn,
]
ok_actions = [aws_sns_topic.plantao.arn]
}
Por que `return_data = false` nas métricas brutas
Erros e total existem só para alimentar a expressão `queima`; publicá-los como série própria do alarme poluiria o histórico sem necessidade. Só a métrica com `return_data = true` é a que o `threshold` avalia — é ela, e apenas ela, que decide o estado do alarme.
Construir: a segunda camada, para consumo que não é pico
Consumir 6x o sustentável por 6 horas seguidas não é ruído — é um problema real, só que mais lento que o cenário de madrugada. O par lento usa o MESMO plano de resposta: a diferença de urgência mora no conteúdo do incidente, não em um canal separado.
# slo_lenta.tf — a segunda camada: consumo moderado, mas sustentado por horas
# Mesma estrutura do par rapido, com limiar mais baixo e janelas maiores:
# 6x consumido de forma sustentada por 6 horas tambem ameaca o mes, so que
# com menos urgencia — nao precisa acordar ninguem as 3h, mas precisa
# aparecer antes do fim do plantao.
resource "aws_cloudwatch_metric_alarm" "queima_lenta_curta" {
alarm_name = "${var.projeto}-slo-disponibilidade-burn-30m"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
threshold = 6
treat_missing_data = "notBreaching"
metric_query {
id = "erros"
metric {
namespace = "AWS/ApplicationELB", metric_name = "HTTPCode_Target_5XX_Count"
stat = "Sum", period = 1800
dimensions = { LoadBalancer = aws_lb.principal.arn_suffix, TargetGroup = aws_lb_target_group.api.arn_suffix }
}
return_data = false
}
metric_query {
id = "total"
metric {
namespace = "AWS/ApplicationELB", metric_name = "RequestCount"
stat = "Sum", period = 1800
dimensions = { LoadBalancer = aws_lb.principal.arn_suffix, TargetGroup = aws_lb_target_group.api.arn_suffix }
}
return_data = false
}
metric_query {
id = "queima"
return_data = true
expression = "(erros / total * 100) / (${local.slo_orcamento_erro} * 100)"
}
}
resource "aws_cloudwatch_metric_alarm" "queima_lenta_longa" {
alarm_name = "${var.projeto}-slo-disponibilidade-burn-6h"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
threshold = 6
treat_missing_data = "notBreaching"
metric_query {
id = "erros"
metric {
namespace = "AWS/ApplicationELB", metric_name = "HTTPCode_Target_5XX_Count"
stat = "Sum", period = 21600
dimensions = { LoadBalancer = aws_lb.principal.arn_suffix, TargetGroup = aws_lb_target_group.api.arn_suffix }
}
return_data = false
}
metric_query {
id = "total"
metric {
namespace = "AWS/ApplicationELB", metric_name = "RequestCount"
stat = "Sum", period = 21600
dimensions = { LoadBalancer = aws_lb.principal.arn_suffix, TargetGroup = aws_lb_target_group.api.arn_suffix }
}
return_data = false
}
metric_query {
id = "queima"
return_data = true
expression = "(erros / total * 100) / (${local.slo_orcamento_erro} * 100)"
}
}
resource "aws_cloudwatch_composite_alarm" "queima_lenta" {
alarm_name = "${var.projeto}-slo-disponibilidade-queima-lenta-confirmada"
alarm_rule = "ALARM(\"${aws_cloudwatch_metric_alarm.queima_lenta_curta.alarm_name}\") AND ALARM(\"${aws_cloudwatch_metric_alarm.queima_lenta_longa.alarm_name}\")"
# Mesmo plano — a diferenca de urgencia esta no CONTEUDO do incidente, nao
# no canal. Quem responde decide o horario de agir a partir do impacto
# declarado no modelo do plano, nao a partir de qual alarme chamou.
alarm_actions = [aws_sns_topic.plantao.arn, aws_ssmincidents_response_plan.orcamento_de_erro.arn]
ok_actions = [aws_sns_topic.plantao.arn]
}
A terceira janela que este módulo NÃO implementa em código
A prática de SRE consolidada recomenda uma terceira camada — janela de 6h/3 dias, limiar 1x, para consumo que só ameaça o orçamento no fim do mês. O código aqui cobre duas para manter o módulo no escopo; a terceira segue a mesma estrutura exata, trocando período e limiar — é exercício direto de replicar o padrão, não uma peça nova a aprender.
Construir: o plano que decide quem é acordado
É aqui que o alarme deixa de terminar numa caixa de entrada e passa a abrir um incidente formal, com escalonamento. O tópico SNS continua existindo — para visibilidade em canal de chat — mas ele não é mais o que garante que alguém responde.
# incident_manager.tf — o topico, o plano de resposta, e o que fica de fora
resource "aws_sns_topic" "plantao" {
name = "${var.projeto}-plantao-slo"
}
# Assinatura de e-mail para visibilidade em equipe. NAO e o mecanismo que
# garante resposta — isso e o plano abaixo, com escalonamento. E-mail aqui
# e auditoria, nao dependencia.
resource "aws_sns_topic_subscription" "plantao_email" {
topic_arn = aws_sns_topic.plantao.arn
protocol = "email"
endpoint = var.email_visibilidade_equipe
}
# O plano de resposta. Uso apenas os campos confirmados na documentacao no
# momento da escrita: nome, modelo do incidente (titulo e impacto) e a lista
# de contatos engajados. Campos adicionais existem (canal de chat, integracao
# com PagerDuty) — confira o esquema atual do provedor antes de os usar, pois
# nao foram conferidos aqui.
resource "aws_ssmincidents_response_plan" "orcamento_de_erro" {
name = "${var.projeto}-orcamento-de-erro"
display_name = "Orcamento de erro do SLO de disponibilidade consumido rapido demais"
incident_template {
title = "Burn rate confirmado em duas janelas — orcamento do mes em risco"
# Escala de impacto do Incident Manager: confirme o intervalo exato (a
# documentacao descreve niveis de 1, mais grave, a 5) antes de aplicar em
# producao. Aqui, 2 = grave o suficiente para acordar plantao, mas nao a
# interrupcao total do servico.
impact = 2
}
# O contato de plantao E o rodizio de escalonamento sao provisionados uma
# vez pelo time de plataforma (aws_ssmcontacts_contact, aws_ssmcontacts_plan
# e aws_ssmcontacts_rotation) e reaproveitados por varios planos de
# resposta — nao recriados por laboratorio. Este modulo referencia a ARN
# de um contato ja existente, deliberadamente fora do escopo aqui: a
# rotacao de plantao e um recurso organizacional, nao deste servico.
engagements = [var.contato_plantao_arn]
}
# O que os 40 alarmes tecnicos deixam de ter: uma acao que notifica gente.
# Continuam existindo (o Terraform que os criou nao muda), so perdem
# alarm_actions apontando para SNS de plantao. Ilustrativo — o recurso real
# de cada um ja existe desde antes deste laboratorio.
resource "aws_cloudwatch_metric_alarm" "exemplo_alarme_tecnico_rebaixado" {
alarm_name = "${var.projeto}-ecs-cpu-alta"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 3
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = 300
statistic = "Average"
threshold = 80
dimensions = {
ClusterName = aws_ecs_cluster.principal.name
ServiceName = aws_ecs_service.api.name
}
# Sem alarm_actions: o estado continua calculado e visivel no painel de
# diagnostico (dashboard), mas nao publica em lugar nenhum que acorde gente.
alarm_actions = []
}
Plano sem contato engajado é alarme silencioso disfarçado de resolvido
Se `engagements` estiver vazio ou apontar para uma ARN de contato que não existe mais, o Terraform aplica sem erro, o incidente abre — e ninguém é notificado. É pior que os 40 alarmes técnicos: aqueles pelo menos geravam e-mail. Um plano de resposta mal configurado produz sensação de cobertura sem cobertura real, e só se descobre no incidente real.
A parte que este módulo trata como pré-provisionada, de propósito
Contato, plano de contato e rotação de plantão (`aws_ssmcontacts_contact`, `aws_ssmcontacts_plan`, `aws_ssmcontacts_rotation`) não são criados aqui. Na prática, o time de plataforma provisiona isso uma vez — quem está de plantão essa semana — e todo plano de resposta de todo serviço referencia a mesma ARN. Recriar o rodízio por laboratório ensinaria errado a unidade real de reuso.
Construir: o sintoma sai do código, o cálculo fica no CloudWatch
A aplicação não sabe o que é burn rate, e está certo que não saiba. A única responsabilidade dela é emitir o sintoma com precisão — sucesso ou erro, por rota. Toda a aritmética de orçamento vive em metric math, fora do código que muda a cada deploy.
// Program.cs (trecho) — o sintoma sai do codigo, o calculo fica no CloudWatch
var builder = WebApplication.CreateBuilder(args);
// Reaproveita o OTel do L51: aqui so acrescentamos os CONTADORES que
// alimentam o terceiro SLO (fim a fim, correlacionado por trace). O calculo
// de burn rate NAO mora aqui — mora inteiramente em metric math no
// CloudWatch. A aplicacao so precisa emitir o sintoma com precisao.
var meter = new Meter("Cadencia.Pedidos", "1.0.0");
var pedidosTotal = meter.CreateCounter<long>(
"pedidos_total", description: "Requisicoes ao endpoint de pedidos, por resultado");
builder.Services.AddOpenTelemetry()
.WithMetrics(m => m
.AddMeter("Cadencia.Pedidos")
// O exportador OTLP do L51 ja aponta para o coletor ADOT; o proprio
// coletor, configurado com o exportador awsemf, converte a metrica
// OTel num Formato de Metrica Embutida e a publica como metrica
// customizada no CloudWatch — sem SDK da AWS na aplicacao.
.AddOtlpExporter());
var app = builder.Build();
app.MapGet("/api/pedidos/{id:guid}", async (Guid id, AppDb db) =>
{
// O rotulo "resultado" e o que faz esta metrica virar SLI: separa
// sucesso de erro por rota, na mesma cardinalidade que o painel de
// trace do L51 ja usa — sem inventar um vocabulario novo de metrica.
try
{
var pedido = await db.Pedidos.FindAsync(id);
pedidosTotal.Add(1, new("rota", "pedidos/id"), new("resultado", pedido is null ? "nao_encontrado" : "sucesso"));
return pedido is { } p ? Results.Ok(p) : Results.NotFound();
}
catch (Exception)
{
pedidosTotal.Add(1, new("rota", "pedidos/id"), new("resultado", "erro"));
throw; // o middleware de erro do ASP.NET Core continua tratando a resposta
}
});
app.Run();
Por que o cálculo NÃO mora na aplicação
Se o burn rate fosse calculado em código, cada serviço reimplementaria a mesma fórmula, com chance de divergir sutilmente. Deixá-lo em metric math significa que mudar a meta do SLO ou o tamanho da janela é uma mudança de infraestrutura — revisável, testável e sem novo deploy de aplicação.
Implantar, e provar que o alarme certo dispara
Cinco provas. A terceira é a que mais gente pula, e é a que captura o defeito mais caro deste laboratório: alarme técnico continuando a notificar gente depois da migração.
#!/usr/bin/env bash
# provas.sh — cinco medicoes; a de numero 3 e a que mais gente pula
set -euo pipefail
PROJETO="${PROJETO:?defina PROJETO}"
# ── Prova 1: carga sintetica de erro sustentado forca o alarme composto ──────
# Gera 5xx real por 70 minutos (mais que a janela longa de 1h) numa rota de
# teste. Espera-se ALARM no composto entre o minuto 60 e 65.
./gerar_erro_sintetico.sh --duracao-min 70 --taxa-erro 0.05 &
sleep 3900
aws cloudwatch describe-alarms --alarm-names "${PROJETO}-slo-disponibilidade-queima-rapida-confirmada" \
--query "MetricAlarms[0].StateValue" --output text
# Esperado: ALARM. Se ainda INSUFFICIENT_DATA ou OK, confira se as duas
# janelas (curta e longa) realmente estao em ALARM cada uma.
# ── Prova 2: pico isolado de 1 minuto NAO dispara o composto ─────────────────
./gerar_erro_sintetico.sh --duracao-min 1 --taxa-erro 0.20
sleep 360
aws cloudwatch describe-alarms --alarm-names "${PROJETO}-slo-disponibilidade-burn-5m" \
--query "MetricAlarms[0].StateValue" --output text # pode entrar em ALARM
aws cloudwatch describe-alarms --alarm-names "${PROJETO}-slo-disponibilidade-queima-rapida-confirmada" \
--query "MetricAlarms[0].StateValue" --output text # tem de continuar OK
# Se o COMPOSTO entrou em ALARM aqui, o alarm_rule esta com OR em vez de AND.
# ── Prova 3: os 40 alarmes tecnicos nao tem acao que notifica gente ──────────
# Nenhum alarme cujo nome NAO comece pelo prefixo do SLO pode ter o topico
# de plantao em AlarmActions. Esta prova falhando e o defeito mais caro do
# laboratorio inteiro: alarme tecnico continuando a acordar gente.
aws cloudwatch describe-alarms --alarm-name-prefix "${PROJETO}-" \
--query "MetricAlarms[?!contains(AlarmName, 'slo-disponibilidade')].{nome:AlarmName,acoes:AlarmActions}" \
--output table
# Esperado: nenhuma linha com a ARN do topico de plantao em "acoes".
# ── Prova 4: o incidente realmente abriu, e o contato foi engajado ───────────
aws ssm-incidents list-incident-records \
--query "incidentRecordSummaries[0].{status:status,inicio:impact,titulo:title}" --output table
aws ssm-contacts list-engagements \
--query "engagements[0].{contato:contactArn,confirmado:*}" --output table
# Esperado: registro de incidente com o titulo do plano, e um engagement
# associado ao contato de plantao — nao so o alarme em ALARM.
# ── Prova 5: burn rate volta a cair depois que o erro sintetico para ─────────
sleep 900
aws cloudwatch get-metric-data --start-time "$(date -u -v-15M +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--metric-data-queries '[{"Id":"queima","Expression":"(erros/total*100)/(0.1)","ReturnData":true},
{"Id":"erros","MetricStat":{"Metric":{"Namespace":"AWS/ApplicationELB","MetricName":"HTTPCode_Target_5XX_Count"},"Period":300,"Stat":"Sum"}},
{"Id":"total","MetricStat":{"Metric":{"Namespace":"AWS/ApplicationELB","MetricName":"RequestCount"},"Period":300,"Stat":"Sum"}}]' \
--query "MetricDataResults[0].Values[:3]"
# Esperado: os ultimos valores abaixo de 1 — a prova de que a mitigacao
# funcionou nao e "o alarme mudou de estado", e o numero caindo.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · Consumo sustentado dispara | carga sintética de 70 min + `describe-alarms` | composto em ALARM entre os minutos 60–65 | se não disparou, confira se as DUAS janelas entraram em ALARM individualmente |
| 2 · Pico isolado NÃO dispara | carga sintética de 1 min + `describe-alarms` | janela curta pode alarmar; o composto permanece OK | composto em ALARM aqui indica `alarm_rule` com OR em vez de AND |
| 3 · Alarme técnico sem ação de plantão | `describe-alarms` filtrando por prefixo | nenhum alarme fora do SLO tem a ARN do tópico de plantão em `AlarmActions` | presença da ARN ali é o defeito mais caro: alarme técnico voltando a acordar gente |
| 4 · Incidente e engajamento reais | `list-incident-records` e `list-engagements` | registro de incidente com o título do plano, e engagement associado ao contato | alarme em ALARM sem incidente correspondente indica falha na ação do alarme, não no cálculo |
| 5 · Burn rate volta a cair | `get-metric-data` após parar a carga sintética | últimos valores abaixo de 1 | mitigação "parece" ter funcionado sem o número caindo é sinal de causa raiz não resolvida |
Quebrar de propósito: três falhas e o diagnóstico
As três produzem o mesmo sintoma superficial no console — "o alarme parece errado" — e são diferentes o bastante para exigir investigação em lugares distintos.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Limiar copiado sem considerar a meta real | aplique threshold 14.4 num SLO de 99% (orçamento de 1%, não 0,1%) | o alarme nunca dispara, mesmo com erro real sustentado, ou dispara sempre demais | recalcule: `14.4` só vale combinado ao orçamento derivado da SUA meta | refaça o cálculo de `slo_orcamento_erro` a partir da meta declarada, não copie o limiar isolado |
| `alarm_rule` com OR em vez de AND | troque `AND` por `OR` entre as duas janelas | volta a disparar em todo pico de 5 min, igual ao desenho mínimo | compare o texto exato do `alarm_rule` — a diferença de uma palavra reintroduz o problema inteiro | AND é o que faz a segunda janela valer alguma coisa; sem ele, ela é decoração |
| `engagements` vazio ou com ARN inválida | aplique o plano de resposta sem contato configurado | incidente abre nos registros do Incident Manager e ninguém recebe notificação | `list-engagements` retorna vazio para o incidente, mesmo com o alarme em ALARM | sempre valide `engagements` com uma ARN real antes de considerar o módulo pronto |
A falha que passa despercebida por mais tempo
Um `alarm_rule` com OR ainda parece funcionar — o alarme dispara, alguém é notificado, nada quebra visivelmente. A regressão só aparece como sensação difusa de "estamos sendo acordados demais de novo", semanas depois, quando já é difícil lembrar que a intenção original era exigir as duas janelas.
Uma equipe tem 40 alarmes de CPU, memória e fila, todos corretos individualmente, e um incidente real ficou seis horas sem resposta porque estava misturado com alarmes benignos. Qual é a causa raiz mais precisa deste cenário?
Segurança: quem pode mudar o que acorda alguém
O risco novo que este laboratório introduz não é vazamento de dado — é alguém silenciar, sem querer ou de propósito, o único caminho que garante resposta a um incidente real.
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Alguém desabilita `actions_enabled` do composto "temporariamente" e esquece | média | alto | exigir aprovação de dois para mudança em recurso com prefixo `slo-` | CloudWatch: alarme com `ActionsEnabled = false` há mais de 24h | reativar e revisar por que ficou desligado tanto tempo |
| Contato de plantão desatualizado (pessoa que saiu da equipe) | média | alto | revisão trimestral da ARN referenciada em `engagements` | engajamento sem confirmação em nenhum incidente recente | atualizar a ARN; auditar quantos incidentes passaram sem resposta no período |
| Publicação falsa no tópico de plantão (spoofing) | baixa | médio | política do tópico restrita aos principais que legitimamente publicam (CloudWatch, Incident Manager) | CloudTrail em `Publish` de origem inesperada | revisar a política do tópico; rotacionar se comprometido |
| Excesso de permissão para editar plano de resposta | baixa | alto | IAM restrito a quem administra plantão, não a todo o time de engenharia | CloudTrail em `UpdateResponsePlan` | reverter para a versão anterior; auditar a mudança |
| Métrica customizada da aplicação com cardinalidade descontrolada | baixa | médio | rótulos fixos (`rota`, `resultado`) na métrica OTel, sem valor de alta cardinalidade como id de pedido | custo de CloudWatch subindo sem aumento de tráfego correspondente | remover o rótulo de alta cardinalidade; recriar a métrica |
O `*` que este módulo não precisa justificar
Diferente de módulos anteriores da série, aqui não há política IAM com recurso amplo: os alarmes de metric math leem métricas já publicadas pelo ALB e pelo coletor OTel, sem permissão adicional, e a ação do alarme para Incident Manager é resolvida pela própria plataforma de alarmes — não por uma role que a aplicação assume.
Observabilidade: o painel de diagnóstico que os 40 alarmes viraram
O painel não decide quem é acordado — o alarme composto decide isso. O painel responde a pergunta seguinte, feita por quem já foi acordado: "por que o burn rate subiu?"
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| O orçamento do mês está sendo consumido rápido? | expressão `queima`, janela de 5 min | é o próprio sinal do alarme rápido — o painel só o torna visível continuamente | ≥ 14,4 sustentado |
| Isso é pico ou tendência? | a mesma expressão, janela de 1h ao lado da de 5 min | as duas convergindo é o que o composto já decidiu; divergirem é sinal de transição em curso | divergência > 2h |
| Qual recurso está sob pressão agora? | CPUUtilization, MemoryUtilization, fila, conexões | os 40 alarmes técnicos, agora sem ação — a primeira parada de quem investiga | os originais, inalterados |
| Quanto orçamento resta no mês? | orçamento acumulado − consumo acumulado | cai a cada erro; sobe (nunca recupera) só ao virar o mês | informativo, sem alarme |
| Quantos incidentes o plano de resposta abriu este mês? | `list-incident-records` do Incident Manager | tendência de subida indica limiar mal calibrado ou problema recorrente não resolvido | revisão mensal |
A métrica que engana quem olha só o painel
Um recurso técnico em alerta (CPU alta, por exemplo) sem o burn rate de nenhum SLO em movimento é, do ponto de vista do cliente, evento que não aconteceu. Tratar alarme técnico isolado como prioridade reintroduz exatamente a confusão entre causa e sintoma que este módulo existe para resolver.
Escala: 10, 10 mil, 1 milhão de requisições
| Volume | O que acontece com o SLO | O que passa a doer | O que fazer |
|---|---|---|---|
| 40 mil req/dia (atual) | janela de 5 min tem centenas de amostras — burn rate estável | nada; é o cenário deste laboratório | nada |
| 4 mil req/min | janelas continuam com amostra suficiente | o mesmo limiar de 14,4x continua válido estatisticamente | nenhuma mudança necessária |
| Tráfego baixo (madrugada, poucas lojas ativas) | poucas requisições por janela de 5 min | um único erro pode mover a taxa observada de forma desproporcional | considerar `treat_missing_data` e janela mínima de amostra antes de confiar no burn rate |
| 1 milhão de req/dia | volume alto estabiliza ainda mais a taxa observada | o custo de metric math cresce com o número de séries, não com o volume de pontos | nenhuma mudança de desenho; revisar custo de CloudWatch na seção seguinte |
| Falha de AZ | taxa de erro sobe de verdade — é exatamente o caso que o SLO deve capturar | se o burn rate NÃO disparar aqui, o limiar está calibrado errado, não a infraestrutura | é o próprio experimento do L57: falha controlada, com o SLO como critério de sucesso |
O efeito que só aparece com poucas amostras
Com tráfego muito baixo, uma janela de 5 min pode ter dezenas de requisições em vez de milhares — e um único erro pode empurrar a taxa observada para um valor que parece consumo agressivo sem ser. Isto não foi medido na Cadência, que tem volume suficiente; se o seu serviço tem período de tráfego muito baixo, meça a variância antes de confiar no limiar.
Custo: o que este laboratório acrescenta à fatura
É um módulo majoritariamente de configuração. O que cobra é o NÚMERO de alarmes e de engajamentos, não o volume de tráfego que eles observam.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | 1 SLO, 2 janelas, sem incidente real | alguns alarmes-métrica e um composto, todos cobrados por alarme-mês | desprezível | nenhuma; o custo aqui é irrisório comparado ao tempo de diagnóstico que economiza |
| Produção pequena | 3 SLOs, 2 camadas cada, plantão semanal ativo | cerca de uma dezena de alarmes de métrica, alguns compostos, poucos engajamentos por mês | baixa e previsível | nenhuma; otimizar aqui é atenção mal direcionada |
| Alta escala | vários serviços, cada um com 3 SLOs próprios | o número de alarmes cresce linearmente com o número de serviços monitorados | linear, visível na fatura de CloudWatch em dezenas de serviços | compartilhar o mesmo plano de resposta e política de escalonamento entre serviços afins |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Alarmes CloudWatch (métrica e composto) | por alarme-mês, valor fixo pequeno | não é onde se economiza; o custo relevante é humano, não de infraestrutura |
| Metric math dentro do alarme | incluído no custo do próprio alarme, sem taxa adicional visível | calcular a expressão não multiplica o custo pelo número de métricas envolvidas |
| Métrica customizada (contador do OTel via EMF) | por métrica customizada publicada | cardinalidade alta (um rótulo por id de pedido, por exemplo) multiplica séries e custo |
| Notificação SNS | por notificação publicada, valor pequeno | baixo mesmo com dezenas de incidentes por mês; não é o gargalo de custo aqui |
| Incident Manager: incidentes e engajamentos | por incidente e por engajamento | cresce com a frequência de acionamento — se estiver alto, o limiar provavelmente está errado |
O ganho de custo que não está em nenhuma linha da AWS
O valor real deste módulo não aparece na fatura de infraestrutura — aparece no tempo até resposta de um incidente real, que passou de "às vezes seis horas" para "confirmado em até 65 minutos, com escalonamento automático se a primeira pessoa não responder". Esse tempo estava sendo pago em impacto ao cliente e em desgaste de quem finalmente lia o alarme certo.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | alarme acionável ligado a plano de resposta com escalonamento | limiar de burn rate ainda não calibrado com dado histórico real da Cadência | medir semanas de burn rate real antes de confiar cegamente no valor padrão | alta |
| Segurança | ação de alarme restrita, plano de resposta com IAM próprio | contato de plantão provisionado fora deste módulo pode ficar desatualizado sem revisão | revisão trimestral de `engagements`, coberta no L60 | média |
| Confiabilidade | consumo sustentado de orçamento gera resposta garantida por escalonamento | o SLO nunca foi testado sob falha real controlada — é hipótese, não fato validado | chaos engineering com o SLO como critério de sucesso (L57) | alta |
| Eficiência de performance | cálculo de burn rate não adiciona latência à aplicação — vive fora do caminho de requisição | nenhum risco de performance introduzido por este módulo | nenhuma ação necessária | baixa |
| Otimização de custos | custo de configuração, não de infraestrutura ligada | proliferação de SLOs por serviço sem consolidação de plano de resposta | compartilhar plano de resposta entre serviços afins conforme a organização cresce | baixa |
| Sustentabilidade | nenhum recurso computacional novo fica ligado continuamente | nenhum relevante para este módulo | nenhuma ação necessária | baixa |
Evolução em níveis: o que muda quando o número de serviços cresce
A terceira arquitetura não é desenho: é a resposta a QUANDO trocar de desenho de novo. Cada nível resolve um risco e compra outro.
Ninguém sabe que algo quebrou até um cliente reclamar.Um alarme por métrica de recurso, adicionado reativamente após cada incidente.3 SLOs de sintoma, duas janelas por SLO, ação de alarme ligada a escalonamento.Chaos engineering com hipótese escrita, usando o SLO como critério de sucesso do experimento (L57).SLOs diferentes para tiers distintos de cliente (ex.: loja grande vs pequena), com orçamento e escalonamento próprios por segmento.CloudWatch Anomaly Detection treina, sobre os DADOS históricos da própria métrica, um MODELO estatístico da faixa esperada por dia da semana e por horário — não é limiar fixo escrito por alguém, é a série temporal ensinando ao sistema o que é "normal" para cada momento. A pergunta que separa isto de decoração: sem o modelo, alguém precisaria olhar o histórico e reescrever manualmente o limiar de Black Friday e o de madrugada de baixo tráfego, um por um — o modelo faz o mesmo ajuste, para toda hora da semana, sem esse trabalho manual.A ordem não é negociável, e o motivo é concreto
Testar resiliência (nível 4) sobre um SLO nunca calibrado (fim do nível 3) produz experimento que "passa" ou "falha" sem que ninguém saiba se o critério fazia sentido. Segmentar por cliente (nível 5) antes de ter um SLO confiável multiplica a mesma incerteza por N. Cada nível pressupõe que o anterior já é dado confiável, não só configuração aplicada.
Onde IA entra nesta arquitetura, e onde não entra
A pergunta central deste módulo — "este consumo é pico ou tendência?" — já tem resposta determinística e barata: duas janelas com o mesmo limiar. Um modelo não melhora essa decisão; ele adicionaria latência e uma fonte de erro a mais numa cadeia que precisa ser confiável às 3 da manhã.
Há um lugar onde aprendizado de máquina já entra de forma nativa e útil, sem ser hype: `CloudWatch Anomaly Detection`, citado no nível 6 da evolução. Em vez de um limiar fixo, ele aprende a faixa esperada de uma métrica por hora do dia e dia da semana — relevante quando o tráfego tem sazonalidade real, como Black Friday ou madrugada de baixo movimento, situações em que um limiar único erra sistematicamente para um lado ou para o outro.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | ajustar a faixa esperada de tráfego conforme sazonalidade, em vez de um limiar fixo que erra em Black Friday ou madrugada |
| Por que uma regra não bastaria? | uma regra de calendário (limiar diferente por dia da semana) cobre boa parte; a faixa aprendida generaliza melhor para sazonalidade que ninguém documentou explicitamente |
| De onde viriam os dados? | o próprio histórico de métrica do CloudWatch — nenhuma instrumentação nova |
| Qual o risco? | uma faixa que se acostuma com degradação lenta para de alarmar sobre ela; é o mesmo risco de qualquer baseline adaptativo |
| Por que não agora? | a Cadência ainda não tem meses de burn rate real para validar se o limiar fixo de 14,4x está certo — introduzir um segundo mecanismo antes de confiar no primeiro dificulta saber qual dos dois está errado quando algo falhar |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo de linguagem para "ler os 40 alarmes técnicos e resumir o que está acontecendo" trata sintoma de organização ruim com mais uma camada em cima da desorganização, em vez de resolvê-la. O painel de diagnóstico já responde às perguntas certas; o problema nunca foi falta de resumo — foi falta de priorização por impacto ao cliente, e isso os SLOs já resolvem sem modelo nenhum.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta | Quando é aceitável |
|---|---|---|---|---|---|
| Adicionar um alarme técnico a cada incidente novo | é a reação mais barata e parece responsável no momento | a coleção cresce sem limite e sem revisão, e cada item compete pela mesma atenção finita | dezenas de alarmes, poucos lidos com atenção, incidente real emaranhado entre eles | perguntar primeiro se o incidente já é capturado por algum SLO de sintoma existente | diagnóstico específico de causa nova, sem ação de notificação — só no painel |
| Alarme de janela única com evaluation_periods alto "para reduzir ruído" | parece resolver o mesmo problema do composite alarm, com menos peça | ainda mistura detecção e confirmação na mesma janela: ou é lento para picos reais ou sensível a ruído — não as duas coisas bem | atraso desproporcional em incidente real, ou ainda disparando por pico isolado | duas janelas independentes com E lógico entre elas | nunca para alarme que decide acordar gente; aceitável para alarme só de painel |
| Copiar o limiar 14,4x sem calcular o orçamento da própria meta | o número aparece em toda referência sobre o assunto e parece pronto para usar | o limiar só faz sentido combinado ao orçamento derivado da meta REAL do serviço | alarme nunca dispara (meta mais frouxa que a assumida) ou dispara sempre (meta mais rígida) | derivar `slo_orcamento_erro` da meta declarada, e então aplicar o multiplicador | nunca; é sempre cálculo, não valor fixo copiável |
| Manter alarm_actions do alarme técnico apontando para o plantão "só por garantia" | parece mais seguro manter os dois caminhos ativos ao mesmo tempo | reintroduz o ruído que os SLOs existem para eliminar; o alarme técnico volta a competir com o de sintoma pela mesma atenção | plantão recebendo notificação de CPU alta que não afeta cliente nenhum | alarme técnico sem ação de notificação, só disponível no painel de diagnóstico | nunca, uma vez que o SLO cobre o sintoma correspondente |
| Response plan sem validar `engagements` antes de considerar pronto | o Terraform aplica sem erro mesmo com a lista vazia — nada avisa | produz sensação de cobertura sem cobertura real; só se descobre no incidente de verdade | incidente aberto nos registros do Incident Manager, zero notificação enviada | validar engajamento com um alarme de teste antes de dar o módulo por concluído | nunca aceitável em produção |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Alarme composto nunca sai de INSUFFICIENT_DATA | uma das duas métricas brutas (erros ou total) nunca publica dado no período | confira se o ALB realmente recebe tráfego na dimensão configurada | `get-metric-statistics` nas métricas `erros` e `total` isoladamente | ajustar `treat_missing_data`, ou confirmar as dimensões `LoadBalancer`/`TargetGroup` |
| Composto dispara mesmo com as janelas individuais em OK | `alarm_rule` com erro de digitação no nome do alarme referenciado | compare caractere a caractere o nome dentro de `ALARM("...")` com `alarm_name` real | o `alarm_rule` do composto lado a lado com os `alarm_name` das duas janelas | corrigir o nome — o casamento é por string exata, sem tolerância a diferença |
| Burn rate calculado vem negativo ou maior que 1000 | divisão por total igual a zero, ou orçamento calculado errado | verifique se `total` teve volume no período; recalcule `slo_orcamento_erro` à mão | os valores brutos de `erros` e `total` no mesmo período do resultado estranho | `treat_missing_data = "notBreaching"` evita alarme por ausência de tráfego, não conserta a fórmula |
| Incidente abre, e-mail de visibilidade chega, mas ninguém é chamado por telefone | `engagements` aponta para contato sem canal de voz/SMS configurado | confira o método de contato associado à ARN referenciada | configuração do contato no Incident Manager (fora deste módulo) | atualizar o contato para incluir o canal de escalonamento adequado à urgência |
| Alarme de recurso técnico voltou a notificar o plantão depois de uma mudança | alguém reaplicou uma versão antiga do Terraform que ainda tinha `alarm_actions` preenchido | `describe-alarms` no alarme específico, comparado ao código atual | histórico de `terraform apply` e o diff do arquivo do alarme técnico | reforçar revisão de código para mudança em qualquer recurso com `alarm_actions` |
A pergunta que resolve metade destes casos
Antes de mexer em limiar, pergunte: o problema está no CÁLCULO (a expressão de metric math produz o número certo?) ou na ENTREGA (o número certo produz a ação certa)? As duas metades falham de formas visualmente parecidas — "o alarme não fez o que eu esperava" — e pedem investigação em lugares opostos.
Limpeza: o que o destroy não leva
Este laboratório cria pouco recurso que cobra continuamente, mas dois tipos de dado sobrevivem ao terraform destroy porque pertencem ao histórico, não à configuração.
# 1. Derrube o que o Terraform administra.
terraform destroy -auto-approve
# 2. ASSINATURA SNS PENDENTE: se ninguem confirmou o e-mail de visibilidade, a
# assinatura fica "pendente" indefinidamente — nao cobra, mas polui a lista.
aws sns list-subscriptions-by-topic --topic-arn "$(terraform output -raw topico_plantao_arn)" \
2>/dev/null || true
# 3. REGISTROS DE INCIDENTE: o Incident Manager mantem historico dos incidentes
# abertos pelos testes deste laboratorio. O destroy do plano de resposta NAO
# apaga o historico — e nao deveria: e o registro de auditoria de quem foi
# chamado e quando.
aws ssm-incidents list-incident-records --query "incidentRecordSummaries[].arn" --output table
# 4. ENGAJAMENTOS DE TESTE: os engagements gerados pelas provas ficam no historico
# do contato de plantao, mesmo depois de o plano de resposta ser destruido.
aws ssm-contacts list-engagements --query "engagements[].engagementArn" --output table
# 5. ALARMES TECNICOS ORIGINAIS: nao pertencem a este laboratorio — vieram de
# antes. O destroy deste modulo nao deve apagar os 40; confirme que o estado
# do Terraform aqui NAO importou esses recursos por engano.
aws cloudwatch describe-alarms --alarm-name-prefix "${PROJETO}-" \
--query "length(MetricAlarms)" --output text
# 6. Prova final: nada com o prefixo do SLO continua ligado a acao de notificacao.
aws cloudwatch describe-alarms --alarm-name-prefix "${PROJETO}-slo-" \
--query "MetricAlarms[].AlarmActions" --output table| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Alarmes de métrica e compostos | sim | não | cobrança é por alarme-mês enquanto existir; some junto |
| Tópico SNS e assinaturas | sim | não | assinatura pendente não confirmada some com o tópico |
| Plano de resposta do Incident Manager | sim | não | o RECURSO some; o histórico de incidentes que ele abriu não |
| Registros de incidente (histórico) | não | não | é auditoria; existe independente do plano que o originou |
| Contato e rotação de plantão | não — não foram criados por este módulo | não | são recurso organizacional, fora do escopo deste laboratório |
| Os 40 alarmes técnicos originais | não — pertencem a outro módulo | sim, alarme-mês | continuam existindo como painel de diagnóstico, por decisão do desenho |
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Quarenta alarmes corretos e ninguém lê nenhum | 3 SLOs sobre métrica de sintoma | reduz o que compete pela atenção a apenas o que o cliente sente |
| Alarme por pico de 1 minuto vira fadiga | burn rate calculado por metric math | converte erro bruto num número que expressa urgência real, não ocorrência isolada |
| Distinguir ruído de tendência | alarme composto com E lógico entre duas janelas | exige que o sinal persista antes de acordar alguém, sem esconder nenhuma das duas janelas |
| Notificação que ninguém confirma | plano de resposta com escalonamento | garante avanço automático para o próximo contato se o primeiro não responder |
| Perder conhecimento de causa técnica | os 40 alarmes viram painel de diagnóstico | preserva o sinal de causa sem deixar que ele compita pela decisão de "quem acorda" |
| Limiar sem base | 14,4x combinado ao orçamento derivado da meta declarada | um número copiado sem o orçamento certo não significa nada — os dois vêm juntos |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Pico transiente de erro acordando alguém à toa | janela longa exigida em conjunto com a curta | consumo real que por acaso também é breve — ainda vale investigar depois |
| Notificação sem resposta | plano de escalonamento com estágios | contato desatualizado — escalonamento não resolve ARN de contato errada |
| Alarme técnico voltando a notificar por engano | revisão de código no `alarm_actions` | reaplicação de estado antigo do Terraform sem revisão — é processo, não ferramenta |
| Limiar mal calibrado nunca ser percebido | nenhuma proteção automática ainda | é o próprio L57 que valida isso, com experimento controlado |
- A meta do SLO (99,9% em 30 dias) define o orçamento de erro tolerável: 0,1%.
- O ALB e o coletor OTel do L51 publicam erros e total — o sintoma, medido na borda.
- Metric math divide a taxa observada pelo orçamento e produz o burn rate.
- Duas janelas — 5 min e 1 h — calculam o mesmo burn rate em períodos diferentes.
- Um alarme composto exige as duas em ALARM ao mesmo tempo antes de mudar de estado.
- A ação do alarme aponta direto para a ARN do plano de resposta do Incident Manager.
- O plano abre o incidente e engaja o contato de plantão da semana.
- Sem confirmação dentro do prazo do estágio, o escalonamento chama o próximo contato.
- Quem responde consulta o painel dos 40 alarmes técnicos para achar a causa.
- O burn rate voltando a ficar abaixo de 1 nas duas janelas é a prova de mitigação.
Perguntas frequentes
❓ Por que 40 alarmes técnicos corretos ainda produzem um sistema de alerta ruim?
❓ O que é burn rate, em uma frase que eu consiga usar numa reunião?
❓ Por que preciso de duas janelas de tempo para o mesmo alarme?
❓ Os 40 alarmes técnicos antigos devem ser apagados depois deste laboratório?
❓ Por que a ação do alarme vai direto ao Incident Manager, sem Lambda no meio?
❓ Qual a diferença entre o tópico SNS e o plano de resposta do Incident Manager?
❓ Como escolher a meta de um SLO — por que 99,9% e não 99,99%?
❓ Um SLO substitui todos os outros alarmes de um serviço?
Fixando
Um alarme composto de burn rate exige `ALARM(janela_5min) AND ALARM(janela_1h)`. Durante um pico de erro de 2 minutos causado por um deploy, a janela de 5 min entra em ALARM. O que acontece com o alarme composto?
Depois de migrar para SLOs com burn rate, um engenheiro sugere apagar os 40 alarmes técnicos de CPU, memória e fila, já que "os SLOs cobrem tudo agora". Por que essa sugestão é um erro?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L51 no ar (métrica de sintoma correlacionada por trace), noção de alarme de métrica única (L08) |
| Conhecimentos adquiridos | SLI/SLO/error budget e a derivação do orçamento a partir da meta; burn rate e por que ele exige duas janelas; composite alarms com `ALARM()`; ação de alarme nativa para Incident Manager; a distinção entre alarme de causa e de sintoma |
| Limitação que fica | o limiar de 14,4x veio da prática consolidada da indústria, não de calibração sobre o tráfego real da Cadência — precisa de semanas de dado histórico antes de ser tratado como definitivo |
| Próximo exemplo recomendado | L57 — chaos engineering, derrubando uma AZ de propósito e usando o SLO deste módulo como critério de sucesso do experimento |
| Também habilitado por este módulo | L53 (dashboard que responde pergunta de operação) usa a mesma distinção entre métrica técnica e de negócio; L60 (revisão Well-Architected) cobra que o alarme acionável já exista |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação e referências consultadas: Using math expressions with CloudWatch metrics — sintaxe de `metric_query`, expressões e a função `IF`, base da divisão que calcula burn rate; AWS::CloudWatch::CompositeAlarm e o recurso `aws_cloudwatch_composite_alarm` do provedor Terraform — o `alarm_rule` com `ALARM()` combinando duas condições; Creating an escalation plan for responder engagement in Incident Manager — estágios, duração e a mecânica de escalonamento sem intervenção humana; o anúncio de burn rate do CloudWatch Application Signals, usado para confirmar que o método de múltiplas janelas popularizado pelo Google SRE Workbook tem equivalente nativo na AWS; e o AWS Cloud Operations Blog, que documenta a ação nativa do CloudWatch para iniciar um incidente do Incident Manager diretamente a partir de um alarme. Os valores de preço não aparecem neste módulo por decisão: use o AWS Pricing Calculator.
O que não foi verificado, e você deve conferir antes de aplicar
O esquema completo de `aws_ssmincidents_response_plan` (campos como `chat_channel` e a escala exata de `impact`) e dos recursos `aws_ssmcontacts_*` não pôde ser lido por completo durante a escrita — a documentação do Terraform Registry não carregou o conteúdo integral nas consultas realizadas. O código usa só os campos confirmados por busca. Confirme o esquema atual do provedor antes de aplicar em produção, especialmente a escala de `impact` e os campos de `engagements`. O limiar de burn rate de 14,4x é o valor de referência da prática de SRE consolidada pela indústria — não foi calibrado com dado histórico real da Cadência, e o módulo diz isso explicitamente em mais de um lugar de propósito.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…