Terraform Associate

Deep Dive

Practicar ahora
D5 · Módulos de Terraform

Módulos: estructura, inputs y outputs

Los módulos son el mecanismo principal de reutilización y encapsulación en Terraform. Entender la diferencia entre módulo raíz y módulos hijos, cómo pasan datos y la estructura estándar de archivos es fundamental para el examen.

¿Qué es un módulo?

Un módulo es simplemente un directorio que contiene uno o más archivos .tf. No hay nada especial en él: cualquier directorio con archivos .tf es un módulo.

Los módulos permiten encapsular y reutilizar configuraciones. En lugar de repetir la misma configuración de VPC en 10 proyectos, la defines una vez en un módulo y la llamas con diferentes parámetros.

Encapsulación

Oculta la complejidad interna. El usuario solo necesita conocer los inputs y outputs.

Reutilización

El mismo módulo puede usarse con diferentes variables para dev, staging y prod.

Consistencia

Todos los proyectos siguen el mismo patrón porque usan el mismo módulo.

Módulo raíz vs módulos hijos

Módulo raíz (root module)

  • • El directorio donde ejecutas terraform init/plan/apply
  • • Siempre existe — es el punto de entrada
  • • Contiene el estado de toda la infraestructura gestionada
  • • Puede llamar a módulos hijos
  • • Sus outputs se muestran en el apply
  • /infra/prod/ ← módulo raíz

Módulos hijos (child modules)

  • • Módulos llamados por el módulo raíz u otros módulos
  • • No tienen estado propio — comparten el del módulo raíz
  • • Reciben datos vía variables (inputs)
  • • Exponen datos vía outputs
  • • Pueden estar locales o en el registry
  • /modules/vpc/ ← módulo hijo
# Estructura típica con módulos:
infra/
├── prod/
│   ├── main.tf         ← módulo raíz (llama a módulos hijos)
│   ├── variables.tf
│   └── outputs.tf
└── modules/
    ├── vpc/            ← módulo hijo (networking)
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── eks/            ← módulo hijo (cluster)
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

Llamar a un módulo

Se usa el bloque module con una etiqueta (nombre local), el argumento source (obligatorio) y los inputs del módulo como argumentos adicionales.

# Llamar a un módulo local
module "vpc" {
  source = "./modules/vpc"      # source obligatorio

  # Inputs del módulo (variables definidas en modules/vpc/variables.tf)
  name       = "mi-vpc"
  cidr       = "10.0.0.0/16"
  azs        = ["us-east-1a", "us-east-1b"]
  environment = var.environment
}

# Llamar a un módulo del registry (con version obligatorio)
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "mi-cluster"
  cluster_version = "1.29"
  vpc_id          = module.vpc.vpc_id         # output del módulo vpc
  subnet_ids      = module.vpc.private_subnet_ids
}

# Acceder a outputs del módulo:
# module.<nombre_local>.<nombre_output>
output "cluster_endpoint" {
  value = module.eks.cluster_endpoint
}

💡 terraform get vs terraform init

Ambos descargan los módulos al directorio .terraform/modules/. La diferencia es que terraform init también instala providers y configura el backend, mientras que terraform get solo descarga módulos. En la práctica siempre usas init.

Inputs y outputs del módulo

Los módulos se comunican con su entorno a través de variables (inputs) y outputs. Este es el contrato del módulo.

modules/vpc/variables.tf (inputs)

variable "name" {
  type        = string
  description = "Nombre de la VPC"
}

variable "cidr" {
  type        = string
  default     = "10.0.0.0/16"
  description = "CIDR block de la VPC"
}

variable "azs" {
  type        = list(string)
  description = "Lista de zonas de disponibilidad"
}

modules/vpc/outputs.tf (outputs)

output "vpc_id" {
  value       = aws_vpc.main.id
  description = "ID de la VPC creada"
}

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

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

⚠️ Solo puedes acceder a lo que el módulo expone como output

Desde el módulo padre, no puedes acceder a recursos internos del módulo hijo directamente (por ejemplo, module.vpc.aws_vpc.main.id no funciona). Solo puedes usar los outputs explícitamente definidos en el módulo hijo (module.vpc.vpc_id). Esto garantiza el encapsulamiento.

Estructura estándar de archivos

HashiCorp define una estructura estándar recomendada para módulos. Los módulos del Terraform Registry deben seguirla.

main.tf

Recomendado

Recursos principales del módulo. Es el punto de entrada conceptual. Para módulos complejos, puede dividirse en múltiples archivos (network.tf, compute.tf, etc.).

variables.tf

Recomendado

Todas las declaraciones variable {}. Define el contrato de entrada del módulo.

outputs.tf

Recomendado

Todos los bloques output {}. Define qué expone el módulo hacia afuera.

README.md

Registry

Documentación del módulo: qué hace, inputs, outputs, ejemplos de uso. Obligatorio para publicar en el registry.

versions.tf

Recomendado

Bloque terraform {} con required_version y required_providers. Separa los requisitos de versión del código principal.

locals.tf

Opcional

Bloque locals {} para valores calculados. Solo necesario si hay muchos locales.

CHANGELOG.md

Registry

Historial de cambios por versión. Importante para módulos publicados.

examples/

Registry

Directorio con ejemplos de uso del módulo. Cada subdirectorio es un ejemplo completo y funcional.

¿Entendiste este tema?

Pon a prueba lo que acabas de aprender

El módulo raíz llama a un módulo hijo 'networking' que crea una VPC y define un output llamado 'vpc_id'. ¿Cómo accede el módulo raíz a ese valor?