English · Español
Break 00 — Deploy sin camino de rollback; simular un release malo; medir el coste de recuperación¶
🇪🇸 Si no preservas la imagen anterior, "rollback" deja de ser un flip atómico y se vuelve "reconstruir desde el commit anterior" — minutos a horas de servicio degradado. Este
/breakquita el paso que preserva la imagen previa, despliega una versión rota, y mide cuánto cuesta recuperarse.
Lo que harás¶
Modificar el flujo de release para que la imagen del release anterior sea garbage-collected del registry tan pronto como se etiquete un nuevo release (un anti-patrón de "cleanup agresivo"). Luego pushear un release que falla un smoke test obvio. Observar que just rollback no puede encontrar la imagen previa y debe rebuildear desde cero.
Paso 1 — Localiza el flujo de release¶
Justfile # the `release` and `rollback` recipes
scripts/release.sh # the script that tags + pushes images
scripts/rollback.sh # the script that flips traffic
(Nombres de fichero aproximados; localiza los exactos en el repo.)
Paso 2 — Introduce el bug¶
En scripts/release.sh, el comportamiento actual mantiene los últimos 5 tags de imagen en artifacts/. Cámbialo para que borre todo excepto el tag actual:
# OLD — preserve last 5 releases
just artifacts-prune --keep 5
# NEW (the broken version)
just artifacts-prune --keep 1 # only the current release survives
El release tiene éxito visualmente. La nueva imagen está arriba. CI está en verde.
Paso 3 — Pushea un release deliberadamente roto¶
Aplica una pequeña regresión intencional: en src/minitutor/agent.py, intercambia el orden de correction y spanish en el JSON de respuesta. Esto rompe el contrato de schema de la API (Fase 30) — los clientes que esperan {"correction": ..., "spanish": ...} obtienen las claves en el orden equivocado, o peor, los valores intercambiados si el cliente deserializa posicionalmente.
# OLD
return {"correction": fix_en, "spanish": fix_es}
# NEW (the broken release content)
return {"spanish": fix_es, "correction": fix_en}
Etiqueta y haz release:
$ just release 1.2.3
[release] image tutor-1.2.3 pushed
[release] artifacts-prune --keep 1
[release] removed tutor-1.2.2.tar
[release] removed tutor-1.2.1.tar
[release] removed tutor-1.2.0.tar
[release] now serving 1.2.3
Corre el smoke test contra el /quiz/submit del portal:
$ just smoke-portal
[smoke] FAIL: response field order differs from contract;
[smoke] FAIL: portal renderer shows Spanish gloss in the English correction slot
Paso 4 — Intenta rollback¶
$ just rollback
[rollback] looking for previous release in artifacts/...
[rollback] ERROR: no prior release tag found (last available: tutor-1.2.3, current)
[rollback] would need to rebuild from previous commit. ETA: 8-12 minutes.
El rollback que se suponía un flip de tráfico de 30 segundos es ahora un rebuild + redeploy de 10 minutos. Durante esos 10 minutos, cada estudiante pegándole al portal ve la respuesta rota.
Paso 5 — Registra el break¶
learners/borja/phase-38/notes/breaks.md:
- bug-id: 38-01
concept: rollback path requires the previous artifact to exist
symptom: just rollback errors out with "no prior release tag found";
recovery now requires a full rebuild from the previous commit;
~10 minutes of broken service vs the expected ~30 seconds.
hidden_cause: release.sh prunes too aggressively (--keep 1);
the previous image is gone before rollback can use it.
hint_1: "What does --keep 1 mean? What does --keep 5 mean?"
hint_2: "What's the relationship between 'rollback is fast' and 'old artifacts exist'?"
hint_3: "Diff release.sh against the previous commit. What flag changed?"
fix_diff: restore --keep 5 in release.sh. Also revert the schema-swap in
agent.py to make the failing test go green.
Paso 6 — Aplica el fix¶
Dos cosas a revertir:
release.sh: restaura--keep 5para que los últimos 5 releases se preserven.agent.py: restaura el orden de claves original — esto también pone el smoke test en verde.
Tras: just rollback vuelve a ser un flip de 30 segundos. El release malo se queda como prunable, pero su predecesor siempre está ahí.
Lo que esto enseña¶
Dos lecciones entrelazadas:
- Rollback no es un procedimiento separado — es "deploy, apuntando a un tag más viejo". Para que eso sea barato, el tag más viejo tiene que existir. El cleanup agresivo destruye la opción de rollback.
- El coste de los releases malos se mide en MTTR, no en severidad del bug. Un bug de 10 segundos para arreglar con MTTR de 10 minutos es peor que un bug de 1 minuto para arreglar con MTTR de 30 segundos.
El conteo de líneas del fix es pequeño; la disciplina operacional detrás es grande.
Reglas duras respetadas¶
- Dos cambios acoplados — uno en
release.sh, uno enagent.py— pero conjuntamente modelan un único escenario "sin opción de rollback + release malo", que es el patrón real de fallo en producción. El bug-id captura ambos. - Reversible en ≤ 5 líneas en total.
- Observable: el smoke test falla, el rollback falla al encontrar artefacto previo, MTTR medido con cronómetro.
- Sin CVE de seguridad introducido.
- Tests no modificados.
Siguiente: cuando esté verde, vuelve a leer ../theory/06-build-deploy-rollback-and-ci-matrix.md — la regla "el paso 5 debe existir antes del paso 4" es la violada aquí.