La lección anterior terminó señalando una grieta incómoda: cd.yml despliega solo, pero da por hecho que existen un clúster reservalia-dev, un servicio reservalia-api, una base de datos RDS, un ALB y unos roles IAM que Nuria creó a mano en la consola de AWS hace meses. La única fuente de verdad sobre en qué se diferencian staging y prod es lo que hay dentro de AWS, y nadie lo ha leído entero. Esta lección cierra esa grieta: vamos a ver por qué un despliegue automatizado sobre infraestructura artesanal sigue siendo frágil, qué es exactamente la Infraestructura como Código, y cómo el módulo infra/ de Reservalia se escribe una sola vez en Terraform y se instancia tres veces para que los tres entornos salgan literalmente del mismo código, con sus diferencias escritas como valores explícitos en lugar de misterios.

Contenido

  1. El problema: deriva de configuración y el "en staging funcionaba"
  2. Qué es la Infraestructura como Código y sus cuatro principios
  3. Panorama de herramientas
  4. El módulo infra/ de Reservalia en Terraform
  5. Un módulo, tres entornos: las diferencias como valores
  6. Estado remoto y bloqueo: por qué el estado local es un accidente esperando
  7. El pipeline de infraestructura: plan en el PR, apply en main
  8. Riesgos reales: recreaciones destructivas, planes que mienten y permisos excesivos
  9. Entornos efímeros de previsualización por pull request
  10. Errores Comunes y Consejos
  11. Ejercicios
  12. Conclusión

  1. El problema: deriva de configuración y el "en staging funcionaba"

Hagamos el ejercicio incómodo. Marta pregunta en una reunión: "¿En qué se diferencia staging de prod?". Nuria responde que prod tiene cuatro tareas y es Multi-AZ; Diego cree que la retención de logs de staging es de una semana, "o de tres días, no me acuerdo". Marta insiste: "¿Y el grupo de seguridad de la base de datos? ¿Y el tiempo de espera del ALB?". Silencio.

Eso es deriva de configuración (configuration drift): la diferencia acumulada e indocumentada entre entornos que deberían parecerse. No aparece de golpe; se construye a base de arreglos urgentes. Un martes de hace ocho meses, prod daba timeouts y Nuria subió el idle_timeout del ALB de 60 a 120 segundos desde la consola. Funcionó, el incidente se cerró y nadie tocó staging. Hoy hay una petición lenta que en staging corta a los 60 segundos y en prod no. O al revés, que es peor.

El resultado clásico tiene nombre propio: "en staging funcionaba". Y arrastra una consecuencia que rompe todo el módulo: si staging no es equivalente a prod, entonces pasar por staging no autoriza nada. El requisito 1 de la lección 03-01 —una suite de pruebas en la que se confía— se derrumba, no porque las pruebas sean malas, sino porque se ejecutan en un sitio que no representa el destino. La infraestructura creada a mano tiene además cuatro defectos que ninguna cantidad de disciplina corrige: no es revisable (nadie puede comentar un cambio en un pull request antes de que ocurra), no es reproducible (recrear el entorno en otra región significa reconstruirlo de memoria), no es auditable (CloudTrail dice quién tocó algo, pero no por qué ni contra qué diseño) y no es reversible (no existe un "estado anterior" al que volver: la consola no tiene git revert).

Nuria: "Un despliegue que no se puede deshacer en cinco minutos no es un despliegue, es una apuesta. Y un entorno que no se puede recrear en una hora tampoco es un entorno: es una reliquia."

  1. Qué es la Infraestructura como Código y sus cuatro principios

Infraestructura como Código (IaC) es describir los recursos de infraestructura —redes, bases de datos, clústeres, permisos— en ficheros de texto versionados, y dejar que una herramienta los cree y los mantenga. La infraestructura pasa a ser un artefacto de software: se revisa en un pull request, se prueba, se versiona y se revierte.

