đź§± Terragrunt + OpenTofu

terraform

O verdadeiro batismo de um DevOps não acontece na certificação, acontece quando a barra de progresso da esteira do CI/CD vira um buraco negro de tempo.

Você dá um terraform apply antes de almoçar e, quando volta, a tela ainda está piscando, tentando refrescar o estado de uma infraestrutura que cresceu até virar um monstro indomável.

No início, o Terraform puro parece feitiçaria da boa. Você escreve três arquivos .tf, joga duzentos registros de DNS, sobe dez instâncias e tudo funciona com a leveza de um castelo de cartas. Mas a física da nuvem é implacável: o sucesso traz o crescimento, e o crescimento traz o caos.

Aquele arquivo terraform.tfstate que começou com poucos kilobytes vira uma entidade obesa de dezenas de megabytes. Ele passa a carregar, no mesmo saco, o banco de dados do cliente mais importante e a entrada de DNS do blog de testes do estagiário.

Mudar uma linha de comentário vira uma operação de desarmamento de bomba. Se a API do provedor engasgar ou a sua internet oscilar no meio do processo, o estado corrompe. O resultado? Horas e mais horas decifrando JSON quebrado e rodando comandos terraform state rm com o suor frio de quem sabe que um ENTER errado apaga a empresa do mapa.

O OpenTofu veio para salvar o motor da infraestrutura como código, garantindo que a engine continue livre e performática. Mas ele sozinho não corrige design ruim. Se você alimentar o OpenTofu com um monolito de código, ele apenas vai processar o seu desastre com uma eficiência open-source invejável.

O peso de um state monolĂ­tico

Misturar mĂşltiplos domĂ­nios, topologias de rede e bancos de dados no mesmo diretĂłrio cria um acoplamento destrutivo.

O raio de implosão (blast radius) fica gigantesco. Se tudo está conectado no mesmo arquivo de estado, qualquer pequena manutenção em um record de DNS coloca toda a infraestrutura da empresa em xeque durante o lock do state. É o equivalente a desligar os disjuntores da fábrica inteira só para trocar a lâmpada do corredor.

A redenção com Terragrunt

O Terragrunt entra em cena nĂŁo como uma ferramenta a mais, mas como o arquiteto que fatia o monstro em pedaços gerenciáveis. Ele atua como um wrapper inteligente sobre o OpenTofu, aplicando o princĂ­pio DRY (Don’t Repeat Yourself) de forma cirĂşrgica. Em vez de espalhar blocos idĂŞnticos de provedores e configurações de backend por centenas de pastas, vocĂŞ declara isso uma Ăşnica vez no topo do projeto.

A mágica do isolamento acontece com uma única linha:

# root.hcl

locals {
  bucket           = get_env("TF_STATE_BUCKET", "tfstate-aws")
  region           = get_env("TF_STATE_REGION", "us-east-1")
  relative_path    = path_relative_to_include()
}

remote_state {
  backend = "s3"
  generate = {
    path           = "backend.tf"
    if_exists      = "overwrite"
  }

  config = {
    bucket         = local.bucket
    key            = "aws/${local.relative_path}/terraform.tfstate"
    region         = local.region
    use_lockfile   = true
    encrypt        = true
  }
}

Repare no poder desse bloco. A função path_relative_to_include() funciona como o sensor de presença do Terragrunt. Ela lê a estrutura de diretórios do seu repositório e, dinamicamente, injeta o caminho exato na chave do S3 (key).

O parâmetro generate elimina a necessidade de criar arquivos backend.tf manualmente em cada subpasta. O Terragrunt escreve e sobrescreve o arquivo em tempo de execução.

O impacto prático disso na sua saúde mental é imediato:

  • Microsserviços de infraestrutura: A rede vira um micro-state. O banco de dados, outro. Os trezentos registros de DNS ficam isolados no canto deles.
  • Blast radius de centĂ­metros: Se vocĂŞ cometer um erro catastrĂłfico na configuração do DNS, o pior cenário Ă© o DNS falhar. O state do seu banco de dados continua trancado a sete chaves, intocado.
  • Ganho de velocidade: Rodar um plan em um micro-state que gerencia apenas trĂŞs recursos leva cinco segundos, nĂŁo quarenta minutos.

Orquestrando o caos Multi-Cloud

Se gerenciar uma única nuvem de forma monolítica já queima neurônios, balancear o ecossistema entre AWS, GCP, AZURE e OCI costuma causar pane.

A tendência natural é criar repositórios separados, gerando uma cópia descarada de variáveis, módulos e segredos.

Com o Terragrunt, a árvore de diretórios do seu repositório passa a desenhar a lógica real do seu negócio multi-cloud:

