← Todos los artículos

Módulos de Terraform que escalan con su equipo, no contra él

Un módulo de Terraform es la forma en que un equipo deja de copiar y pegar infraestructura. Pero un módulo que no se versiona, revisa ni prueba no escala al equipo: escala el radio del daño. Un cambio descuidado en un módulo 'vpc' compartido y cada entorno que lo referencia hereda el fallo en el siguiente apply. La solución es dejar de tratar los módulos como carpetas compartidas y empezar a tratarlos como productos con versiones.

Esta es la versión práctica: qué hace fiable a un módulo, cómo publicarlo a través de un pipeline de verdad, y cómo los equipos lo consumen —en local y en CI— directamente desde GitHub, fijado a un tag o, cuando la seguridad lo exige, a un commit hash inmutable. Sin necesidad de un registry privado.

Qué hace fiable a un módulo

La fiabilidad empieza antes de cualquier pipeline. Un módulo en el que otros equipos confían es pequeño y hace una sola cosa —una red, una base de datos, un servicio— no una 'plataforma' que lo hace todo. Expone un conjunto claro y documentado de inputs y outputs, trae defaults sensatos, y fija sus propias versiones de provider para comportarse igual hoy que dentro de seis meses.

Nada de esto sobrevive a un equipo que crece a menos que dos cosas sean innegociables: cada cambio se revisa y cada cambio se prueba contra infraestructura real y desechable. Un módulo es el único trozo de código cuyos fallos se multiplican por cada consumidor: merece el listón más alto que tengas.

La versión es el contrato

A medida que la organización crece y aparecen nuevos entornos —un sandbox aquí, una segunda región allá— la versión deja de ser un lujo y se convierte en el contrato entre el módulo y todo el que lo usa. Cada consumidor fija una versión explícita; nadie sigue a main. El versionado semántico le dice entonces al consumidor qué significa un salto: un patch es seguro, un minor añade, un major le obliga a cambiar código.

Fijar es también cómo avanzas y retrocedes con confianza. Promover a producción es un salto de versión; una mala release es un salto de vuelta a la anterior. Sin versiones, 'hacer rollback del módulo' significa de verdad 'ir a averiguar en qué commit estaba el martes pasado'.

Consúmelo desde GitHub — sin registry

No necesitas un registry privado ni Artifactory para compartir módulos como es debido. Referencia el módulo directamente por su source de Git y un ref. La misma source funciona en un portátil y en CI; la autenticación es solo un token de GitHub —un PAT en local, el token del runner en CI— enlazado una vez para que los clones por HTTPS lleven la credencial.

# main.tf — fijar a un tag publicado
module "vpc" {
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"

  cidr_block = "10.20.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b"]
}

La credencial se define una vez con el insteadOf de git, así que ni tu código ni tu state contienen un secreto: Terraform se limita a clonar por HTTPS en tu nombre:

# local + CI: dejar que git use un token para github.com por HTTPS
git config --global url."https://oauth2:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"

Inmutabilidad: fija un commit hash cuando tiene que quedar bloqueado

Un tag es cómodo, pero es móvil: cualquiera con permisos de escritura puede reapuntar o forzar v1.4.0 a otro código, y tu siguiente apply lo trae. Para la mayoría de los módulos ese compromiso vale. Para los que no pueden cambiar bajo tus pies —todo lo sensible en seguridad o cumplimiento— fija mejor el commit hash completo. Un commit no se puede intercambiar: ref=<sha> resuelve siempre exactamente a los bytes que revisaste.

# bloqueado a un commit exacto e imposible de intercambiar
module "kms" {
  source = "git::https://github.com/acme/tf-modules.git//modules/kms?ref=3f9a1c4e8b7d6c5a4f3e2d1c0b9a8f7e6d5c4b3a"
}
Fijar un módulo: tag (móvil) vs commit hash (inmutable) source = "git::https://github.com/acme/tf-modules.git//vpc?ref=v1.4.0" tag — cómodo, pero móvil: se puede reapuntar o forzar source = "git::https://github.com/acme/tf-modules.git//vpc?ref=3f9a1c4e8b7d" commit hash — inmutable: nadie cambia el código bajo tus pies La misma source funciona en local y en CI — autentícate con un token de GitHub. Sin registry ni Artifactory.
La misma source de Git funciona en local y en CI tras un token. Un tag es cómodo pero se puede mover; un commit hash es inmutable: úsalo cuando el módulo no puede cambiar bajo tus pies.

Publica el módulo como un producto

