Pular para conteúdo

Criando Módulos

Um módulo Terraform é simplesmente um diretório com arquivos .tf. Qualquer diretório é um módulo. A questão é como estruturá-lo bem para que seja fácil de usar e manter.


Anatomia de um Módulo

modules/vpc/
├── main.tf        # recursos que o módulo cria
├── variables.tf   # interface de entrada (o que o consumidor precisa informar)
├── outputs.tf     # interface de saída (o que o consumidor pode usar)
└── versions.tf    # versões do Terraform e provider requeridas

Exemplo Completo: Módulo VPC

modules/vpc/variables.tf

variable "name" {
  type        = string
  description = "Nome do projeto — usado como prefixo em todos os recursos"
}

variable "environment" {
  type        = string
  description = "Ambiente (dev, staging, prod)"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Ambiente deve ser dev, staging ou prod."
  }
}

variable "vpc_cidr" {
  type        = string
  description = "Bloco CIDR da VPC (ex: 10.0.0.0/16)"
  default     = "10.0.0.0/16"
}

variable "public_subnet_cidrs" {
  type        = list(string)
  description = "Lista de CIDRs para subnets públicas (uma por AZ)"
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "private_subnet_cidrs" {
  type        = list(string)
  description = "Lista de CIDRs para subnets privadas (uma por AZ)"
  default     = ["10.0.11.0/24", "10.0.12.0/24"]
}

variable "tags" {
  type        = map(string)
  description = "Tags adicionais a serem aplicadas em todos os recursos"
  default     = {}
}

modules/vpc/main.tf

locals {
  name_prefix = "${var.name}-${var.environment}"

  common_tags = merge(var.tags, {
    Name        = local.name_prefix
    Environment = var.environment
    ManagedBy   = "terraform"
  })
}

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = merge(local.common_tags, { Name = "${local.name_prefix}-vpc" })
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = merge(local.common_tags, { Name = "${local.name_prefix}-igw" })
}

resource "aws_subnet" "public" {
  count = length(var.public_subnet_cidrs)

  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  map_public_ip_on_launch = true

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-public-${count.index + 1}"
    Tier = "public"
  })
}

resource "aws_subnet" "private" {
  count = length(var.private_subnet_cidrs)

  vpc_id     = aws_vpc.main.id
  cidr_block = var.private_subnet_cidrs[count.index]

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-private-${count.index + 1}"
    Tier = "private"
  })
}

modules/vpc/outputs.tf

output "vpc_id" {
  value       = aws_vpc.main.id
  description = "ID da VPC criada"
}

output "vpc_cidr" {
  value       = aws_vpc.main.cidr_block
  description = "Bloco CIDR da VPC"
}

output "public_subnet_ids" {
  value       = aws_subnet.public[*].id
  description = "Lista de IDs das subnets públicas"
}

output "private_subnet_ids" {
  value       = aws_subnet.private[*].id
  description = "Lista de IDs das subnets privadas"
}

output "internet_gateway_id" {
  value       = aws_internet_gateway.main.id
  description = "ID do Internet Gateway"
}

Consumindo o Módulo

# dev/main.tf

module "vpc" {
  source = "../modules/vpc"   # caminho relativo ao módulo

  name        = "meu-projeto"
  environment = "dev"
  vpc_cidr    = "10.0.0.0/16"

  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]
}

# Usando outputs do módulo em outro recurso
resource "aws_security_group" "app" {
  vpc_id = module.vpc.vpc_id   # ← acessa output do módulo

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [module.vpc.vpc_cidr]
  }
}

Boas Práticas de Interface de Módulo

Faça

  • Inputs opcionais com defaults sensatos — o consumidor não deve ser forçado a passar tudo
  • Validações nas variáveis — falhe cedo com mensagem clara
  • Outputs de todos os IDs e ARNs criados — outros módulos vão precisar
  • Uma única responsabilidade — módulo VPC cria rede, não EC2

Evite

  • Módulos que criam recursos de tipos completamente diferentes — difícil de testar e manter
  • Hardcodar valores dentro do módulo — use variáveis
  • Expor recursos internos pelo estado — use outputs definidos

Próximo

➡️ Terraform Registry — como usar módulos prontos e validados pela comunidade.