Automatizando FinOps na AWS: Como construímos um módulo de Monitoramento de Custos com Terraform e incident.io

Published: (February 3, 2026 at 07:03 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

Gerenciar custos em nuvem: módulo Terraform reutilizável para alertas de orçamento na AWS

A disciplina de FinOps busca trazer visibilidade e accountability para os gastos em nuvem, mas sem as ferramentas certas é 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 que automatiza alertas de orçamento na AWS, integrando‑o diretamente com a plataforma de incidentes incident.io – eliminando intermediários desnecessários como funções Lambda.

Contexto

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
  • um canal de alerta configurado

Requisitos

#Requisito
1Definir um limite mensal em dólares
2Alertar quando o gasto real atingir certas porcentagens (ex.: 80 %, 100 %)
3Alertar quando a previsão (forecast) indicar que o orçamento será estourado
4Enviar esses alertas para nossa plataforma de gerenciamento de incidentes
5Garantir segurança (dados criptografados em repouso)

Inicialmente consideramos usar AWS Lambda para processar os alertas do AWS Budgets e formatar o JSON para o webhook, mas percebemos que poderíamos simplificar a arquitetura usando a capacidade nativa do SNS de realizar chamadas HTTPS (Webhooks).

Arquitetura final

flowchart LR
    A[AWS Budgets] --> B[SNS Topic (criptografado)]
    B --> C[HTTP POST]
    C --> D[incident.io]

Essa abordagem reduz a complexidade operacional (menos código para manter) e o custo.

Implementação

1️⃣ KMS Key para criptografia do SNS

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    = "EnableIAMUserPermissions"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${local.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      },
      {
        Sid    = "AllowAWSBudgetsToUseTheKey"
        Effect = "Allow"
        Principal = {
          Service = "budgets.amazonaws.com"
        }
        Action = [
          "kms:GenerateDataKey*",
          "kms:Decrypt"
        ]
        Resource = "*"
      }
    ]
  })
}

Observação: A permissão explícita para budgets.amazonaws.com é essencial. Sem ela, o orçamento falha silenciosamente ao tentar publicar no tópico.


2️⃣ SNS Topic e política de publicação

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️⃣ Orçamento (Budget) com notificações dinâmicas

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 dinâmico para notificações
  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]
    }
  }
}

Variável notifications (exemplo em terraform.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"
  }
]

Isso permite que o consumidor do módulo configure alertas de forma declarativa e simples.


4️⃣ Assinatura HTTPS para o webhook do incident.io

resource "aws_sns_topic_subscription" "incident_io" {
  topic_arn = aws_sns_topic.cost_alerts.arn
  protocol  = "https"   # Detectado dinamicamente no código real
  endpoint  = var.incident_io_webhook_url
}

5️⃣ Desafio da confirmação da assinatura

  1. O Terraform aplica a infraestrutura.
  2. O estado da assinatura fica como PendingConfirmation.
  3. O administrador acessa os logs do incident.io, localiza a mensagem de SubscriptionConfirmation e clica no link manualmente.

Embora não seja 100 % automatizado (zero‑touch), esse 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 é essencial para escalar o uso da nuvem de forma segura e econômica.

Com o módulo apresentado:

  • Segurança – dados em repouso criptografados via KMS
  • Simplicidade – elimina a necessidade de Lambda intermediário
  • Flexibilidade – notificações configuráveis por meio de variáveis
  • Integração direta – webhook HTTPS para incident.io

A abordagem reduz a complexidade operacional, diminui custos e garante que as equipes de engenharia tenham visibilidade contínua sobre os gastos da nuvem.

Responsável

Ao centralizar a lógica de orçamentos, criptografia e notificações em um único módulo, garantimos que todos os ambientes sigam as melhores práticas de segurança e observabilidade financeira.

A escolha de remover camadas intermediárias (como Lambdas) simplificou a stack, tornando‑a mais robusta e fácil de manter a longo prazo.

Back to Blog

Related posts

Read more »

14.Provision IAM User with Terraform

Lab Information The Nautilus DevOps team is experimenting with Terraform provisioners. Your task is to create an IAM user and use a local-exec provisioner to l...