Na era da automação e da escalabilidade, a Infraestrutura como Código (IaC) mudou a forma como os times de TI gerenciam e provisionam recursos. O que antes dependia de processos manuais demorados ou de scripts improvisados agora pode ser descrito em arquivos de configuração claros, versionáveis e reutilizáveis.
Entre as ferramentas de IaC, o Terraform é uma das mais potentes e populares. Ele permite que engenheiros descrevam infraestruturas complexas em código, o que traz consistência, colaboração e eficiência.
Aqui na Cheesecake Labs, usamos Terraform para automatizar processos e criar soluções escaláveis, principalmente em projetos que envolvem múltiplas clouds e múltiplas regiões.
Mas como estruturar um projeto Terraform para que ele fique organizado, fácil de manter e pronto para crescer?
Neste post, compartilhamos a nossa abordagem para montar uma estrutura de projeto Terraform clara e escalável para multi-cloud, com boas práticas, uma sugestão de estrutura de pastas e a explicação dos arquivos que compõem cada parte do projeto.
Se você já se perguntou “como organizar os diretórios do meu Terraform?”, este guia é para você.
Por que Terraform?
Quando você trabalha com infraestruturas complexas espalhadas por vários provedores de cloud e regiões, precisa de ferramentas que ofereçam flexibilidade, consistência e um ecossistema sólido.
O Terraform atende a esses requisitos por vários motivos, entre eles:
- Agnóstico de cloud por design: o Terraform suporta os principais provedores de cloud, como AWS, Azure e Google Cloud, através de uma linguagem de configuração unificada (HCL). Isso o torna ideal para gerenciar ambientes multi-cloud a partir de uma única base de código.
- Declarativo e previsível: você descreve o estado desejado da sua infraestrutura e o Terraform cuida de criar, atualizar ou remover recursos até chegar nele. Isso reduz erro humano e deixa as mudanças mais previsíveis.
- Suporte forte a modularidade: com módulos, você quebra a infraestrutura em componentes reutilizáveis. Isso ajuda bastante quando você quer replicar infraestrutura entre regiões ou clouds sem perder consistência.
- Estado remoto e rastreio de mudanças: o Terraform mantém um arquivo de estado com tudo o que ele gerencia. É isso que viabiliza a prévia de mudanças (terraform plan), o apply direcionado e as estratégias de rollback quando você usa backends de estado remoto.
- Ferramentas e ecossistema: de integrações com ferramentas de CI/CD a módulos mantidos pela comunidade no Terraform Registry, existe um ecossistema maduro em volta do Terraform que sustenta automação, testes e boas práticas de escala.
O Terraform dá as peças necessárias para estruturar uma infraestrutura entre clouds e regiões que seja escalável, modular e fácil de manter, sem prender o projeto a um único provedor.
Leia mais: Deploy de FastAPI no AWS App Runner: um guia pronto para produção com CI/CD
Os desafios de estruturar um projeto Terraform multi-cloud e multi-região
Implementar um projeto Terraform que atravessa várias clouds e regiões traz desafios reais. Estes são alguns que você provavelmente vai encontrar:
Gerenciar o estado do Terraform
Um dos principais desafios é gerenciar o estado do Terraform, que guarda o mapeamento entre os recursos definidos nos arquivos de configuração e os recursos de fato provisionados.
Em um ambiente multi-cloud e multi-região, esse estado pode ficar fragmentado e difícil de administrar, principalmente quando times ou processos diferentes precisam acessar partes distintas do estado ao mesmo tempo. E o risco de conflito ou corrupção do estado é concreto se o projeto não for bem estruturado.
Garantir modularidade e reúso de código
Garantir modularidade e reúso de código é o que evita redundância e mantém a eficiência do desenvolvimento.
Criar módulos que possam ser reaproveitados com facilidade em clouds e regiões diferentes é fundamental para o projeto continuar escalável e administrável no longo prazo.
Isso também ajuda a propagar boas práticas entre os ambientes. Em troca, exige organização cuidadosa de arquivos e pastas e um entendimento claro das particularidades de cada provedor de cloud.
Configurar os provedores de cloud
Por fim, configurar provedores de cloud no Terraform pode ser complexo. Cada provedor tem as próprias APIs e exigências de configuração, o que obriga você a escrever configurações específicas para cada um sem perder consistência nem facilidade de manutenção.
Além disso, cada região pode ter as próprias peculiaridades, como disponibilidade de serviços ou requisitos de compliance, que adicionam mais uma camada de complexidade ao projeto.
Leia mais: Conheça nossos serviços de desenvolvimento de software e design
Estrutura de pastas para o seu projeto Terraform multi-cloud/multi-região
Para administrar com eficiência um projeto que atravessa várias clouds e regiões, é essencial ter uma estrutura de pastas bem organizada. Esta é uma estrutura que você pode adaptar às necessidades do seu projeto:
/terraform/
├── docs/
├── live/
│ ├── development/
│ ├── aws/
│ ├── us-east-1/
│ ├── _setup/
│ ├── azure/
│ ├── eastus/
│ ├── _setup/
│ ├── staging/
│ ├── aws/
│ ├── ...
│ └── production/
│ ├── aws/
│ ├── ...
├── modules/
│ ├── aws/
│ ├── network/
│ ├── 1.0.0/
│ ├── 1.3.1/
│ ├── ...
│ ├── database/
│ ├── ...
│ ├── azure/
│ ├── network/
│ ├── ...Por que essa estrutura?
Por que essa estrutura funciona bem? Veja como ela se sustenta:
- Separação por ambiente: diretórios como development, staging e production isolam os ambientes e facilitam gerenciar diferenças de tamanho de instância, políticas de segurança ou variáveis específicas. Isso impede que uma mudança em um ambiente atinja outro sem querer.
- Organização por provedor: pastas como AWS e Azure dentro de cada ambiente refletem os provedores de cloud e permitem configurações específicas sem misturar lógicas diferentes.
- Separação por região: pastas como us-east-1 (AWS) ou eastus (Azure) separam as regiões e refletem particularidades como latência, disponibilidade de serviços ou requisitos de compliance. Isso facilita gerenciar recursos regionais e aplicar configurações específicas, como zonas de disponibilidade ou regras locais de firewall.
- Segregação do estado do Terraform: ao dividir o projeto em ambientes, provedores e regiões, o estado do Terraform (terraform.tfstate) fica naturalmente segregado. Em estruturas grandes, isso reduz bastante a chance de corrupção do estado, porque cada pasta live (e as subpastas regionais dela) mantém o próprio arquivo de estado independente. Em vez de um estado monolítico único, que vira ponto de falha em projetos complexos, essa abordagem distribui o risco e deixa a infraestrutura mais resiliente e mais fácil de depurar.
- Módulos reutilizáveis: a pasta modules guarda blocos de código específicos de cada provedor (por exemplo, aws/network, azure/database), o que favorece reúso e consistência em todos os ambientes e regiões.
- Versionamento de módulos: módulos versionados deixam o código evoluir de forma controlada. Uma mudança pode ser testada em development, promovida para staging e só então aplicada em production. Escolhemos esse versionamento por pastas (1.0.0, 1.3.1 e assim por diante) em um monorepo pela simplicidade, mas você também pode usar repositórios separados para cada módulo, dependendo da necessidade.
- Documentação: este ponto costuma ser deixado de lado, mas ter documentação da infraestrutura ajuda muito. Ela facilita o entendimento e a entrada de pessoas novas no projeto, além de reduzir a complexidade ao explicar o projeto inteiro em termos mais simples.
Estrutura de arquivos
Com a estrutura de pastas definida, vale detalhar os arquivos que compõem as pastas live (os ambientes reais, onde os recursos são provisionados) e modules (onde ficam os blocos reutilizáveis).
Esta é a nossa sugestão de estrutura de arquivos:
Pastas live (por exemplo, /live/development/aws/)
- main.tf: o ponto de entrada do Terraform. Define os recursos a provisionar chamando módulos e configurando as dependencies. Mantemos esse arquivo simples e delegamos a lógica pesada para os módulos.
module "network" {
source = "./modules/network"
name_prefix = var.name_prefix
cidr_prefix = var.cidr_prefix
}Code language: JavaScript (javascript)- variables.tf: declara as variáveis do projeto, como região, tipo de instância ou nome do ambiente. Permite customizar sem mexer no código principal.
variable "region" {
description = "AWS region to deploy resources"
type = string
default = "us-east-1"
}Code language: JavaScript (javascript)
- outputs.tf: define os outputs do Terraform, como IPs de instâncias ou URLs de serviços. Essa informação é útil para pipelines de CI/CD ou scripts de automação.
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.vpc.id
}Code language: JavaScript (javascript)- locals.tf: guarda variáveis locais, como combinações de valores ou constantes específicas do ambiente. Ajuda a manter o código DRY (Don’t Repeat Yourself).
locals {
name_prefix = var.name_prefix
}Code language: JavaScript (javascript)- provider.tf: configura os provedores de cloud (AWS, Azure e afins) com credenciais e parâmetros como região. Parametrizamos com variáveis para ganhar flexibilidade.
terraform {
backend "s3" {
bucket = "project-terraform-state"
dynamodb_table = "project-terraform-locks"
key = "terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}
provider "aws" {
region = var.region
allowed_account_ids = var.allowed_account_ids
default_tags {
tags = var.default_tags
}
}
Code language: JavaScript (javascript)
- terraform.auto.tfvars: contém os valores padrão das variáveis, carregados automaticamente pelo Terraform. Ideal para configurações específicas de cada ambiente.
region = "us-east-2"Code language: JavaScript (javascript)Pastas modules (por exemplo, /modules/aws/network/)
- main.tf: define os recursos do módulo, como uma VPC ou uma subnet. É o coração do módulo, mas com uma responsabilidade só.
resource "aws_vpc" "vpc" {
cidr_block = "${var.cidr_prefix}.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(local.tags, {
Name = "${local.name_prefix}-vpc"
})
}Code language: JavaScript (javascript)
- variables.tf: lista as variáveis que o módulo aceita, como blocos CIDR ou tags. É o que torna o módulo configurável na hora da chamada.
variable "name_prefix" {
description = "Name prefix"
type = string
}Code language: JavaScript (javascript)
- outputs.tf: devolve valores gerados pelo módulo, como IDs de recursos, para uso em outros módulos ou nas pastas live.
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.vpc.id
}Code language: JavaScript (javascript)
- locals.tf: guarda cálculos ou valores derivados dentro do módulo, mantendo a lógica encapsulada.
locals {
Name_prefix = var.name_prefix
azs = [for i, az in var.azs : "${data.aws_region.current.name}${az}"]
}Code language: JavaScript (javascript)
- version.tf: especifica as versões mínimas do Terraform e do provedor (por exemplo, terraform { required_providers { aws = “>= 4.0” } }). Garante compatibilidade e consistência.
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}Code language: JavaScript (javascript)
Essa separação mantém os módulos genéricos e as pastas live específicas, equilibrando reúso e customização.
A pasta _setup: como gerenciar o estado do Terraform
Um ponto crítico de qualquer projeto Terraform é o gerenciamento de estado (terraform.tfstate). Em ambientes multi-cloud e multi-região, armazenar esse estado de forma central e segura é essencial. Para isso, criamos a pasta _setup dentro da pasta de cada região (por exemplo, /live/development/aws/_setup/).
Estrutura da pasta _setup
A pasta _setup segue a mesma estrutura de arquivos da pasta live:
- main.tf, variables.tf, outputs.tf, locals.tf, provider.tf, terraform.auto.tfvars.
Objetivo
O propósito da _setup é provisionar os recursos necessários para gerenciar o estado do Terraform de forma remota e robusta, configurando o backend do Terraform que as pastas live vão usar. Isso inclui, por exemplo:
- Um bucket S3 (na AWS) ou uma Storage Account (no Azure) para armazenar o arquivo de estado remoto.
- Uma tabela DynamoDB (na AWS) para controle de lock, evitando conflitos em execuções simultâneas.
Ao rodar terraform apply na pasta _setup de cada região, esses recursos são criados e o backend daquela região pode ser configurado para usá-los. Assim, o estado de cada região fica armazenado remotamente e de forma separada.
O backend traz centralização, versionamento, segurança e controle de concorrência, o que torna o gerenciamento de estado mais confiável em projetos complexos.
Leia mais: Serviços de cloud: como escolher o que faz sentido para o seu projeto
Uma particularidade desse estado
O arquivo terraform.tfstate gerado pela pasta _setup fica versionado dentro dela mesma, no repositório Git.
Por quê? Porque ele só contém informação sobre os recursos de gerenciamento de estado (o bucket S3, por exemplo), que não são sensíveis. O estado das pastas live, que descreve a infraestrutura de verdade, fica armazenado remotamente no bucket provisionado, com criptografia e controle de acesso.
Essa abordagem simplifica o início de novos projetos ou regiões e garante que o backend remoto esteja pronto antes de provisionar os recursos principais.
Para fechar
Estruturar um projeto Terraform multi-cloud e multi-região é trabalhoso, mas com uma abordagem organizada e modular você chega a uma solução escalável e fácil de administrar.
O que faz diferença é adotar boas práticas: segregar ambientes e regiões, reutilizar módulos e parametrizar variáveis.
Com essa base, você e o seu time encaram os desafios de um ambiente multi-cloud com confiança e eficiência.
Precisa de ajuda para provisionar e gerenciar infraestruturas escaláveis e de alta disponibilidade? Na Cheesecake Labs, nosso time de DevOps e Cloud Computing monta ambientes sob medida para a necessidade de cada projeto e cliente, de forma ágil e documentada, sempre de olho no custo-benefício.
