Lab 59 — FinOps: medir antes de comprar compromisso
O problema, e a empresa que o tem
A Rotamix otimiza rotas de entrega para trinta transportadoras. Duas cargas diferentes rodam na mesma frota de EC2: uma API síncrona que recalcula uma rota quando o motorista pede, e um lote noturno que reotimiza todas as rotas do dia seguinte de uma vez. As duas nasceram juntas, na mesma frota, porque separar pareceu trabalho demais para um time pequeno.
Oito meses atrás, uma mudança no produto reduziu o volume do lote noturno em 40%: passou a reotimizar só rotas que mudaram, não todas. A capacidade da frota não mudou — ninguém desligou instância nenhuma, porque nada quebrou e não havia sinal óbvio de sobra. O trimestre seguinte trouxe pressão de corte de custo de nuvem, e o time financeiro comprou um Compute Savings Plan de três anos, tudo adiantado, dimensionado pelo gasto on-demand faturado no ano anterior — que ainda incluía o volume antigo.
O resultado: uma taxa de US$/hora travada por 1.095 dias sobre uma frota que já usa 40% menos capacidade do que quando o número foi calculado. O Compute Optimizer — habilitado desde o L09 — tinha a recomendação de redimensionar pronta havia semanas. Ninguém abriu o painel antes de assinar.
O que este laboratório existe para impedir
Comprometer 1 a 3 anos de US$/hora sobre um número que ninguém mediu é uma decisão financeira, não uma configuração. Os termos de um Savings Plan não podem ser alterados depois da compra — não existe "ajustar" nem "cancelar", só esperar o prazo terminar. Errar aqui não é um bug que se corrige no próximo deploy: é capital preso.
O que este laboratório NÃO é
Não é uma calculadora de preço nem um painel de recomendação automática. O Compute Optimizer e o Cost Explorer já fazem essa análise — o laboratório é sobre a ORDEM em que as decisões acontecem, e sobre por que essa ordem tem gate técnico, não só recomendação de boas práticas.
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.
- Ler um achado do Compute Optimizer e decidir se ele autoriza redimensionar.
- Explicar por que o valor comprometido de um Savings Plan não muda depois da compra.
- Separar uma carga em duas frotas por tolerância a interrupção, sem reescrever a aplicação.
- Configurar uma política de instâncias mistas com alocação capacity-optimized para Spot.
- Implementar um worker que detecta interrupção Spot pelo próprio processo, sem depender de evento externo.
- Medir o consumo do tamanho novo antes de cotar qualquer compromisso.
- Comprar um Savings Plan por script com aprovação humana explícita, fora do Terraform.
- Provar redução de custo sem regressão de SLO, com número em cada uma das duas pontas.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Compute Savings Plans vs EC2 Instance Savings Plans | SAA-C03, SAP-C02 | o compromisso da frota síncrona escolhe o tipo Compute | Compute cobre qualquer família/tamanho/OS/tenência/região e também Fargate e Lambda; EC2 Instance amarra a uma família numa região, com desconto maior |
| Savings Plans vs Reserved Instances | SAA-C03 | por que o laboratório não usa RI | Savings Plans se aplica automaticamente ao uso via valor por hora; RI amarra a um atributo específico — mais rígido, com uso legítimo em reserva de AZ |
| Termo e forma de pagamento | SAA-C03, CLF-C02 | 1 ano Partial Upfront na primeira compra | 1 ou 3 anos; No/Partial/All Upfront — trade-off entre desconto e capital adiantado |
| Rightsizing antes do compromisso | SAA-C03, SAP-C02 | Compute Optimizer é o primeiro nó do fluxo, não o último | por que comprar sobre gasto histórico herda o desperdício que o histórico contém |
| Termos de Savings Plans são imutáveis após a compra | SAA-C03, SAP-C02 | a compra pede confirmação humana explícita, fora do Terraform | a implicação de irreversibilidade: por que a decisão exige medição prévia |
| Interrupção de Spot: aviso de 2 minutos | DVA-C02, SAA-C03 | worker detecta via endpoint de metadados da instância | diferenciar carga elegível (tolerante) de carga inelegível (síncrona) para Spot |
| Alocação de Spot: diversificação e capacity-optimized | SAA-C03, SAP-C02 | política de instâncias mistas com múltiplos tipos elegíveis | por que uma única família de instância em Spot aumenta a chance de interrupção |
| Recomendações de Savings Plans no Cost Explorer | CLF-C02, SAA-C03 | usadas só depois do rightsizing, nunca antes | a recomendação do Cost Explorer nasce do histórico faturado e herda o mesmo desperdício que o Compute Optimizer existe para revelar |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve uma equipe que "economizou" comprando Savings Plans pelo maior desconto anunciado e pede o motivo do gasto continuar alto. A resposta quase nunca está no tipo de plano — está na ordem: o compromisso foi dimensionado antes de medir, e nenhum desconto percentual desfaz um número de base errado.
Requisitos, e como cada um muda o desenho
Requisito não funcional que não aparece numa linha do desenho é intenção. A coluna da direita é onde cada um deixou marca.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Redução de custo comprovada | sem valor absoluto — comprovada por medição | obriga o Compute Optimizer a ser o primeiro nó do fluxo, antes de qualquer cotação |
| SLO da rota síncrona não pode regredir | orçamento de erro definido no L52 | a frota síncrona fica fora do Spot; painel do L53 monitora antes/durante/depois |
| Carga do lote tolera interrupção com checkpoint curto | até 5 min de progresso perdido | habilita migrar essa frota para Spot com política de instâncias mistas |
| Compromisso não pode travar 100% do gasto no mesmo prazo | decisão de risco financeiro | primeira compra em termo de 1 ano, Partial Upfront — não 3 anos All Upfront |
| Decisão de compra exige aprovação humana | sem automação de ponta a ponta | compra por script com MFA e confirmação explícita, fora do `terraform apply` |
| Rightsizing não é evento único | revisão recorrente | agenda trimestral de releitura do Compute Optimizer, cruzada com utilização do plano vigente |
| Nenhum job do lote pode se perder numa interrupção | zero mensagens na fila de mortas | fila com redrive policy e worker que zera a visibilidade da mensagem ao detectar aviso |
O requisito mais fácil de esquecer
Revisão trimestral não é um requisito técnico — é o único item desta lista sem uma peça de infraestrutura que o force. Nenhum alarme dispara sozinho lembrando de reler o Compute Optimizer; é responsabilidade humana, e por isso é o primeiro a cair quando a equipe fica ocupada.
Arquitetura mínima: o compromisso que ignora a métrica que já existia
Esta é a arquitetura que a Rotamix tinha até a compra do Savings Plan. É literalmente implantável — comprar um compromisso não exige nenhuma infraestrutura nova — e é por isso que o defeito passa despercebido: nada quebra, nada alarma, o gasto deixa de refletir o uso real, sem nenhum sinal técnico do porquê.
- → gasto on-demand faturado, 12 meses
- → valor médio por hora usado para dimensionar o compromisso
- → métricas de CPU e memória, 14 dias
- → recomendação de redimensionar, nunca aberta antes da compra
- → taxa comprometida aplicada ao consumo real, ocioso incluso
- Compute
- Gestão e governança
Existem dois caminhos até a caixa de decisão, e só um foi percorrido. O gasto histórico do Cost Explorer chega sozinho; a recomendação do Compute Optimizer está desenhada, tem seta e nunca é lida. Percorra os passos: a pergunta não é se a informação existia — é que ela nunca chegou a ser aberta antes da assinatura de três anos.
- A base é o que já foi pago, não o que é preciso. O Cost Explorer soma o gasto on-demand faturado no último ano e vira o número de partida. Esse valor carrega o volume antigo, de antes da queda de 40%, porque fatura não sabe o motivo de um gasto — só o soma.
- O compromisso herda o número errado. O valor médio por hora vira o alvo da cotação de Savings Plans. Nenhum ajuste acontece aqui: o histórico entra, o compromisso sai, e a diferença entre "quanto foi pago" e "quanto é preciso" desaparece no meio do caminho.
- A métrica que desmentiria o número já existia. O Compute Optimizer está ativo e coletando CPU e memória da mesma frota há mais de 14 dias — o mínimo que o serviço exige para gerar recomendação. A informação não falta; ela só não foi aberta.
- A recomendação chegou e não foi lida antes de assinar. Existe uma recomendação de tipo menor, pronta, com a economia projetada. A seta chega até a caixa de decisão — só não foi essa a seta seguida.
- A taxa comprometida se aplica ao ocioso também. Depois de assinado, o desconto vale sobre o consumo real até o teto comprometido — e abaixo dele a cobrança do compromisso é fixa. Capacidade ociosa dentro do teto não é uso de graça: é gasto que não compra nada.
- Por que alguém assina assim. Porque o painel de gasto do financeiro já está na tela, o prazo do orçamento fiscal não espera opt-in de outro serviço, e "comprometer sobre o que já se paga" soa conservador — até o dia em que o uso real aparece bem abaixo do teto.
Por que não existe nada para "quebrar" aqui
A arquitetura mínima não introduz nenhum recurso novo: comprar um Savings Plan é uma ação de billing, não uma mudança de infraestrutura. É exatamente por isso que o defeito não aparece em nenhum plano de Terraform nem em nenhum diff de código — só na fatura, meses depois.
Arquitetura para produção
A topologia muda, não só o rótulo. A frota deixa de ser uma coisa só: a carga que não tolera interrupção fica separada da que tolera, o Compute Optimizer é consultado antes de qualquer número entrar numa cotação, e o painel de SLO que já existe desde o L52/L53 é a prova, não um adorno.
- → recomendação de instância aplicada à frota síncrona
- → recomendação de tipo aplicada também à frota Spot
- → consumo do tamanho novo, medido por 7 dias antes de comprometer
- → desconto do compromisso aplicado ao consumo real
- → job de reotimização, um por mensagem
- → aviso de interrupção Spot, 2 minutos antes
- → checkpoint salvo; visibilidade zerada para reprocessar agora
- → latência p99 e taxa de erro da rota síncrona
- → frequência de interrupção, para tendência
- Gestão e governança
- Compute
- Integração de apps
A frota deixa de ser uma coisa só. A carga que não tolera interrupção fica em on-demand redimensionado e coberto por compromisso; a carga que tolera vai para Spot e nunca entra em compromisso algum, porque sua vantagem é justamente não travar nada. Percorra os passos: cada peça nova rastreia a um requisito da seção anterior, e o painel de SLO é quem prova que nada regrediu.
- Medir vem primeiro no desenho, não só no discurso. O Compute Optimizer é o primeiro nó, e as duas frotas recebem recomendação antes de qualquer cotação de compromisso existir. É a inversão literal do diagrama anterior, onde ele era um apêndice nunca aberto.
- O compromisso espera o número novo se confirmar. Depois do rightsizing, a frota síncrona roda 7 dias no tamanho novo antes de qualquer compra. É o consumo medido nesse tamanho — não o histórico do tamanho antigo — que dimensiona a cotação de Savings Plans.
- O desconto se aplica ao tamanho certo. Só depois de medido o consumo do tamanho novo o compromisso é assinado sobre ele. O desconto de compromisso passa a cobrir capacidade que de fato é usada, não capacidade que sobra.
- A carga tolerante recebe o mesmo rightsizing e nenhum compromisso. O Compute Optimizer também analisa a frota do lote. A diferença é o destino da recomendação: aqui ela não vira cotação de Savings Plans, vira tipo de instância dentro de uma política de instâncias mistas majoritariamente Spot.
- A fila absorve a interrupção; a instância não decide sozinha quando parar. Cada job do lote é uma mensagem. Enquanto ela não é confirmada, continua existindo — trocar a instância que a processa não apaga trabalho, atrasa.
- Os dois minutos viram checkpoint, não pânico. No aviso de interrupção, a instância salva o progresso e zera a visibilidade da mensagem na fila — outra instância pega o job em segundos, não no prazo padrão de espera. O EventBridge, nesse mesmo instante, só está contando: a proteção do job já aconteceu antes de o evento terminar de ser entregue.
- A prova de que nada regrediu mora no painel que já existia. O L52 já define o SLO da rota síncrona; o L53 já sabe respondê-lo. Este módulo não cria um painel novo — lê o que já está lá, antes e depois da migração, para provar redução de custo sem o número que mais importa piorar.
Por que a fila SQS só aparece no segundo diagrama
O lote sempre precisou de alguma forma de retomar trabalho, mas sem Spot uma interrupção nunca acontecia — então redrive policy explícita nunca foi prioridade. Migrar para Spot é o que torna esse requisito visível: tolerância a interrupção não é propriedade só da aplicação, é propriedade do par aplicação e fila.
Como funciona ponta a ponta
O evento que dispara a métrica de interrupção — não a proteção do job, que é local — é publicado pelo próprio EC2 no EventBridge:
{
"version": "0",
"id": "9f6a2b3c-...",
"detail-type": "EC2 Spot Instance Interruption Warning",
"source": "aws.ec2",
"account": "111122223333",
"time": "2026-08-08T03:14:00Z",
"region": "us-east-1",
"resources": ["arn:aws:ec2:us-east-1:111122223333:instance/i-0abc123def456789a"],
"detail": {
"instance-id": "i-0abc123def456789a",
"instance-action": "terminate"
}
}
Por que o payload não basta para proteger o job
Entre o evento ser publicado, entregue à regra do EventBridge e processado por um destino, passa tempo — e a janela inteira é de 2 minutos. O worker deste laboratório não espera esse caminho: ele lê o mesmo aviso direto do endpoint de metadados da própria instância, sem intermediário.
As decisões
📋 A Rotamix precisa reduzir o gasto da frota EC2 sem degradar o SLO da rota síncrona de otimização de rotas, com uma pessoa de FinOps dedicada meio período e sem orçamento para reescrever a arquitetura de computação.
É a única ordem em que o número que entra no compromisso já é o número certo. Rightsizing não pede nova arquitetura — troca o tipo de instância dentro do mesmo desenho — e o Compute Optimizer já está ativo desde o L09. Separar a carga por tolerância a interrupção não exige reescrever a aplicação: o lote noturno já é assíncrono e já processa por fila, então só passa a rodar em capacidade mais barata. O compromisso entra por último, sobre o que sobrou depois de cortar o óbvio, e é sobre esse número que vale a pena travar 1 a 3 anos.
Alt: Comprar Savings Plan primeiro e fazer rightsizing depois — É o próprio erro que este laboratório resolve: os termos não mudam depois da compra, então o compromisso trava o número antigo por até 3 anos, e qualquer rightsizing posterior só reduz o uso, nunca o valor comprometido.
Alt: Reserved Instance específica em vez de Savings Plan — Amarra família, tamanho e às vezes zona de disponibilidade da instância. Savings Plans oferece desconto comparável com a flexibilidade de mudar de família ou região sem perder o benefício — o que a Rotamix ainda vai precisar, porque a carga do lote está mudando de perfil.
Alt: Mover toda a frota para Spot, síncrona inclusa — O aviso de interrupção é de 2 minutos, curto demais para uma requisição síncrona em voo sem um mecanismo de failover que a Rotamix não tem hoje. Resolve custo e quebra o requisito de disponibilidade que motivou o projeto.
Alt: Não comprometer nada, permanecer 100% on-demand — Deixa na mesa um desconto documentado de até 66% (Compute) ou 72% (EC2 Instance) sobre uma carga síncrona que já é estável e previsível havia meses — é conservador demais para um padrão de uso que já parou de mudar.
Construir: duas frotas por tolerância a interrupção
O tipo da frota síncrona vem de uma variável, não de um valor fixo — porque ele muda a cada ciclo de rightsizing, e o Terraform não deveria precisar de edição manual para refletir uma recomendação nova.
# frotas.tf — duas frotas por tolerancia a interrupcao, dimensionadas pelo
# Compute Optimizer. Nenhum tipo de instancia aqui e escolhido a olho.
variable "tipo_sincrono_novo" {
description = "Tipo recomendado pelo Compute Optimizer para a frota sincrona, apos 14+ dias de metrica real"
type = string
}
variable "tipos_lote" {
description = "Tipos elegiveis para a frota Spot do lote — diversificar reduz a chance de interrupcao simultanea"
type = list(string)
}
# ── Frota sincrona: on-demand, e so ela vai receber compromisso ──────────────
resource "aws_launch_template" "sincrono" {
name_prefix = "${var.projeto}-sincrono-"
image_id = var.ami_dotnet8
instance_type = var.tipo_sincrono_novo
iam_instance_profile {
name = aws_iam_instance_profile.sincrono.name
}
# IMDSv2 obrigatorio: token exigido, sem fallback para v1. E tambem o que
# torna o polling de interrupcao do worker do lote seguro de replicar aqui,
# caso a Rotamix decida um dia rodar rightsizing nesta frota tambem.
metadata_options {
http_tokens = "required"
http_endpoint = "enabled"
}
network_interfaces {
security_groups = [aws_security_group.sincrono.id]
}
tag_specifications {
resource_type = "instance"
tags = { Perfil = "sincrono", Projeto = var.projeto, RightsizedEm = var.data_rightsizing }
}
}
resource "aws_autoscaling_group" "sincrono" {
name = "${var.projeto}-sincrono"
min_size = 2
max_size = 4
desired_capacity = 2
vpc_zone_identifier = aws_subnet.privada[*].id
launch_template {
id = aws_launch_template.sincrono.id
version = "$Latest"
}
target_group_arns = [aws_lb_target_group.sincrono.arn]
tag {
key = "Perfil"
value = "sincrono"
propagate_at_launch = true
}
}
# ── Frota do lote: politica de instancias mistas, majoritariamente Spot ──────
# `on_demand_base_capacity = 0` e `on_demand_percentage_above_base_capacity = 0`
# autorizam 100% Spot: o requisito de tolerancia a interrupcao ja foi validado
# na secao de requisitos, e e ele que justifica zero capacidade garantida aqui.
resource "aws_launch_template" "lote" {
name_prefix = "${var.projeto}-lote-"
image_id = var.ami_dotnet8
iam_instance_profile {
name = aws_iam_instance_profile.lote.name
}
metadata_options {
http_tokens = "required"
http_endpoint = "enabled"
}
network_interfaces {
security_groups = [aws_security_group.lote.id]
}
tag_specifications {
resource_type = "instance"
tags = { Perfil = "lote", Projeto = var.projeto }
}
}
resource "aws_autoscaling_group" "lote" {
name = "${var.projeto}-lote"
min_size = 0
max_size = 20
desired_capacity = 4
vpc_zone_identifier = aws_subnet.privada[*].id
mixed_instances_policy {
instances_distribution {
on_demand_base_capacity = 0
on_demand_percentage_above_base_capacity = 0
# capacity-optimized: a AWS escolhe o pool com mais capacidade
# disponivel entre os tipos elegiveis, o que documentadamente reduz a
# frequencia de interrupcao frente a alocacao so por preco mais baixo.
spot_allocation_strategy = "capacity-optimized"
}
launch_template {
launch_template_specification {
launch_template_id = aws_launch_template.lote.id
version = "$Latest"
}
dynamic "override" {
for_each = var.tipos_lote
content {
instance_type = override.value
}
}
}
}
tag {
key = "Perfil"
value = "lote"
propagate_at_launch = true
}
}
resource "aws_sqs_queue" "lote" {
name = "${var.projeto}-lote"
# Cobre a duracao normal de um job (media medida: ~12 min). Curto demais
# reprocessaria job em andamento como se tivesse falhado; longo demais
# atrasa a recuperacao de um job genuinamente perdido.
visibility_timeout_seconds = 900
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.lote_dlq.arn
maxReceiveCount = 5
})
}
resource "aws_sqs_queue" "lote_dlq" {
name = "${var.projeto}-lote-dlq"
message_retention_seconds = 1209600 # 14 dias — tempo para investigar antes de expirar
}
Zero capacidade garantida tem um custo de disponibilidade
Com `on_demand_base_capacity = 0`, se não houver oferta Spot suficiente nos tipos elegíveis no momento, o ASG do lote pode não atingir a capacidade desejada. Para o lote noturno da Rotamix isso é aceitável — o requisito é concluir até de manhã, não a cada minuto. Para uma carga tolerante com prazo mais apertado, uma base pequena on-demand pode ser necessária.
Construir: a interrupção vira métrica, o compromisso vira alarme
O orçamento de utilização não observa o gasto total — observa se o que foi comprometido está sendo usado. É a métrica que, se estivesse ligada oito meses atrás, teria mostrado o desperdício em semanas, não em auditoria trimestral.
# observabilidade.tf — o evento de interrupcao vira metrica, e o compromisso
# vira alarme quando comeca a sobrar
# O EventBridge aqui NAO protege o job — quem protege e o polling local do
# worker, no proprio processo. Este caminho existe para a frota ser
# observavel: quantas interrupcoes por hora, e se a taxa esta subindo.
resource "aws_cloudwatch_log_group" "interrupcoes_spot" {
name = "/${var.projeto}/interrupcoes-spot"
retention_in_days = 30
}
resource "aws_cloudwatch_event_rule" "interrupcao_spot" {
name = "${var.projeto}-interrupcao-spot"
event_pattern = jsonencode({
source = ["aws.ec2"]
detail-type = ["EC2 Spot Instance Interruption Warning"]
})
}
resource "aws_cloudwatch_event_target" "para_log" {
rule = aws_cloudwatch_event_rule.interrupcao_spot.name
arn = aws_cloudwatch_log_group.interrupcoes_spot.arn
}
resource "aws_cloudwatch_log_metric_filter" "taxa_interrupcao" {
name = "taxa-interrupcao-spot"
log_group_name = aws_cloudwatch_log_group.interrupcoes_spot.name
pattern = "{ $.detail-type = \"EC2 Spot Instance Interruption Warning\" }"
metric_transformation {
name = "InterrupcoesSpot"
namespace = "${var.projeto}/FinOps"
value = "1"
}
}
# Orcamento do TIPO de compromisso, nao do gasto total: alarma quando o
# compromisso comprado esta sendo usado abaixo de um limiar — e e exatamente
# o sintoma de ter comprado sobre o numero errado. O limite deste tipo de
# orcamento e sempre 100 (percentual de utilizacao), por exigencia da API.
resource "aws_budgets_budget" "utilizacao_savings_plan" {
name = "${var.projeto}-utilizacao-savings-plan"
budget_type = "SAVINGS_PLANS_UTILIZATION"
limit_amount = "100"
limit_unit = "PERCENTAGE"
time_unit = "MONTHLY"
cost_types {
include_credit = false
include_refund = false
include_subscription = true
}
notification {
comparison_operator = "LESS_THAN"
threshold = 90
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = [var.email_finops]
}
}
Por que o alarme dispara abaixo de 90%, não acima de um teto de gasto
Orçamento de utilização mede o percentual do compromisso que está sendo aproveitado, não o valor gasto em si. Um compromisso saudável fica perto de 100% de utilização; abaixo de 90% já indica capacidade comprada e não usada, mesmo que a fatura total pareça estável e não dispare nenhum alarme de gasto.
Construir: menor privilégio para quem lê, quem compra e quem processa
# iam.tf — quem le recomendacao, quem compra, e quem processa a fila
#
# ComputeOptimizer e SavingsPlans nao aceitam Resource especifico nas acoes
# de leitura/criacao usadas aqui: sao operacoes de conta, sem ARN de recurso
# individual antes de existirem. E por isso — e so por isso — que o "*"
# aparece nessas duas policies, com o motivo escrito ao lado de cada uma.
data "aws_iam_policy_document" "revisao_finops" {
statement {
sid = "LerRecomendacoes"
effect = "Allow"
actions = [
"compute-optimizer:GetEC2InstanceRecommendations",
"compute-optimizer:GetAutoScalingGroupRecommendations",
"compute-optimizer:GetEnrollmentStatus",
]
# Operacao de conta: nao existe ARN de "recomendacao" antes dela ser
# calculada, entao nenhuma API do Compute Optimizer aceita Resource.
resources = ["*"]
}
statement {
sid = "ConsultarOfertasDeCompromisso"
effect = "Allow"
actions = [
"savingsplans:DescribeSavingsPlansOfferings",
"savingsplans:DescribeSavingsPlans",
]
resources = ["*"] # descoberta de ofertas nao e escopada por recurso
}
statement {
sid = "ComprarCompromisso"
effect = "Allow"
actions = ["savingsplans:CreateSavingsPlan"]
resources = ["*"] # a compra cria o recurso; nao existe ARN anterior a ela
# Quem assume este papel tem de ser humano, nao a role de execucao do
# pipeline: a acao e irreversivel, e nenhuma esteira automatizada deveria
# ter permissao de assinar 1 a 3 anos de compromisso sozinha.
condition {
test = "Bool"
variable = "aws:MultiFactorAuthPresent"
values = ["true"]
}
}
}
# Papel do worker do lote: SQS e S3 escopados a recursos especificos — nada
# de "*" aqui, porque as duas acoes suportam Resource e a fila e o prefixo
# de checkpoint sao conhecidos com antecedencia.
data "aws_iam_policy_document" "worker_lote" {
statement {
effect = "Allow"
actions = [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:ChangeMessageVisibility",
]
resources = [aws_sqs_queue.lote.arn]
}
statement {
effect = "Allow"
actions = ["s3:PutObject"]
resources = ["${aws_s3_bucket.checkpoints.arn}/lote/*"]
}
}
Construir: a compra do compromisso, por script — de propósito, fora do apply
Por que este script não é um recurso do Terraform
Existe um recurso do provedor da AWS para Savings Plans, e ele pode até ser usado para IMPORTAR um plano já comprado ao estado do Terraform. O que este laboratório evita é usá-lo para CRIAR o compromisso: um `apply` sugere repetibilidade e um `destroy` sugere reversão, e nenhuma das duas é verdade aqui. A compra é uma decisão financeira assinada por humano, com MFA, fora do ciclo de pipeline.
#!/usr/bin/env bash
# comprar-savings-plan.sh — a compra e uma decisao humana, nao um `apply`
#
# Este script NAO e Terraform, de proposito. O provedor da AWS ja expoe um
# recurso para Savings Plans, mas uma compra assinada por 1 a 3 anos nao tem
# operacao inversa: nao existe `destroy` que devolva o compromisso. Modelar
# uma decisao financeira irreversivel como estado gerenciavel ensina o habito
# errado — o de tratar "posso reverter com terraform destroy" como verdade
# geral sobre todo recurso do provedor. Aqui, a compra pede confirmacao
# explicita de quem tem mandato para assinar, fora do ciclo de CI/CD.
set -euo pipefail
PROJETO="${PROJETO:?defina PROJETO}"
DIAS_MEDINDO_TAMANHO_NOVO="${DIAS_MEDINDO_TAMANHO_NOVO:-7}"
# ── Passo 1: confirmar que o rightsizing ja foi aplicado ha dias suficientes ──
RIGHTSIZED_EM="$(aws ec2 describe-instances \
--filters "Name=tag:Perfil,Values=sincrono" \
--query 'Reservations[0].Instances[0].Tags[?Key==`RightsizedEm`].Value' --output text)"
if [[ -z "$RIGHTSIZED_EM" ]]; then
echo "ABORTA: a frota sincrona nao tem a tag RightsizedEm — rightsizing nao confirmado." >&2
exit 1
fi
# ── Passo 2: pedir a recomendacao mais recente, para comparar com o que sera comprado ──
aws compute-optimizer get-ec2-instance-recommendations \
--instance-arns "$(aws ec2 describe-instances --filters "Name=tag:Perfil,Values=sincrono" \
--query 'Reservations[].Instances[].InstanceId' --output text | tr '\t' '\n' | \
xargs -I{} echo "arn:aws:ec2:${AWS_REGION:-us-east-1}:$(aws sts get-caller-identity --query Account --output text):instance/{}")" \
--query 'instanceRecommendations[].{achado:finding,recomendado:recommendationOptions[0].instanceType}' \
--output table
echo "Confira: o achado deve ser OPTIMIZED para o tipo atual antes de prosseguir."
read -r -p "O achado confirma OPTIMIZED para ${DIAS_MEDINDO_TAMANHO_NOVO} dias de medicao? (digite SIM) " confirmacao
[[ "$confirmacao" == "SIM" ]] || { echo "ABORTA: confirmacao nao recebida."; exit 1; }
# ── Passo 3: ofertas disponiveis, para o tamanho JA medido ───────────────────
aws savingsplans describe-savings-plans-offerings \
--plan-types "Compute" \
--query 'searchResults[].{oferta:offeringId,taxa:paymentOption}' \
--output table
read -r -p "ID da oferta escolhida (Compute, 1 ano, Partial Upfront recomendado neste estagio): " OFERTA_ID
read -r -p "Compromisso em USD/hora (baseado no consumo medido, NAO no historico): " COMPROMISSO_HORA
echo "Revisao final — compromisso de ${COMPROMISSO_HORA} USD/hora, oferta ${OFERTA_ID}."
read -r -p "Confirma a COMPRA IRREVERSIVEL? (digite CONFIRMO) " confirmacao_final
[[ "$confirmacao_final" == "CONFIRMO" ]] || { echo "ABORTA: compra nao confirmada."; exit 1; }
aws savingsplans create-savings-plan \
--savings-plan-offering-id "$OFERTA_ID" \
--commitment "$COMPROMISSO_HORA" \
--upfront-payment-amount 0
echo "Savings Plan assinado. Agende a revisao trimestral — ela nao acontece sozinha."
O script pede confirmação interativa de propósito
`read -p` trava a execução esperando alguém digitar. Rodar este script sem terminal interativo — dentro de um pipeline, por exemplo — falha, e essa falha é a intenção: nenhuma esteira automatizada deveria conseguir assinar um compromisso de 1 a 3 anos sozinha.
Construir: o worker que se protege sozinho
Cada fatia do job é curta o bastante para caber várias vezes dentro dos 2 minutos de aviso — é essa cadência, e não o tamanho do checkpoint, que decide quanto progresso se perde numa interrupção.
// SpotAwareWorker.cs — le a fila do lote, e reage a interrupcao Spot sozinho,
// sem depender de nenhum outro servico responder a tempo.
public sealed class SpotAwareWorker : BackgroundService
{
private static readonly Uri EndpointAcao =
new("http://169.254.169.254/latest/meta-data/spot/instance-action");
private static readonly Uri EndpointToken =
new("http://169.254.169.254/latest/api/token");
private readonly IAmazonSQS _sqs;
private readonly string _urlFila;
private readonly HttpClient _http;
private readonly ILogger<SpotAwareWorker> _log;
public SpotAwareWorker(IAmazonSQS sqs, IOptions<FilaOptions> opcoes,
HttpClient http, ILogger<SpotAwareWorker> log)
{
_sqs = sqs;
_urlFila = opcoes.Value.UrlFila;
_http = http;
_log = log;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var resposta = await _sqs.ReceiveMessageAsync(new ReceiveMessageRequest
{
QueueUrl = _urlFila,
MaxNumberOfMessages = 1,
WaitTimeSeconds = 10,
}, ct);
if (resposta.Messages.Count == 0) continue;
var mensagem = resposta.Messages[0];
// Processa o job em fatias curtas, com checkpoint entre elas — e
// a CADA fatia consulta o endpoint local de metadados. Isso e o
// que faz o worker perceber a interrupcao sozinho, sem esperar
// nenhum evento externo chegar dentro dos 2 minutos de aviso.
var job = JsonSerializer.Deserialize<JobDeRota>(mensagem.Body)!;
await ProcessarComCheckpointAsync(job, mensagem, ct);
}
}
private async Task ProcessarComCheckpointAsync(
JobDeRota job, Message mensagem, CancellationToken ct)
{
foreach (var fatia in job.Fatias)
{
if (await InterrupcaoIminenteAsync(ct))
{
_log.LogWarning(
"Interrupcao Spot detectada localmente; checkpoint salvo em {Fatia}, "
+ "devolvendo job {JobId} a fila", fatia.Indice, job.Id);
await SalvarCheckpointAsync(job, fatia, ct);
// Zera a visibilidade: a mensagem fica disponivel para outra
// instancia AGORA, em vez de esperar o timeout padrao de 15
// minutos. E o mecanismo que evita o job dobrar de duracao a
// cada interrupcao.
await _sqs.ChangeMessageVisibilityAsync(new ChangeMessageVisibilityRequest
{
QueueUrl = _urlFila,
ReceiptHandle = mensagem.ReceiptHandle,
VisibilityTimeout = 0,
}, ct);
return; // nao apaga a mensagem: outra instancia reprocessa
}
await ProcessarFatiaAsync(job, fatia, ct);
}
// Escrita idempotente por Id do job — se a mesma fatia for
// processada duas vezes por uma corrida entre a checagem local e a
// interrupcao real, a segunda escrita nao duplica resultado.
await GravarResultadoIdempotenteAsync(job, ct);
await _sqs.DeleteMessageAsync(_urlFila, mensagem.ReceiptHandle, ct);
}
private async Task<bool> InterrupcaoIminenteAsync(CancellationToken ct)
{
try
{
var tokenResposta = await _http.PutAsync(EndpointToken,
new StringContent(""), ct);
tokenResposta.EnsureSuccessStatusCode();
var token = await tokenResposta.Content.ReadAsStringAsync(ct);
using var pedido = new HttpRequestMessage(HttpMethod.Get, EndpointAcao);
pedido.Headers.Add("X-aws-ec2-metadata-token", token);
var resposta = await _http.SendAsync(pedido, ct);
// 200 com corpo = interrupcao agendada. 404 = nenhuma pendente.
// Qualquer outro erro NAO deve ser tratado como "sem interrupcao"
// — se o endpoint estiver instavel, o worker segue conservador e
// volta a checar na proxima fatia, curta o bastante para caber
// na janela de 2 minutos mesmo perdendo uma leitura.
return resposta.StatusCode == HttpStatusCode.OK;
}
catch (HttpRequestException)
{
return false; // ver comentario acima: conservador, nao otimista
}
}
private Task SalvarCheckpointAsync(JobDeRota job, Fatia fatia, CancellationToken ct) =>
Task.CompletedTask; // grava em S3/DynamoDB o progresso ate a fatia atual
private Task ProcessarFatiaAsync(JobDeRota job, Fatia fatia, CancellationToken ct) =>
Task.CompletedTask; // o calculo de reotimizacao em si
private Task GravarResultadoIdempotenteAsync(JobDeRota job, CancellationToken ct) =>
Task.CompletedTask; // upsert condicional por Id do job
}
Implantar, e provar com número
A ordem das provas segue a ordem do fluxo
Meça a folga antes de redimensionar (prova 1), confirme que o compromisso comprado reflete o tamanho novo (prova 2), prove que o SLO não regrediu (prova 3), prove que nenhum job se perde (prova 4), e só então confirme que o compromisso está sendo usado (prova 5). Pular a ordem invalida a conclusão de qualquer uma das outras.
# provas.sh — cinco medicoes; nenhuma conclusao vem de "parece que reduziu"
PROJETO=rotamix; REGIAO=us-east-1
# ── Prova 1: a frota sincrona tinha folga ANTES do rightsizing ───────────────
# Rode antes de aplicar qualquer mudanca. Esperado: achado = OVER_PROVISIONED
# e utilizacao media de CPU medida abaixo de 40% no periodo de 14 dias — o
# valor exato depende da sua carga; o que importa e o achado nao ser OPTIMIZED.
aws compute-optimizer get-ec2-instance-recommendations \
--instance-arns "$(terraform output -raw arns_frota_sincrona_antiga)" \
--query 'instanceRecommendations[].{achado:finding,cpu_media:utilizationMetrics[?name==`CPU`].value|[0]}' \
--output table
# ── Prova 2: o compromisso comprado bate com o tamanho medido, nao o antigo ──
# Compara o $/hora do plano assinado com o preco on-demand do tipo NOVO
# multiplicado pela contagem da frota. Esperado: diferenca abaixo de 5%.
COMPROMISSO=$(aws savingsplans describe-savings-plans \
--query 'savingsPlans[0].commitment' --output text)
TAXA_TIPO_NOVO=$(aws pricing get-products --service-code AmazonEC2 \
--filters "Type=TERM_MATCH,Field=instanceType,Value=$(terraform output -raw tipo_sincrono_novo)" \
--query 'PriceList[0]' --output text | python3 -c \
"import json,sys; print(json.load(sys.stdin)['terms']['OnDemand'])")
echo "compromisso=$COMPROMISSO taxa_esperada≈$TAXA_TIPO_NOVO"
# ── Prova 3: o SLO da rota sincrona nao regrediu ──────────────────────────────
# Compara p99 e taxa de erro de 7 dias antes e 7 dias depois da migracao.
# Esperado: p99 dentro do orcamento de erro definido no L52; se subir acima
# do limiar declarado, a migracao reprova mesmo com custo menor.
aws cloudwatch get-metric-statistics \
--namespace "AWS/ApplicationELB" --metric-name TargetResponseTime \
--dimensions Name=TargetGroup,Value="$(terraform output -raw tg_sincrono_suffix)" \
--start-time "$(date -u -d '7 days ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--period 3600 --statistics p99 --output table
# ── Prova 4: nenhum job do lote se perde numa interrupcao ────────────────────
# Conta mensagens que caíram na fila de mortas (DLQ) na janela do teste.
# Esperado: zero. Mensagem na DLQ significa que o checkpoint nao salvou a
# tempo, ou que a visibilidade nao foi zerada antes da instancia sumir.
aws sqs get-queue-attributes \
--queue-url "$(terraform output -raw url_dlq_lote)" \
--attribute-names ApproximateNumberOfMessages --output table
# ── Prova 5: o compromisso nao esta sendo desperdicado ───────────────────────
# Esperado: utilizacao >= 90% nas primeiras semanas apos a compra. Abaixo
# disso, parte do que foi comprometido nao esta cobrindo uso nenhum — o
# proprio sintoma que este laboratorio existe para evitar.
aws ce get-savings-plans-utilization \
--time-period Start=$(date -u -d '14 days ago' +%F),End=$(date -u +%F) \
--query 'Total.Utilization.UtilizationPercentage' --output text
Uma equipe compra um Compute Savings Plan de 3 anos, dimensionado pelo gasto on-demand faturado nos últimos 12 meses. Dois meses depois, uma mudança de produto reduz o uso real da frota em 40%. Qual é a consequência direta desse compromisso?
Quebrar de propósito
Três falhas provocadas, com o sintoma que cada uma produz e onde a investigação começa.
| Falha provocada | Sintoma | Onde olhar | Correção |
|---|---|---|---|
| Desligar o polling de IMDS no worker (comentar a checagem) | Jobs completos somem sem chegar à DLQ nem ao resultado final | CloudWatch Logs do worker — ausência total do log de checkpoint | religar a checagem a cada fatia; sem ela, a interrupção mata o processo sem aviso tratado |
| Reduzir o timeout de visibilidade da fila para menos que a duração de uma fatia | O mesmo job é processado por duas instâncias ao mesmo tempo | métrica ApproximateNumberOfMessagesNotVisible caindo antes do job terminar | ajustar o timeout para cobrir a fatia mais longa medida, com folga |
| Remover a diversificação de tipos da política de instâncias mistas (um tipo só) | Taxa de interrupção sobe visivelmente nos horários de pico de demanda da AWS | log de interrupções por hora, filtrado por tipo de instância | reintroduzir 3 a 4 tipos elegíveis, para o `capacity-optimized` ter o que escolher |
Segurança: dado de custo e decisão de compromisso são dado de negócio
| Risco | Probabilidade | Impacto | Prevenção | Detecção | Resposta |
|---|---|---|---|---|---|
| Compra de compromisso sem segunda aprovação | Baixa | Alto — capital preso por até 3 anos | MFA obrigatório na policy de `CreateSavingsPlan`, executor fora do pipeline | CloudTrail em `savingsplans:CreateSavingsPlan` | revisão financeira imediata; contato com a AWS para casos excepcionais |
| Relatório de custo por tag exposto a todos os times | Média | Médio — vazamento de estratégia | IAM restrito ao Cost Explorer só para FinOps e liderança | CloudTrail em `ce:GetCostAndUsage` | revogar acesso, revisar política de menor privilégio |
| Credencial de longa duração no worker do lote | Média | Alto — acesso à fila e ao checkpoint | IAM Role de instância via instance profile, nunca chave estática | GuardDuty e IAM Access Analyzer | rotação da role, revogação de sessão ativa |
| Carga síncrona migrada para Spot por engano de configuração | Baixa | Médio — SLA quebrado | tag `Perfil` obrigatória e ASG separado por perfil de carga | alarme de latência por grupo de destino | mover tráfego para a frota on-demand, investigar a mudança de configuração |
Observabilidade: as perguntas que o painel de FinOps tem de responder
- Qual é a taxa de utilização do Savings Plan hoje, e está caindo?
- Qual percentual da frota do lote é Spot, e qual a taxa de interrupção por hora?
- Há quantos dias foi o último rightsizing aplicado à frota síncrona?
- A latência p99 da rota síncrona mudou depois da migração?
- Quanto do compromisso está pagando capacidade que não está sendo usada?
- Quantos jobs do lote foram reprocessados por interrupção nesta semana?
| Alarme | Métrica | Limiar inicial |
|---|---|---|
| Utilização de Savings Plan em queda | SAVINGS_PLANS_UTILIZATION (Budgets) | abaixo de 90% |
| Latência da rota síncrona acima do SLO | TargetResponseTime p99 (L52) | orçamento de erro do L52 |
| Taxa de interrupção Spot subindo | InterrupcoesSpot (filtro de métrica) | crescimento sobre a média móvel de 7 dias |
| Mensagens acumulando na DLQ do lote | ApproximateNumberOfMessages (DLQ) | maior que 0 |
Escala: 10, 10 mil, 1 milhão — e a falha de AZ
| Ordem de grandeza | O que muda |
|---|---|
| 10 rotas/dia | volume não justifica compromisso: uma instância on-demand pequena, rightsizing revisado só quando o volume crescer de patamar |
| 10 mil rotas/dia | ASG rightsized, Savings Plan de 1 ano Partial Upfront cobrindo a frota síncrona; lote inteiro em Spot com 3-4 tipos elegíveis |
| 1 milhão de rotas/dia | múltiplos ASGs por região; compromisso escalonado entre 1 e 3 anos, para não vencer tudo no mesmo trimestre; diversificação de Spot ampliada para mais famílias de instância |
| Falha de uma AZ | lote tolera: SQS e ASG multi-AZ redistribuem sem intervenção. síncrona precisa de capacidade on-demand de reserva na AZ remanescente, coberta pelo mesmo Compute Savings Plan — que, por ser flexível entre AZs de uma região, continua descontando |
Compute Savings Plans atravessa região; EC2 Instance Savings Plans não
Na escala de 1 milhão de rotas/dia, com múltiplas regiões em jogo, essa diferença decide qual tipo de plano usar: só o Compute Savings Plan continua descontando se a Rotamix mover carga entre regiões para atender pico local — o EC2 Instance Savings Plan fica preso à região escolhida no momento da compra.
Custo: as dimensões, três cenários e o custo oculto
| Cenário | Perfil de compromisso | O que domina o custo |
|---|---|---|
| Protótipo / validação | 100% on-demand, sem compromisso | flexibilidade total; sem desconto de compromisso, mas sem risco de sobra |
| Produção pequena (este laboratório) | Compute Savings Plan de 1 ano, Partial Upfront, só na frota síncrona rightsized | desconto de compromisso sobre capacidade medida; lote em Spot sem compromisso algum |
| Alta escala | compromisso escalonado (parte 1 ano, parte 3 anos), revisão trimestral | desconto maior no longo prazo, com exposição limitada por não vencer tudo junto |
O custo oculto: compromisso pago e não usado não aparece como alarme de gasto
Um Savings Plan subutilizado não gera pico de fatura — ele mantém a fatura estável e baixa em aparência, porque a taxa comprometida já é menor que on-demand mesmo ociosa. É por isso que a métrica certa é UTILIZAÇÃO do plano, não valor total pago: o alarme de gasto nunca vai disparar para este problema.
Comprometer 100% do gasto num único prazo de 3 anos é exposição, não economia
Se a demanda cair de novo — como caiu 40% no lote — não existe saída antes do fim do prazo. Escalonar entre 1 e 3 anos, e entre planos parciais, troca uma fração do desconto máximo por a possibilidade real de corrigir o rumo no próximo ciclo.
Well-Architected nos seis pilares
| Pilar | Situação | Risco | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | Rightsizing feito uma vez, sem processo de revisão | recomendação envelhece e ninguém percebe | revisão trimestral agendada e documentada | Alta |
| Segurança | Compra de compromisso sem exigência de MFA | assinatura por credencial comprometida trava capital sem revisão | condição de MFA na policy de compra | Alta |
| Confiabilidade | Lote sem redrive policy antes deste laboratório | job perdido sem rastro em caso de falha repetida | DLQ com retenção de 14 dias para investigação | Média |
| Eficiência de performance | Rightsizing só por CPU, sem olhar memória | instância subdimensionada em memória sob pico | usar a recomendação completa do Compute Optimizer | Média |
| Otimização de custo | Compromisso dimensionado por gasto histórico faturado | desconto sobre desperdício | medir antes de comprometer — o núcleo deste laboratório | Alta |
| Sustentabilidade | Frota ociosa continua ligada mesmo coberta por compromisso | consumo de energia sem trabalho útil correspondente | rightsizing reduz também a pegada, não só a fatura | Média |
Por que Otimização de custo e Segurança dividem a prioridade mais alta
Uma compra de compromisso mal informada e uma compra sem segunda aprovação têm o mesmo potencial de dano: capital preso por anos. Tratar uma como "só FinOps" e a outra como "só segurança" separa dois sintomas do mesmo problema, que é decisão irreversível sem controle suficiente.
Evolução em níveis
Uma instância on-demand, sem tag, sem opt-in do Compute Optimizer.ASG único on-demand, rightsizing manual ocasional, sem separação por carga.Compute Optimizer contínuo, rightsizing antes de qualquer compra, Savings Plan sobre o tamanho medido, lote em Spot.Múltiplas frotas por serviço; compromissos escalonados entre 1 e 3 anos; revisão trimestral automatizada dos achados do Compute Optimizer.FinOps como função central: cota por time (rateio do L09), chargeback, painel de compromisso vs uso real por conta, política que bloqueia nova compra sem 30 dias de dado do Compute Optimizer.A mesma disciplina aplicada a carga de inferência: cache de prompt e lote no Bedrock (L89) entram na mesma revisão de compromisso, e o MODELO certo por tarefa é escolhido pelos MESMOS DADOS de utilização que hoje dimensionam a frota EC2 — não por quanto ele "parece" caro.Onde IA entra nesta arquitetura, e onde não entra
Aqui, IA já está — só que operada pela AWS, não construída por você
O Compute Optimizer e as recomendações de Savings Plans do Cost Explorer já usam aprendizado de máquina internamente para projetar padrão de uso e sugerir tipo de instância. Você não constrói esse modelo — você lê a saída dele, e este laboratório é inteiro sobre LER essa saída antes de decidir, não sobre substituí-la.
Onde IA não entra: usar um modelo de linguagem para "estimar" o tamanho certo de instância a partir de uma descrição em texto do workload é pior do que não medir nada — troca uma omissão honesta por um número que parece confiável e não é. O lugar certo para IA neste domínio de custo é outro: gasto de token e cache de prompt em sistemas de IA generativa, que é o assunto do L89, e custo de GPU e endpoint ocioso em ML clássico, que é o assunto do L80.
Anti-padrões deste laboratório
| Antipadrão | Por que alguém faz | Sintoma em produção | Forma correta |
|---|---|---|---|
| Comprar Savings Plan pelo gasto histórico faturado | o painel de gasto já está aberto no financeiro; o Compute Optimizer exige opt-in e é outra aba que ninguém lembra de abrir | compromisso cobre capacidade ociosa; utilização do plano fica baixa por meses sem alarme | ler a recomendação de rightsizing antes de qualquer cotação de compromisso |
| Comprar Reserved Instance específica em vez de Savings Plan | "reservar" soa mais garantido do que "comprometer valor por hora" | mudança de família ou tamanho de instância perde o desconto sem ninguém ligar o motivo à compra antiga | usar Compute Savings Plan quando a carga ainda pode mudar de forma |
| Colocar a carga síncrona em Spot para maximizar desconto | o gráfico de economia do Spot é o maior número do console, e um teste manual de interrupção parece ter validado o risco | erro 5xx correlacionado com evento de interrupção nos horários de pico de tráfego | reservar Spot para trabalho tolerante; síncrono fica em on-demand coberto por compromisso |
| Comprometer 100% do gasto num único plano de 3 anos, All Upfront | o desconto anunciado é o maior número da tela, e o capital adiantado parece "vai ser gasto de qualquer jeito" | demanda cai e não existe forma de sair do compromisso antes do prazo | escalonar termos — parte em 1 ano, parte em 3 — e revisar antes de cada renovação |
| Tratar a compra do Savings Plan como recurso do Terraform | o time versiona toda a infraestrutura, e não versionar a compra parece uma exceção inconsistente | `terraform destroy` documentado ao redor de um recurso que não pode, de fato, ser destruído | compra por script com confirmação humana explícita, fora do ciclo de apply/destroy |
| Rightsizing uma vez e nunca mais revisitar | "já foi feito" vira crença permanente, e não existe lembrete automático cobrando revisão | compromisso comprado certo em um trimestre está desalinhado no seguinte, sem ninguém perceber | revisão trimestral agendada, cruzando nova leitura do Compute Optimizer com a utilização do plano vigente |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Fatura de Savings Plan mostra cobertura, mas gasto on-demand continua alto | frota rodando fora da família/região elegível de um EC2 Instance Savings Plan específico | comparar tipo/região da frota com o escopo do plano comprado | relatório de cobertura de Savings Plans no Cost Explorer | trocar para Compute Savings Plan, ou expandir para a família correta |
| Instância Spot é interrompida sem o worker salvar checkpoint | polling do IMDS com intervalo longo demais, ou hop limit de metadados bloqueando a leitura | checar logs do worker e testar o endpoint manualmente com curl | CloudWatch Logs do worker; `HttpPutResponseHopLimit` do launch template | reduzir o intervalo de polling; corrigir o hop limit de metadados |
| Compute Optimizer não gera recomendação para a frota | conta sem opt-in, ou métricas com menos de 14 dias acumulados | consultar o status de matrícula do serviço na conta | console do Compute Optimizer, seção de status de opt-in | ativar o opt-in e aguardar o período mínimo de coleta |
| Job do lote processado duas vezes depois de uma interrupção | escrita do resultado não é idempotente; o processamento antigo termina de escrever depois do novo começar | checar duplicidade pelo Id do job no armazenamento de resultado | log de gravação de resultado; condição de escrita no banco/DynamoDB | gravar por upsert condicional pelo Id do job, nunca por append simples |
| Custo caiu, mas a taxa de erro da API síncrona subiu junto | rightsizing decidido só pela métrica de CPU, ignorando memória no pico | reler a recomendação completa do Compute Optimizer, não só o tipo sugerido | painel de latência p99 do L53; recomendação de memória do Compute Optimizer | reverter para o tipo anterior e redimensionar considerando pico de memória, não média de CPU |
Limpeza: o que o destroy não leva, e o que é irreversível
#!/usr/bin/env bash
# limpeza.sh — o que o destroy leva, e o que continua cobrando de qualquer jeito
set -euo pipefail
PROJETO="${PROJETO:?defina PROJETO}"
# Esvaziar a fila antes do destroy: mensagem em voo durante a destruicao vira
# job perdido sem passar pela DLQ, porque a fila deixa de existir com ela.
aws sqs purge-queue --queue-url "$(terraform output -raw url_fila_lote)"
terraform destroy -auto-approve
echo "Recursos do Terraform removidos. O Savings Plan NAO foi tocado — nada"
echo "no destroy alcanca um compromisso assinado. Ele continua cobrando pelo"
echo "prazo inteiro, com ou sem instancia rodando."
| Recurso | O `destroy` remove? | Continua cobrando/existindo depois? |
|---|---|---|
| ASGs, launch templates, instâncias | Sim | Não |
| Fila SQS e DLQ | Sim (purgar antes evita perda silenciosa em voo) | Não |
| Log group e métrica de interrupção | Sim | Não |
| Orçamento de utilização (Budgets) | Sim | Não |
| Savings Plan comprado | Não — não é gerenciado pelo Terraform deste módulo | Sim, pelo prazo inteiro contratado |
O Savings Plan não é destruído por nada neste laboratório
Nenhum comando de limpeza alcança um compromisso já assinado. Ele continua cobrando a taxa comprometida, ligada ou não a instância nenhuma, até o fim do prazo — 1 ou 3 anos depois da compra. É a mesma irreversibilidade que motivou tirar a compra do Terraform: nada neste laboratório finge que ela pode ser desfeita.
Resumo: problema, peça e motivo
| Problema | Peça | Motivo |
|---|---|---|
| Compromisso dimensionado por gasto histórico | Compute Optimizer antes de qualquer cotação | o histórico faturado herda o desperdício; a métrica de uso não |
| Carga síncrona e assíncrona na mesma frota | duas ASGs separadas por perfil | só a carga tolerante pode ir para Spot sem quebrar requisito de disponibilidade |
| Interrupção de 2 minutos sem proteção | worker com polling local do IMDS | reação local não depende de nenhum outro serviço responder a tempo |
| Compra tratada como infraestrutura versionável | script fora do Terraform, com MFA | compromisso de 1 a 3 anos não tem `destroy`; a decisão pede humano, não pipeline |
| Compromisso comprado e nunca revisado | orçamento de utilização + revisão trimestral | rightsizing não é evento único; a carga muda de novo |
- Compute Optimizer analisa 14+ dias de métrica real da frota atual.
- O achado (OVER_PROVISIONED, OPTIMIZED, UNDER_PROVISIONED) decide se há o que corrigir.
- Rightsizing é aplicado à frota síncrona, com o painel de SLO aberto durante a troca.
- A frota do lote, tolerante a interrupção, migra para uma política de instâncias mistas majoritariamente Spot.
- O consumo do tamanho novo é medido por 7 dias — não o histórico do tamanho antigo.
- Só então uma cotação de Savings Plans é solicitada, sobre o número medido.
- A compra é executada por script, com MFA e confirmação humana explícita, fora do Terraform.
- O orçamento de utilização passa a alarmar se o compromisso ficar abaixo de 90% de uso.
- A revisão trimestral repete o ciclo: nova leitura do Compute Optimizer decide o próximo passo.
Perguntas frequentes
❓ Devo redimensionar a frota antes ou depois de comprar Savings Plans?
❓ Qual a diferença entre Compute Savings Plans e EC2 Instance Savings Plans?
❓ Reserved Instance ainda faz sentido depois que Savings Plans existe?
❓ Quantos minutos de aviso a AWS dá antes de encerrar uma instância Spot?
❓ Posso rodar minha API síncrona em instâncias Spot para economizar mais?
❓ O Terraform consegue desfazer a compra de um Savings Plan com terraform destroy?
❓ Como o Compute Optimizer decide qual instância recomendar?
❓ O que acontece com o desconto do Savings Plan se eu usar menos do que comprometi?
Fixando
Um worker .NET rodando em instância Spot faz polling do endpoint de metadados a cada poucos segundos para detectar interrupção. Por que essa checagem local — e não a regra do EventBridge que também existe na arquitetura — é o mecanismo que efetivamente protege o job em processamento?
Depois de um rightsizing baseado só na métrica de CPU, a fatura cai como esperado, mas a taxa de erro da API síncrona sobe nos horários de pico. Qual é a causa mais provável?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L09 (custo, tags, Cost Explorer, Budgets) e L53 (painel que responde 6 perguntas de operação) concluídos; opt-in do Compute Optimizer feito na conta |
| Conhecimentos adquiridos | a ordem entre medir e comprometer; a diferença entre Compute e EC2 Instance Savings Plans; por que compromisso não cabe em Terraform; detecção local de interrupção Spot via IMDS; por que EventBridge aqui é observação, não proteção |
| Limitação que fica | a revisão trimestral ainda depende de alguém lembrar de rodá-la; automatizar o gatilho da revisão — sem automatizar a decisão de compra — fica para um ciclo futuro |
| Próximo exemplo recomendado | L89 — custo e latência de GenAI, que aplica a mesma disciplina de medir antes de comprometer a cache de prompt e escolha de modelo |
| Também habilitado por este módulo | L98 (plataforma multi-time com cota e chargeback) reaproveita o painel de utilização de compromisso construído aqui, por conta |
| Data da última validação técnica | 8 de agosto de 2026 |
Documentação oficial consultada: What are Savings Plans? e Savings Plans types — os quatro tipos de plano, os termos de 1 e 3 anos, as três formas de pagamento e os tetos de desconto documentados (até 66% em Compute, até 72% em EC2 Instance); What is AWS Compute Optimizer? — os recursos suportados, o período padrão de 14 dias e a exigência de opt-in; documentação de Spot Instances — o aviso de 2 minutos e os dois caminhos de captura, endpoint de metadados e EventBridge. 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
A utilização de CPU de 18% e a duração média de 12 minutos por job são medições de exemplo desta Rotamix fictícia, não valores universais — meça a sua própria frota antes de decidir. O `savings-plan-offering-id` e os parâmetros exatos de `aws savingsplans create-savings-plan` variam por oferta disponível no momento da compra; confira a saída de `describe-savings-plans-offerings` na sua conta antes de executar o script. O recurso `aws_savingsplans_savings_plan` existe no provedor da AWS para importar planos já comprados — se a sua versão do provedor mudar esse comportamento, releia a documentação antes de assumir que ele também compra.
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…