Principio 1: declarativo, no imperativo. Un enfoque imperativo describe pasos ("crea un clúster, luego añade un servicio, luego sube las tareas a cuatro"). Un enfoque declarativo describe el resultado ("existe un clúster con un servicio de cuatro tareas") y la herramienta calcula qué hacer para llegar ahí desde donde esté.

Imperativo (un script bash con aws cli) Declarativo (Terraform)
Qué escribes La secuencia de pasos El estado final deseado
Si se ejecuta dos veces Suele fallar o duplicar No hace nada: ya coincide
Si alguien tocó algo a mano El script no se entera Lo detecta y lo corrige
Legibilidad Hay que simular la ejecución mentalmente Se lee como una descripción del sistema

Principio 2: idempotencia. Ya apareció en la 03-02 hablando de despliegues, y aquí es todavía más central: aplicar la misma configuración diez veces deja el sistema igual que aplicarla una. Eso es lo que permite ejecutar terraform apply en cada merge sin miedo.

Principio 3: estado. Para saber qué cambiar, la herramienta necesita recordar qué creó. Terraform guarda un fichero de estado que mapea cada bloque del código con su recurso real en AWS. No es un detalle de implementación: es el activo más delicado del sistema, y por eso tiene su propio apartado (el 6).

Principio 4: plan antes de aplicar. Antes de tocar nada, la herramienta calcula y muestra el diferencial entre lo que hay y lo que se pide. Ese plan es lo que convierte un cambio de infraestructura en algo revisable, y es la pieza que engancha la IaC con el pipeline.

  1. Panorama de herramientas

Herramienta Modelo Lenguaje Ámbito Cuándo encaja
Terraform / OpenTofu Declarativo, con estado HCL Multiproveedor Estándar de facto; OpenTofu es la bifurcación libre tras el cambio de licencia
AWS CloudFormation Declarativo, estado gestionado por AWS YAML/JSON Solo AWS Si no sales de AWS y no quieres gestionar el estado
AWS CDK Imperativo que genera CloudFormation TypeScript, Python… Solo AWS Equipos que prefieren un lenguaje real y abstracciones propias
Pulumi Declarativo con lenguaje general TypeScript, Go… Multiproveedor Como CDK pero sin atarse a AWS
Ansible Procedimental idempotente, sin estado YAML Configuración dentro de máquinas Servidores que ya existen; complementa, no sustituye

La distinción que más confunde al principio: aprovisionar (crear la máquina, la red, la base de datos) no es lo mismo que configurar (instalar paquetes dentro de una máquina que ya existe). Terraform hace lo primero; Ansible, lo segundo. Reservalia apenas necesita lo segundo, porque sus cargas van en contenedores y la "configuración de la máquina" ya está en el Dockerfile de la 02-03. Reservalia elige Terraform: es multiproveedor, su ecosistema de módulos es el más amplio y el equipo ya sabe leer HCL. El código de esta lección funciona igual con OpenTofu cambiando el binario.

  1. El módulo infra/ de Reservalia en Terraform

La estructura que Nuria crea en el monorepo:

infra/
├── modulos/entorno/        # UN módulo: main.tf · variables.tf · outputs.tf
└── entornos/
    ├── dev/main.tf         # instancia el módulo con los valores de dev
    ├── staging/main.tf
    └── prod/main.tf

Un módulo de Terraform es una carpeta con código parametrizable, como una función: infra/modulos/entorno/ describe "un entorno de Reservalia" y no sabe si es dev o prod, porque recibe esos datos por variables.

# infra/modulos/entorno/variables.tf
variable "entorno" {
  description = "Nombre del entorno: dev, staging o prod"
  type        = string
  validation {                                     # 1
    condition     = contains(["dev", "staging", "prod"], var.entorno)
    error_message = "El entorno debe ser dev, staging o prod."
  }
}

