Lab 53 — Dashboard que responde pergunta de operação
O problema, e a empresa que o tem
A Cadência tem, desde o L51, telemetria correlacionada: log, métrica e trace de Pedidos e Faturamento compartilham o mesmo trace_id. E tem, desde o L52, um plano de resposta que acorda alguém só quando o orçamento de erro de um SLO real está sendo consumido rápido demais. As duas peças funcionam. Nenhuma delas responde "o sistema está bem?" em uma tela.
Numa terça-feira, o alarme de burn rate do L52 não disparou — a taxa de erro HTTP estava normal. O que parou foi o consumidor da fila de pedidos do L22: uma alteração de permissão no papel de execução da função derrubou silenciosamente o processamento, e a fila cresceu por duas horas. Durante esse tempo, o ALB continuou respondendo 200 para todo POST de pedido — a API aceitava a requisição, gravava a mensagem na fila e devolvia sucesso. O cliente via confirmação na tela. O pedido nunca virava cobrança.
Quem descobriu foi o time comercial, comparando pedidos recebidos com cobranças do dia. Todo painel de infraestrutura estava verde o tempo inteiro: CPU normal, memória normal, 5xx em zero. Nenhum deles perguntava se o EFEITO que o cliente queria — pedido confirmado, cobrança processada — estava de fato acontecendo. Esse é o problema que este laboratório ataca: não falta de métrica, falta de métrica que responda a pergunta certa.
O que este laboratório NÃO é
Não decide quem é acordado — isso é o L52, e o tópico SNS dele continua sendo o único caminho garantido de notificação. Este painel é para quem JÁ está olhando: depois de um alarme, ou por rotina. Também não é FinOps: a pergunta de custo aqui é "algo mudou de forma anormal", respondida com um número; medir e agir sobre a fatura com rigor é o L59, que depende deste laboratório para o sinal de custo já existir.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando na seção de implantação, não com a sensação de ter entendido.
- Nomear as 6 perguntas que o painel tem de responder, antes de abrir o console.
- Diferenciar métrica técnica (a porta está aberta) de métrica de negócio (o efeito aconteceu).
- Publicar uma métrica de negócio via EMF, sem SDK de métrica nem chamada de API nova.
- Explicar por que essa métrica desaparece em silêncio se publicada pelo logger estruturado errado.
- Escrever e salvar uma consulta de Logs Insights que responda qual componente está falhando.
- Construir o dashboard por Terraform, com layout justificado por urgência, não por ordem de criação.
- Ler a métrica de billing da conta e explicar por que ela não é dado em tempo real.
- Provar, com número, que o painel teria flagrado um incidente que a arquitetura mínima não via.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Métrica técnica vs. métrica de negócio | SOA-C02 | EMF publicando PedidoConfirmado e CobrancaProcessada ao lado de 5xx e latência | reconhecer, num cenário, qual pergunta um painel "tudo verde" não está respondendo |
| CloudWatch Logs Insights, sintaxe de consulta | SOA-C02, DVA-C02 | `filter` / `stats ... by` / `sort` numa consulta salva | ler uma consulta e prever a forma da saída, inclusive agrupamento |
| Embedded Metric Format (EMF) | SOA-C02, DVA-C02 | métrica extraída de log estruturado, sem `PutMetricData` | saber quando EMF substitui a API de métrica, e o custo por combinação de dimensão |
| Restrição regional de métrica de billing | CLF-C02, SOA-C02 | widget de custo fixo em `us-east-1`, mesmo com o resto em outra região | lembrar que `AWS/Billing` só existe nessa região, e que é preciso habilitar antes |
| Estrutura de um CloudWatch Dashboard | SOA-C02 | `widgets[]` com `x`/`y`/`width`/`height` num grid de 24 unidades | interpretar um corpo de dashboard dado numa questão e prever o layout |
| Menor privilégio para observabilidade | SAA-C03, SOA-C02 | `logs:StartQuery` escopado por grupo de log, não `logs:*` | separar quem PUBLICA log de quem CONSULTA log de quem CONFIGURA o painel |
| Custo de Logs Insights por dado escaneado | CLF-C02 | consulta salva com período padrão curto e grupo de log específico | prever que uma consulta ampla, sem filtro de tempo, escala custo com volume de log |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve um sistema com taxa de erro HTTP em zero e cliente reclamando de efeito que não aconteceu, e pede qual métrica falta. A resposta não é "mais métrica de infraestrutura" — é métrica de negócio, porque o sintoma descrito é exatamente a lacuna entre "a porta aceitou" e "o efeito ocorreu".
Requisitos, e como cada um muda o desenho
Requisito não funcional que não aparece numa linha de configuração é intenção. A coluna da direita é onde cada um deixou marca.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Responder as 6 perguntas nomeadas em uma tela | obrigatório | um widget por pergunta — nunca um widget por métrica que já estava disponível |
| Distinguir infraestrutura de negócio | obrigatório, é a causa raiz do incidente | métrica de negócio via EMF, em namespace e dimensão próprios, ao lado da técnica |
| Apontar QUAL componente, não só QUE algo quebrou | obrigatório | grupo de destino por serviço mais consulta salva de Logs Insights agrupando por serviço |
| Sinal de fila antes do efeito virar reclamação | minutos de antecedência | métrica gerenciada da fila do L22 entra no painel, mesmo sem código de app novo |
| Custo visível sem esperar a fatura fechar | aceita atraso de horas | widget de billing fixo em `us-east-1`, com o atraso do dado declarado no título |
| Layout por urgência | pergunta mais urgente maior e no topo | disponibilidade e latência ocupam `y=0`; custo ocupa a linha de baixo, mais estreito |
| Não duplicar o L52 | o painel não decide quem é acordado | nenhum widget aqui vira alarme; o tópico SNS e o disparo continuam só no L52 |
| Custo do próprio painel sob controle | consulta não pode escalar sem limite | consulta salva com filtro de tempo padrão curto e grupo de log nomeado, nunca `SOURCE *` |
Arquitetura mínima: o painel que cresceu por acidente
Este é o painel que a Cadência tem hoje, e ele é legítimo como ponto de partida: mostra dado real, sem custo extra sobre o que o L01/L51 já publicam. O laboratório começa por nomear a lacuna dele — porque uma pergunta sem resposta é mais concreta do que "o painel podia ser melhor".
- → CPUUtilization, MemoryUtilization
- → CPUUtilization, MemoryUtilization
- → RequestCount, latência média
- → widget adicionado após cada incidente
- → catorze gráficos, nenhuma pergunta nomeada
- Compute
- Rede e entrega
- Gestão e governança
- Fora da AWS
Este dashboard existe de verdade e cada widget nele está correto — mede o que diz medir. O defeito não é nenhum gráfico errado: é a ausência de dois sinais que nunca foram adicionados porque nenhum incidente os pediu ainda. Percorra os passos e repare que o engenheiro de plantão só descobre a lacuna no meio do incidente, não antes dele.
- Cada widget nasceu de um incidente, não de uma pergunta. O gráfico de CPU entrou depois de um OOM; o de memória, depois de outro; a contagem de requisição, porque "parecia útil" numa reunião. Nenhum deles responde a uma pergunta escrita antes — respondem ao último susto.
- Os dois serviços despejam métrica técnica no mesmo balde. Pedidos e Faturamento publicam CPU e memória com o mesmo formato, sem nada que separe "isto é sobre qual serviço" de forma imediata no painel — o engenheiro precisa ler o título de cada gráfico para saber.
- Nada aqui pergunta se o negócio aconteceu. Não existe métrica de "pedido confirmado" nem "cobrança processada". O painel mede se o processo está de pé, nunca se o efeito que o cliente queria realmente ocorreu.
- A fila de pedidos (L22) está fora do desenho. O consumidor que transforma mensagem em pedido gravado pode parar por completo, e nenhum widget deste painel se move — porque nenhum deles olha para a fila.
- Não há Logs Insights: "qual componente" não tem resposta pronta. Quando os dois serviços erram ao mesmo tempo, descobrir qual causou o quê exige abrir cada grupo de log à mão e ler linha por linha — o painel não tem consulta salva nenhuma.
- O plantão gasta os primeiros minutos decidindo por onde olhar. Catorze linhas verdes e amarelas, sem prioridade visual, sem indicação de qual pergunta cada uma responde. O tempo perdido aqui não é de investigação — é de orientação, e acontece antes de qualquer diagnóstico começar.
O incidente que este desenho não via, e não é hipotético
Durante as duas horas em que o consumidor da fila do L22 ficou parado, este painel inteiro permaneceu verde: CPU normal nos dois serviços, memória normal, 5xx do ALB em zero. A API continuava aceitando e confirmando pedido — só que "confirmar" significava apenas "a requisição HTTP terminou com sucesso", não "o pedido foi processado". Nenhuma métrica aqui mede a segunda coisa.
Arquitetura para produção
Cada fonte nova abaixo rastreia a uma linha da tabela de requisitos. Se você não conseguir apontar o requisito, a fonte é adorno — e este desenho não tem nenhuma.
- → health check + tráfego real
- → health check + tráfego real
- → roteia por padrão de caminho
- → roteia por padrão de caminho
- → HTTPCode_Target_5XX_Count, TargetResponseTime
- → HTTPCode_Target_5XX_Count, TargetResponseTime
- → ApproximateNumberOfMessagesVisible, idade da mais antiga
- → EMF: PedidoConfirmado, dimensão Servico
- → EMF: CobrancaProcessada, dimensão Servico
- → log estruturado com trace_id, nível e Servico (L51)
- → log estruturado com trace_id, nível e Servico (L51)
- → um widget de métrica por pergunta
- → uma consulta salva por pergunta
- → resposta às 6 perguntas em uma tela
- Compute
- Rede e entrega
- Integração de apps
- Gestão e governança
- Fora da AWS
A estrutura deixa de ser "tudo que o SDK publica por padrão" e passa a ser "o que cada pergunta nomeada precisa para ter resposta". O que muda no desenho é a PLURALIDADE das fontes — grupo de destino por serviço, fila, métrica de negócio via EMF, métrica de billing e Logs Insights — convergindo por trás de um painel com layout deliberado. Percorra os passos: cada fonte nova rastreia a uma das seis perguntas da seção de requisitos.
- A métrica já nasce rotulada por serviço. Com um grupo de destino por serviço, o 5xx do alvo já separa "Pedidos está errando" de "Faturamento está errando" antes mesmo de chegar ao painel — responde "está disponível" e "qual componente" com a mesma métrica.
- A fila entra como sinal cedo, não como detalhe de implementação. Profundidade crescente na fila do L22 aparece minutos antes de o efeito virar reclamação de cliente — é o sinal que a arquitetura mínima nunca olhava, porque a fila estava fora do desenho dela.
- EMF publica negócio sem chamada de API nova. A mesma linha de log estruturado que o L51 já gravava ganha um bloco `_aws` e vira métrica de CloudWatch automaticamente — nenhum SDK novo, nenhuma credencial nova, só o formato do payload.
- Onde métrica pré-agregada não alcança, entra Logs Insights. "Qual componente está falhando" quando os dois erram junto não se resolve com um número só — precisa do log correlacionado do L51, filtrado por nível e agrupado por serviço, numa consulta salva.
- O painel organiza por pergunta, não por fonte. Um widget de métrica e uma consulta de log convivem lado a lado quando a mesma pergunta pede os dois — é o oposto de agrupar por "gráficos de ECS" e "gráficos de ALB", que é como o painel mínimo cresceu.
- O layout segue urgência, não ordem de criação. Disponibilidade ocupa o canto superior esquerdo, maior. Custo — a pergunta menos urgente às três da manhã — fica embaixo, pequena. Ninguém precisa rolar a tela para saber se o sistema está de pé.
- O engenheiro sai com resposta, não com trabalho. É a diferença que a arquitetura mínima nunca ofereceu: não é mais gráfico, é a pergunta certa já respondida — e quando não está, o painel aponta qual das seis abrir primeiro.
A diferença estrutural em relação ao desenho anterior não é "mais um gráfico": é a pluralidade de fontes — grupo de destino por serviço, fila, EMF de negócio, billing e Logs Insights — cada uma alimentando exatamente uma das seis perguntas, e nenhuma sobrando.
O widget que muda o resultado do incidente do callout acima
Com `Cadencia/Negocio.PedidoConfirmado` e `CobrancaProcessada` no painel, a mesma janela de duas horas mostraria disponibilidade em 100% e volume de negócio caindo para perto de zero ao mesmo tempo — o padrão exato de "a porta está aberta e nada está passando por ela". É a leitura que separa este desenho do anterior.
De uma linha de log até uma pergunta respondida
A métrica de negócio não é publicada por uma chamada de API dedicada — ela nasce como uma linha de log em formato EMF (Embedded Metric Format), e o próprio serviço de ingestão de log do CloudWatch extrai o valor e o publica como métrica, na mesma passada que grava a linha.
O payload abaixo é a linha exata que NegocioMetricas.CobrancaProcessada() escreve em Console.Out. Repare que o bloco _aws está na RAIZ do objeto, sem nenhum campo de outro logger em volta — é o requisito que a seção de construção detalha.
A linha exata que sai para o log group /ecs/ffv-lab-faturamento. O CloudWatch reconhece o bloco "_aws" e publica CobrancaProcessada=1 no namespace Cadencia/Negocio, dimensao Servico=Faturamento — sem nenhuma chamada de API.
{
"_aws": {
"Timestamp": 1754640000000,
"CloudWatchMetrics": [
{
"Namespace": "Cadencia/Negocio",
"Dimensions": [["Servico"]],
"Metrics": [{ "Name": "CobrancaProcessada", "Unit": "Count" }]
}
]
},
"Servico": "Faturamento",
"CobrancaProcessada": 1
}Por que o valor é sempre 1, e não um contador acumulado
Cada linha representa UM evento de negócio; o CloudWatch é quem soma. Publicar `1` a cada cobrança e deixar a estatística `Sum` do widget agregar por período é mais simples e mais correto sob concorrência do que a aplicação manter um contador em memória — que se perde a cada reinício de task e conta errado com múltiplas tasks rodando ao mesmo tempo.
As decisões, e o que se perde em cada uma
📋 A Cadência tem telemetria correlacionada (L51) e alarme que acorda alguém com orçamento de erro (L52) — mas nenhum lugar único responde "o sistema está bem" em uma tela, e a equipe de seis pessoas não tem gente sobrando para manter um BI paralelo.
O dashboard não pede outro sistema para operar: mora no mesmo serviço que já recebe os logs e métricas do L51, é versionado como o resto da infraestrutura e não exige login separado nem pipeline de dado próprio. Ele resolve o problema declarado — uma tela que responde seis perguntas nomeadas — sem introduzir a superfície operacional de uma ferramenta de BI. O custo é de dashboard e de dado escaneado por consulta, não de licença por usuário.
Alt: Amazon QuickSight — É uma ferramenta de BI, feita para exploração analítica e relatório para negócio — não para decisão em segundos durante um incidente. Pedir a ela que seja o painel de operação é usar régua de arquiteto para medir febre.
Alt: Amazon Managed Grafana — Painel mais rico e fontes de dado plugáveis, e é a escolha certa quando o time já tem múltiplas fontes fora da AWS. Aqui adicionaria um segundo sistema de login e de versionamento para um problema que o CloudWatch já resolve com o que a conta já paga.
Alt: Ferramenta de observabilidade de terceiro (SaaS) — Cobre os seis pilares e mais, com preço por host ou por evento ingerido — e exige exportar dado da conta para fora dela. Fica fora do escopo deste laboratório, que é sobre o que a própria AWS já oferece.
Alt: Manter o painel ad hoc (arquitetura mínima) — Não custa nada além do que já existe, e é exatamente por isso que ele nunca é revisado — cresce por acidente. Continua legítimo em ambiente de desenvolvimento, onde ninguém é acordado às 3h por ele.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Publicação de métrica de negócio | EMF via log estruturado | `PutMetricData` direto; biblioteca de terceiro para EMF | reaproveita o pipeline de log que o L51 já validou; sem SDK novo nem credencial nova | a linha precisa nascer fora do logger estruturado da aplicação, o que exige disciplina de código |
| Diagnóstico de "qual componente" | grupo de destino por serviço + Logs Insights | um único grupo de destino para os dois serviços; ferramenta de APM de terceiro | a métrica pré-agregada já responde a maioria dos casos; Logs Insights cobre o resto sem novo sistema | consulta de Logs Insights cobra por dado escaneado — não é gratuita como o widget de métrica |
| Sinal de custo | métrica `AWS/Billing`, região fixa `us-east-1` | AWS Budgets; Cost Explorer embutido | já é CloudWatch, não pede outro serviço nem outra permissão além de habilitar uma vez | não é tempo real — atualiza poucas vezes ao dia, e o painel precisa dizer isso |
| Onde mora o painel | CloudWatch Dashboard versionado em Terraform | Grafana gerenciado; QuickSight; ferramenta de terceiro | nenhum sistema novo para o time de seis pessoas operar, e o dado já está na mesma conta | menos recurso visual que uma ferramenta de BI dedicada — não é o objetivo deste painel |
| Dimensão da métrica de negócio | só `Servico` | incluir `ClienteId` ou `PedidoId` como dimensão | uma dimensão de baixa cardinalidade mantém o número de métricas customizadas previsível | não dá para filtrar o painel por cliente específico sem ir ao Logs Insights |
O que este desenho não resolve, e onde isso mora
O painel responde às seis perguntas; ele não decide se alguém é acordado por causa delas. Um valor de `CobrancaProcessada` caindo a zero não dispara nada sozinho — transformar essa leitura em alarme acionável, com orçamento de erro e escalonamento, é exatamente o que o L52 já construiu para outras métricas, e é o próximo passo natural depois deste laboratório: dar ao burn rate uma métrica de negócio para vigiar.
Construir: a métrica de negócio, sem SDK de métrica
O arquivo abaixo é o ponto mais fino do laboratório. Ele não usa nenhum cliente do CloudWatch — só escreve uma linha de texto no lugar certo, no formato certo.
// NegocioMetricas.cs — metrica de negocio publicada sem SDK de metrica nenhum
//
// A ARMADILHA deste arquivo, e o motivo de ele nao usar o mesmo logger
// estruturado (Serilog) que o resto da aplicacao: o CloudWatch exige que a
// linha de log SEJA o objeto JSON do EMF, sem nada antes nem depois. Se voce
// mandar este payload pelo Serilog, ele embrulha a string dentro do PROPRIO
// envelope JSON dele (timestamp, level, message...) — e o "_aws" deixa de
// estar na RAIZ do evento. A extracao falha em silencio: nenhum erro, nenhum
// log de aviso, so a metrica que nunca aparece no CloudWatch. A correcao e
// escrever direto em Console.Out, fora do pipeline de log da aplicacao.
using System.Text.Json;
namespace Cadencia.Observabilidade;
public static class NegocioMetricas
{
// Um metodo por evento de negocio — nao um metodo generico "PublicarMetrica" —
// porque o NOME da metrica e o nome do efeito, nao um parametro livre.
// Livre demais e como "log.Info(algumaCoisa)": tecnicamente funciona,
// pedagogicamente nao ensina nada sobre o que aconteceu.
public static void PedidoConfirmado(string servico = "Pedidos") =>
Emitir(nome: "PedidoConfirmado", servico);
public static void CobrancaProcessada(string servico = "Faturamento") =>
Emitir(nome: "CobrancaProcessada", servico);
private static void Emitir(string nome, string servico)
{
// "Dimensions" e uma lista de LISTAS: cada lista interna e um
// conjunto de dimensao que gera uma metrica propria. Uma dimensao so
// (Servico) e suficiente aqui — NUNCA adicione PedidoId ou CobrancaId
// como dimensao: cada valor unico vira uma metrica de CloudWatch
// nova, e customer-id-como-dimensao e a causa mais comum de fatura
// de metrica customizada que ninguem sabe explicar.
var payload = new Dictionary<string, object>
{
["_aws"] = new Dictionary<string, object>
{
["Timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
["CloudWatchMetrics"] = new object[]
{
new Dictionary<string, object>
{
["Namespace"] = "Cadencia/Negocio",
["Dimensions"] = new object[] { new[] { "Servico" } },
["Metrics"] = new object[]
{
new Dictionary<string, object> { ["Name"] = nome, ["Unit"] = "Count" },
},
},
},
},
["Servico"] = servico,
[nome] = 1,
};
// Console.Out, nao ILogger: esta linha PRECISA chegar ao driver
// awslogs do conteiner exatamente como este JSON, sem prefixo de
// nivel nem timestamp de outro formato na frente.
Console.Out.WriteLine(JsonSerializer.Serialize(payload));
}
}
// Ponto de chamada, dentro do endpoint que ja existe desde o L01/L51 — uma
// linha, no lugar exato onde o efeito de negocio se torna verdade:
//
// await db.Pedidos.AddAsync(pedido);
// await db.SaveChangesAsync();
// NegocioMetricas.PedidoConfirmado(); // <- aqui, depois do commit, nao antes
//
// Antes do SaveChangesAsync a metrica mentiria sobre um pedido que pode nao
// ter sido persistido. E o mesmo raciocinio do health check de prontidao do
// L03: o sinal so vale alguma coisa se disser a verdade sobre o que JA aconteceu.
Por que isto falha em silêncio, e não com um erro
Se `NegocioMetricas.Emitir` usasse o `ILogger` estruturado da aplicação (Serilog, herdado do L51) em vez de `Console.Out` diretamente, o Serilog embrulharia o payload EMF dentro do PRÓPRIO envelope JSON — timestamp, nível e a mensagem original como uma STRING dentro de um campo, não como objeto na raiz. A especificação do EMF exige que `_aws` seja membro de nível raiz do evento de log; embrulhado, ele deixa de ser. O CloudWatch não gera erro nenhum: ele simplesmente não reconhece o padrão e não extrai métrica nenhuma. O sintoma é "a métrica nunca aparece", sem nenhuma pista de por quê.
Construir: o dashboard e as consultas salvas
Seis widgets, cada um citado por número na propriedade `title` — não por elegância, por rastreabilidade: quem olha o painel sabe exatamente qual das seis perguntas está vendo, sem precisar abrir este arquivo.
# dashboard.tf — um widget por pergunta, layout por urgência
# O corpo do dashboard e uma string JSON. O grid tem 24 unidades de largura —
# e e por isso que os dois widgets do topo, com width = 12 cada, ocupam a
# largura inteira lado a lado: a pergunta mais urgente nao compete por espaco
# com nenhuma outra.
resource "aws_cloudwatch_dashboard" "operacao" {
dashboard_name = "${var.projeto}-6-perguntas"
dashboard_body = jsonencode({
widgets = [
# ── Pergunta 1, y=0: o sistema esta disponivel? ─────────────────────
# Topo, esquerda, largura total dividida com a pergunta 2 — as duas
# mais urgentes as 3h da manha.
{
type = "metric", x = 0, y = 0, width = 12, height = 6
properties = {
title = "1. Esta disponivel? (5xx do alvo, por servico)"
view = "timeSeries"
region = var.regiao
period = 60
stat = "Sum"
metrics = [
["AWS/ApplicationELB", "HTTPCode_Target_5XX_Count", "TargetGroup", aws_lb_target_group.pedidos.arn_suffix, "LoadBalancer", aws_lb.principal.arn_suffix, { label = "Pedidos" }],
["AWS/ApplicationELB", "HTTPCode_Target_5XX_Count", "TargetGroup", aws_lb_target_group.faturamento.arn_suffix, "LoadBalancer", aws_lb.principal.arn_suffix, { label = "Faturamento" }],
]
}
},
# ── Pergunta 2, y=0: esta lento? ──────────────────────────────────
{
type = "metric", x = 12, y = 0, width = 12, height = 6
properties = {
title = "2. Esta lento? (p99 de resposta do alvo, por servico)"
view = "timeSeries"
region = var.regiao
period = 60
stat = "p99"
metrics = [
["AWS/ApplicationELB", "TargetResponseTime", "TargetGroup", aws_lb_target_group.pedidos.arn_suffix, "LoadBalancer", aws_lb.principal.arn_suffix, { label = "Pedidos" }],
["AWS/ApplicationELB", "TargetResponseTime", "TargetGroup", aws_lb_target_group.faturamento.arn_suffix, "LoadBalancer", aws_lb.principal.arn_suffix, { label = "Faturamento" }],
]
}
},
# ── Pergunta 3, y=6: ha mensagens acumuladas? ─────────────────────
# Metrica gerenciada pela AWS sobre a fila do L22 — nenhum codigo novo.
{
type = "metric", x = 0, y = 6, width = 8, height = 6
properties = {
title = "3. Ha mensagens acumuladas? (fila de pedidos, L22)"
view = "timeSeries"
region = var.regiao
period = 60
stat = "Maximum"
metrics = [
["AWS/SQS", "ApproximateNumberOfMessagesVisible", "QueueName", aws_sqs_queue.pedidos.name],
["AWS/SQS", "ApproximateAgeOfOldestMessage", "QueueName", aws_sqs_queue.pedidos.name, { yAxis = "right" }],
]
}
},
# ── Pergunta 4, y=6: quantas operacoes de negocio foram concluidas? ─
# Metrica de EMF: o mesmo namespace onde o app publica PedidoConfirmado
# e CobrancaProcessada — ver NegocioMetricas.cs.
{
type = "metric", x = 8, y = 6, width = 8, height = 6
properties = {
title = "4. Quantas operacoes de negocio concluiram? (EMF)"
view = "timeSeries"
region = var.regiao
period = 300
stat = "Sum"
metrics = [
["Cadencia/Negocio", "PedidoConfirmado", "Servico", "Pedidos"],
["Cadencia/Negocio", "CobrancaProcessada", "Servico", "Faturamento"],
]
}
},
# ── Pergunta 5, y=6: houve aumento inesperado de custo? ────────────
# AWS/Billing SO existe na regiao us-east-1, MESMO que este dashboard
# e todo o resto da infraestrutura estejam em outra regiao — por isso
# o "region" deste widget e fixo, nao var.regiao. E a unica pergunta
# cuja resposta nao e proxima de tempo real: atualiza poucas vezes ao dia.
{
type = "metric", x = 16, y = 6, width = 8, height = 6
properties = {
title = "5. Custo inesperado? (atualiza poucas vezes/dia, nao e tempo real)"
view = "timeSeries"
region = "us-east-1"
period = 21600 # 6 h — o mesmo periodo recomendado para alarme de billing
stat = "Maximum"
metrics = [
["AWS/Billing", "EstimatedCharges", "Currency", "USD"],
]
}
},
# ── Pergunta 6, y=12: qual componente esta falhando? ───────────────
# Onde metrica pre-agregada nao alcanca: widget do tipo "log", que
# executa uma consulta salva de Logs Insights sobre o log correlacionado
# do L51. E o unico widget que NAO e "metric" neste dashboard.
{
type = "log", x = 0, y = 12, width = 24, height = 8
properties = {
title = "6. Qual componente esta falhando? (Logs Insights, log correlacionado do L51)"
region = var.regiao
view = "table"
query = "SOURCE '/ecs/${var.projeto}-pedidos' | SOURCE '/ecs/${var.projeto}-faturamento'\n| filter nivel = \"ERROR\"\n| stats count(*) as erros by servico\n| sort erros desc"
}
},
]
})
}
# A consulta acima tambem existe SALVA — para nao depender de ninguem lembrar
# a sintaxe as 3h. Aparece no console em "Consultas salvas", com o mesmo texto.
resource "aws_cloudwatch_query_definition" "qual_componente" {
name = "${var.projeto}/6-perguntas/qual-componente-esta-falhando"
log_group_names = [
aws_cloudwatch_log_group.pedidos.name,
aws_cloudwatch_log_group.faturamento.name,
]
query_string = <<-QUERY
filter nivel = "ERROR"
| stats count(*) as erros by servico
| sort erros desc
QUERY
}
# Segunda consulta salva: reconstroi a jornada de UM trace especifico quando o
# widget 6 apontou um servico, mas nao qual requisicao. Parametrizada por
# trace_id, informado na hora de rodar — nao um valor fixo no Terraform.
resource "aws_cloudwatch_query_definition" "jornada_por_trace" {
name = "${var.projeto}/6-perguntas/jornada-de-um-trace"
log_group_names = [
aws_cloudwatch_log_group.pedidos.name,
aws_cloudwatch_log_group.faturamento.name,
]
query_string = <<-QUERY
filter trace_id = "$traceId"
| sort @timestamp asc
| fields @timestamp, servico, nivel, mensagem
QUERY
}
O grid tem 24 unidades de largura, sempre
É por isso que os dois widgets do topo somam `width = 12` cada um: ocupam a largura inteira, lado a lado, sem competir por espaço com nada mais abaixo. Um dashboard com widget de `width = 24` ocupa a tela inteira sozinho — reserve isso para o único widget que não pode dividir atenção, que aqui é o de Logs Insights no rodapé.
| Widget | Tipo | Fonte | Por que esta posição |
|---|---|---|---|
| 1 · Disponível? | metric | AWS/ApplicationELB por grupo de destino | topo-esquerda, a pergunta mais urgente às 3h |
| 2 · Lento? | metric | AWS/ApplicationELB por grupo de destino | topo-direita, mesma urgência da 1, layout lado a lado |
| 3 · Mensagem acumulada? | metric | AWS/SQS, fila do L22 | segunda linha: sintoma cedo, mas ainda não é o efeito final |
| 4 · Negócio concluído? | metric | Cadencia/Negocio, EMF | ao lado da 3: é a leitura que junto com ela expõe o incidente do callout |
| 5 · Custo anormal? | metric | AWS/Billing, região fixa | segunda linha, mais estreito: menos urgente às 3h, mas visível sem rolar |
| 6 · Qual componente? | log | Logs Insights sobre log correlacionado do L51 | rodapé, largura total: é a investigação, não o placar |
Implantar, e provar que o painel responde
Cinco provas. Nenhuma aceita "o painel parece certo" como resultado — cada uma tem um número, e a quarta é a que reproduz o incidente descrito na abertura.
# provas.sh — cinco medições; nenhuma conclusão vem de "o painel parece certo"
PROJETO=ffv-lab; REGIAO=us-east-1
# ── Prova 1: o dashboard existe e tem os seis widgets ────────────────────────
aws cloudwatch get-dashboard --dashboard-name "${PROJETO}-6-perguntas" \
--query 'DashboardBody' --output text | jq '.widgets | length'
# Esperado: 6. Se vier menos, algum widget falhou silenciosamente no apply —
# jsonencode() nao valida o SCHEMA do dashboard, so a sintaxe JSON.
# ── Prova 2: a consulta "qual componente" devolve número, não suposição ──────
QUERY_ID=$(aws logs start-query \
--log-group-names "/ecs/${PROJETO}-pedidos" "/ecs/${PROJETO}-faturamento" \
--start-time "$(date -u -d '15 minutes ago' +%s)" --end-time "$(date -u +%s)" \
--query-string 'filter nivel = "ERROR" | stats count(*) as erros by servico | sort erros desc' \
--query 'queryId' --output text)
sleep 5
aws logs get-query-results --query-id "$QUERY_ID" --query 'results' --output table
# Esperado: uma linha por serviço com erro, ordenada. Tabela vazia significa
# "sem erro nos últimos 15 min" — resultado legítimo, não falha da prova.
# ── Prova 3: a métrica de negócio tem dado, e não é zero por engano ──────────
aws cloudwatch get-metric-statistics --namespace "Cadencia/Negocio" \
--metric-name PedidoConfirmado --dimensions Name=Servico,Value=Pedidos \
--start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%S)" --period 300 --statistics Sum \
--query 'Datapoints[].Sum' --output text
# Esperado: soma > 0 se houve pedido na última hora. Zero persistente com
# tráfego real na aplicação indica o erro de pipeline descrito no callout
# sobre o Serilog — confira se a linha chegou ao log group como JSON puro.
# ── Prova 4: o sinal de fila se move quando o consumidor para ────────────────
# Publica 20 mensagens de teste SEM rodar o consumidor (pare a função do L22
# antes, e suba de novo depois).
for i in $(seq 1 20); do
aws sqs send-message --queue-url "$(terraform output -raw fila_pedidos_url)" \
--message-body "{\"teste\": $i}" >/dev/null
done
sleep 30
aws sqs get-queue-attributes --queue-url "$(terraform output -raw fila_pedidos_url)" \
--attribute-names ApproximateNumberOfMessagesVisible ApproximateAgeOfOldestMessage \
--query 'Attributes'
# Esperado: ApproximateNumberOfMessagesVisible perto de 20 e a idade da mais
# antiga subindo — é exatamente o sintoma que a arquitetura mínima não via.
# ── Prova 5: a métrica de billing existe, na região certa ────────────────────
aws cloudwatch list-metrics --namespace "AWS/Billing" --region us-east-1 \
--metric-name EstimatedCharges --query 'Metrics[].Dimensions' --output table
# Esperado: ao menos uma linha, com dimensão Currency=USD. Lista vazia quase
# sempre significa que "Receive Billing Alerts" não foi habilitado na conta
# — é passo de console, e leva cerca de 15 min para aparecer depois de habilitado.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · O dashboard tem os 6 widgets | `get-dashboard` + contagem | exatamente 6 | menos que isso indica falha silenciosa de `jsonencode()` — ele valida sintaxe, não schema |
| 2 · "Qual componente" devolve número | `start-query` + `get-query-results` | uma linha por serviço com erro, ordenada | tabela vazia é resultado válido; erro na chamada indica nome de grupo de log errado |
| 3 · A métrica de negócio tem dado | `get-metric-statistics` em Cadencia/Negocio | soma > 0 com tráfego real na última hora | zero persistente com tráfego real aponta o erro do Serilog descrito no callout acima |
| 4 · A fila se move quando o consumidor para | enviar 20 mensagens com consumidor parado | `ApproximateNumberOfMessagesVisible` perto de 20, idade subindo | se o número não sobe, o consumidor não estava de fato parado — confira o L22 |
| 5 · A métrica de billing existe | `list-metrics --namespace AWS/Billing` | ao menos uma métrica com dimensão Currency | lista vazia quase sempre é "Receive Billing Alerts" nunca habilitado na conta |
Quebrar de propósito: três falhas e o diagnóstico
As três reproduzem sintomas reais deste tipo de painel — inclusive o segundo, que é silencioso da mesma forma que o incidente da abertura foi.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Métrica de negócio publicada pelo logger errado | chame `NegocioMetricas` através do `ILogger` do Serilog em vez de `Console.Out` | requisição completa com sucesso, mas o widget 4 nunca sobe do zero | log group bruto: a linha aparece, mas o `_aws` está dentro de um campo `Message`, não na raiz | escrever a linha EMF direto em `Console.Out`, fora do pipeline de log estruturado |
| Consulta salva referenciando o grupo de log errado | aponte `qual_componente` para um log group que não recebe mais tráfego | widget 6 sempre vazio, mesmo com erro real acontecendo nos serviços | compare o nome do grupo em `aws_cloudwatch_query_definition` com o que o ECS realmente grava | usar referência ao recurso Terraform (`aws_cloudwatch_log_group.pedidos.name`), nunca string solta |
| Widget de billing sem billing habilitado na conta | crie o widget 5 numa conta que nunca marcou "Receive CloudWatch Billing Alerts" | widget aparece sem erro, mas permanentemente sem dado | `list-metrics --namespace AWS/Billing` devolve lista vazia | habilitar o alerta de billing no console, uma vez por conta, e esperar cerca de 15 min |
A falha que mais se parece com o incidente da abertura
A primeira falha desta tabela é, por construção, indistinguível de "está tudo bem" olhando só os widgets 1, 2 e 3: disponibilidade normal, latência normal, fila vazia. É exatamente o padrão que escondeu o consumidor parado por duas horas — só que agora o widget 4 existe para flagrar, e por isso ele não pode falhar em silêncio sem que ninguém perceba a régua toda ter voltado ao ponto cego original.
Segurança: quem vê o quê, e o que uma consulta ampla custa
Um painel de operação concentra sinal de negócio — volume de cobrança é dado comercialmente sensível — e dá a quem o lê a capacidade de rodar consulta arbitrária sobre log de produção. As duas coisas pedem menor privilégio explícito.
| Risco | Probabilidade | Impacto | Prevenção | Detecção | Resposta |
|---|---|---|---|---|---|
| Volume de negócio exposto a quem não deveria ver | média | médio | IAM restringindo `cloudwatch:GetDashboard` e `GetMetricData` por grupo/função | CloudTrail em `GetDashboard` fora do time esperado | revisar a política, remover o principal |
| Consulta de Logs Insights ampla, sem filtro de tempo, gera custo inesperado | média | baixo a médio | período padrão curto nas consultas salvas; nunca `SOURCE *` sem intervalo | Cost Explorer por serviço, linha de CloudWatch Logs | adicionar filtro de tempo e de grupo de log mais restrito à consulta salva |
| Métrica de negócio com dimensão de alta cardinalidade explode custo de métrica customizada | baixa | médio | dimensão só `Servico`; nunca `PedidoId` ou `ClienteId` como dimensão | contagem de métricas customizadas por namespace, no console de billing | remover a dimensão de alta cardinalidade e republicar sem ela |
| Acesso à métrica de billing dado a quem não deveria ver gasto da conta inteira | baixa | alto | menor privilégio: só quem já tem acesso de billing enxerga o widget 5 | CloudTrail em `GetMetricData` no namespace `AWS/Billing` | revogar o acesso; considerar dashboard separado para esse widget |
| Dado pessoal do cliente entra na mesma linha de log que carrega o EMF | baixa | alto | a linha EMF do `NegocioMetricas` nunca inclui campo de cliente — só `Servico` e o valor | Macie sobre o grupo de log, procurando padrão de dado pessoal | mascarar o campo; mover qualquer dado pessoal para uma trilha de log separada |
A permissão mínima para quem só lê o painel
`cloudwatch:GetDashboard`, `cloudwatch:GetMetricData` e `logs:GetQueryResults` — nada de `logs:StartQuery` para quem só consome consulta já salva, e nada de `logs:*`. Quem CRIA consulta nova precisa de `logs:StartQuery` e `logs:PutQueryDefinition`; são papéis diferentes, e confundi-los dá a qualquer leitor do painel a capacidade de escanear log inteiro por conta própria, com o custo que isso pode gerar.
Um dashboard mostra CPU, memória e taxa de erro HTTP normais para dois serviços, e mesmo assim um cliente reclama que pedidos não estão virando cobrança. Qual é a explicação mais provável, à luz deste laboratório?
Observabilidade: as seis perguntas, com métrica e limiar
Esta tabela É o contrato deste laboratório — o resto do módulo existe para fazer cada linha dela ter uma fonte de dado real por trás.
| Pergunta | Métrica ou consulta | O que muda no widget | Limiar de leitura inicial |
|---|---|---|---|
| 1 · O sistema está disponível? | `HTTPCode_Target_5XX_Count` por grupo de destino | linha sobe quando um serviço específico começa a errar | qualquer valor > 0 sustentado por 2 min |
| 2 · Está lento? | `TargetResponseTime` p99 por grupo de destino | regressão de latência que health check não pega | > 2× a linha de base medida |
| 3 · Há mensagens acumuladas? | `ApproximateNumberOfMessagesVisible` e idade da mais antiga | consumidor da fila do L22 parou ou não acompanha o ritmo | idade da mais antiga > 5 min |
| 4 · Quantas operações de negócio concluíram? | EMF: `PedidoConfirmado`, `CobrancaProcessada` | o efeito que o cliente queria, não a porta HTTP | queda > 50% frente à mesma hora do dia anterior |
| 5 · Houve aumento inesperado de custo? | `AWS/Billing` `EstimatedCharges`, `us-east-1` | sinal de conta inteira, não por serviço, atualizado poucas vezes ao dia | salto entre duas leituras consecutivas do dia |
| 6 · Qual componente está falhando? | Logs Insights, log correlacionado do L51 | nomeia o serviço, não só confirma que existe erro | usado sob demanda, não tem limiar automático |
A pergunta que este painel deliberadamente não responde
"Por que o burn rate do SLO subiu?" é a pergunta do L52, não a deste laboratório. Os dois painéis convivem: o alarme composto do L52 decide quem é acordado; este painel é o que essa pessoa abre depois, para entender o quadro inteiro em vez de uma métrica isolada.
Escala: 10, 10 mil, 1 milhão
| Volume | O que acontece com o painel | O que passa a doer | O que fazer |
|---|---|---|---|
| 10 req/s, 2 serviços | um dashboard com 6 widgets cobre tudo | nada; é o cenário deste laboratório | nada |
| 500 req/s, 5 serviços | o widget 6 de Logs Insights consulta mais grupo de log | a consulta salva escaneia mais dado, e o custo por execução cresce com o volume | criar índice de campo no grupo de log mais consultado, para reduzir dado escaneado |
| 5 mil req/s, 20 serviços | um dashboard de 6 perguntas por serviço vira 20 dashboards | ninguém decora onde está cada um, e a pergunta "está tudo bem?" exige abrir vinte telas | um dashboard-resumo com uma linha por serviço, que aponta para o dashboard detalhado de cada um |
| Múltiplas contas AWS | a fila e os serviços moram em contas diferentes do painel | métrica e log não aparecem juntos sem configuração extra | CloudWatch cross-account observability: uma conta monitora, as outras compartilham |
| Pico durante campanha (dez vezes o normal) | os limiares fixos da tabela de observabilidade não valem mais | a mesma leitura ("idade > 5 min") que é alarme em dia normal é esperada no pico | limiar dinâmico por perfil de tráfego é assunto do L57 (chaos) e do L52 (SLO), não deste painel |
| Falha de AZ | metade das tasks de cada serviço some ao mesmo tempo | os widgets 1 e 2 sobem juntos nos dois serviços — parece um problema, é dois | o painel não distingue "AZ caiu" de "os dois serviços quebraram" sozinho; Logs Insights compara horário de início |
O limite real do widget 6, em escala
CloudWatch Logs Insights aceita até 100 consultas concorrentes por conta, contando as adicionadas a dashboard. Um painel-resumo que embute a mesma consulta de log em 20 dashboards de serviço, todos abertos ao mesmo tempo por plantonistas diferentes, pode se aproximar desse teto — é um limite de conta, não de uma consulta isolada.
Custo: o que este laboratório acrescenta à fatura
Este painel não liga nenhum recurso por hora. O que ele cobra é por dashboard, por métrica customizada e por dado escaneado em consulta — três dimensões que crescem com uso, não com o relógio.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | 1 dashboard, 6 widgets, poucas consultas manuais | um dashboard e duas métricas customizadas novas | desprezível | nenhuma; otimizar aqui é gastar atenção onde não há dinheiro |
| Produção pequena | 1 dashboard por serviço, 2 consultas salvas rodando por rotina de plantão | dashboard-mês mais dado escaneado nas consultas mais usadas | baixa e previsível | índice de campo no grupo de log mais consultado; período padrão curto na consulta salva |
| Alta escala | 20 dashboards, dezenas de consultas ad hoc por dia | dado escaneado passa a ser a linha visível — consulta sem filtro de tempo escaneia o grupo inteiro | cresce com o hábito da equipe, não só com o tráfego | padronizar consultas salvas com intervalo padrão, e medir quais consultas escaneiam mais |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| CloudWatch Dashboard | dashboard-mês, não por widget dentro dele | seis widgets custam o mesmo que um; não hesite em separar por pergunta |
| Métrica customizada (EMF) | combinação única de nome + dimensão | dimensão de alta cardinalidade multiplica isso — é o risco já listado na seção de segurança |
| CloudWatch Logs Insights | GB de dado BRUTO escaneado, não o tamanho do resultado | consulta sem filtro de tempo escaneia o grupo de log inteiro, mesmo devolvendo uma linha |
| Ingestão do log que já existia (L51) | GB ingerido, sem custo adicional deste laboratório | a linha EMF é uma linha de log a mais por evento de negócio — desprezível frente ao volume técnico |
A economia que não aparece em nenhuma linha da AWS
O incidente da abertura levou duas horas para ser percebido pelo time comercial, comparando planilhas. Com o widget 4 no ar, a mesma falha aparece como uma linha caindo a zero ao lado de uma linha em 100% — visível em segundos para quem olhar o painel, mesmo sem alarme disparado. O custo que se ataca aqui não está na fatura da AWS: está no tempo até alguém perceber que o negócio parou.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | painel único responde 6 perguntas nomeadas, com prova de que teria flagrado o incidente real | nenhum widget vira alarme automaticamente — alguém precisa abrir a tela | ligar leitura de negócio a alarme acionável (L52) | alta |
| Segurança | menor privilégio separado entre ler dashboard, ler resultado de consulta e criar consulta nova | dado de negócio e de billing convivem no mesmo dashboard sem isolamento de audiência | dashboard separado ou `accountId` restrito para o widget de billing | média |
| Confiabilidade | sinal de negócio detecta falha que sinal de infraestrutura não detecta | o painel depende do L51 continuar publicando log corretamente — se ele quebrar, o widget 6 fica cego junto | alarme de ausência de dado no próprio pipeline de log (L52 cobre parcialmente) | média |
| Eficiência de performance | consultas salvas com período padrão curto, evitando escanear log além do necessário | nenhum índice de campo criado ainda — não é necessário no volume atual | criar índice de campo se o volume de log crescer 10× (é o L57/L59) | baixa |
| Otimização de custos | métrica de negócio com dimensão única, de cardinalidade baixa e previsível | nenhum orçamento (`Budgets`) monitorando o crescimento de métrica customizada | alerta de orçamento por serviço de CloudWatch — parte do L59 | baixa |
| Sustentabilidade | nenhum recurso ligado por hora além do que o L01/L22/L51 já mantinham | consulta ampla desperdiça capacidade de processamento de log sem necessidade | revisar consultas salvas periodicamente e remover as que ninguém mais usa | baixa |
O pilar que este laboratório mais move
Excelência operacional é onde a mudança é mais concreta: antes, "o sistema está bem?" exigia abrir catorze gráficos e decidir por conta própria o que eles significavam juntos; depois, a mesma pergunta tem seis respostas nomeadas, cada uma com fonte e limiar declarados.
Evolução em níveis: o que muda, e o que passa a doer
A terceira arquitetura não é um desenho: é a resposta a QUANDO trocar de painel. Cada nível resolve um risco de visibilidade e compra outro.
Console do CloudWatch aberto manualmente, gráfico avulso por métrica que alguém lembrou de olhar. É onde a Cadência estava antes deste laboratório.Dashboard estruturado pelas 6 perguntas, com métrica técnica, de negócio (EMF) e de custo lado a lado, mais Logs Insights salvo.A métrica de negócio deste laboratório vira insumo de SLO e alarme composto do L52, com escalonamento formal.Dashboards separados por quem lê: plantão técnico vê os 6 originais; liderança vê só volume de negócio e custo, sem widget de log.Dashboard-resumo com uma linha por serviço, agregando dezenas de painéis detalhados, com cross-account observability.Quando surge uma sétima pergunta que ninguém previu, o modelo de linguagem embutido no Logs Insights (Query Assist) traduz a pergunta em consulta a partir do dado já correlacionado — sem ninguém escrever sintaxe de consulta na madrugada.A ordem não é negociável, e o motivo é concreto
Painel por audiência (nível 4) supõe que já exista uma fonte de dado confiável para separar — que é o nível 2. Quem tenta separar audiência antes de ter a métrica de negócio publicada corretamente apenas divide o mesmo painel incompleto em dois painéis incompletos.
Onde IA entra nesta arquitetura, e onde não entra
As seis perguntas deste laboratório têm resposta determinística: métrica agregada ou consulta de log escrita à mão. Um modelo não decide melhor que um `stats count(*) by servico` qual serviço está errando — ele só adicionaria latência e uma fonte de erro a mais numa cadeia que precisa ser confiável às 3h.
Há um uso real e já disponível na própria ferramenta: geração de consulta de Logs Insights por linguagem natural (`Query Assist`). Ele não decide o que o painel mostra — ajuda a escrever a SINTAXE de uma consulta nova, quando a sétima pergunta aparece e ninguém tem a query pronta. É o único uso de IA que este laboratório recomenda.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | traduzir uma pergunta nova em sintaxe de consulta de log, quando não há uma salva |
| Por que uma regra não bastaria? | aqui não é regra — é geração de sintaxe determinística a partir de linguagem natural; o valor está em economizar a fricção de lembrar a sintaxe, não em decidir algo |
| De onde viriam os dados? | o próprio log correlacionado que o L51 já produz — nenhum dado novo é necessário |
| Qual o risco? | consulta sintaticamente válida contando o campo errado, com aparência de resposta confiável |
| Por que não confiar direto? | porque o painel é para decisão sob pressão; toda consulta gerada precisa ser validada contra um número já conhecido antes de virar consulta salva permanente |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "olhar os seis widgets e dizer se está tudo bem" troca seis sinais determinísticos, cada um com limiar explícito, por um julgamento probabilístico que esconde QUAL das seis perguntas motivou a resposta. É o oposto do que este laboratório defende: a resposta tem de vir com a pergunta nomeada ao lado, não resumida por trás de um parágrafo gerado.
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 |
|---|---|---|---|---|---|
| Um widget por métrica disponível, não por pergunta escrita | é como o console sugere adicionar gráfico — clique em "Add widget" e escolha uma métrica | o painel cresce sem que ninguém tenha decidido o que ele precisa responder | catorze gráficos, nenhuma prioridade visual, plantão perdido nos primeiros minutos | escrever as perguntas antes de abrir o console, e um widget por pergunta | protótipo pessoal, de exploração, nunca usado sob pressão de incidente |
| Só métrica técnica, nenhuma de negócio | métrica técnica já vem de graça do SDK e do ALB; negócio exige uma linha de código a mais | o painel fica cego exatamente para o tipo de falha que o cliente sente primeiro | infraestrutura 100% saudável e reclamação de cliente crescendo ao mesmo tempo | publicar métrica de negócio via EMF, na fronteira onde o efeito realmente ocorre | sistema interno sem cliente externo, onde efeito de negócio não existe como conceito |
| Métrica de negócio publicada pelo logger estruturado da aplicação | parece consistente usar o mesmo `ILogger` de tudo o mais | o envelope do logger embrulha o payload EMF e o `_aws` deixa de estar na raiz | a métrica nunca aparece no CloudWatch, sem nenhum erro que aponte a causa | escrever a linha EMF direto em `Console.Out`, fora do pipeline de log estruturado | nunca; é sempre o erro, não uma escolha válida em algum cenário |
| Dimensão de alta cardinalidade na métrica de negócio (PedidoId, ClienteId) | parece útil poder filtrar por pedido específico direto no painel | cada valor único de dimensão vira uma métrica customizada nova, sem teto natural | contagem de métrica customizada cresce sem parar, e ninguém sabe explicar por quê | dimensão só por `Servico`; filtro por pedido específico vive em Logs Insights, não em métrica | nunca em produção; aceitável só em ambiente de teste com volume desprezível |
| Consulta de Logs Insights sem filtro de tempo em produção | copiar e colar uma consulta de exemplo sem ajustar o intervalo | escaneia o grupo de log inteiro, cobrando por GB bruto mesmo devolvendo uma linha | a mesma consulta que custava pouco em teste vira linha visível na fatura em produção | consulta salva sempre com intervalo padrão explícito, curto o suficiente para o uso comum | investigação pontual de incidente grave, onde o custo de uma consulta importa menos que o tempo |
| Confundir este painel com o alarme do L52 | os dois vivem no CloudWatch e parecem a mesma coisa vistos de longe | esperar que um widget "avise" alguém é confundir visualização com notificação | incidente real sem ninguém notificado, porque "o painel mostrava" mas ninguém estava olhando | alarme e escalonamento continuam no L52; este painel é para quem já foi chamado ou está de rotina | nunca; são complementares, não substitutos um do outro |
O teste rápido para saber se um widget novo é adorno
Antes de adicionar qualquer gráfico ao painel, pergunte: a qual das seis perguntas ele responde? Se a resposta for "nenhuma, mas parece útil", ele é candidato ao primeiro anti-padrão desta tabela — está entrando porque a métrica estava disponível, não porque uma pergunta escrita precisa dele.
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Dashboard falha ao aplicar (`InvalidParameterInput`) | JSON do `dashboard_body` sintaticamente válido, mas com campo fora do schema | valide o corpo isoladamente antes do apply | mensagem de erro do Terraform cita o campo exato | conferir contra a documentação de estrutura de dashboard; `jsonencode()` não valida schema |
| Widget de métrica aparece sem linha nenhuma | nome de métrica, namespace ou dimensão não batem com o que é publicado de fato | liste a métrica manualmente antes de confiar no widget | `aws cloudwatch list-metrics --namespace ...` | corrigir nome/dimensão no `dashboard_body` para bater exatamente com o publicado |
| Métrica de negócio (EMF) nunca aparece | linha passou pelo logger estruturado, não por `Console.Out` | leia a linha bruta no grupo de log e confira se `_aws` está na raiz do JSON | grupo de log do serviço, linha mais recente após um evento de negócio | reescrever o ponto de emissão para escrever direto em `Console.Out` |
| Widget 6 (Logs Insights) sempre vazio | consulta aponta grupo de log errado, ou período curto demais | rode a mesma consulta manualmente com `start-query`/`get-query-results` | nome do grupo de log na consulta salva vs. nome real no ECS | referenciar o recurso Terraform do grupo de log, nunca string solta |
| Widget de billing sempre sem dado | "Receive CloudWatch Billing Alerts" nunca habilitado na conta | confira a preferência de billing no console da conta pagadora | AWS Billing and Cost Management → Billing Preferences | habilitar o alerta, uma vez por conta; esperar cerca de 15 min |
| Consulta de Logs Insights demora ou expira | grupo de log grande demais sem filtro de tempo restrito | meça o volume de dado escaneado pela consulta | resultado da consulta mostra "records matched" vs. "records scanned" | reduzir o intervalo de tempo, ou criar índice de campo no grupo de log |
| Dois widgets de métrica com o mesmo nome mostram números diferentes | um usa `Sum`, outro usa `Average`, sobre o mesmo dado | compare a propriedade `stat` de cada widget | JSON do `dashboard_body`, campo `stat` de cada widget de métrica | escolher a estatística que responde à pergunta do widget — contagem pede `Sum`, latência pede `p99` |
A pergunta que resolve metade destes casos
O widget mostra o que foi PEDIDO a ele, não o que existe de fato. Antes de suspeitar do painel, confirme que o dado bruto existe — com `list-metrics` para métrica, com `start-query` manual para log — e só depois compare com o que o `dashboard_body` está pedindo. A maioria dos "o painel está errado" é, na origem, um nome ou uma dimensão que não bate.
Limpeza: o que o destroy não leva
Este laboratório acrescenta pouco recurso que sobrevive ao terraform destroy — mas um deles, se esquecido, continua contando para uma cota de conta mesmo sem cobrar nada.
# limpar.sh — o que este laboratório acrescenta, e o que sobrevive ao destroy
# 1. O que o Terraform administra: dashboard, consultas salvas, alarme de
# billing se você o criou. Nada aqui tem custo por hora ligada.
terraform destroy -auto-approve
# 2. CONSULTAS SALVAS: seguem o ciclo do Terraform e saem no destroy. Confira,
# porque consulta salva "órfã" (criada à mão no console) NÃO aparece no
# estado e continua contando para a cota de 100 concorrentes por conta.
aws logs describe-query-definitions \
--query-definition-name-prefix "ffv-lab/6-perguntas" --query 'queryDefinitions'
# 3. A MÉTRICA DE NEGÓCIO (EMF) não é um recurso: é dado extraído de log já
# existente. Ela não sobrevive à exclusão do grupo de logs, mas SOBREVIVE
# ao destroy do dashboard — o histórico de datapoints fica retido pelo
# prazo padrão de métrica customizada, e volta a aparecer se você recriar
# o dashboard apontando para o mesmo namespace.
# 4. O ALARME DE BILLING, se criado: billing alarm mora sempre em us-east-1,
# independentemente da região do resto da infraestrutura — confira lá.
aws cloudwatch describe-alarms --region us-east-1 \
--alarm-name-prefix "ffv-lab" --query 'MetricAlarms[].AlarmName'
# 5. O QUE JÁ VINHA DO L01/L22/L51 E CONTINUA COBRANDO: ALB, NAT Gateway,
# fila SQS, RDS. Este laboratório não cria nenhum deles — se você não vai
# seguir para o L59, rode a limpeza desses laboratórios também.
# 6. Prova final: nada com o nome do projeto de pé.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=ffv-lab \
--query "ResourceTagMappingList[].ResourceARN" --output table
| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| CloudWatch Dashboard | sim | não; para de cobrar dashboard-mês | nenhuma pendência |
| Consultas salvas (`query_definition`) | sim, se criadas em Terraform | não | criadas à mão no console não aparecem no estado e sobrevivem, contando para a cota da conta |
| Métrica de negócio (dado histórico) | não é um recurso; não sai | sim, retenção padrão de métrica customizada | o histórico de datapoints existe independente do dashboard que o exibia |
| Grupos de log (herdados do L51) | não pertencem a este laboratório | sim, por retenção | este módulo não cria nem apaga log group — só consulta os que já existem |
| Fila SQS (herdada do L22) | não pertence a este laboratório | não, SQS não cobra parado | este módulo só lê métrica dela; a fila em si é gerida pelo L22 |
| Preferência de billing alerts | não é revogável | não cobra nada | é uma preferência de conta, não um recurso — uma vez habilitada, não se desliga |
Por que a limpeza deste laboratório é curta
Diferente de laboratórios que sobem ALB, NAT Gateway ou banco, este cria apenas dashboard e consultas salvas — nenhum dos dois cobra por hora ligada. O cuidado real não é técnico, é organizacional: consulta salva criada à mão no console, fora do Terraform, não aparece em nenhum estado e sobrevive sem que ninguém perceba.
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Painel cresce por incidente passado, não por pergunta | as 6 perguntas escritas antes do widget | inverte a ordem: a pergunta decide o widget, não o widget que sugere uma pergunta depois |
| Infraestrutura verde e negócio parado ao mesmo tempo | métrica de negócio via EMF | é a única forma de o painel enxergar o efeito, não só a porta de entrada |
| EMF nunca aparece no CloudWatch | linha escrita direto em `Console.Out`, fora do logger estruturado | `_aws` precisa estar na raiz do evento; qualquer envelope em volta quebra a extração |
| "Qual componente" sem resposta pronta | grupo de destino por serviço + consulta salva de Logs Insights | a métrica já nasce rotulada por serviço; a consulta cobre o que a métrica não agrega |
| Custo sem visibilidade até a fatura fechar | `AWS/Billing` `EstimatedCharges`, região fixa `us-east-1` | é CloudWatch nativo, sem novo serviço; a região fixa é restrição documentada, não escolha |
| Consulta ampla custando sem controle | período padrão curto em toda consulta salva | Logs Insights cobra por dado bruto escaneado, não pelo tamanho do resultado |
| Painel confundido com notificação | nenhum widget vira alarme; L52 continua sendo o único caminho de aviso | visualização e notificação são responsabilidades diferentes, e confundi-las deixa incidente sem resposta |
- O app grava o pedido no banco e só então emite uma linha EMF de negócio.
- O CloudWatch reconhece o bloco `_aws` na raiz da linha e extrai a métrica, sem chamada de API.
- O grupo de destino de cada serviço publica 5xx e latência já rotulados por serviço.
- A fila do L22 publica profundidade e idade da mensagem mais antiga, de forma gerenciada.
- A conta publica `EstimatedCharges` algumas vezes ao dia, sempre em `us-east-1`.
- O dashboard organiza os seis sinais por pergunta, com layout por urgência.
- Quando métrica pré-agregada não basta, uma consulta salva de Logs Insights nomeia o componente.
- O engenheiro de plantão abre uma tela e sai com resposta às seis perguntas.
Perguntas frequentes
❓ Por que meu dashboard do CloudWatch fica verde mesmo com o negócio parado?
❓ O que é o formato EMF do CloudWatch, e por que usar em vez de PutMetricData?
❓ Por que a métrica de negócio não aparece se eu publico pelo mesmo logger da aplicação?
❓ A métrica AWS/Billing do CloudWatch é dado em tempo real?
❓ Por que não basta uma métrica agregada para dizer qual componente está falhando?
❓ Este dashboard substitui os alarmes do laboratório de SLO e error budget?
❓ Por que a métrica de negócio usa só uma dimensão, e não uma por pedido ou por cliente?
❓ Quanto custa uma consulta de CloudWatch Logs Insights?
Fixando
Uma métrica de negócio publicada via EMF nunca aparece no CloudWatch, embora a aplicação esteja processando pedidos normalmente e gravando log sem erro visível. Qual é a causa mais provável?
Por que a métrica AWS/Billing EstimatedCharges precisa ser lida com `region = "us-east-1"` num widget de dashboard, mesmo que o resto da infraestrutura da conta esteja em outra região?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L51 no ar (telemetria correlacionada por trace_id), L22 no ar (fila de pedidos), Terraform básico |
| Conhecimentos adquiridos | a diferença entre métrica técnica e de negócio; formato EMF e sua armadilha de envelope; estrutura de um CloudWatch Dashboard; sintaxe básica de Logs Insights; restrição regional da métrica de billing |
| Limitação que fica | o painel não decide quem é acordado — depende do L52 para isso; e depende do L51 continuar publicando log corretamente, ou o widget de negócio fica cego junto |
| Próximo exemplo recomendado | L59 — FinOps: medir antes de comprar compromisso. Reutiliza o sinal de custo deste laboratório como ponto de partida, em vez de decidir às cegas |
| Também habilitado por este módulo | ligar a métrica de negócio a um SLO e alarme acionável no L52; e servir de fonte de evidência correlacionada para um agente de diagnóstico de incidente, no L96 |
| Data da última validação técnica | 8 de agosto de 2026 |
Documentação oficial consultada: Create a billing alarm to monitor your estimated AWS charges — a restrição de região `us-east-1`, o pré-requisito de habilitar o alerta e a natureza não instantânea do dado; Analyzing log data with CloudWatch Logs Insights — sintaxe de consulta, o limite de 100 consultas concorrentes por conta e o custo por dado escaneado; CloudWatch Dashboard Body Structure and Syntax — o schema exato de `widgets`, o grid de 24 unidades e as propriedades de widget de métrica e de log; e Specification: Embedded metric format — a exigência de o bloco `_aws` estar na raiz do evento, que é a base do callout sobre o Serilog. Os valores de preço não aparecem neste módulo por decisão: use o AWS Pricing Calculator, porque preço varia por região e envelhece mais rápido que o conteúdo.
O que não foi verificado, e você deve conferir na sua conta
O limiar de "queda maior que 50% frente ao mesmo horário do dia anterior" para a métrica de negócio é um ponto de partida razoável, não um valor validado contra o tráfego real da sua aplicação — sazonalidade de fim de semana ou de campanha muda essa leitura, e vale medir antes de transformar em alarme. O tempo de "cerca de 15 minutos" para a métrica de billing aparecer após habilitar o alerta vem da documentação oficial, não de medição própria neste laboratório.
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…