Infrastructure

📦 Modules in Terraform: Reusable and Scalable Architecture

A Terraform Module is a set of .tf configuration files packaged in a single folder to define a reusable infrastructure component (for example: a full VPC network, a Kubernetes EKS cluster, or a PostgreSQL database with replicas).

1. What is a Module and existing types?

In Terraform, any directory that contains .tf files is considered a module. There are two main classifications:

  • Root Module: The main directory from which you run the terraform apply or terraform plan commands. Contains global configuration and calls other modules.
  • Child Module: A packaged child module that is called within the Root Module using a module "nombre" {} block.

Programmatic Analogy: Think of a Module as a Function in traditional languages. It accepts input parameters (variables.tf), executes business logic internally (main.tf), and returns a result (outputs.tf).

To follow community and HashiCorp conventions, the internal structure of any module must be clean and predictable:

text
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. Creating your First Reusable Module (Child Module)

Let's design a basic module to provision an EC2 web server with its corresponding Security Group:

hcl
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"
}
hcl
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
  }
}
hcl
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. Invocation of Modules from the Root Module

Once the module is created, it is invoked from the main main.tf file by passing the required parameters through the module block:

hcl
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. Module Sources

The source directive tells Terraform where to download the module code from:

  • Relative Local Route: source = "./modules/vpc"
  • Private or Public Git Repository: source = "git::https://example.com/storage.git?ref=v1.2.0"
  • Public Terraform Registry: source = "terraform-aws-modules/vpc/aws"
  • Private Registry (HCP / Terraform Cloud): source = "app.terraform.io/mi-org/vpc/aws"

6. Good Versioning Practices

When modules are shared across multiple teams or projects, it is critical to use strict versioning to not break infrastructures in production if the module definition changes:

hcl
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. Golden Rules in Module Construction

  1. Single Responsibility Principle: A module must do only one thing well (e.g. only manage the database or only the network, not everything together).
  1. Avoid Hardcoding Values: Do not place regions, subnet IDs or fixed names within the module code. State them as variables.tf.
  1. Document Outputs: Expose primary identifiers (IDs, ARNs, endpoints) using outputs.tf to allow composition with other modules.