variable "numero_tareas"    { type = number }
variable "cpu_tarea"        { type = number, default = 512 }
variable "memoria_tarea"    { type = number, default = 1024 }
variable "clase_db"         { type = string }
variable "db_multi_az"      { type = bool,   default = false }
variable "retencion_logs"   { type = number, default = 7 }
variable "version_postgres" { type = string, default = "16.3" }   # 2
  1. El bloque validation convierte un error de dedo en un fallo del plan con un mensaje claro, en lugar de crear un entorno llamado prodd.
  2. version_postgres tiene valor por defecto y ningún entorno lo cambia. Eso no es casualidad: es el requisito 3 de la 03-01 escrito en código. Si mañana alguien quisiera probar PostgreSQL 17 en staging, tendría que escribirlo explícitamente en un pull request, y la revisión preguntaría por qué.

El cuerpo del módulo, con lo esencial de un entorno de Reservalia:

# infra/modulos/entorno/main.tf
locals {
  nombre    = "reservalia-${var.entorno}"                    # 1
  etiquetas = { Proyecto = "reservalia", Entorno = var.entorno, Gestion = "terraform" }  # 2
}

resource "aws_ecs_cluster" "este" {
  name = local.nombre
  tags = local.etiquetas
}

resource "aws_db_instance" "postgres" {
  identifier              = "${local.nombre}-db"
  engine                  = "postgres"
  engine_version          = var.version_postgres
  instance_class          = var.clase_db
  allocated_storage       = 20
  multi_az                = var.db_multi_az
  backup_retention_period = var.entorno == "prod" ? 30 : 1   # 3
  deletion_protection     = var.entorno == "prod"            # 4
  skip_final_snapshot     = var.entorno != "prod"
  tags                    = local.etiquetas
}

resource "aws_ecs_service" "api" {
  name            = "reservalia-api"                         # 5
  cluster         = aws_ecs_cluster.este.id
  desired_count   = var.numero_tareas
  launch_type     = "FARGATE"
  task_definition = aws_ecs_task_definition.api.arn

  lifecycle { ignore_changes = [task_definition, desired_count] }   # 6
}

resource "aws_cloudwatch_log_group" "api" {
  name              = "/ecs/${local.nombre}/api"
  retention_in_days = var.retencion_logs
}
  1. locals son valores calculados una vez y reutilizados; evitan repetir la interpolación del nombre en veinte sitios.
  2. La etiqueta Gestion = "terraform" parece decorativa y es de las más útiles: permite auditar qué recursos de la cuenta están gestionados por código y cuáles siguen siendo artesanales.
  3. Un condicional (condición ? valor_si : valor_no) expresa una diferencia real entre entornos sin duplicar el recurso: 30 días de copias en producción, 1 en el resto.
  4. deletion_protection en prod impide que un terraform destroy accidental borre la base de datos de los 340 negocios. Es una red de seguridad barata y obligatoria.
  5. El nombre del servicio es idéntico en los tres entornos (reservalia-api), tal como asume el cd.yml de la 03-02; lo que cambia es el clúster.
  6. ignore_changes = [task_definition, desired_count] es la línea más importante del fichero y la que más equipos olvidan. Terraform crea el servicio, pero quien cambia la versión desplegada es cd.yml. Sin esta línea, cada terraform apply devolvería el servicio a la imagen que figura en el código de infraestructura, deshaciendo el último despliegue. La regla general: Terraform es dueño de la forma del entorno; el pipeline de CD es dueño de la versión que corre dentro.

  1. Un módulo, tres entornos: las diferencias como valores

Aquí está la idea central de la lección. Los tres entornos son tres llamadas al mismo módulo:

# infra/entornos/dev/main.tf
module "entorno" {
  source         = "../../modulos/entorno"
  entorno        = "dev"
  numero_tareas  = 1
  clase_db       = "db.t4g.micro"
  retencion_logs = 3
}

# infra/entornos/prod/main.tf
module "entorno" {
  source         = "../../modulos/entorno"
  entorno        = "prod"
  numero_tareas  = 4
  cpu_tarea      = 1024
  memoria_tarea  = 2048
  clase_db       = "db.m6g.large"
  db_multi_az    = true
  retencion_logs = 30
}

