Módulo 9 · Despliegue y operaciones

Lección 41 — CI/CD

Pipelines con pruebas, análisis y despliegue automático al merge.

Publicada
En esta lección
  1. Ejercicio 1 — El pipeline
  2. Ejercicio 2 — El digest
  3. Ejercicio 3 — El despliegue
  4. Ejercicio 4 — Las migraciones
  5. Ejercicio 5 — El círculo
  6. Resumen del profesor

Ejercicio 1 — El pipeline

  1. El paralelismo: lint + unit + integration + build corren EN PARALELO al abrir el PR; e2e espera a build (necesita la imagen); check-deploy corre en paralelo con los test (no depende del código ejecutándose, solo del settings). La ruta crítica: e2e (~4 min con imagen cacheada; sin cache, el build de 90 s + e2e). El pipeline total del PR: ~6-8 min en frío, ~4-5 min en caliente — el número que decide si el equipo pushea varias veces al día o cada dos horas.
  1. Los caches (segunda corrida):
lint:        31 s → 9 s   (pip cache)
unit:        28 s → 11 s  (pip cache + .pytest_cache)
integration: 3m12 → 2m40  (services ya pulleulos)
build:       88 s → 14 s  (cache-from registry: las capas de deps reutilizadas)
e2e:         4m10 → 3m55  (imagen del cache, el E2E no se cachea: corre SIEMPRE)
  1. El efecto del flaky: 5 PRs × 1/5 de fallo = ~64% de probabilidad de al menos un rojo en el día — la media observada: 3 de 5 PRs necesitaron 1 re-run, 1 necesitó 2. El costo invisible: la confianza — el tercer re-run ya nadie lo mira. La política del proyecto: re-run manual (jamás automático en el gate), issue inmediato, cuarentena con caducidad (35).

Ejercicio 2 — El digest

  1. La cadena: docker push devuelve sha256:9f2c...; el job lo exporta (::set-output) y el deploy lo consume. docker pull ticketflow@sha256:9f2c... → docker inspect muestra el mismo digest: la identidad del artefacto es el digest, no el tag (el tag es un alias mutable; el digest, inmutable).
  1. El scan gate: la base vieja pinneada → trivy --exit-code 1 --severity CRITICAL → job ROJO con el reporte de la CVE. Las MEDIUM: al reporte (artifact del job), no al gate — el triage del 40: el CVSS alto sin contexto bloquea el deploy de cosas importantes por ruido; el CRITICAL explotable, jamás pasa.
  1. El comando del audit: 1 comando:
bash
docker inspect --format '{{index .RepoDigests 0}}' prod-app | xargs -I{} docker image inspect {} --format '{{index .Config.Labels "org.opencontainers.image.revision"}}'

Con el label org.opencontainers.image.revision horneado en el build (OCI label estándar): digest → sha del commit. Un comando, cadena completa — la auditoría del 47 ("¿qué código corrió cuando el cliente llamó?") es una línea.

Ejercicio 3 — El despliegue

  1. El log del switch:
10:14:02 deploy.sh green   → green up (imagen a1b2c3d)
10:14:18 wait_healthy green → /healthz/ OK ×3
10:14:22 switch_traffic.sh → nginx upstream → green
10:14:23 smoke.sh: /events 200, /reservations 201, /healthz 200 → GREEN CONFIRMED
0 respuestas 500 en el switch (el nginx drena las conexiones del blue por keepalive)
  1. El rollback medido: el smoke rojo → rollback.sh → switch de vuelta a blue → tráfico restablecido en 11 s. El despliegue sin rollback probado es una apuesta: el ejercicio cuesta 20 minutos y compra la tranquilidad de TODOS los futuros deploys.
  1. La decisión: blue-green (el canary exige el enrutado por porcentaje + métricas por versión que el nginx local no da; el costo del 2× infra en TicketFlow es ~30 €/mes — el rollback de 11 s lo paga). El párrafo: "Blue-green: rollback <15 s probado, infra 2× asumida. Si pasamos a canary: el rollback automático lo dispara http_5xx_rate > 2% durante 2 min o p95 > 2× línea base (46), con el alarm de canary como dueño".

Ejercicio 4 — Las migraciones

  1. El guard del release: el job corre python manage.py makemigrations --check (nadie olvida generarlas) + el linter del 11 (django-migration-linter) con --fail-on-danger: una migración que renombra columna sin el expand-contract red-paintea el job con el remedio en el mensaje.
  1. El estado intermedio demostrado: el test del 11 corre la suite contra el esquema EXPANDIDO con el CÓDIGO VIEJO (el checkout de la app blue): verde — la columna nueva es nullable/sin default que rompa, el código viejo ni la ve. Esa es la prueba de que el expand-contract permite el convivio blue/green.
  1. Los JAMÁS documentados: (a) app nueva sin esquema: column "org_id" does not exist en el primer request → 500 en cadena (~14 en 3 s hasta el rollback) — el migrate ANTES del deploy es la ley; (b) migrate && runserver en el contenedor: el rollback re-despliega blue... que RE-CORRE el migrate del arranque (de nuevo, con lock, con lentitud) y, peor, el rollback no puede "no migrar": la migración ya está aplicada y el código blue con el esquema nuevo convive — el rollback deja de ser atómico. El runbook del 47 lleva ambas advertencias.

Ejercicio 5 — El círculo

  1. La anotación: el webhook del pipeline llama al log (45):
json
{"event": "deployment_completed", "commit": "a1b2c3d", "digest": "sha256:9f2c...", "actor": "buffy", "ts": "..."}

La gráfica del p95 con la línea vertical: el "¿qué cambió a las 14:20?" del 47 se responde con hover.

  1. Las 4 DORA de los últimos 10 deploys del proyecto: lead time PR→prod: 1.8 h (sana); frecuencia: 2.1/día (sana); tasa de fallo del deploy: 20% (2 de 10: el smoke lo cazó — el fallo del deploy DETECTADO es casi gratis, la vergüenza sería el 502 en prod); MTTR: 14 min (el rollback de 11 s + el diagnóstico). La vergüenza del equipo de 1: los deploys de viernes por la tarde siguen ocurriendo — el documento la prohíbe y el calendario no la cumple.
  1. El docs/ci-cd.md (esqueleto de la página): ASCII del pipeline con gates → estrategia (blue-green, razón: rollback <15 s) → orden canónico del release (migrate expand → deploy → smoke → switch → contract) → política de rollback → los dos JAMÁS del ejercicio 4 → DORA mensual. Una página que el tú de las 3 a.m. lee de arriba a abajo sin escalas.

Resumen del profesor

  • El gate del merge es verde y determinista; lo lento corre después: el pipeline protege el ciclo de feedback tanto como el código.
  • El digest es el artefacto del release: commit→imagen→entorno auditable en un comando; el deploy despliega, jamás reconstruye.
  • Blue-green + expand-contract + rollback probado: la pareja que convierte el deploy en un evento aburrido — y aburrido es la meta.