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
- El problema: deriva de configuración y el "en staging funcionaba"
- Qué es la Infraestructura como Código y sus cuatro principios
- Panorama de herramientas
- El módulo
infra/de Reservalia en Terraform - Un módulo, tres entornos: las diferencias como valores
- Estado remoto y bloqueo: por qué el estado local es un accidente esperando
- El pipeline de infraestructura:
planen el PR,applyenmain - Riesgos reales: recreaciones destructivas, planes que mienten y permisos excesivos
- Entornos efímeros de previsualización por pull request
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- 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."
- 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.
- 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.
- El módulo
infra/ de Reservalia en Terraform
infra/ de Reservalia en TerraformLa 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.tfUn 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- El bloque
validationconvierte un error de dedo en un fallo delplancon un mensaje claro, en lugar de crear un entorno llamadoprodd. version_postgrestiene 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 enstaging, 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
}localsson valores calculados una vez y reutilizados; evitan repetir la interpolación del nombre en veinte sitios.- 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. - 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. deletion_protectionenprodimpide que unterraform destroyaccidental borre la base de datos de los 340 negocios. Es una red de seguridad barata y obligatoria.- El nombre del servicio es idéntico en los tres entornos (
reservalia-api), tal como asume elcd.ymlde la 03-02; lo que cambia es el clúster. 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 escd.yml. Sin esta línea, cadaterraform applydevolverí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.
- 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.
- 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
}
}- Un bucket S3 con versionado activado: si un
applycorrompe el estado, se recupera la versión anterior. - Una clave por entorno: tres estados independientes. Es lo que garantiza que un error aplicando
devno pueda tocar recursos deprod, porque el estado dedevni siquiera los conoce. - La tabla DynamoDB implementa el bloqueo: quien empieza un
applytoma el cerrojo y el resto espera con un mensaje explícito en lugar de pisarse. Yencrypt = truecifra en reposo un fichero que, como acabamos de decir, contiene secretos.
- El pipeline de infraestructura:
plan en el PR, apply en main
plan en el PR, apply en mainCon 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() })' }paths: ['infra/**']evita ejecutar el pipeline de infraestructura en cada cambio de código de la API, ypull-requests: write(2) permite comentar el plan en el PR.- 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ó.
- Versión fijada de Terraform. Dos versiones distintas pueden producir planes distintos sobre el mismo código; en infraestructura eso es inaceptable.
- 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.
- 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.
- 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 replacementLos 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.
- 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 sí 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
- Conceptos Básicos de CI/CD
- Beneficios de CI/CD
- Herramientas Populares de CI/CD
- El Proyecto del Curso: la Aplicación que Vamos a Automatizar
- Métricas DORA: Cómo se Mide la Entrega de Software
Módulo 2: Integración Continua (CI)
- Introducción a la Integración Continua
- Configuración de un Entorno de CI
- Automatización de la Construcción
- Pruebas Automatizadas
- Calidad de Código y Análisis Estático
- Artefactos, Versionado y Promoción
- Integración con Control de Versiones
Módulo 3: Despliegue Continuo (CD)
- Introducción al Despliegue Continuo
- Automatización del Despliegue
- Infraestructura como Código y Entornos Reproducibles
- Estrategias de Despliegue
- Feature Flags, Rollback y Recuperación ante Fallos
- Monitoreo y Retroalimentación
Módulo 4: Prácticas Avanzadas de CI/CD
- Pipelines de CI/CD
- Gestión de Dependencias
- Seguridad en CI/CD
- Escalabilidad y Rendimiento
- Pipeline as Code: Plantillas, Reutilización y Pruebas del Pipeline
- Bases de Datos en el Pipeline: Migraciones Seguras
Módulo 5: Implementación de CI/CD en Proyectos Reales
- Caso de Estudio: Proyecto Web
- Caso de Estudio: Aplicación Móvil
- Caso de Estudio: Microservicios
- Caso de Estudio: Modernizar un Proyecto Legacy
Módulo 6: Herramientas y Tecnologías
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker y Kubernetes
- GitHub Actions a Fondo
- Comparativa y Criterios para Elegir Herramienta
Módulo 7: Ejercicios Prácticos
- Ejercicio 1: Configuración de un Pipeline Básico
- Ejercicio 2: Integración de Pruebas Automatizadas
- Ejercicio 3: Despliegue en un Entorno de Producción
- Ejercicio 4: Monitoreo y Retroalimentación
- Ejercicio 5: Endurecer el Pipeline con Seguridad y Secretos
- Proyecto Final: Pipeline Completo de Extremo a Extremo
