Módulo 5: Security Scanning y Rollback Inteligente
Rollback Automático con Triggers
Rollback Automático con Triggers
Descripción
Esta cápsula cubre la última línea de defensa del pipeline: rollback automático cuando algo sale mal en producción. Aunque tengas code review, security scanning, readiness validation y approval humano, siempre va a haber un deploy que falla en producción. La diferencia entre un equipo que sobrevive a esos incidents y uno que sufre semanas de fallout es cuánto tarda en revertir.
Aprendes a definir qué métricas monitorear post-deploy, configurar triggers automáticos de rollback, ejecutar el revert sin intervención humana cuando las señales son claras, y mantener el balance correcto entre "rollback agresivo" (false positives = downtime innecesario) y "rollback conservador" (slow response = más usuarios afectados).
Al terminar, vas a tener un sistema donde un deploy fallido se revierte en menos de 5 minutos automáticamente — y donde el rollback explica qué pasó (lo que cubre la cápsula 05).
Por Qué el Rollback Manual Falla
ROLLBACK MANUAL EN INCIDENT REAL:
T+0: Deploy a producción
T+2min: Métricas empiezan a degradar
T+5min: Alert llega al on-call (si está configurada)
T+8min: On-call abre el laptop
T+12min: Identifica que es post-deploy
T+15min: Decide hacer rollback
T+18min: Encuentra el comando exacto
T+22min: Ejecuta revert
T+25min: Verifica que la métrica volvió
TIEMPO TOTAL: ~25 minutos de degradación
USUARIOS AFECTADOS: variable, potencialmente miles
Versus rollback automático:
ROLLBACK AUTOMÁTICO:
T+0: Deploy a producción
T+2min: Métricas degradan (error rate >5%)
T+3min: Trigger detecta el threshold
T+3min: Rollback automático ejecutándose
T+5min: Métricas estabilizadas
T+5min: On-call notificado del rollback completado
TIEMPO TOTAL: ~5 minutos de degradación
USUARIOS AFECTADOS: orden de magnitud menor
5 min vs 25 min es la diferencia entre un blip casi invisible y un incident que tu equipo va a recordar.
Las Métricas que Disparan Rollback
TIER 1 — Síntomas claros (rollback automático apropiado):
✅ Error rate sube significativamente
→ Threshold típico: >2% sobre baseline
→ Tiempo de observación: 2-5 minutos sostenidos
✅ Latency p95 sube significativamente
→ Threshold típico: 2-3x el baseline
→ Tiempo de observación: 3-5 minutos sostenidos
✅ Health check falla
→ Threshold: 3+ health checks consecutivos fallidos
→ Tiempo de observación: inmediato
TIER 2 — Síntomas que requieren juicio (alert para humano):
⚠️ Throughput baja
→ Puede ser real (bug) o esperado (cambio de comportamiento)
→ Mejor: alert al on-call, no rollback automático
⚠️ Memory/CPU sube
→ Puede ser memory leak o cambio en workload
→ Mejor: alert al on-call
TIER 3 — Síntomas indirectos (no disparan rollback):
❌ Métricas de negocio (signups, conversiones)
→ Demasiado ruido del comportamiento del usuario
❌ Errors de un tercero (Stripe, AWS)
→ No es nuestro deploy
La regla: rollback automático solo en Tier 1. Otras métricas → alert humano que decide.
Setup del Monitoring
Antes de configurar rollback, necesitas monitoring con métricas accesibles vía API. Stack típico:
APPLICATION METRICS:
→ Prometheus + Grafana (open source, self-hosted)
→ Datadog (paid, full-featured)
→ New Relic (paid, focus en APM)
→ CloudWatch (AWS native)
LOGS / ERRORS:
→ Sentry (errors)
→ Loki / Splunk / Elasticsearch (logs)
INFRASTRUCTURE:
→ Mismas herramientas, métricas de cluster
Cualquiera funciona. Lo importante es tener API accesible desde el workflow que diga: "en los últimos N minutos, ¿el error rate fue >X%?"
El Workflow con Rollback
# .github/workflows/deploy-with-rollback.yml
name: Deploy with Auto-Rollback
on:
push:
branches: [main]
permissions:
contents: read
deployments: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
outputs:
previous_release: ${{ steps.deploy.outputs.previous_release }}
new_release: ${{ steps.deploy.outputs.new_release }}
steps:
- uses: actions/checkout@v4
- name: Capture previous release
id: previous
run: |
# Tu mecanismo: git tag, kubectl, etc.
PREV=$(./scripts/get_current_release.sh production)
echo "previous=$PREV" >> $GITHUB_OUTPUT
- name: Deploy
id: deploy
run: |
NEW=$(./scripts/deploy.sh production)
echo "previous_release=${{ steps.previous.outputs.previous }}" >> $GITHUB_OUTPUT
echo "new_release=$NEW" >> $GITHUB_OUTPUT
- name: Initial smoke tests
run: ./scripts/smoke_tests.sh https://app.example.com
monitor-and-rollback:
needs: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install requests anthropic
- name: Monitor metrics for 10 minutes
id: monitor
env:
METRICS_API_URL: ${{ secrets.METRICS_API_URL }}
METRICS_API_TOKEN: ${{ secrets.METRICS_API_TOKEN }}
DEPLOY_TIME: ${{ needs.deploy.outputs.new_release }}
run: |
python scripts/monitor_post_deploy.py
echo "rollback_needed=$?" >> $GITHUB_OUTPUT
- name: Execute rollback if needed
if: steps.monitor.outputs.rollback_needed == '1'
env:
PREVIOUS_RELEASE: ${{ needs.deploy.outputs.previous_release }}
run: |
echo "🚨 Rolling back to $PREVIOUS_RELEASE"
./scripts/rollback.sh production "$PREVIOUS_RELEASE"
- name: Notify team of rollback
if: steps.monitor.outputs.rollback_needed == '1'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: python scripts/notify_rollback.py
- name: Notify success
if: steps.monitor.outputs.rollback_needed == '0'
run: echo "✅ Deploy estable, no rollback necesario"
El Script de Monitoring
"""scripts/monitor_post_deploy.py
Monitorea métricas post-deploy. Retorna exit 1 si necesita rollback.
"""
import json
import os
import sys
import time
from dataclasses import dataclass
from datetime import datetime, timedelta
import requests
@dataclass
class MetricThreshold:
name: str
threshold: float
description: str
sustained_minutes: int = 3 # cuánto tiempo debe estar sobre el threshold
THRESHOLDS = [
MetricThreshold(
name="error_rate",
threshold=0.02, # 2%
description="Error rate > 2% sostenido por 3 min",
sustained_minutes=3,
),
MetricThreshold(
name="latency_p95",
threshold=2.0, # 2x baseline
description="Latency p95 > 2x baseline sostenido por 5 min",
sustained_minutes=5,
),
MetricThreshold(
name="health_check_failures",
threshold=3,
description="3+ health checks consecutivos fallidos",
sustained_minutes=1,
),
]
def fetch_metric(name: str, window_minutes: int = 1) -> float | None:
"""Obtener métrica del sistema de monitoring."""
api_url = os.environ.get("METRICS_API_URL")
api_token = os.environ.get("METRICS_API_TOKEN")
if not api_url:
# Mock para testing
# En producción real, query a Prometheus/Datadog/etc.
return mock_metric(name)
headers = {"Authorization": f"Bearer {api_token}"}
params = {
"metric": name,
"window": f"{window_minutes}m",
}
try:
r = requests.get(f"{api_url}/query", headers=headers, params=params, timeout=10)
if r.ok:
data = r.json()
return data.get("value")
except Exception as e:
print(f"WARNING: failed to fetch {name}: {e}", file=sys.stderr)
return None
def mock_metric(name: str) -> float:
"""Mock para demo — en producción reemplazar con API real."""
# Simula valores normales por defecto
return {
"error_rate": 0.005,
"latency_p95": 1.0,
"health_check_failures": 0,
}.get(name, 0.0)
def check_threshold(threshold: MetricThreshold) -> tuple[bool, float]:
"""Verificar si la métrica está sostenidamente sobre el threshold.
Retorna (is_over_threshold, current_value).
"""
samples = []
samples_needed = threshold.sustained_minutes
for i in range(samples_needed):
value = fetch_metric(threshold.name, window_minutes=1)
if value is not None:
samples.append(value)
else:
samples.append(0) # asumir normal si no hay data
if i < samples_needed - 1:
time.sleep(60) # esperar 1 min entre samples
# Todos los samples sobre el threshold
all_over = all(s > threshold.threshold for s in samples)
avg_value = sum(samples) / len(samples) if samples else 0
return all_over, avg_value
def main() -> int:
"""Monitorear por 10 minutos y decidir si necesita rollback."""
print(f"=== Post-deploy monitoring ({datetime.utcnow().isoformat()}) ===\n")
# Esperar 1 min después del deploy para que las métricas se estabilicen
print("Waiting 60s for metrics to stabilize...")
time.sleep(60)
# Check cada threshold
for threshold in THRESHOLDS:
print(f"Monitoring {threshold.name}...")
is_over, value = check_threshold(threshold)
if is_over:
print(f" 🚨 ALERT: {threshold.description}")
print(f" Current value: {value}")
print(f" Action: ROLLBACK")
# Salvar info del rollback para los siguientes steps
with open("rollback_reason.json", "w") as f:
json.dump({
"metric": threshold.name,
"threshold": threshold.threshold,
"current_value": value,
"description": threshold.description,
"timestamp": datetime.utcnow().isoformat(),
}, f, indent=2)
return 1 # exit 1 → rollback needed
print(f" ✅ OK: {threshold.name} = {value}")
print("\n✅ Monitoring complete. Deploy stable.")
return 0
if __name__ == "__main__":
sys.exit(main())
El Script de Rollback
#!/bin/bash
# scripts/rollback.sh — ejecutar rollback al release anterior
set -euo pipefail
ENV="${1:?'Usage: rollback.sh <env> <previous_release>'}"
PREVIOUS_RELEASE="${2:?'Usage: rollback.sh <env> <previous_release>'}"
echo "[rollback] Rolling back $ENV to $PREVIOUS_RELEASE..."
# Ajusta según tu plataforma:
# Kubernetes
# kubectl rollout undo deployment/app -n $ENV --to-revision=$PREVIOUS_RELEASE
# Heroku
# heroku releases:rollback v$PREVIOUS_RELEASE --app $ENV
# AWS ECS
# aws ecs update-service --cluster $ENV --service app \
# --task-definition app:$PREVIOUS_RELEASE
# Vercel / Netlify
# vercel rollback $PREVIOUS_RELEASE
# Custom deploy script
./scripts/deploy.sh "$ENV" --release "$PREVIOUS_RELEASE"
echo "[rollback] Rollback to $PREVIOUS_RELEASE complete"
# Verificar smoke tests
./scripts/smoke_tests.sh https://app.example.com
echo "[rollback] Verification complete"
Notificación Post-Rollback
"""scripts/notify_rollback.py
Notificar al equipo del rollback automático con contexto.
"""
import json
import os
import sys
from pathlib import Path
import requests
from anthropic import Anthropic
def generate_notification(reason: dict) -> str:
"""Generar mensaje de notificación con contexto."""
client = Anthropic()
prompt = f"""El sistema acaba de hacer rollback automático en producción.
CONTEXTO:
{json.dumps(reason, indent=2)}
Genera un mensaje BREVE para Slack que:
1. Empiece con "🚨 Auto-rollback executed"
2. Explique qué métrica disparó el rollback
3. Diga qué hace el on-call ahora (verificar estabilización, investigar root cause)
4. Sea profesional y actionable, máximo 100 palabras
Output: solo el mensaje, sin texto adicional.
"""
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=300,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text.strip()
def send_slack(message: str, webhook: str):
"""Enviar a Slack."""
payload = {"text": message, "username": "Auto-Rollback Bot", "icon_emoji": ":rotating_light:"}
r = requests.post(webhook, json=payload, timeout=10)
if not r.ok:
print(f"WARNING: Slack failed: {r.status_code}", file=sys.stderr)
def main() -> int:
reason_file = Path("rollback_reason.json")
if not reason_file.exists():
print("ERROR: rollback_reason.json no encontrado", file=sys.stderr)
return 1
reason = json.loads(reason_file.read_text())
message = generate_notification(reason)
print("--- Notification ---")
print(message)
print("---")
webhook = os.environ.get("SLACK_WEBHOOK")
if webhook:
send_slack(message, webhook)
print("Notificación enviada a Slack")
# Adicional: PagerDuty para incidents urgentes
pd_token = os.environ.get("PAGERDUTY_TOKEN")
if pd_token:
# Crear incident en PagerDuty
# ...
pass
return 0
if __name__ == "__main__":
sys.exit(main())
Calibrar los Thresholds
Los valores de threshold son específicos de tu sistema. Calíbralos basándote en:
DATOS A RECOPILAR:
1. Baseline normal: ¿cuál es el error rate típico? Latency p95?
2. Variabilidad histórica: ¿cuánto fluctúa naturalmente?
3. Falsos positivos previos: ¿hubo alerts que no eran reales?
REGLA GENERAL:
- Threshold = baseline_max + 2-3x desviación estándar
- Sustained time: lo suficiente para descartar spikes (2-5 min típico)
- Cuando dudes, threshold más permisivo (rollback only si claro)
Ejemplo de calibración
Si tu error rate normal es 0.5% con desviación estándar de 0.2%:
- Baseline + 3σ = 0.5% + 0.6% = ~1.1%
- Threshold conservador: 2% (claramente sobre baseline)
- Sustained: 3 min (descarta spikes momentáneos)
Trampas Comunes
Error 1: Rollback agresivo (false positives)
Síntoma: Rollbacks frecuentes en deploys que no tenían bug real. El equipo pierde confianza.
Por qué pasa: Thresholds demasiado bajos o tiempo de observación demasiado corto.
Cómo corregir: Aumentar thresholds y/o sustained time. Mejor ser conservador con rollback que con alert humano.
Error 2: Rollback conservador (false negatives)
Síntoma: Bug llega a producción, métricas degradan, pero el rollback no dispara porque "no llegó al threshold".
Por qué pasa: Threshold demasiado alto o ventana muy larga.
Cómo corregir: Calibrar con datos históricos. Revisar incidents pasados — ¿qué thresholds los hubieran detectado?
Error 3: No capturar el release anterior antes del deploy
Síntoma: Necesitas revertir pero no sabes a qué versión.
Por qué pasa: El script de deploy no guarda el release anterior antes de aplicar el nuevo.
Cómo corregir: Step explícito que captura el current release antes del deploy y lo expone como output del job.
Error 4: Rollback sin verificar que estabilizó
Síntoma: Rollback ejecutado, pero las métricas siguen mal porque el rollback también falló.
Por qué pasa: El script de rollback ejecuta sin verificar smoke tests post-rollback.
Cómo corregir: Smoke tests después del rollback. Si el rollback en sí falla, escalar a humano (PagerDuty).
Error 5: Thresholds estáticos sin actualizar
Síntoma: Thresholds configurados hace 1 año cuando el sistema era distinto. Ya no son apropiados.
Por qué pasa: Falta de revisión periódica.
Cómo corregir: Review trimestral de thresholds. Comparar con incidents recientes — ¿hubieran disparado correctamente?
Diagnóstico
Pregunta 1: ¿Tu pipeline tiene rollback automático configurado?
Sin rollback automático, el primer incident en producción tarda 25+ minutos. Con rollback, ~5 minutos.
Pregunta 2: ¿Qué métricas disparan tu rollback?
Tier 1: error rate, latency p95, health checks. Si solo monitoreas "is the app up?", te faltan signals importantes.
Pregunta 3: ¿Cuánto tarda tu rollback desde detection hasta restored service?
Target: <5 minutos. Si es más, optimizar el script de rollback (cache de imágenes, etc.).
Pregunta 4: ¿Capturas el "previous release" antes del deploy?
Si no, no puedes revertir programáticamente. Step de captura es esencial.
Pregunta 5: ¿Los thresholds están calibrados a tu sistema o son genéricos?
Genéricos = false positives o false negatives. Calibrados a baseline real = preciso.
Ejercicios
Ejercicio 1: Capture previous release (Fácil)
Implementa un script get_current_release.sh que retorna el release actualmente deployado en producción. Adáptalo a tu plataforma (Kubernetes, Heroku, etc.).
Ejercicio 2: Monitoring básico con thresholds (Medio)
Implementa monitor_post_deploy.py con:
- Mock de métricas (función
mock_metric) - Thresholds para error_rate, latency, health checks
- Sustained time check (samples cada minuto)
- Exit code 1 si necesita rollback
Probarlo simulando métricas que excedan el threshold.
Ejercicio 3: Pipeline completo con rollback (Difícil)
Combina:
- Deploy
- Capture previous release
- Monitor post-deploy (10 min)
- Rollback automático si necesita
- Notificación con contexto
Probarlo en un ambiente de staging — forzar un "fake fail" para verificar el rollback.
Resumen
- Rollback manual = 25 min, rollback automático = 5 min — orden de magnitud diferencia
- Tier 1 metrics disparan rollback: error rate, latency p95, health checks
- Tier 2 metrics alertan a humano: throughput, memory, CPU
- Sustained time evita false positives de spikes momentáneos
- Capture previous release ANTES del deploy — necesario para revertir programáticamente
- Verificar smoke tests post-rollback — el rollback en sí puede fallar
- Calibrar thresholds a tu sistema, no usar valores genéricos
Próxima cápsula: 05 — Diagnóstico inteligente post-rollback. El rollback funciona, pero no enseña. La cápsula final del módulo: cuando el rollback se ejecuta, Claude Code analiza qué pasó, identifica root cause, y sugiere fix. El rollback se vuelve aprendizaje sistematizado.
Recursos Adicionales
- Site Reliability Engineering: Effective Troubleshooting — Capítulo del libro de Google
- Argo Rollouts — Rollback automático en Kubernetes
- Spinnaker Canary Analysis — Para canary + rollback combinado
- Datadog Synthetics — Monitoring continuo de endpoints
- Prometheus Alertmanager — Alerting open source
- Honeycomb Observability — Observability moderna para sistemas distribuidos