Compara esto con la escena del apartado 1. La pregunta "¿en qué se diferencia staging de prod?" ya no requiere memoria ni arqueología en la consola: es un diff de dos ficheros de diez líneas.

Parámetro dev staging prod
numero_tareas 1 2 4
clase_db db.t4g.micro db.t4g.small db.m6g.large
db_multi_az false false true
retencion_logs 3 7 30
version_postgres 16.3 16.3 16.3
Topología de red, grupos de seguridad, ALB, IAM idénticos idénticos idénticos

Las diferencias son seis números y un booleano, todas justificables por coste o por resiliencia, y ninguna capaz de cambiar el comportamiento del software. Eso es lo que significa "entornos equivalentes" del requisito 3. Y hay un efecto secundario valioso: cualquier ajuste que Nuria haga en el módulo —un parámetro de PostgreSQL, un timeout del ALB— llega a los tres entornos a la vez, así que la deriva ya no puede acumularse en silencio.

  1. Estado remoto y bloqueo: por qué el estado local es un accidente esperando

El fichero terraform.tfstate guarda la correspondencia entre cada bloque del código y el recurso real. Si vive en el portátil de Nuria pasan tres cosas, todas malas: nadie más puede aplicar cambios sin él; si el portátil se pierde, Terraform ya no sabe que esos recursos son suyos e intentará crearlos de nuevo; y si Nuria y Diego aplican a la vez, cada uno escribe un estado que ignora lo que hizo el otro. Además, el estado contiene valores sensibles en claro, como la contraseña generada de RDS: no debe acabar en el repositorio. La solución es un backend remoto con bloqueo:

# infra/entornos/prod/backend.tf
terraform {
  backend "s3" {
    bucket         = "reservalia-tfstate"          # 1
    key            = "entornos/prod/terraform.tfstate"   # 2
    region         = "eu-west-1"
    dynamodb_table = "reservalia-tflock"           # 3
    encrypt        = true                          # 4
  }
}
  1. Un bucket S3 con versionado activado: si un apply corrompe el estado, se recupera la versión anterior.
  2. Una clave por entorno: tres estados independientes. Es lo que garantiza que un error aplicando dev no pueda tocar recursos de prod, porque el estado de dev ni siquiera los conoce.
  3. La tabla DynamoDB implementa el bloqueo: quien empieza un apply toma el cerrojo y el resto espera con un mensaje explícito en lugar de pisarse. Y encrypt = true cifra en reposo un fichero que, como acabamos de decir, contiene secretos.

  1. El pipeline de infraestructura: plan en el PR, apply en main

Con el estado remoto, la infraestructura puede entrar en el pipeline con el mismo flujo que el código: propuesta, revisión, aplicación.

flowchart TD
    A["PR sobre infra/**"] --> B["infra.yml · job plan<br/>rol de solo lectura"]
    B --> C{"Plan comentado en el PR<br/>¿algo se destruye?"}
    C -- "cambios no deseados" --> A
    C -- "aprobado" --> E["Merge a main"]
    E --> F["job aplicar<br/>dev y staging: automático"]
    F --> G{"environment: prod<br/>revisor requerido"}
    G -- aprueba --> H["terraform apply en prod"]
    H --> I["cd.yml despliega sobre<br/>infraestructura conocida"]
# .github/workflows/infra.yml
name: Infra
on:
  pull_request: { paths: ['infra/**'] }      # 1
  push:         { branches: [main], paths: ['infra/**'] }

permissions: { id-token: write, contents: read, pull-requests: write }   # 2
concurrency: { group: infra-${{ github.ref }}, cancel-in-progress: false }

