Guía técnica

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.

20 de abril de 202616 min de lecturaEquipo de Ingeniería Oronts

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"
    }
}
ReglaPor qué
Estado remoto en S3 (o equivalente)Los archivos de estado locales se pierden, no se pueden compartir
Cifrado en reposoEl estado contiene secretos (contraseñas de base de datos, claves API)
Bloqueo con DynamoDBEvita que dos ingenieros ejecuten apply simultáneamente
Versionado en el bucket S3Recuperació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 usoHerramientaPor qué
Red, bases de datos, clustersTerraformDeclarativo, plan-before-apply, gestión de estado
Funciones Lambda + API GatewayCDKMejor bundling de Lambda, constructs de API Gateway
Políticas IAM complejasCDKLógica TypeScript para políticas condicionales
Recursos KubernetesKustomize + FluxGitOps, bucles de reconciliación
Infraestructura estáticaTerraformSimple, 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

CausaPrevención
Cambios por consola (ClickOps)Aplicar política de "sin cambios por consola". Usar SCPs para restringir.
Cambios de auto-scalingIgnorar atributos de auto-scaling en Terraform (lifecycle { ignore_changes })
Actualizaciones de servicios AWSFijar versiones de providers. Actualizar deliberadamente.
Terraform de otro equipoArchivos de estado separados por equipo/dominio.
Hotfix manual durante un incidenteDocumentar 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íaServicios¿Módulo Terraform?
RedVPC, subnets, NAT, ALB, Route53Sí (módulo vpc)
ComputeEKS, ECS Fargate, LambdaSí (por tipo de servicio)
Base de datosRDS PostgreSQL, DynamoDBSí (módulo rds-postgres)
CacheElastiCache RedisSí (módulo redis)
BúsquedaOpenSearchSí (módulo opensearch)
AlmacenamientoS3, EFSInline (bastante simple)
CDNCloudFrontInline
MensajeríaSQS, MSK (Kafka), RabbitMQInline
AuthCognitoCDK (config compleja)
MonitoringCloudWatch, X-RayInline
CI/CDECR, CodeBuildInline
SeguridadIAM, KMS, Secrets ManagerWorkspace 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

  1. Un solo archivo de estado para todo. Un terraform plan que toca 200 recursos tarda minutos y un error afecta a todo. Divide por dominio y entorno.

  2. Sin bloqueo de estado. Dos ingenieros ejecutan terraform apply simultáneamente. Los cambios de uno se pierden o el estado se corrompe. Usa bloqueo con DynamoDB.

  3. 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.

  4. 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.

  5. 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.

  6. Sin fijar versiones de providers. Una actualización de provider cambia el comportamiento de los recursos. Fija versiones en required_providers y actualiza deliberadamente.

  7. 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.

  8. 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 plan diariamente 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

Terraform producciónlecciones IaCinfrastructure as code realTerraform AWSCDK vs Terraformgestión de estado TerraformGitOps Fluxmódulos Terraform

¿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