.
├── environment
│   ├── aws
│   │   ├── project_a
│   │   │   ├── account.hcl
│   │   │   ├── kubernetes
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── iam
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── networking
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── bd
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── bucket
│   │   └── root.hcl
│   ├── gcp
│   │   ├── project_b
│   │   │   ├── account.hcl
│   │   │   ├── kubernetes
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── iam
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── networking
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── bd
│   │   │   │   └── terragrunt.hcl
│   │   │   ├── bucket
│   │   └── root.hcl
│   ├── azure
│   └── oci
├── modules
│   ├── aws
│   │   ├── eks
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   ├── iam
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   ├── networking
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   ├── rds
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   ├── s3
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   └── vpc
│   │       ├── input.tf
│   │       ├── main.tf
│   │       └── output.tf
│   ├── azure
│   │   ├── aks
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   ├── iam
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   ├── networking
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   ├── storage
│   │   │   ├── input.tf
│   │   │   ├── main.tf
│   │   │   └── output.tf
│   │   └── workload-identity
│   │       ├── input.tf
│   │       ├── main.tf
│   │       └── output.tf
│   ├── gcp
│   └── oci
└── tools
    └── install-sre-tools.sh

Cada arquivo terragrunt.hcl nas pontas da árvore funciona como um manifesto enxuto. Ele apenas aponta para o módulo original do OpenTofu (hospedado no GitHub ou localmente) e injeta as variáveis específicas daquele ambiente.

O ambiente multi-cloud é o bloco dependency. Se o seu cluster Kubernetes na GCP precisa autenticar em um serviço ou consumir um banco que está rodando na AWS, o Terragrunt lê o output do state da AWS em tempo de execução e o entrega de bandeja para o ambiente da GCP. Sem hardcoding, sem variáveis estáticas em arquivos secretos e sem suposições.

O maestro do caos

AtĂ© aqui, a teoria Ă© linda. Mas o cĂ©tico da TI (aquele que já foi queimado por promessas de ferramentas milagrosas) vai olhar para essa estrutura fatiada e questionar: “Beleza, agora se eu precisar subir um ambiente de staging do zero para testes, eu vou ter que entrar em pasta por pasta e rodar trinta applies na mĂŁo seguindo uma planilha?”

Se vocĂŞ estivesse usando apenas o OpenTofu puro com backends separados, a resposta seria um doloroso “sim”. Mas o Terragrunt resolve isso com um Ăşnico comando que parece heresia, mas Ă© pura engenharia:

terragrunt run-all apply

Quando vocĂŞ dispara o run-all apply na raiz do seu projeto, o Terragrunt nĂŁo joga os comandos no ventilador de forma aleatĂłria. Ele assume o papel de um compilador de sistemas:

  1. Mapeamento genético: Ele varre todas as subpastas e lê os blocos dependency que você configurou.
  2. Grafo de dependĂŞncias: Ele constrĂłi uma árvore lĂłgica (um DAG – Directed Acyclic Graph) e entende quem precisa nascer primeiro.
  3. Paralelismo inteligente: Se a rede da AWS e a rede da GCP não dependem de ninguém, ele roda o apply de ambas ao mesmo tempo. O banco de dados só começa a ser provisionado no exato segundo em que a rede respectiva terminar de subir.

O mesmo vale para o pior momento da vida de um SRE: a hora de desligar as luzes e passar o rodo. Mudar o parâmetro para terragrunt run-all destroy vai desmontar o seu castelo multi-cloud de cabeça para baixo, respeitando a ordem inversa das dependências, sem travar recursos órfãos e sem deixar lixo legado para a contabilidade da empresa chorar no mês seguinte.

É a facilidade de um monolito com a segurança e isolamento de microsserviços.

Lições de um SRE

  1. Gere states por ciclo de vida: Recursos que mudam em ritmos diferentes pertencem a estados diferentes. O DNS muda trĂŞs vezes por dia; a VPC muda uma vez por ano. Nunca os coloque na mesma pasta.
  2. Habilite o use_lockfile: No cĂłdigo de exemplo que usamos, o use_lockfile = true no backend do S3 Ă© crucial para o OpenTofu moderno, evitando concorrĂŞncia destrutiva no state quando duas esteiras rodam ao mesmo tempo.
  3. Cuidado com o efeito teia: O recurso dependency e o run-all sĂŁo poderosos, mas se a pasta A depende da B, que depende da C, que depende da A, vocĂŞ criou um monolito lĂłgico distribuĂ­do e um belo loop infinito. Mantenha as dependĂŞncias lineares e limpas.
  4. Alimente o OpenTofu direto na fonte: Mantenha os seus módulos puros no OpenTofu e use o Terragrunt apenas como o maestro. Misturar lógica de criação de recursos dentro do wrapper quebra o propósito de ter uma engenharia limpa.

No fim das contas, a elegância de uma infraestrutura não está no tamanho do provedor ou na quantidade de recursos rodando. Está na capacidade de alterar uma peça complexa na nuvem durante o horário comercial, ou botar uma região inteira abaixo com um comando, e ir tomar um café com a certeza absoluta de que o resto do mundo vai continuar de pé.