Infrastructure as Code con 200 recursos: lo que los tutoriales de Terraform no te cuentan
Patrones IaC de producción para sistemas reales. Gestión de estado a escala, diseño de módulos, híbrido CDK + Terraform, detección de drift, GitOps con Flux.
IaC no es "terraform init"
Todos los tutoriales de Terraform empiezan igual: escribes un archivo .tf, ejecutas terraform init, ejecutas terraform apply y ves aparecer tu instancia EC2. Eso te lleva de cero a un recurso. No te prepara para gestionar 200+ recursos repartidos en 30 servicios AWS con un equipo de ingenieros que necesitan hacer cambios de infraestructura de forma segura.
Gestionamos infraestructura para múltiples sistemas en producción, desde clusters Kubernetes con Pimcore y OpenSearch hasta arquitecturas serverless con Lambda, DynamoDB y API Gateway. Los patrones de este artículo son los que sobrevivieron a producción. Para saber cómo desplegamos aplicaciones sobre esta infraestructura, consulta nuestra página de servicios cloud.
Gestión de estado a escala
El estado de Terraform es el archivo más crítico de tu infraestructura. Mapea tus archivos .tf a recursos reales. Si lo pierdes, Terraform no sabe qué existe. Si lo corrompes, Terraform podría destruir recursos en producción.
Estado remoto (no negociable)
# backend.tf
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "prod/platform/terraform.tfstate"
region = "eu-central-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
| Regla | Por qué |
|---|---|
| Estado remoto en S3 (o equivalente) | Los archivos de estado locales se pierden, no se pueden compartir |
| Cifrado en reposo | El estado contiene secretos (contraseñas de base de datos, claves API) |
| Bloqueo con DynamoDB | Evita que dos ingenieros ejecuten apply simultáneamente |
| Versionado en el bucket S3 | Recuperación ante corrupción del estado |
Organización del estado
Un archivo de estado grande para todo es un desastre de mantenimiento. Divide por entorno y dominio:
terraform/
├── environments/
│ ├── prod/
│ │ ├── platform/ # EKS, VPC, red
│ │ ├── databases/ # RDS, ElastiCache, OpenSearch
│ │ ├── compute/ # Lambda, ECS, Fargate
│ │ ├── storage/ # Buckets S3, CloudFront
│ │ └── monitoring/ # CloudWatch, alertas
│ ├── staging/
│ │ └── (misma estructura)
│ └── dev/
│ └── (misma estructura)
├── modules/
│ ├── vpc/
│ ├── eks-cluster/
│ ├── rds-postgres/
│ ├── opensearch/
│ ├── redis/
│ └── lambda-function/
└── global/
├── iam/ # Roles IAM, políticas
├── route53/ # Zonas DNS
└── ecr/ # Registros de contenedores
Cada directorio es un workspace de Terraform separado con su propio archivo de estado. Los cambios en la red no arriesgan romper la base de datos. Los cambios en el monitoring no requieren un plan que toque todos los recursos.
Referencias entre estados
Los workspaces necesitan referenciarse entre sí. El workspace del VPC exporta el ID del VPC. El workspace de la base de datos lo lee:
# En databases/main.tf
data "terraform_remote_state" "platform" {
backend = "s3"
config = {
bucket = "company-terraform-state"
key = "prod/platform/terraform.tfstate"
region = "eu-central-1"
}
}
resource "aws_db_instance" "main" {
vpc_security_group_ids = [data.terraform_remote_state.platform.outputs.db_security_group_id]
db_subnet_group_name = data.terraform_remote_state.platform.outputs.db_subnet_group_name
}
Diseño de módulos
Cuándo extraer un módulo
No todos los recursos necesitan un módulo. Extrae cuando:
- El mismo patrón se usa en 3+ lugares (DRY)
- El grupo de recursos tiene un límite claro (VPC, cluster de base de datos)
- La configuración tiene valores por defecto sensatos que reducen la duplicación
No extraigas cuando:
- Se usa una sola vez (abstracción prematura)
- El módulo tendría 20+ variables (demasiadas perillas)
- La abstracción oculta detalles importantes (red, seguridad)
Diseño de la interfaz del módulo
Un buen módulo tiene pocas variables requeridas, valores por defecto sensatos y outputs claros:
# modules/rds-postgres/variables.tf
variable "name" {
description = "Nombre de la instancia de base de datos"
type = string
}
variable "vpc_id" {
description = "VPC donde desplegar"
type = string
}
variable "subnet_ids" {
description = "Subnets para el grupo de subnets de la DB"
type = list(string)
}
variable "instance_class" {
description = "Tipo de instancia RDS"
type = string
default = "db.t3.medium"
}
variable "engine_version" {
description = "Versión de PostgreSQL"
type = string
default = "15.4"
}
variable "allocated_storage" {
description = "Almacenamiento en GB"
type = number
default = 50
}
variable "multi_az" {
description = "Habilitar despliegue multi-AZ"
type = bool
default = false # true para prod, false para staging/dev
}
El consumidor del módulo escribe:
module "database" {
source = "../../modules/rds-postgres"
name = "pimcore-prod"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
multi_az = true
}
Cinco líneas en lugar de cincuenta. El módulo gestiona los security groups, parameter groups, subnet groups, cifrado, retención de backups y monitoring.
CDK + Terraform: el enfoque híbrido pragmático
Algunos equipos apuestan todo a CDK. Otros todo a Terraform. Nosotros usamos ambos, y funciona.
| Caso de uso | Herramienta | Por qué |
|---|---|---|
| Red, bases de datos, clusters | Terraform | Declarativo, plan-before-apply, gestión de estado |
| Funciones Lambda + API Gateway | CDK | Mejor bundling de Lambda, constructs de API Gateway |
| Políticas IAM complejas | CDK | Lógica TypeScript para políticas condicionales |
| Recursos Kubernetes | Kustomize + Flux | GitOps, bucles de reconciliación |
| Infraestructura estática | Terraform | Simple, legible, bien entendido |
La frontera es clara: Terraform gestiona la infraestructura que cambia raramente (VPC, RDS, cluster EKS). CDK gestiona la infraestructura que cambia con los despliegues de aplicaciones (funciones Lambda, rutas API). Kustomize + Flux gestiona los workloads de Kubernetes.
Coexisten usando outputs. Terraform exporta el ID del VPC, el endpoint del cluster y la cadena de conexión a la base de datos. CDK los lee desde SSM Parameter Store o el remote state de Terraform.
El problema del drift
El drift ocurre cuando alguien modifica la infraestructura a través de la consola (ClickOps), a través de un comando CLI o a través de otra herramienta. El estado real diverge del estado de Terraform.
Detectar drift
# Ejecutar plan regularmente (CI, job programado)
terraform plan -detailed-exitcode
# Códigos de salida:
# 0 = sin cambios (el estado coincide con la realidad)
# 1 = error
# 2 = cambios detectados (¡drift!)
Ejecuta la detección de drift en CI de forma programada (diaria para producción, semanal para staging). Alerta cuando se detecta drift. No hagas remediación automática. Investiga primero.
Causas comunes de drift
| Causa | Prevención |
|---|---|
| Cambios por consola (ClickOps) | Aplicar política de "sin cambios por consola". Usar SCPs para restringir. |
| Cambios de auto-scaling | Ignorar atributos de auto-scaling en Terraform (lifecycle { ignore_changes }) |
| Actualizaciones de servicios AWS | Fijar versiones de providers. Actualizar deliberadamente. |
| Terraform de otro equipo | Archivos de estado separados por equipo/dominio. |
| Hotfix manual durante un incidente | Documentar el cambio. Aplicarlo en Terraform después del incidente. |
# Ignorar cambios de auto-scaling (drift esperado)
resource "aws_ecs_service" "app" {
desired_count = 2
lifecycle {
ignore_changes = [desired_count] # El auto-scaling modifica este valor
}
}
GitOps con Flux
Para los workloads de Kubernetes, usamos Flux para GitOps. El bucle de reconciliación reemplaza kubectl apply con un modelo pull-based.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Repo Git │────▶│ Flux │────▶│ Kubernetes │
│ (manifests) │ │ Controller │ │ Cluster │
└─────────────┘ └─────────────┘ └─────────────┘
│
│ Reconcilia cada 1 min
│ Detecta drift
│ Aplica automáticamente
# flux-system/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: platform
namespace: flux-system
spec:
interval: 1m
sourceRef:
kind: GitRepository
name: infrastructure
path: ./kubernetes/resources/overlay/prod
prune: true
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: pimcore
namespace: production
Flux consulta el repositorio Git cada minuto. Si los manifests cambiaron, los aplica. Si alguien modificó un recurso manualmente (drift), Flux lo revierte para que coincida con Git. Esto es reconciliación real, no solo automatización de despliegues.
Sealed Secrets
Los secretos no pueden ir en Git como texto plano. Usa Bitnami Sealed Secrets:
# Cifrar el secreto para el cluster
kubeseal --cert sealed-secrets.pem \
-f secrets/database-secrets.yaml \
-o yaml > secrets/database-secrets-sealed.yaml
# Hacer commit de la versión sellada (seguro en Git)
# Flux la aplica, el controller la descifra dentro del cluster
Para saber cómo gestionamos los secretos en los despliegues Kubernetes de Pimcore específicamente, consulta nuestra guía de actualización de Pimcore que cubre el orden completo de despliegue.
Gestionar 30+ servicios AWS
A escala enterprise, estás gestionando muchos servicios. La organización importa.
Catálogo de servicios
| Categoría | Servicios | ¿Módulo Terraform? |
|---|---|---|
| Red | VPC, subnets, NAT, ALB, Route53 | Sí (módulo vpc) |
| Compute | EKS, ECS Fargate, Lambda | Sí (por tipo de servicio) |
| Base de datos | RDS PostgreSQL, DynamoDB | Sí (módulo rds-postgres) |
| Cache | ElastiCache Redis | Sí (módulo redis) |
| Búsqueda | OpenSearch | Sí (módulo opensearch) |
| Almacenamiento | S3, EFS | Inline (bastante simple) |
| CDN | CloudFront | Inline |
| Mensajería | SQS, MSK (Kafka), RabbitMQ | Inline |
| Auth | Cognito | CDK (config compleja) |
| Monitoring | CloudWatch, X-Ray | Inline |
| CI/CD | ECR, CodeBuild | Inline |
| Seguridad | IAM, KMS, Secrets Manager | Workspace global |
Estrategia de tagging
Cada recurso debe estar etiquetado para asignación de costos, identificación de propietario y gestión del ciclo de vida:
locals {
common_tags = {
Environment = var.environment # prod, staging, dev
Project = var.project_name # pimcore, commerce, ai
ManagedBy = "terraform"
Team = var.team # platform, backend, data
CostCenter = var.cost_center
}
}
resource "aws_instance" "example" {
tags = merge(local.common_tags, {
Name = "pimcore-web-1"
Role = "web"
})
}
Filtra AWS Cost Explorer por el tag Project para ver exactamente cuánto cuesta cada sistema. Filtra por ManagedBy para encontrar recursos creados manualmente (no etiquetados como "terraform").
Errores comunes
-
Un solo archivo de estado para todo. Un
terraform planque toca 200 recursos tarda minutos y un error afecta a todo. Divide por dominio y entorno. -
Sin bloqueo de estado. Dos ingenieros ejecutan
terraform applysimultáneamente. Los cambios de uno se pierden o el estado se corrompe. Usa bloqueo con DynamoDB. -
Módulos con 20+ variables. Si la interfaz de tu módulo es tan compleja como los recursos en crudo, la abstracción no aporta valor. Mantén las interfaces de los módulos reducidas.
-
Remediación automática de drift. Detectar drift está bien. Corregirlo automáticamente es peligroso. El "drift" podría ser un hotfix válido durante un incidente. Investiga antes de revertir.
-
Secretos en los archivos de estado. El estado de Terraform contiene cada atributo de cada recurso, incluyendo contraseñas de base de datos. Cifra el estado en reposo y restringe el acceso.
-
Sin fijar versiones de providers. Una actualización de provider cambia el comportamiento de los recursos. Fija versiones en
required_providersy actualiza deliberadamente. -
ClickOps para "solo esta cosita". Los cambios en la consola crean drift invisible hasta el próximo
terraform plan. Aplica infrastructure-as-code para todo. -
Sin tagging. Sin tags, no puedes atribuir costos, identificar propietarios ni encontrar recursos creados manualmente.
Puntos clave
-
Divide el estado por dominio y entorno. Red, bases de datos, compute y monitoring deben ser workspaces separados. Los cambios en uno no deberían arriesgar otro.
-
Los módulos son para patrones, no para abstracción. Extrae cuando el mismo grupo de recursos aparece 3+ veces. No crees módulos para recursos que se usan una sola vez.
-
CDK + Terraform es pragmático. Terraform para infraestructura estática, CDK para Lambda/API Gateway, Kustomize + Flux para Kubernetes. Cada herramienta donde es más fuerte.
-
La detección de drift es un job programado. Ejecuta
terraform plandiariamente en CI. Alerta sobre drift. Investiga antes de remediar. -
GitOps con Flux ofrece reconciliación real. No solo automatización de despliegues. Flux detecta y revierte cambios manuales. Sealed Secrets mantienen las credenciales seguras en Git.
-
Etiqueta todo. Entorno, proyecto, equipo, centro de costos, managed-by. Sin tags, la atribución de costos y la auditoría de recursos son imposibles.
Gestionamos infraestructura para despliegues cloud, plataformas de software a medida y sistemas de ingeniería de datos. Si necesitas ayuda con IaC a escala, habla con nuestro equipo o solicita un presupuesto.
Temas cubiertos
Guías relacionadas
Guía Empresarial de Sistemas de IA Agéntica
Guia tecnica de sistemas de IA agentica en entornos empresariales. Descubre la arquitectura, capacidades y aplicaciones de agentes IA autonomos.
Leer guíaComercio Agéntico: Cómo Dejar que los Agentes IA Compren de Forma Segura
Cómo diseñar comercio iniciado por agentes IA con gobernanza. Motores de políticas, puertas de aprobación HITL, recibos HMAC, idempotencia, aislamiento de tenants y el Agentic Checkout Protocol completo.
Leer guíaLos 9 Puntos Donde Tu Sistema de IA Filtra Datos (y Cómo Sellar Cada Uno)
Un mapa sistemático de cada lugar donde se filtran datos en sistemas de IA. Prompts, embeddings, logs, llamadas a herramientas, memoria de agentes, mensajes de error, caché, datos de fine-tuning y handoffs entre agentes.
Leer guía¿Listo para construir sistemas de IA listos para producción?
Nuestro equipo se especializa en sistemas de IA listos para producción. Hablemos de cómo podemos ayudar.
Iniciar una conversación