Gerenciar custos em nuvem e um desafio constante para equipes de engenharia. A disciplina de FinOps busca trazer visibilidade e accountability para esses gastos, mas sem as ferramentas certas, e fácil ser surpreendido pela fatura no final do mês.
Neste artigo, compartilho a experiência e os detalhes técnicos da criação de um módulo Terraform reutilizável para automatizar alertas de orçamento na AWS, integrando diretamente com ferramentas de resposta a incidentes (neste caso, o incident.io), eliminando intermediários desnecessários como funções Lambda para esse fim específico.
O Problema
Precisávamos de uma maneira padronizada de criar "guardrails" financeiros para novos projetos. Toda nova conta ou ambiente (staging, production) na AWS deveria nascer com um orçamento definido e um canal de alerta configurado.
Os requisitos eram:
- Definir um limite mensal em dólares.
- Alertar quando o gasto real atingir certas porcentagens (ex: 80%, 100%).
- Alertar quando a previsão (forecast) indicar que o orçamento será estourado.
- Enviar esses alertas para nossa plataforma de gerenciamento de incidentes.
- Ser seguro (dados criptografados em repouso).
A Arquitetura Simplificada
Inicialmente, considerou-se usar AWS Lambda para processar os alertas do AWS Budgets e formatar o JSON para o webhook. No entanto, percebemos que poderíamos simplificar a arquitetura utilizando a capacidade nativa do SNS de realizar chamadas HTTPS (Webhooks).
O fluxo ficou assim:
AWS Budgets -> SNS Topic (Criptografado) -> HTTP POST -> Incident.io
Essa abordagem reduz a complexidade operacional (menos código para manter) e o custo.
A Implementação com Terraform
Abaixo, detalho as partes cruciais do módulo, focando em como resolvemos os desafios de segurança e flexibilidade.
1. Segurança Primeiro: Criptografia KMS
O AWS SNS suporta criptografia de dados (Server-Side Encryption). Para ambientes corporativos, isso não é opcional. No entanto, usar uma chave KMS gerenciada pelo cliente (CMK) requer politicas de acesso especificas para permitir que o serviço de AWS Budgets (que é um serviço global) chame um recurso regional e use a chave para criptografar a mensagem antes de enviá-la ao tópico.
resource "aws_kms_key" "cost_alerts_key" {
description = "KMS key for encrypting FinOps SNS topics"
deletion_window_in_days = 10
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Enable IAM User Permissions"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${local.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "Allow AWS Budgets to use the key"
Effect = "Allow"
Principal = {
Service = "budgets.amazonaws.com"
}
Action = [
"kms:GenerateDataKey*",
"kms:Decrypt"
]
Resource = "*"
}
]
})
}
Observe a permissão explicita para budgets.amazonaws.com. Sem isso, o orçamento falha silenciosamente ao tentar publicar no tópico.
2. O Tópico SNS e a política de Publicação
O tópico SNS atua como o roteador. Além de criar o tópico, precisamos de uma aws_sns_topic_policy para autorizar o serviço de orçamento a publicar nele.
resource "aws_sns_topic" "cost_alerts" {
name_prefix = "${local.name_prefix}-cost-alerts-"
kms_master_key_id = aws_kms_key.cost_alerts_key.arn
}
resource "aws_sns_topic_policy" "default" {
arn = aws_sns_topic.cost_alerts.arn
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AWSBudgets-Publish"
Effect = "Allow"
Principal = {
Service = "budgets.amazonaws.com"
}
Action = "SNS:Publish"
Resource = aws_sns_topic.cost_alerts.arn
}
]
})
}
3. Flexibilidade com Dynamic Blocks
Um dos maiores desafios ao criar módulos reutilizáveis e atender as diferentes necessidades de notificação de cada time. Alguns querem alertas aos 50%, outros apenas aos 100%.
Utilizamos o recurso dynamic do Terraform para gerar as configurações de notificação com base em uma lista de objetos fornecida via variável.
resource "aws_budgets_budget" "cost_budget" {
name = "${local.name_prefix}-budget"
budget_type = "COST"
limit_amount = var.limit_amount
limit_unit = var.limit_unit
time_period_start = "2024-01-01_00:00"
time_unit = var.time_unit
# Bloco dinamico para notificacoes
dynamic "notification" {
for_each = var.notifications
content {
comparison_operator = notification.value.comparison_operator
threshold = notification.value.threshold
threshold_type = notification.value.threshold_type
notification_type = notification.value.notification_type
subscriber_sns_topic_arns = [aws_sns_topic.cost_alerts.arn]
}
}
}
Isso permite que o consumidor do module configure alertas de forma declarativa e simples no arquivo .tfvars:
notifications = [
{
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
},
{
comparison_operator = "GREATER_THAN"
threshold = 100
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
}
]
4. Integração via Webhook (A Parte "Chata")
A integração final e feita via uma assinatura SNS com protocolo HTTPS:
resource "aws_sns_topic_subscription" "incident_io" {
topic_arn = aws_sns_topic.cost_alerts.arn
protocol = "https" # Detectado dinamicamente no codigo real
endpoint = var.incident_io_webhook_url
}
O Desafio da Confirmação da Assinatura:
Diferente de Lambda ou SQS, endpoints HTTP/HTTPS requerem uma confirmação de assinatura. O SNS envia um POST inicial com uma URL de confirmação.
Como estamos usando uma ferramenta de terceiros (incident.io), não temos controle direto para clicar programaticamente nesse link.
A solução adotada foi processual:
- O Terraform aplica a infraestrutura.
- O estado da assinatura fica como "PendingConfirmation".
- O administrador acessa os logs do incident.io, localiza a mensagem de
SubscriptionConfirmatione clica no link manualmente.
Embora não seja 100% automatizado (zero-touch), e um compromisso aceitável para uma configuração que ocorre apenas uma vez por ambiente.
Conclusão
Criar módulos de infraestrutura como código para FinOps e essencial para escalar o uso da nuvem de forma responsável. Ao centralizar a lógica de orçamentos, criptografia e notificações em um único modulo, garantimos que todos os ambientes seguem as melhores praticas de segurança e observabilidade financeira.
A escolha de remover camadas intermediarias (como Lambdas) simplificou a stack, tornando-a mais robusta e fácil de manter a longo prazo.
Top comments (0)