jobs:
  plan:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-22.04
    strategy:
      matrix: { entorno: [dev, staging, prod] }        # 3
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with: { terraform_version: 1.8.5 }             # 4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::…:role/reservalia-terraform-plan   # 5
          aws-region: eu-west-1
      - run: |
          cd infra/entornos/${{ matrix.entorno }}
          terraform init
          terraform plan -no-color -out=plan.bin
          terraform show -no-color plan.bin > plan.txt
      - uses: actions/github-script@v7                 # 6 · comenta plan.txt en el PR
        with: { script: 'github.rest.issues.createComment({ …, body: leerPlan() })' }
  1. paths: ['infra/**'] evita ejecutar el pipeline de infraestructura en cada cambio de código de la API, y pull-requests: write (2) permite comentar el plan en el PR.
  2. Una matriz ejecuta el plan de los tres entornos: el revisor ve el impacto completo del cambio, no solo el del entorno que se editó.
  3. Versión fijada de Terraform. Dos versiones distintas pueden producir planes distintos sobre el mismo código; en infraestructura eso es inaceptable.
  4. Un rol de solo lectura para el plan. El job corre con código venido de un pull request y no necesita escritura: dársela sería regalar producción a quien abra una rama.
  5. El plan como comentario es el corazón del flujo: convierte un cambio de infraestructura en algo que se lee y se discute antes de que ocurra.

El job aplicar se ejecuta sobre main, con environment: dev/staging automáticos y environment: prod con revisor requerido —exactamente el mecanismo de la 03-02—, ejecutando terraform apply -auto-approve con el rol reservalia-terraform-apply.

  1. Riesgos reales: recreaciones destructivas, planes que mienten y permisos excesivos

Riesgo 1 (el más caro): cambios que recrean recursos con datos. Algunos atributos no se pueden modificar en caliente. Cambiar el identifier de una instancia RDS no la renombra: la destruye y crea otra vacía. El plan lo dice, pero hay que saber leerlo:

