📦 Módulos en Terraform: Arquitectura Reutilizable y Escalable
Un Módulo de Terraform es un conjunto de archivos de configuración .tf empaquetados en una misma carpeta para definir un componente de infraestructura reutilizable (por ejemplo: una red VPC completa, un clúster de Kubernetes EKS o una base de datos PostgreSQL con réplicas).
1. ¿Qué es un Módulo y tipos existentes?
En Terraform, cualquier directorio que contenga archivos .tf se considera un módulo. Existen dos clasificaciones principales:
- Root Module (Módulo Raíz): El directorio principal desde el cual ejecutas los comandos
terraform applyoterraform plan. Contiene la configuración global y llama a otros módulos. - Child Module (Módulo Hijo): Un módulo secundario empaquetado que es llamado dentro del Root Module mediante un bloque
module "nombre" {}.
variables.tf), ejecuta lógica de negocio internamente (main.tf) y devuelve un resultado (outputs.tf).
2. Estructura de Archivos Recomendada
Para seguir las convenciones de la comunidad y HashiCorp, la estructura interna de cualquier módulo debe ser limpia y predecible:
modules/aws-vpc/
├── README.md # Documentación de uso y parámetros
├── main.tf # Recursos principales (VPC, Subnets, Gateways)
├── variables.tf # Variables de entrada expuestas al usuario
├── outputs.tf # Valores retornados (IDs de subnets, VPC ID, etc.)
├── versions.tf # Versión mínima de Terraform y proveedores
└── examples/ # Ejemplos de implementación listos para usar
└── basic-vpc/
└── main.tf
3. Creando tu Primer Módulo Reutilizable (Child Module)
Diseñemos un módulo básico para aprovisionar un servidor web EC2 con su Security Group correspondiente:
variable "instance_type" {
type = string
default = "t3.micro"
description = "Tipo de instancia EC2"
}
variable "server_name" {
type = string
description = "Nombre identificador del servidor"
}
variable "vpc_id" {
type = string
description = "ID de la VPC donde residirá el servidor"
}
resource "aws_security_group" "web_sg" {
name = "${var.server_name}-sg"
description = "Allow HTTP traffic"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2023
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web_sg.id]
tags = {
Name = var.server_name
}
}
output "instance_id" {
value = aws_instance.web.id
description = "ID de la instancia EC2 creada"
}
output "public_ip" {
value = aws_instance.web.public_ip
description = "Dirección IP pública del servidor"
}
4. Invocación de Módulos desde el Root Module
Una vez creado el módulo, se invoca desde el archivo main.tf principal pasándole los parámetros requeridos mediante el bloque module:
module "frontend_server" {
source = "./modules/web-server"
server_name = "mc-frontend-prod"
instance_type = "t3.small"
vpc_id = "vpc-0a1b2c3d4e5f"
}
# Referenciando la salida (output) del módulo en otro recurso
output "app_url" {
value = "http://${module.frontend_server.public_ip}"
}
5. Fuentes de Módulos (Module Sources)
La directiva source indica a Terraform desde dónde debe descargar el código del módulo:
- Ruta Local Relative:
source = "./modules/vpc" - Repositorio Git Privado o Público:
source = "git::https://example.com/storage.git?ref=v1.2.0" - Terraform Registry Público:
source = "terraform-aws-modules/vpc/aws" - Private Registry (HCP / Terraform Cloud):
source = "app.terraform.io/mi-org/vpc/aws"
6. Buenas Prácticas de Versionado
Cuando los módulos se comparten entre múltiples equipos o proyectos, es crítico usar control de versiones estricto para no romper infraestructuras en producción si cambia la definición del módulo:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.8.1" # Fijar siempre la versión exacta en producción
name = "mc-production-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
7. Reglas de Oro en la Construcción de Módulos
- Principio de Responsabilidad Única: Un módulo debe hacer una sola cosa bien (ej. solo gestionar la base de datos o solo la red, no todo junto).
- Evita Hardcodear Valores: No coloques regiones, IDs de subnets ni nombres fijos dentro del código del módulo. Exponlos como `variables.tf`.
- Documenta los Outputs: Expon los identificadores principales (IDs, ARNs, endpoints) mediante `outputs.tf` para permitir la composición con otros módulos.