En los artículos anteriores fuimos construyendo las piezas fundamentales de Terraform.
Ya vimos:
- Resources.
- Providers.
- Variables.
- Outputs.
- State.
Hasta ahora nuestros ejemplos fueron pequeños.
Pero imaginemos un proyecto real.
Podríamos tener:
- network
- database
- application
- load balancer
- monitoring
- security
Y cada una de esas partes puede tener varios recursos.
Nuestro main.tf podría empezar a crecer. Y además podemos terminar repitiendo configuraciones.
Por ejemplo, quizás necesitamos crear la misma infraestructura para:
- development
- staging
- production
¿Cómo evitamos copiar y pegar cientos de líneas?
La respuesta son los modules.
¿Qué es un módulo?
Un módulo de Terraform es, básicamente, un conjunto de archivos Terraform que encapsula una determinada funcionalidad o parte de nuestra infraestructura.
Podemos pensar en un módulo como una función.
Una función recibe parámetros:
entrada
↓
función
↓
resultado
Un módulo hace algo conceptualmente parecido:
variables
↓
módulo
↓
resources
↓
outputs
Por ejemplo, podríamos crear un módulo llamado: database. Que se encargue de crear una base de datos completa.
Desde afuera solamente necesitamos decir:
- Quiero una database
- con este nombre
- con este tamaño
- en este ambiente
El módulo se ocupa del resto.
Vamos a crear una estructura sencilla:
terraform/
│
├── main.tf
│
└── modules/
│
└── file/
│
├── main.tf
├── variables.tf
└── outputs.tf
Nuestro módulo estará en:
modules/file
Y tendrá tres archivos.
En:
modules/file/main.tf
podemos tener:
resource "local_file" "file" {
filename = var.filename
content = var.content
}
El módulo necesita dos valores:
- filename
- content
Por eso los definimos como variables.
En:
modules/file/variables.tf
tenemos:
variable "filename" {
type = string
}
variable "content" {
type = string
}
Ahora nuestro módulo tiene una interfaz. Desde afuera no necesitamos saber cómo está implementado.
Solamente necesitamos conocer sus entradas.
Podemos devolver información desde el módulo.
En:
modules/file/outputs.tf
escribimos:
output "filename" {
value = local_file.file.filename
}
Ahora nuestro módulo tiene:
Entradas
│
├── filename
└── content
│
▼
módulo
│
▼
resource
│
▼
Outputs
│
└── filename
Ya tenemos algo parecido a una función.
Ahora podemos utilizar nuestro módulo desde el main.tf principal:
terraform {
required_providers {
local = {
source = "hashicorp/local"
}
}
}
provider "local" {}
module "hello" {
source = "./modules/file"
filename = "hello.txt"
content = "Hola desde un módulo!"
}
La parte importante es:
module "hello" {
source = "./modules/file"
filename = "hello.txt"
content = "Hola desde un módulo!"
}
Estamos diciendo: Utilizá el módulo que está en `./modules/file`.
Y le pasamos sus parámetros.
Nuestro main.tf ya no necesita conocer los detalles internos del resource. Y esto nos permite separar responsabilidades. El módulo sabe cómo crear el recurso.
El código principal sabe qué recurso quiere utilizar.
Podemos utilizar el mismo módulo varias veces.
Por ejemplo:
module "hello" {
source = "./modules/file"
filename = "hello.txt"
content = "Hola!"
}
module "goodbye" {
source = "./modules/file"
filename = "goodbye.txt"
content = "Chau!"
}
Tenemos dos instancias del mismo módulo.
No copiamos la implementación. La reutilizamos.
Recordemos que nuestro módulo tiene:
output "filename" {
value = local_file.file.filename
}
Desde el módulo principal podemos acceder al output:
output "hello_file" {
value = module.hello.filename
}
La sintaxis:
module.hello.filename
significa:
module
│
└── instancia "hello"
│
└── output "filename"
Esto es muy parecido a acceder al resultado de una función.
Este concepto es muy importante.
Nuestro módulo tiene una especie de API.
Entradas:
- filename
- content
Salidas:
- filename
Internamente podría tener:
- 10 resources
- 20 resources
- 100 resources
Pero quien lo utiliza no necesita conocer todos esos detalles. Esto permite encapsular complejidad.
Y en infraestructura real esto es extremadamente útil.
Supongamos que nuestra aplicación necesita una máquina virtual.
Podríamos tener un módulo:
modules/
└── application/
├── main.tf
├── variables.tf
└── outputs.tf
Internamente podría crear:
- Virtual Machine
- Network Interface
- Security Group
- Disk
Desde nuestro proyecto principal podríamos utilizarlo así:
module "application" {
source = "./modules/application"
environment = "production"
instance_type = "small"
}
El módulo podría encargarse de todos los detalles.
El código principal queda mucho más limpio:
main.tf
application
│
├── network
├── machine
├── security
└── disk
En lugar de tener cientos de recursos directamente en el main.tf.
Ahora podemos combinar lo que vimos en artículos anteriores.
Supongamos que queremos:
- development
- staging
- production
Podemos tener diferentes configuraciones que utilizan los mismos módulos.
Por ejemplo:
terraform/
│
├── modules/
│ └── application/
│
├── environments/
│ ├── development/
│ │ └── main.tf
│ │
│ ├── staging/
│ │ └── main.tf
│ │
│ └── production/
│ └── main.tf
En development:
module "application" {
source = "../../modules/application"
environment = "development"
instance_type = "small"
}
En producción:
module "application" {
source = "../../modules/application"
environment = "production"
instance_type = "large"
}
El módulo es el mismo.
Lo que cambia es la configuración que le pasamos.
Esto nos permite evitar una práctica peligrosa:
development/
copia de todo Terraform
production/
otra copia de todo Terraform
Si copiamos infraestructura, tarde o temprano las copias empiezan a divergir.
Con módulos podemos reutilizar la implementación.
Hasta ahora utilizamos:
source = "./modules/application"
Es decir, un módulo local.
Pero Terraform también permite utilizar módulos almacenados en otros lugares.
Por ejemplo, podemos obtener módulos desde:
- Git;
- registros de módulos;
- repositorios privados;
- otros sistemas compatibles con Terraform.
Un ejemplo podría ser:
module "application" {
source = "git::https://example.com/infrastructure.git"
}
La idea es que un módulo puede convertirse en una pieza reutilizable entre diferentes proyectos.
Los módulos también pueden tener versiones. Cuando una infraestructura se vuelve importante, queremos evitar que una actualización de un módulo rompa todos nuestros ambientes.
Por eso es habitual trabajar con versiones.
Un proyecto puede utilizar una versión determinada.
Esto es especialmente importante cuando los módulos son compartidos por diferentes equipos.
Este es un error bastante común cuando empezamos a utilizar módulos.
Podemos terminar haciendo:
module "resource1"
module "resource2"
module "resource3"
module "resource4"
para absolutamente todo.
Y terminamos con una infraestructura más complicada que la original.
Un módulo tiene sentido cuando encapsula una unidad reutilizable o conceptualmente coherente.
Por ejemplo:
- network
- database
- application
- monitoring
pueden ser buenos candidatos.
Crear un módulo solamente porque tenemos un resource:
module "s3_bucket"
no necesariamente aporta valor.
Depende del proyecto y de cuánto comportamiento adicional encapsule.
Una forma interesante de pensar Terraform es como si estuviéramos construyendo una aplicación.
En una aplicación tenemos:
- clases
- funciones
- componentes
- librerías
En Terraform podemos tener:
- resources
- modules
- providers
Un módulo puede encapsular una parte de nuestra infraestructura de la misma manera que una librería encapsula funcionalidad de una aplicación.
Por ejemplo:
Aplicación
│
├── UserService
├── PaymentService
└── NotificationService
Terraform:
Infraestructura
│
├── Network
├── Database
└── Application
La comparación no es perfecta, pero ayuda a entender la idea.
Un módulo no es solamente una forma de ordenar archivos. Este punto es importante.
Podríamos pensar:"Entonces los módulos sirven solamente para tener el código más ordenado."
No.
Su verdadero valor aparece cuando conseguimos:
- reutilizar infraestructura;
- encapsular detalles;
- definir interfaces mediante variables;
- devolver resultados mediante outputs;
- versionar componentes;
- compartir componentes entre proyectos.
Por eso un módulo puede convertirse en una verdadera unidad de diseño de infraestructura.



.jpeg)
.jpeg)