-/+ resource "aws_db_instance" "postgres" {   # must be replaced
      ~ identifier = "reservalia-prod-db" -> "reservalia-prod-postgres" # forces replacement

Los dos símbolos que hay que buscar siempre son -/+ y forces replacement. Reservalia adopta tres defensas: deletion_protection y prevent_destroy en el lifecycle de la base de datos, revisión humana obligatoria del plan de prod, y una regla de equipo —cualquier PR cuyo plan contenga forces replacement necesita aprobación de Nuria, sin excepciones.

Riesgo 2: el plan no coincide con el apply. Entre el plan del pull request del lunes y el apply del miércoles alguien puede haber tocado algo a mano, o haber fusionado otro PR de infraestructura. El plan es una foto, no un contrato. Mitigaciones: aplicar el fichero plan.bin guardado y no recalcular (Terraform aborta si el estado cambió), concurrency para serializar, y un plan programado semanalmente que detecte deriva. Riesgo 3: el pipeline con permisos excesivos. El rol de apply necesita crear y borrar infraestructura, así que es la credencial más poderosa de la organización: separa plan (lectura) de apply (escritura), acota la condición OIDC del sub al entorno concreto sin comodines —como vimos en la 03-02— y limita el rol a los servicios que realmente usa. La superficie de ataque del pipeline se trata a fondo en la lección 04-03, Seguridad en CI/CD.

  1. Entornos efímeros de previsualización por pull request

Cuando un entorno es una llamada a un módulo, crear uno nuevo deja de ser un proyecto y pasa a ser una variable. Eso habilita algo que antes era impensable en Reservalia: un entorno por pull request, creado al abrirlo y destruido al cerrarlo.

module "entorno" {
  source        = "../../modulos/entorno"
  entorno       = "pr-${var.numero_pr}"    # clúster reservalia-pr-482
  numero_tareas = 1
  clase_db      = "db.t4g.micro"
}

Marta puede probar la funcionalidad en https://pr-482.reservalia.com antes de fusionar, y el diseñador puede verla sin instalar nada. Tres cautelas: destrúyelos siempre al cerrar el PR (un job on: pull_request: types: [closed] más un barrido nocturno de los que sobrevivan más de 72 horas), nunca uses datos reales en ellos, y vigila el coste, que es lineal en el número de PR abiertos. Un módulo de entorno completo con RDS puede ser demasiado caro; muchos equipos usan una variante ligera con base de datos compartida y esquema por PR. Y conviene mencionar una familia relacionada: GitOps (Argo CD, Flux) lleva esta idea al extremo, con un agente dentro del clúster que vigila el repositorio y reconcilia continuamente el estado real con el declarado, en lugar de que un pipeline empuje los cambios; es un modelo distinto y muy extendido en Kubernetes, y se trata en la lección 06-05.

Errores Comunes y Consejos

Error 1: subir terraform.tfstate al repositorio. Contiene secretos en claro y no resuelve el trabajo concurrente. Añádelo a .gitignore y usa backend remoto desde el primer día. Error 2: escribir tres copias del código, una por entorno, que es lo que más deriva produce, porque un arreglo urgente se aplica a un fichero y no a los otros dos.

Error 3: hacer apply desde el portátil. Aunque el estado sea remoto, un apply local no deja rastro revisable ni comprobaciones. La regla de Reservalia: si no pasó por infra.yml, no existe. Error 4: olvidar ignore_changes en la task definition, cuyo síntoma es desconcertante: un cambio de infraestructura sin relación aparente devuelve producción a una versión de hace tres semanas.

Error 5: no leer el plan. Un plan de 400 líneas que nadie mira es peor que no tenerlo, porque da sensación de control; busca siempre forces replacement y el recuento final de destroy. Consejo 1: empieza importando lo que ya existe. terraform import (o los bloques import) trae la infraestructura artesanal al estado sin recrearla. Nuria hizo exactamente eso: importó prod, ajustó el código hasta que terraform plan dijo "No changes", y solo entonces se fio del módulo. Consejo 2: fija las versiones del binario y de los proveedores (required_providers) y sube .terraform.lock.hcl al repositorio. Consejo 3: programa un plan semanal sobre los tres entornos; si aparece un diferencial sin que nadie haya tocado el código, alguien ha vuelto a la consola.

Ejercicios

Ejercicio 1

Nuria propone acelerar así: "En dev uso PostgreSQL 14 porque la instancia es más barata, y en prod mantengo la 16". Explica por qué esta decisión rompe una parte del módulo 3 y qué diferencias son aceptables entre entornos.

Ejercicio 2

Un pull request cambia clase_db de db.t4g.small a db.t4g.medium en staging. El plan muestra ~ instance_class y, más abajo, Plan: 0 to add, 1 to change, 0 to destroy. Otro PR cambia el identifier de la base de datos y muestra Plan: 1 to add, 0 to change, 1 to destroy. Explica la diferencia y qué harías en cada caso.

Ejercicio 3

Diego dice: "Ya que terraform apply corre en el pipeline, quitemos la aprobación manual de prod: total, el plan ya se revisó en el PR". Da dos argumentos en contra y una condición bajo la cual sí sería razonable.

Soluciones

Solución 1. Rompe el requisito 3 de la 03-01, entornos equivalentes, y con él el valor de toda la cadena de la 03-02. Entre PostgreSQL 14 y 16 cambian el planificador de consultas, funciones disponibles y comportamientos de tipos: una consulta de calcularHuecos puede funcionar en dev y fallar o degradarse en prod, y —peor— al revés, con lo que dev daría falsos positivos que nadie sabría interpretar. La regla: son aceptables las diferencias de escala (número de tareas, clase de instancia, Multi-AZ, retención de logs, volumen de datos), porque afectan al coste y a la resiliencia pero no al comportamiento funcional del software; no son aceptables las diferencias de versión o topología (motor de base de datos, versión de Node, presencia de un ALB, arquitectura de CPU), porque cambian lo que el código hace. Si el coste de dev preocupa, la palanca correcta es bajar la clase de instancia o apagar el entorno por las noches, no cambiar la versión del motor.

Solución 2. El primer plan es una modificación en sitio: ~ indica que el atributo se cambia sin destruir el recurso. AWS aplicará el cambio de clase con un breve reinicio, pero los datos se conservan. Es un cambio de rutina; conviene aplicarlo en una ventana de bajo tráfico si es prod. El segundo es una sustitución destructiva: 1 to destroy sobre una base de datos significa que Terraform borrará la instancia actual —con todos los datos— y creará una vacía con el nombre nuevo. En staging supondría perder la copia anonimizada; en prod, perder los datos de los 340 negocios. Acción: no fusionar. Si el renombrado es realmente necesario, el camino seguro es cambiar la dirección del recurso en el estado con un bloque moved o terraform state mv cuando aplique, o directamente renunciar al cambio cosmético. Y el prevent_destroy del lifecycle debería haber hecho fallar el plan antes de llegar a la revisión: si no lo hizo, falta esa defensa.

Solución 3. En contra: (1) el plan del PR y el apply posterior no son el mismo cálculo —entre ambos pueden haberse fusionado otros PR o haber cambiado el estado—, así que aprobar el plan del lunes no equivale a aprobar la aplicación del miércoles; (2) el revisor del PR revisa código, y quien aprueba el entorno prod revisa momento: puede haber una campaña en marcha, un incidente abierto o un despliegue en curso que desaconsejen tocar la infraestructura ahora, información que no está en el diff. Sería razonable quitarla cuando el pipeline aplique el mismo plan.bin aprobado (Terraform aborta si el estado cambió), exista una comprobación automática que falle ante cualquier forces replacement o destroy, y el histórico muestre que la aprobación humana no ha rechazado nada en un número significativo de aplicaciones —el mismo criterio de la 03-01.

Conclusión

La grieta con la que empezamos está cerrada. Los tres entornos de Reservalia ya no son tres artesanías que se parecen por casualidad: son tres llamadas al mismo módulo de infra/modulos/entorno/, y sus diferencias son seis números y un booleano escritos en un fichero que cualquiera puede leer en veinte segundos. La pregunta "¿en qué se diferencia staging de prod?" tiene por fin una respuesta que no depende de la memoria de nadie. Las ideas que conviene llevarse: la deriva de configuración se acumula por arreglos urgentes y destruye el valor de staging como autorización para producción; la IaC es declarativa, idempotente, con estado y con plan previo, y ese plan es lo que hace revisable un cambio de infraestructura; el estado remoto con bloqueo es innegociable en cuanto hay más de una persona; el pipeline separa plan con permisos de lectura en el PR y apply con aprobación en main; hay que leer siempre el plan buscando forces replacement; y Terraform es dueño de la forma del entorno mientras el CD es dueño de la versión que corre dentro —de ahí el ignore_changes. Como regalo, los entornos efímeros por pull request pasan a ser posibles.

Con los cinco requisitos, Reservalia va por tres: pruebas fiables, artefacto inmutable y ahora entornos equivalentes. Pero el cd.yml sigue desplegando a prod de la forma más simple posible, sustituyendo tareas sin más control, y todavía no hay prod en el pipeline. La siguiente lección, Estrategias de Despliegue, se ocupa de eso: veremos recreate, rolling update, blue-green, canary, pruebas A/B y shadow, con su coste, su riesgo y su facilidad de vuelta atrás; montaremos un rolling update fino en ECS y un canary por peso de ALB al 10 %; distinguiremos liveness de readiness y veremos por qué un health check que responde 200 sin comprobar nada es peor que no tener ninguno; y aceptaremos el requisito que todas estas estrategias comparten: que dos versiones del software van a convivir, y tienen que entenderse.

Curso de CI/CD: Integración y Despliegue Continuo

Módulo 1: Introducción a CI/CD

Módulo 2: Integración Continua (CI)

Módulo 3: Despliegue Continuo (CD)

Módulo 4: Prácticas Avanzadas de CI/CD

Módulo 5: Implementación de CI/CD en Proyectos Reales

Módulo 6: Herramientas y Tecnologías

Módulo 7: Ejercicios Prácticos

Módulo 8: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados