<h2>Entendendo Módulos no Terraform</h2>
<p>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.</p>
<p>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 <code>modules/ec2</code> 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.</p>
<h3>Estrutura e Organização de Módulos</h3>
<pre><code>projeto-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
└── modules/
└── ec2_instance/
├── main.tf
├── variables.tf
└── outputs.tf</code></pre>
<h3>Exemplo Prático: Módulo EC2</h3>
<p>Vamos criar um módulo reutilizável para provisionar uma instância EC2. Primeiro, o arquivo de variáveis do módulo (<code>modules/ec2_instance/variables.tf</code>):</p>
<pre><code class="language-hcl">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 = {}
}</code></pre>
<p>Agora a implementação (<code>modules/ec2_instance/main.tf</code>):</p>
<pre><code class="language-hcl">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
}
)
}</code></pre>
<p>E os outputs do módulo (<code>modules/ec2_instance/outputs.tf</code>):</p>
<pre><code class="language-hcl">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
}</code></pre>
<p>Agora, para usar este módulo na configuração principal (<code>main.tf</code>):</p>
<pre><code class="language-hcl">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
}</code></pre>
<p>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.</p>
<p>---</p>
<h2>Workspaces: Gerenciando Múltiplos Ambientes</h2>
<p>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.</p>
<p>Cada workspace tem seu próprio arquivo de estado (<code>.tfstate</code>), 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.</p>
<h3>Funcionamento Básico de Workspaces</h3>
<p>Por padrão, o Terraform começa com um workspace chamado <code>default</code>. Você pode criar novos workspaces e alternar entre eles. Vamos criar um exemplo prático onde deployamos a mesma infraestrutura em diferentes ambientes.</p>
<pre><code class="language-hcl"># 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"
)
}</code></pre>
<pre><code class="language-hcl"># 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
}
}</code></pre>
<p>Para usar workspaces na prática:</p>
<pre><code class="language-bash"># 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</code></pre>
<p>Observe que o estado é mantido separado. Quando você faz <code>terraform apply</code> no workspace <code>production</code>, o arquivo de estado é específico daquele workspace (geralmente armazenado como <code>terraform.tfstate.d/production/terraform.tfstate</code>). 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.</p>
<p>---</p>
<h2>Remote State: Compartilhando Estado entre Equipes</h2>
<p>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.</p>
<p>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.</p>
<h3>Configurando Remote State com AWS S3</h3>
<p>O backend mais comum é AWS S3 com DynamoDB para locking. Você precisa criar esses recursos previamente (uma única vez):</p>
<pre><code class="language-hcl"># 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"
}
}</code></pre>
<p>Após criar S3 e DynamoDB, configure o backend no seu projeto Terraform (<code>backend.tf</code>):</p>
<pre><code class="language-hcl">terraform {
backend "s3" {
bucket = "meu-projeto-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}</code></pre>
<p>Quando você executa <code>terraform init</code> com essa configuração, o Terraform migra o estado local para S3:</p>
<pre><code class="language-bash">terraform init
Output: Backend has been successfully initialized!</code></pre>
<h3>Integrando Remote State com Workspaces</h3>
<p>Remote state funciona perfeitamente com workspaces. Você pode organizar o estado por ambiente usando diferentes chaves S3:</p>
<pre><code class="language-hcl"># 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
}
}</code></pre>
<p>Na realidade, você precisa usar <code>-backend-config</code> durante o init ou arquivo separado de configuração:</p>
<pre><code class="language-bash"># Inicializar com configuração dinâmica
terraform init -backend-config="key=dev/terraform.tfstate" -backend-config="bucket=meu-projeto-terraform-state"</code></pre>
<p>Ou use arquivo de configuração backend por workspace:</p>
<pre><code class="language-hcl"># 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
}
}</code></pre>
<h3>Acessando Outputs de Remote State em Outro Projeto</h3>
<p>Um caso de uso poderoso é compartilhar outputs entre projetos Terraform diferentes via remote state:</p>
<pre><code class="language-hcl"># 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"]
}
}</code></pre>
<p>---</p>
<h2>Padrão Integrado: Módulos + Workspaces + Remote State</h2>
<p>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.</p>
<h3>Estrutura do Projeto Integrado</h3>
<pre><code>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</code></pre>
<h3>Backend com Isolamento por Workspace</h3>
<pre><code class="language-hcl"># 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</code></pre>
<p>Inicializar para cada workspace com chaves diferentes:</p>
<pre><code class="language-bash"># 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"</code></pre>
<h3>Configuração Principal com Módulos</h3>
<pre><code class="language-hcl"># 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]
}</code></pre>
<h3>Arquivos de Variáveis por Ambiente</h3>
<pre><code class="language-hcl"># environments/dev.tfvars
aws_region = "us-east-1"
vpc_cidr = "10.0.0.0/16"
database_allocated_storage = 20
app_instance_count = 1</code></pre>
<pre><code class="language-hcl"># environments/prod.tfvars
aws_region = "us-east-1"
vpc_cidr = "10.0.0.0/16"
database_allocated_storage = 100
app_instance_count = 3</code></pre>
<h3>Executando com Tudo Integrado</h3>
<pre><code class="language-bash"># 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</code></pre>
<p>---</p>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>módulos encapsulam lógica reutilizável</strong>, 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.</p>
<p><strong>Workspaces isolam estados completamente</strong>, 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.</p>
<p><strong>Remote state compartilha o estado entre equipes de forma segura</strong>, 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.</p>
<p>---</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.terraform.io/language/modules" target="_blank" rel="noopener noreferrer">Terraform Modules - Documentação Oficial</a></li>
<li><a href="https://www.terraform.io/language/state" target="_blank" rel="noopener noreferrer">State Management with Terraform - Hashicorp</a></li>
<li><a href="https://www.terraform.io/cloud-docs/workspaces" target="_blank" rel="noopener noreferrer">Terraform Workspaces - Official Documentation</a></li>
<li><a href="https://www.terraform.io/language/settings/backends/s3" target="_blank" rel="noopener noreferrer">AWS S3 Backend Configuration - Terraform</a></li>
<li><a href="https://www.terraformbestpractices.com/" target="_blank" rel="noopener noreferrer">Terraform Best Practices - Yevgeniy Brikman</a></li>
</ul>
<p><!-- FIM --></p>