Voltar para Artigos

Terraform: Boas Práticas para Infrastructure as Code

10 min

Aprenda as melhores práticas para escrever código Terraform limpo, seguro e manutenível em projetos de produção.


titulo: "Terraform: Boas Práticas para Infrastructure as Code" descricao: "Aprenda as melhores práticas para escrever código Terraform limpo, seguro e manutenível em projetos de produção." data: "2025-11-28" autor: "Rany" tags: ["Terraform", "IaC", "DevOps", "Cloud"] tempoLeitura: "10 min"

Introdução

O Terraform se tornou a ferramenta padrão para Infrastructure as Code (IaC). Mas escrever código Terraform de qualidade vai além de apenas fazer funcionar. Vamos explorar as melhores práticas adotadas pela indústria.

Estrutura de Diretórios

Organização Recomendada

terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   └── ...
│   └── prod/
│       └── ...
├── modules/
│   ├── networking/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── compute/
│   │   └── ...
│   └── database/
│       └── ...
└── global/
    └── s3-backend/
        └── main.tf

1. Use Remote State

NUNCA guarde o state file localmente em produção.

Configuração com S3 e DynamoDB

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

Por que DynamoDB?

O DynamoDB fornece state locking, prevenindo que múltiplas pessoas executem terraform apply simultaneamente.

resource "aws_dynamodb_table" "terraform_state_lock" {
  name           = "terraform-state-lock"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }

  tags = {
    Name        = "Terraform State Lock"
    Environment = "global"
  }
}

2. Use Módulos para Reusabilidade

Não duplique código! Crie módulos reutilizáveis.

Exemplo de Módulo de VPC

# modules/networking/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = merge(
    var.common_tags,
    {
      Name = "${var.project_name}-vpc"
    }
  )
}

resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  map_public_ip_on_launch = true

  tags = merge(
    var.common_tags,
    {
      Name = "${var.project_name}-public-${count.index + 1}"
      Type = "public"
    }
  )
}

Uso do Módulo

# environments/prod/main.tf
module "networking" {
  source = "../../modules/networking"

  project_name         = "myapp"
  vpc_cidr            = "10.0.0.0/16"
  public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
  availability_zones  = ["us-east-1a", "us-east-1b"]

  common_tags = {
    Environment = "production"
    ManagedBy   = "Terraform"
    Team        = "DevOps"
  }
}

3. Use Variáveis e Outputs Corretamente

variables.tf

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"

  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Instance type must be from t3 family."
  }
}

variable "environment" {
  description = "Environment name"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "common_tags" {
  description = "Common tags to apply to all resources"
  type        = map(string)
  default     = {}
}

outputs.tf

output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "List of public subnet IDs"
  value       = aws_subnet.public[*].id
}

output "db_endpoint" {
  description = "RDS instance endpoint"
  value       = aws_db_instance.main.endpoint
  sensitive   = true  # Não mostra no output
}

4. Gerencie Secrets com Segurança

NUNCA coloque secrets diretamente no código!

Use AWS Secrets Manager

data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "prod/database/password"
}

resource "aws_db_instance" "main" {
  identifier        = "myapp-db"
  engine            = "postgres"
  instance_class    = "db.t3.micro"
  allocated_storage = 20

  username = "admin"
  password = jsondecode(data.aws_secretsmanager_secret_version.db_password.secret_string)["password"]

  skip_final_snapshot = false
  final_snapshot_identifier = "myapp-db-final-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
}

Use .tfvars para Valores Sensíveis

# terraform.tfvars (não commite este arquivo!)
db_password = "super-secret-password"
api_key     = "your-api-key"

Adicione ao .gitignore:

# .gitignore
*.tfvars
.terraform/
.terraform.lock.hcl
terraform.tfstate*

5. Use Data Sources para Recursos Existentes

Não hardcode valores. Use data sources!

# Buscar AMI mais recente do Ubuntu
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name = "web-server"
  }
}

6. Use Lifecycle Rules

Controle o comportamento do Terraform em situações específicas.

resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  lifecycle {
    # Previne destruição acidental
    prevent_destroy = true

    # Cria novo recurso antes de destruir o antigo
    create_before_destroy = true

    # Ignora mudanças em tags específicas
    ignore_changes = [
      tags["LastUpdated"],
    ]
  }
}

7. Use Workspaces para Ambientes

# Criar e usar workspaces
terraform workspace new dev
terraform workspace new prod

# Listar workspaces
terraform workspace list

# Selecionar workspace
terraform workspace select prod

No código:

locals {
  environment = terraform.workspace

  instance_counts = {
    dev     = 1
    staging = 2
    prod    = 5
  }
}

resource "aws_instance" "app" {
  count         = local.instance_counts[local.environment]
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.environment == "prod" ? "t3.large" : "t3.micro"

  tags = {
    Name        = "app-${local.environment}-${count.index + 1}"
    Environment = local.environment
  }
}

8. Use Formatação e Validação

Comandos Essenciais

# Formatar código
terraform fmt -recursive

# Validar configuração
terraform validate

# Planejar mudanças
terraform plan -out=tfplan

# Aplicar plano salvo
terraform apply tfplan

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.77.1
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_docs
      - id: terraform_tflint

9. Use Count e For_Each Sabiamente

Count - Para Recursos Idênticos

resource "aws_instance" "web" {
  count         = 3
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = {
    Name = "web-${count.index + 1}"
  }
}

For_Each - Para Recursos Variados

variable "users" {
  type = map(object({
    role   = string
    groups = list(string)
  }))
  default = {
    "john" = {
      role   = "developer"
      groups = ["dev", "qa"]
    }
    "jane" = {
      role   = "admin"
      groups = ["admin", "dev"]
    }
  }
}

resource "aws_iam_user" "users" {
  for_each = var.users
  name     = each.key

  tags = {
    Role = each.value.role
  }
}

10. Documente Seu Código

Use comentários e terraform-docs:

# Create VPC with public and private subnets across multiple AZs
# This module follows AWS best practices for network segmentation
#
# Example usage:
#   module "vpc" {
#     source = "./modules/networking"
#     vpc_cidr = "10.0.0.0/16"
#   }
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

Gere documentação automática:

terraform-docs markdown table . > README.md

Checklist Final

✅ Remote state configurado com locking ✅ Módulos para código reutilizável ✅ Variáveis com validação ✅ Secrets gerenciados com segurança ✅ Data sources ao invés de hardcoded values ✅ Lifecycle rules onde apropriado ✅ Workspaces para ambientes ✅ Código formatado e validado ✅ Documentação atualizada ✅ .gitignore configurado

Conclusão

Seguir essas práticas vai te ajudar a:

  • Manter código mais limpo e manutenível
  • Evitar erros comuns
  • Facilitar colaboração em equipe
  • Aumentar segurança
  • Reduzir tempo de troubleshooting

Terraform é poderoso, mas com grandes poderes vêm grandes responsabilidades. Código de infraestrutura mal escrito pode causar desastres em produção. Invista tempo em fazer certo desde o início!