Entendendo Módulos no Terraform
Um módulo no Terraform é um diretório contendo um conjunto de arquivos de configuração que funcionam juntos. Ele encapsula lógica de infraestrutura reutilizável, permitindo que você organize código complexo em componentes menores e gerenciáveis. A verdadeira potência dos módulos está na abstração: você define a interface (inputs e outputs) e a implementação fica encapsulada, possibilitando reutilização sem duplicação de código.
A estrutura básica de um módulo segue um padrão simples. Imagine que você quer criar um módulo para provisionar uma instância EC2 com segurança básica. Você criará um diretório chamado modules/ec2 com arquivos de configuração. O módulo precisa expor inputs via variáveis e outputs para que o código chamador acesse informações importantes, como o ID da instância ou o IP público.
Estrutura e Organização de Módulos
projeto-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
└── modules/
└── ec2_instance/
├── main.tf
├── variables.tf
└── outputs.tf
Exemplo Prático: Módulo EC2
Vamos criar um módulo reutilizável para provisionar uma instância EC2. Primeiro, o arquivo de variáveis do módulo (modules/ec2_instance/variables.tf):
variable "instance_name" {
description = "Nome da instância EC2"
type = string
}
variable "instance_type" {
description = "Tipo da instância"
type = string
default = "t3.micro"
}
variable "ami_id" {
description = "ID da AMI a usar"
type = string
}
variable "security_group_id" {
description = "ID do grupo de segurança"
type = string
}
variable "tags" {
description = "Tags para a instância"
type = map(string)
default = {}
}
Agora a implementação (modules/ec2_instance/main.tf):
resource "aws_instance" "this" {
ami = var.ami_id
instance_type = var.instance_type
vpc_security_group_ids = [var.security_group_id]
tags = merge(
var.tags,
{
Name = var.instance_name
}
)
}
E os outputs do módulo (modules/ec2_instance/outputs.tf):
output "instance_id" {
description = "ID da instância"
value = aws_instance.this.id
}
output "public_ip" {
description = "IP público da instância"
value = aws_instance.this.public_ip
}
output "private_ip" {
description = "IP privado da instância"
value = aws_instance.this.private_ip
}
Agora, para usar este módulo na configuração principal (main.tf):
module "app_server" {
source = "./modules/ec2_instance"
instance_name = "servidor-aplicacao"
instance_type = "t3.small"
ami_id = "ami-0c55b159cbfafe1f0"
security_group_id = aws_security_group.app.id
tags = {
Environment = "production"
Project = "meu-projeto"
}
}
output "app_server_ip" {
value = module.app_server.public_ip
}
A vantagem aqui é clara: você pode chamar o módulo múltiplas vezes com diferentes parâmetros, e toda a lógica está centralizada. Se precisar ajustar como as instâncias são criadas, faz apenas uma vez no módulo.
Workspaces: Gerenciando Múltiplos Ambientes
Workspaces no Terraform permitem manter múltiplos estados separados dentro da mesma configuração. Eles são úteis quando você precisa de ambientes distintos (desenvolvimento, staging, produção) usando a mesma lógica de código. Sem workspaces, você precisaria duplicar toda a configuração ou usar variáveis complexas para diferenciar ambientes — workspaces oferecem uma solução elegante.
Cada workspace tem seu próprio arquivo de estado (.tfstate), isolando completamente a infraestrutura de um ambiente. Quando você alterna entre workspaces, o Terraform carrega o estado correspondente, aplicando as mudanças apenas àquele ambiente. Isso é particularmente poderoso porque sua configuração permanece idêntica; apenas os valores das variáveis mudam por ambiente.
Funcionamento Básico de Workspaces
Por padrão, o Terraform começa com um workspace chamado default. Você pode criar novos workspaces e alternar entre eles. Vamos criar um exemplo prático onde deployamos a mesma infraestrutura em diferentes ambientes.
# variables.tf
variable "environment" {
description = "Ambiente de deployment"
type = string
default = terraform.workspace
}
variable "instance_count" {
description = "Número de instâncias por ambiente"
type = number
default = (
terraform.workspace == "production" ? 3 :
terraform.workspace == "staging" ? 2 : 1
)
}
variable "instance_type" {
description = "Tipo de instância por ambiente"
type = string
default = (
terraform.workspace == "production" ? "t3.large" :
terraform.workspace == "staging" ? "t3.medium" : "t3.micro"
)
}
# main.tf
resource "aws_instance" "web" {
count = var.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
tags = {
Name = "web-server-${terraform.workspace}-${count.index + 1}"
Environment = var.environment
}
}
Para usar workspaces na prática:
# Listar workspaces existentes
terraform workspace list
# Criar novo workspace
terraform workspace new production
terraform workspace new staging
# Alternar para um workspace
terraform workspace select production
# Verificar workspace atual
terraform workspace show
# Aplicar configuração no workspace atual
terraform apply
# Voltar para default
terraform workspace select default
Observe que o estado é mantido separado. Quando você faz terraform apply no workspace production, o arquivo de estado é específico daquele workspace (geralmente armazenado como terraform.tfstate.d/production/terraform.tfstate). Isso significa que você pode ter 3 instâncias em produção, 2 em staging e 1 em desenvolvimento, tudo gerenciado pelo mesmo código.
Remote State: Compartilhando Estado entre Equipes
Remote state resolve o maior problema do Terraform em equipes: o estado não fica mais em um arquivo local. Quando múltiplas pessoas trabalham no mesmo projeto, alguém precisa gerenciar a versão "correta" do estado. Remote state armazena esse estado em um backend centralizado (como AWS S3, Terraform Cloud, ou Azure Storage), permitindo que toda a equipe trabalhe sobre a mesma versão.
Além de compartilhamento, remote state oferece locking automático — quando alguém está aplicando mudanças, outros são impedidos de fazer o mesmo simultaneamente, evitando corrupção do estado. Também fornece melhor segurança: você pode usar encryption em repouso e em trânsito, além de controlar acesso via políticas de permissão.
Configurando Remote State com AWS S3
O backend mais comum é AWS S3 com DynamoDB para locking. Você precisa criar esses recursos previamente (uma única vez):
# Arquivo separado: backends.tf (criar manualmente na AWS ou via outro Terraform)
resource "aws_s3_bucket" "terraform_state" {
bucket = "meu-projeto-terraform-state"
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Após criar S3 e DynamoDB, configure o backend no seu projeto Terraform (backend.tf):
terraform {
backend "s3" {
bucket = "meu-projeto-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Quando você executa terraform init com essa configuração, o Terraform migra o estado local para S3:
terraform init
# Output: Backend has been successfully initialized!
Integrando Remote State com Workspaces
Remote state funciona perfeitamente com workspaces. Você pode organizar o estado por ambiente usando diferentes chaves S3:
# backend.tf com dynamic path
terraform {
backend "s3" {
bucket = "meu-projeto-terraform-state"
key = "${terraform.workspace}/terraform.tfstate" # NOTA: isso não funciona literalmente
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Na realidade, você precisa usar -backend-config durante o init ou arquivo separado de configuração:
# Inicializar com configuração dinâmica
terraform init -backend-config="key=dev/terraform.tfstate" -backend-config="bucket=meu-projeto-terraform-state"
Ou use arquivo de configuração backend por workspace:
# backend-dev.tf
terraform {
backend "s3" {
bucket = "meu-projeto-terraform-state"
key = "dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Acessando Outputs de Remote State em Outro Projeto
Um caso de uso poderoso é compartilhar outputs entre projetos Terraform diferentes via remote state:
# Em outro projeto que precisa do VPC ID criado em outro projeto
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "meu-projeto-terraform-state"
key = "networking/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
}
}
resource "aws_security_group" "app" {
name = "app-sg"
vpc_id = data.terraform_remote_state.networking.outputs.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Padrão Integrado: Módulos + Workspaces + Remote State
A verdadeira força emerge quando você combina esses três conceitos. Vamos construir uma arquitetura realista com todos os três elementos trabalhando juntos.
Estrutura do Projeto Integrado
infraestrutura-producao/
├── terraform.tf
├── backend.tf
├── variables.tf
├── main.tf
├── terraform.tfvars
├── environments/
│ ├── dev.tfvars
│ ├── staging.tfvars
│ └── prod.tfvars
└── modules/
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── rds/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── app_server/
├── main.tf
├── variables.tf
└── outputs.tf
Backend com Isolamento por Workspace
# backend.tf
terraform {
required_version = ">= 1.0"
backend "s3" {
bucket = "meu-projeto-terraform-state"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# Note: a chave será configurada por workspace dinamicamente
Inicializar para cada workspace com chaves diferentes:
# Development
terraform workspace new dev
terraform init -backend-config="key=dev/terraform.tfstate"
# Staging
terraform workspace new staging
terraform init -backend-config="key=staging/terraform.tfstate"
# Production
terraform workspace new prod
terraform init -backend-config="key=prod/terraform.tfstate"
Configuração Principal com Módulos
# variables.tf
variable "aws_region" {
type = string
}
variable "vpc_cidr" {
type = string
}
variable "database_allocated_storage" {
type = number
}
variable "app_instance_count" {
type = number
}
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
module "vpc" {
source = "./modules/vpc"
cidr_block = var.vpc_cidr
environment = terraform.workspace
}
module "rds" {
source = "./modules/rds"
db_subnet_group_name = module.vpc.db_subnet_group_name
allocated_storage = var.database_allocated_storage
environment = terraform.workspace
}
module "app_servers" {
source = "./modules/app_server"
count = var.app_instance_count
vpc_id = module.vpc.vpc_id
subnet_id = module.vpc.public_subnets[count.index % length(module.vpc.public_subnets)]
database_endpoint = module.rds.endpoint
environment = terraform.workspace
}
output "database_endpoint" {
value = module.rds.endpoint
}
output "app_server_ips" {
value = [for server in module.app_servers : server.public_ip]
}
Arquivos de Variáveis por Ambiente
# environments/dev.tfvars
aws_region = "us-east-1"
vpc_cidr = "10.0.0.0/16"
database_allocated_storage = 20
app_instance_count = 1
# environments/prod.tfvars
aws_region = "us-east-1"
vpc_cidr = "10.0.0.0/16"
database_allocated_storage = 100
app_instance_count = 3
Executando com Tudo Integrado
# Alternar para workspace de produção
terraform workspace select prod
# Aplicar com arquivo de variáveis específico
terraform apply -var-file="environments/prod.tfvars"
# Para desenvolvimento
terraform workspace select dev
terraform apply -var-file="environments/dev.tfvars"
# Verificar qual workspace está ativo
terraform workspace show
Conclusão
Você aprendeu que módulos encapsulam lógica reutilizável, permitindo organizar código complexo em componentes gerenciáveis que definem uma interface clara via inputs e outputs. Isso elimina duplicação e facilita manutenção centralizada da infraestrutura.
Workspaces isolam estados completamente, permitindo múltiplos ambientes (dev, staging, prod) usando o mesmo código com valores diferentes. Cada workspace mantém seu próprio estado separado, e variáveis condicionais adaptam a infraestrutura conforme o ambiente selecionado.
Remote state compartilha o estado entre equipes de forma segura, armazenando em backend centralizado (S3 + DynamoDB) com encryption e locking automático. Isso previne conflitos simultâneos e oferece auditoria de mudanças via versionamento. A integração com remote state data sources permite compartilhar outputs entre projetos diferentes, criando dependências limpas entre stacks de infraestrutura.