Si los consumidores fijan versiones, algo tiene que producir esas versiones de forma fiable: un pipeline en el repositorio del módulo. Y vale la pena ser preciso: un módulo no 'se despliega'. Se valida, se prueba y se publica. El despliegue es trabajo del consumidor.

  1. En cada pull request, corre las comprobaciones estáticas: terraform fmt -check, terraform validate y tflint.
  2. Luego prueba de verdad — levanta el ejemplo del módulo en una cuenta sandbox desechable, valídalo (terraform test, o Terratest para comprobaciones más ricas) y destrúyelo. Un módulo que nunca se ha aplicado es un módulo sin probar.
  3. En el merge a main, deja que GitVersion calcule la siguiente versión semántica a partir del historial de commits, cree el tag de Git correspondiente y publique — anuncia la release en el canal de Slack o Teams del equipo (y espéjala a un registry, si por casualidad tienes uno).
Construye el módulo como un producto: del PR a una versión fijable EN EL PULL REQUEST validarfmt · validate · tflint probaraplica en un sandbox · verifica · destruye merge a main EN MAIN GitVersioncalcula la siguiente semver tagv1.4.0 publicaranuncia en Slack / Teams · (espeja a un registry)
Una versión solo existe después de que el cambio se haya revisado, validado y aplicado de verdad. En el merge, GitVersion deriva la siguiente semver, crea el tag y lo anuncia.
# .github/workflows/release.yml (esquema)
on: { pull_request: {}, push: { branches: [main] } }

jobs:
  check:
    steps:
      - run: terraform fmt -check -recursive
      - run: terraform validate
      - run: tflint
      - run: terraform test          # aplica los ejemplos en un sandbox y destruye

  release:
    if: github.ref == 'refs/heads/main'
    needs: check
    steps:
      - uses: gittools/actions/gitversion/execute@v3   # -> siguiente semver
      - run: gh release create "v${VERSION}"
      - run: ./notify.sh "tf-modules ${VERSION} publicado"  # Slack / Teams

La herramienta exacta da igual —GitVersion, release-please y semantic-release sirven todas. Lo que importa es que una versión solo pasa a existir después de que el cambio se haya revisado, validado y de verdad aplicado.

Ahora úsalo: de Terraform a Terragrunt

Con un módulo probado y con tag, un proyecto de Terraform sencillo se limita a fijarlo: el bloque module de arriba es toda la historia. Basta para uno o dos entornos. Pero en cuanto corres la misma stack en dev, staging y producción, en una o varias regiones, copiar y pegar ese enlace por entorno es exactamente la duplicación que los módulos venían a matar.

Terragrunt mantiene los entornos DRY

Terragrunt envuelve a Terraform para quitar esa repetición: genera la configuración de backend y de provider, mantiene la versión del módulo en un solo sitio por stack, y deja que cada entorno sea un fichero fino que dice 'este módulo, esta versión, estos inputs'. Promover por la cadena pasa a ser un salto de versión de una línea.

# live/prod/eu-west-1/vpc/terragrunt.hcl
terraform {
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"
}
include "root" { path = find_in_parent_folders() }

inputs = {
  cidr_block = "10.30.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

Una estructura que escala: proyecto / entorno / región

Organiza el repositorio live para que la ruta sea la dirección de la infraestructura. Una forma deliberadamente aburrida es entorno → región → componente — adapta los niveles a cómo piensa de verdad tu organización (hay quien separa primero por proyecto, cuenta o equipo):

live/
├── root.hcl                    # state remoto, provider, tags comunes
├── dev/
│   └── eu-west-1/
│       ├── vpc/terragrunt.hcl
│       └── eks/terragrunt.hcl
├── staging/
│   └── eu-west-1/ ...
└── prod/
    ├── eu-west-1/
    │   ├── vpc/terragrunt.hcl
    │   └── eks/terragrunt.hcl
    └── eu-central-1/ ...

Cada hoja fija una versión del módulo, y la carpeta te dice exactamente qué corre dónde. Actualizar producción pasa a ser un PR revisado que cambia un ref= de v1.4.0 a v1.5.0: pequeño, obvio y trivial de revertir.

Módulos fiables y de propósito único; revisados y probados en cada cambio; versionados y publicados por un pipeline; consumidos desde GitHub por tag —o por commit hash inmutable cuando importa— y montados con una estructura de Terragrunt que refleja tu parque. Esa es la diferencia entre módulos que escalan con tu equipo y módulos que, en silencio, escalan contra él.

Todos los artículos

¿Tiene un sistema como este?

Póngase en contacto

Encuéntreme en las redes sociales