Skip to content

feat(vpc): optional NAT strategy and no-default-route private subnets - #314

Open
alissonrosa-lang wants to merge 1 commit into
masterfrom
feat/vpc-optional-nat-strategy
Open

feat(vpc): optional NAT strategy and no-default-route private subnets#314
alissonrosa-lang wants to merge 1 commit into
masterfrom
feat/vpc-optional-nat-strategy

Conversation

@alissonrosa-lang

Copy link
Copy Markdown
Contributor

Motivação

O módulo vpc cria hoje 1 NAT Gateway por subnet pública, incondicionalmente (aws_nat_gateway.this com for_each = local.public_subnets), e a validação de var.subnets obriga toda subnet privada a referenciar um nat_key. Na prática, isso induz o padrão "1 NAT por AZ" mesmo quando o tráfego não justifica, e impede o desenho "subnet privada sem rota default" (egress somente via VPC endpoints), cada vez mais comum em arquiteturas serverless.

Caso real — conta Escale Data - Prod (950509508909)

Durante a revisão de custos de ago/2026 encontramos exatamente esse padrão em produção: 4 NAT Gateways (1 por AZ). Medição de 7 dias via CloudWatch:

NAT Gateway BytesOutToDestination (7d) ActiveConnectionCount máx (7d)
nat-006890db6c5ac5ef2 103,8 GB em uso
nat-090026cac9919b1c6 0 0
nat-06e6ee383ae09aa1d 0 0
nat-028f7430427b8ee56 0 0

100% do tráfego flui por um único NAT. Os outros 3 custam ~US$ 32,85/mês cada em horas provisionadas (+ 3 EIPs): ~US$ 98/mês (~US$ 1.180/ano) sem função alguma.

O que muda

  1. Nova variável nat_strategy (string, default "per_public_subnet"):
    • per_public_subnet (default) — comportamento idêntico ao atual;
    • referenced_only — cria NAT/EIP apenas para subnets públicas referenciadas pelo nat_key de ao menos uma subnet privada (elimina NATs ociosos por construção).
  2. nat_key aceita "" em subnets privadas — a subnet fica sem rota default (padrão para cargas que saem só por VPC endpoints). A aws_route.private_internet simplesmente não é criada para essas subnets.

Compatibilidade retroativa (garantias)

  • Default preserva endereços de recurso e chaves de for_each: com nat_strategy = "per_public_subnet" (default), local.nat_subnets == local.public_subnetsterraform plan é no-op para consumidores existentes. Nenhum NAT/EIP é destruído ou recriado (recriação de NAT = nova EIP = quebra de whitelists externas).
  • Sem mudança de tipo em var.subnets — nada de optional(), sem exigência de versão nova de Terraform.
  • Configurações existentes (todas com nat_key preenchido, por força da validação antiga) passam na validação nova sem alteração.
  • terraform fmt -check e terraform validate OK.

Exemplo de uso novo

module "vpc" {
  source   = "git::https://github.com/escaletech/terraform-modules.git//modules/vpc?ref=<tag>"
  name     = "data-platform-dev"
  vpc_cidr = "10.100.0.0/16"

  nat_strategy = "referenced_only"

  subnets = {
    nat-a = { name = "nat-a", cidr = "10.100.0.0/24",  az = "us-east-1a", public = true,  nat_key = "" }
    eks-a = { name = "eks-a", cidr = "10.100.10.0/24", az = "us-east-1a", public = false, nat_key = "nat-a" }
    eks-b = { name = "eks-b", cidr = "10.100.11.0/24", az = "us-east-1b", public = false, nat_key = "nat-a" }
    dms-a = { name = "dms-a", cidr = "10.100.1.0/24",  az = "us-east-1a", public = false, nat_key = "" } # só endpoints
  }
}

Pós-merge

Sugerimos criar uma tag de release (ex.: v1.1.0): a última tag do repo (v1.0.0) é de jan/2025 e anterior à criação deste módulo (#275, jan/2026) — hoje não há como pinar o modules/vpc por tag, o que força consumidores a apontar master ou SHA.

Contexto

Mudança motivada pela criação da infra do projeto data-platform (contas novas dev/prd), que usará este módulo. Feliz em ajustar o que o time preferir — inclusive dividir em dois PRs (estratégia de NAT / nat_key vazio) se facilitar o review.

Adds nat_strategy variable (default per_public_subnet, preserving current
behavior and resource addresses) with a referenced_only option that creates
NAT Gateways only for public subnets referenced by a private subnet nat_key.
Also allows nat_key = "" for private subnets with no default route (egress
via VPC endpoints only).
@alissonrosa-lang alissonrosa-lang changed the title feat(vpc): optional NAT strategy and nullable nat_key feat(vpc): optional NAT strategy and no-default-route private subnets Aug 11, 2026
@alissonrosa-lang

Copy link
Copy Markdown
Contributor Author

Evidências de validação local (nota: o CircleCI não reportou checks neste PR — detalhes no fim):

  • terraform fmt -check ✅ e terraform validate
  • Lógica dos locals testada isoladamente nos 4 cenários:
Cenário NATs criados Rotas privadas
per_public_subnet (default), 2 públicas + 2 privadas pub-a, pub-b priv-a, priv-bidêntico ao comportamento atual; chaves de for_each preservadas → plan no-op para consumidores existentes
referenced_only, só pub-a referenciada pub-a priv-a, priv-b (ambas → pub-a)
referenced_only, nenhum nat_key preenchido nenhum nenhuma
default + nat_key = "" misto todas as públicas só onde há nat_key
  • tflint (ruleset terraform): os 8 apontamentos no módulo são pré-existentes (outputs sem description, required_version ausente) — nenhum introduzido por este diff.

Comportamento a destacar no modo referenced_only: remover a última referência a uma subnet pública destrói o NAT e libera a EIP (o IP não retorna). É o objetivo do modo, mas vale atenção em ambientes com IPs whitelistados externamente.

Nota sobre o CI: o job tf-check instala o tflint mais recente, mas o .tflint.hcl pina o plugin aws em 0.17.1, incompatível com ele (Plugin "aws" SDK version is incompatible). Provável causa de o CI não rodar/reportar há um tempo. Se fizer sentido, mando PR separado bumpando o plugin (ou migrando o check para GitHub Actions).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant