SRE: Gestión de Incidentes, Guardias y Postmortems como Código

2026-02-23 | Gabriel Garrido | 17 min de lectura
Share:

Apoya este blog

Si te resulta util este contenido, considera apoyar el blog.

Introducción

En el artículo anterior cubrimos cómo definir SLIs y SLOs como código, desplegarlos con ArgoCD, y usar servidores MCP para automatizar flujos de trabajo de SRE. Pero dejamos afuera una pregunta crítica: ¿qué pasa cuando tus SLOs efectivamente se rompen?


De eso se trata este artículo. Vamos a cubrir el ciclo de vida completo de un incidente: detectar problemas a través de buenas alertas, responder efectivamente con rotaciones de guardia y políticas de escalamiento, mitigar incidentes rápido con automatización de runbooks, y aprender de las fallas a través de postmortems sin culpas. Todo como código, porque si no está automatizado y versionado, se pudre.


Vamos al tema.


El ciclo de vida de un incidente

Antes de meternos con herramientas, entendamos las fases por las que pasa todo incidente:


  1. Detección: Tu monitoreo detecta que algo anda mal (idealmente antes que los usuarios)
  2. Triaje: Alguien evalúa la severidad y decide si es un incidente real
  3. Respuesta: Se contacta a las personas correctas y empiezan a trabajar en el tema
  4. Mitigación: Parás la hemorragia, aunque no entiendas completamente la causa raíz
  5. Resolución: Arreglás el problema de fondo
  6. Aprendizaje: Hacés un postmortem para evitar que vuelva a pasar

La mayoría de los equipos son decentes en los pasos 1 al 5 pero pésimos en el paso 6. Eso es lo que vamos a arreglar.


Alertas que no apestan

La base de la gestión de incidentes son las alertas. Malas alertas llevan a fatiga de alertas, que lleva a que la gente las ignore, que lleva a incidentes que pasan desapercibidos por horas. Vi este ciclo más veces de las que me gustaría admitir.


Si seguiste el artículo anterior, ya tenés alertas basadas en SLOs generadas por Sloth. Esas son geniales porque usan detección multi-ventana y multi-tasa-de-quemado. Pero hablemos de los principios que hacen que las alertas sean realmente útiles:


1. Alertá por síntomas, no por causas

A tus usuarios no les importa que el CPU esté al 90%. Les importa que la página tarde 10 segundos en cargar. Alertá por los SLIs (latencia, disponibilidad, calidad), no por métricas de infraestructura.


# MAL: alertando por causa
- alert: HighCPUUsage
  expr: node_cpu_seconds_total > 0.9
  labels:
    severity: critical

# BIEN: alertando por síntoma (basado en SLI)
- alert: TrWebHighLatency
  expr: |
    sli:latency:ratio_rate5m < 0.99
    and
    sli:latency:ratio_rate1h < 0.99
  labels:
    severity: critical
    team: platform
  annotations:
    summary: "Se está violando el SLO de latencia"
    runbook: "https://runbooks.example.com/tr-web-high-latency"

2. Cada alerta debe tener un runbook

Si una alerta salta y la persona de guardia no tiene idea de qué hacer, la alerta es inútil. Cada anotación de alerta debería incluir un link a un runbook que explique qué significa la alerta, cómo hacer triaje y cómo mitigarla.


3. Dos niveles de severidad son suficientes

Mantenelo simple. Necesitás dos niveles:


  • Pager (crítico): Despertá a alguien a las 3am. El servicio está caído o el SLO se está quemando rápido.
  • Ticket (warning): Creá un ticket para horario laboral. Algo se está degradando lentamente.

Si tenés cinco niveles de severidad, nadie sabe cuáles tomar en serio. Dos niveles te fuerzan a tomar una decisión clara: ¿vale la pena despertar a alguien por esto? Si no, es un ticket.


Guardias que no queman a la gente

Las guardias son una de las partes más difíciles de SRE. Mal hechas, destruyen la moral y queman a tus mejores ingenieros. Bien hechas, son una responsabilidad manejable que hace que todos sean mejores construyendo sistemas confiables.


Acá van los principios que funcionan:


1. Schedule de rotación como código

Almacená tu schedule de guardia en un repositorio Git y sincronizalo con tu herramienta de alertas. Acá hay un ejemplo usando el provider de Terraform para PagerDuty:


# on-call/main.tf
resource "pagerduty_schedule" "platform_primary" {
  name      = "Platform Team - Primary"
  time_zone = "America/Argentina/Buenos_Aires"

  layer {
    name                         = "Rotación Semanal"
    start                        = "2026-01-01T09:00:00-03:00"
    rotation_virtual_start       = "2026-01-01T09:00:00-03:00"
    rotation_turn_length_seconds = 604800  # 1 semana

    users = [
      pagerduty_user.alice.id,
      pagerduty_user.bob.id,
      pagerduty_user.charlie.id,
      pagerduty_user.diana.id,
    ]

    restriction {
      type              = "daily_restriction"
      start_time_of_day = "09:00:00"
      duration_seconds  = 57600  # 16 horas (9am a 1am)
    }
  }
}

resource "pagerduty_escalation_policy" "platform" {
  name      = "Escalamiento Equipo de Plataforma"
  num_loops = 2

  rule {
    escalation_delay_in_minutes = 15

    target {
      type = "schedule_reference"
      id   = pagerduty_schedule.platform_primary.id
    }
  }

  rule {
    escalation_delay_in_minutes = 15

    target {
      type = "user_reference"
      id   = pagerduty_user.team_lead.id
    }
  }
}

2. Seguí al sol (si podés)

Si tu equipo está distribuido en múltiples zonas horarias, configurá rotaciones que sigan al sol para que nadie reciba un pager a las 3am. Incluso con un equipo chico, podés superponer turnos para que la persona en la mejor zona horaria maneje la mayoría de las alertas fuera de horario.


3. Compensá las guardias apropiadamente

Este no es un punto técnico, pero importa. Las guardias son trabajo extra. Si tu organización no las compensa (pago extra, días libres compensatorios, o como mínimo reconocerlo), la gente se va a resentir e irse. El libro de SRE de Google recomienda que las guardias no superen el 25% del tiempo de un ingeniero.


4. Medí métricas de salud de las guardias

Medí cómo van tus guardias:


  • Pagers por turno: Si alguien recibe más de dos pagers por turno, tus alertas tienen mucho ruido
  • Tiempo de reconocimiento: ¿Cuánto tarda alguien en responder?
  • Tasa de falsos positivos: ¿Qué porcentaje de pagers resultan ser nada?
  • Pagers fuera de horario: ¿Cuántos pagers pasan fuera del horario laboral?

Podés sacar estas métricas de la API de PagerDuty y meterlas en tus dashboards:


# on-call-health/cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: oncall-health-metrics
  namespace: monitoring
spec:
  schedule: "0 8 * * 1"  # Todos los lunes a las 8am
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: metrics-collector
              image: kainlite/oncall-health:latest
              env:
                - name: PAGERDUTY_API_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: pagerduty-token
                      key: token
                - name: PROMETHEUS_PUSHGATEWAY
                  value: "http://prometheus-pushgateway:9091"
              command:
                - /bin/sh
                - -c
                - |
                  # Traer incidentes de los últimos 7 días
                  SINCE=$(date -d '7 days ago' -Iseconds)
                  UNTIL=$(date -Iseconds)

                  curl -s -H "Authorization: Token token=$PAGERDUTY_API_TOKEN" \
                    "https://api.pagerduty.com/incidents?since=$SINCE&until=$UNTIL" \
                    | jq '{
                        total_incidents: .incidents | length,
                        acknowledged_avg_seconds: ([.incidents[].acknowledges[0].at // empty | fromdateiso8601] | add / length),
                        off_hours: [.incidents[] | select((.created_at | strftime("%H") | tonumber) < 9 or (.created_at | strftime("%H") | tonumber) > 17)] | length
                      }' > /tmp/metrics.json

                  # Pushear a Prometheus
                  TOTAL=$(jq .total_incidents /tmp/metrics.json)
                  OFF_HOURS=$(jq .off_hours /tmp/metrics.json)

                  cat <<PROM | curl --data-binary @- http://prometheus-pushgateway:9091/metrics/job/oncall_health
                  # HELP oncall_incidents_total Incidentes totales en los últimos 7 días
                  oncall_incidents_total $TOTAL
                  # HELP oncall_off_hours_total Incidentes fuera de horario en los últimos 7 días
                  oncall_off_hours_total $OFF_HOURS
                  PROM
          restartPolicy: Never

Runbooks como código

Un runbook es una guía paso a paso para manejar un tipo específico de alerta o incidente. Los mejores runbooks no son documentos juntando polvo en una wiki — son código ejecutable que vive al lado de tu infraestructura.


Acá hay una estructura que funciona bien:


runbooks/
├── tr-web-high-latency.md
├── tr-web-high-error-rate.md
├── database-connection-pool-exhausted.md
├── certificate-expiring.md
├── disk-space-critical.md
└── templates/
    └── runbook-template.md

Y acá cómo se ve un buen runbook:


# TR-Web Alta Latencia - Runbook

## Alerta
- **Nombre**: TrWebHighLatency
- **Severidad**: critical (pager) / warning (ticket)
- **SLO**: 99% de las requests en menos de 300ms

## Diagnóstico rápido

1. Verificar si es un problema relacionado con un deploy:

kubectl -n default rollout history deployment/tr-web argocd app history tr-web


2. Verificar uso de recursos de los pods:

kubectl -n default top pods -l app=tr-web


3. Verificar pool de conexiones a la base de datos:

kubectl -n default exec deploy/tr-web – bin/tr rpc “Ecto.Adapters.SQL.query(Tr.Repo, \”SELECT count(*) FROM pg_stat_activity\”)”


4. Verificar problemas con dependencias externas:

kubectl -n default logs deploy/tr-web –since=10m | grep -i error | head -20


## Acciones de mitigación

### Si fue causado por un deploy reciente

argocd app rollback tr-web


### Si fue causado por la base de datos

kubectl -n default rollout restart deployment/tr-web


### Si fue causado por una dependencia externa (ej: API de GitHub)
- Revisá `lib/tr/sponsors.ex` - el fetch de sponsors de GitHub corre en un schedule
- El pool dedicado de Hackney `:github_pool` debería aislarlo, pero verificá:

kubectl -n default logs deploy/tr-web –since=10m | grep “github_pool\|sponsors”


## Escalamiento
- Si no se resuelve en 30 minutos, escalar al team lead
- Si se sospecha pérdida de datos, escalar inmediatamente

La clave es que cada paso de diagnóstico tiene comandos reales que podés copiar y pegar. Cuando estás medio dormido a las 3am, no querés pensar — querés seguir pasos.


Automatizando pasos del runbook con Kubernetes Jobs

Algunos pasos del runbook se pueden automatizar completamente. Por ejemplo, si una alerta de alta latencia siempre se resuelve con un restart de pods, ¿por qué no automatizarlo?


Acá hay un patrón usando webhooks de Alertmanager y un controlador simple de automatización:


# alertmanager-config.yaml
receivers:
  - name: auto-remediation
    webhook_configs:
      - url: http://remediation-controller:8080/webhook
        send_resolved: true

route:
  routes:
    - match:
        alertname: TrWebHighLatency
        auto_remediate: "true"
      receiver: auto-remediation
      continue: true  # también enviar a los receivers normales

El controlador de remediación recibe el webhook y crea un Job de Kubernetes:


# remediation-controller/handler.py
from kubernetes import client, config
import json

def handle_alert(alert):
    alertname = alert["labels"]["alertname"]
    namespace = alert["labels"].get("namespace", "default")

    if alertname == "TrWebHighLatency" and alert["status"] == "firing":
        # Crear un job de remediación
        job = client.V1Job(
            metadata=client.V1ObjectMeta(
                name=f"remediate-{alertname.lower()}-{int(time.time())}",
                namespace=namespace,
            ),
            spec=client.V1JobSpec(
                ttl_seconds_after_finished=3600,
                template=client.V1PodTemplateSpec(
                    spec=client.V1PodSpec(
                        service_account_name="remediation-sa",
                        restart_policy="Never",
                        containers=[
                            client.V1Container(
                                name="remediate",
                                image="bitnami/kubectl:latest",
                                command=[
                                    "/bin/sh", "-c",
                                    f"kubectl -n {namespace} rollout restart deployment/tr-web"
                                ],
                            )
                        ],
                    )
                ),
            ),
        )

        batch_v1 = client.BatchV1Api()
        batch_v1.create_namespaced_job(namespace=namespace, body=job)
        print(f"Job de remediación creado para {alertname}")

Esto es poderoso pero peligroso. La remediación automática debería:


  • Solo dispararse para acciones bien entendidas y seguras (como un restart de pods)
  • Tener un período de cooldown (no reiniciar pods en loop)
  • Siempre notificar a humanos sobre lo que hizo
  • Nunca hacer cambios destructivos (escalar a cero, borrar datos)

Postmortems sin culpas

Esta es posiblemente la parte más importante de la gestión de incidentes, y la que la mayoría de los equipos saltean. Un postmortem no se trata de encontrar quién la cagó. Se trata de entender qué pasó, por qué pasó, y cómo evitar que vuelva a pasar.


La palabra “sin culpas” es crítica. Si la gente tiene miedo de ser culpada, van a esconder información y el postmortem se vuelve inútil. El objetivo es mejorar el sistema, no castigar individuos.


Template de postmortem como código

Almacená tu template de postmortem en Git y generá nuevos postmortems a partir de él:


# postmortems/template.md
# Postmortem: [TÍTULO]

## Resumen del incidente
- **Fecha**: AAAA-MM-DD
- **Duración**: X horas Y minutos
- **Severidad**: SEV-1 / SEV-2
- **Impacto**: [Quiénes fueron afectados y cómo]
- **Detección**: [Cómo se detectó - alerta, reporte de usuario, etc.]

## Línea de tiempo (todos los horarios en UTC)
| Hora | Evento |
|------|--------|
| HH:MM | Alerta salta |
| HH:MM | Guardia reconoce |
| HH:MM | Causa raíz identificada |
| HH:MM | Mitigación aplicada |
| HH:MM | Resolución completa |

## Causa raíz
[Qué salió mal a nivel técnico]

## Factores contribuyentes
- [Factor 1]
- [Factor 2]

## Qué salió bien
- [Cosa 1]
- [Cosa 2]

## Qué se puede mejorar
- [Cosa 1]
- [Cosa 2]

## Acciones
| Acción | Responsable | Prioridad | Fecha límite | Estado |
|--------|-------------|-----------|-------------|--------|
| [Acción 1] | @persona | P1 | AAAA-MM-DD | TODO |
| [Acción 2] | @persona | P2 | AAAA-MM-DD | TODO |

## Lecciones aprendidas
[Conclusiones clave para el equipo]

## Impacto en presupuesto de error
- **SLO afectado**: [Qué SLO fue impactado]
- **Presupuesto consumido**: [Cuánto presupuesto de error quemó este incidente]
- **Presupuesto restante**: [Presupuesto de error actual después del incidente]

Automatizando la creación de postmortems

Podés automatizar la creación de un documento de postmortem cuando se resuelve un incidente SEV-1:


# postmortem-bot/create.sh
#!/bin/bash
# Disparado por webhook de PagerDuty cuando se resuelve un incidente SEV-1

INCIDENT_ID=$1
DATE=$(date +%Y-%m-%d)
TITLE=$(curl -s -H "Authorization: Token token=$PAGERDUTY_API_TOKEN" \
  "https://api.pagerduty.com/incidents/$INCIDENT_ID" | jq -r '.incident.title')

SLUG=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd '[:alnum:]-')
FILENAME="postmortems/${DATE}-${SLUG}.md"

# Copiar template y llenar campos conocidos
cp postmortems/template.md "$FILENAME"
sed -i "s/\[TÍTULO\]/$TITLE/g" "$FILENAME"
sed -i "s/AAAA-MM-DD/$DATE/g" "$FILENAME"

# Crear un issue de GitHub para la revisión del postmortem
gh issue create \
  --title "Postmortem: $TITLE" \
  --body "Documento de postmortem creado en \`$FILENAME\`. Por favor completá los detalles dentro de las 48 horas de la resolución del incidente." \
  --label "postmortem" \
  --assignee "$ON_CALL_ENGINEER"

# Crear un PR con el stub del postmortem
git checkout -b "postmortem/$SLUG"
git add "$FILENAME"
git commit -m "chore: crear postmortem para $TITLE"
git push origin "postmortem/$SLUG"
gh pr create --title "Postmortem: $TITLE" --body "Postmortem auto-generado. Por favor completar los detalles."

Esto asegura que los postmortems nunca se olviden. La regla de las 48 horas es importante — los detalles se pierden rápido, y si esperás una semana para escribir el postmortem, la gente se va a haber olvidado del contexto crucial.


Rastreando acciones

El postmortem solo es útil si las acciones realmente se hacen. Demasiados equipos escriben excelentes postmortems y después nunca completan las acciones.


Acá hay un enfoque simple: rastreá las acciones de postmortem como issues de GitHub con un label dedicado, y reportá las tasas de completitud semanalmente:


# action-item-tracker/report.sh
#!/bin/bash
# Correr semanalmente para reportar completitud de acciones de postmortem

OPEN=$(gh issue list --label "postmortem-action" --state open --json number | jq length)
CLOSED=$(gh issue list --label "postmortem-action" --state closed --json number | jq length)
TOTAL=$((OPEN + CLOSED))

if [ "$TOTAL" -gt 0 ]; then
  RATE=$(echo "scale=1; $CLOSED * 100 / $TOTAL" | bc)
else
  RATE="N/A"
fi

OVERDUE=$(gh issue list --label "postmortem-action" --state open --json number,title,createdAt \
  | jq '[.[] | select((.createdAt | fromdateiso8601) < (now - 2592000))] | length')

cat <<EOF | curl -X POST -H 'Content-Type: application/json' \
  -d @- "$SLACK_WEBHOOK_URL"
{
  "text": "Reporte Semanal de Acciones de Postmortem",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Reporte de Acciones de Postmortem*\n\n:white_check_mark: Completadas: $CLOSED/$TOTAL ($RATE%)\n:hourglass: Abiertas: $OPEN\n:warning: Vencidas (>30 días): $OVERDUE"
      }
    }
  ]
}
EOF

Si tu tasa de completitud está por debajo del 80%, algo anda mal. O las acciones son muy ambiciosas, no se priorizan bien, o el equipo no les ve valor. Arreglá el proceso, no a las personas.


Niveles de severidad de incidentes

Tener niveles de severidad claros previene el debate de “¿esto es tan grave como para molestar a alguien?” durante un incidente activo. Acá hay un marco simple:


  • SEV-1 (Crítico): El servicio está caído o severamente degradado para la mayoría de los usuarios. El presupuesto de error se quema rápido. Pager inmediato, todas las manos a la obra.
  • SEV-2 (Mayor): El servicio está degradado para algunos usuarios o una función no crítica está caída. Pager al guardia, pero no hace falta despertar a todo el equipo.
  • SEV-3 (Menor): Algo anda mal pero los usuarios no se ven significativamente impactados. Crear ticket, manejar en horario laboral.

Mapeá estos a tus tasas de quemado de SLO:


# severity-mapping.yaml
# Basado en alertas multi-tasa-de-quemado generadas por Sloth

severity_mapping:
  SEV-1:
    description: "Quemado rápido en SLO crítico"
    conditions:
      - burn_rate: "> 14.4x"
        window: "5m y 1h"
    response:
      - pager al guardia
      - abrir canal de incidente
      - iniciar línea de tiempo

  SEV-2:
    description: "Quemado medio en cualquier SLO"
    conditions:
      - burn_rate: "> 6x"
        window: "30m y 6h"
    response:
      - pager al guardia
      - crear ticket de incidente

  SEV-3:
    description: "Quemado lento en cualquier SLO"
    conditions:
      - burn_rate: "> 3x"
        window: "2h y 1d"
    response:
      - crear ticket
      - revisar el próximo día hábil

Comunicación durante incidentes

Durante un incidente, la comunicación es tan importante como el arreglo técnico. Los usuarios y stakeholders necesitan saber qué está pasando, y los que responden necesitan un canal claro para coordinarse.


1. Canal de incidente dedicado

Cuando salta un SEV-1 o SEV-2, creá automáticamente un canal de Slack dedicado. Esto mantiene el ruido del incidente fuera de los canales principales:


# incident-bot/create_channel.py
import slack_sdk
import datetime

def create_incident_channel(incident_title, severity):
    client = slack_sdk.WebClient(token=os.environ["SLACK_BOT_TOKEN"])

    date = datetime.datetime.now().strftime("%Y%m%d")
    slug = incident_title.lower().replace(" ", "-")[:30]
    channel_name = f"inc-{date}-{slug}"

    channel = client.conversations_create(name=channel_name)
    channel_id = channel["channel"]["id"]

    # Postear encabezado del incidente
    client.chat_postMessage(
        channel=channel_id,
        text=f"*Incidente: {incident_title}*\n"
             f"*Severidad*: {severity}\n"
             f"*Estado*: Investigando\n"
             f"*Guardia*: <@{get_current_oncall()}>\n\n"
             f"Por favor usá este canal para toda la comunicación del incidente.\n"
             f"Actualizá el estado con `/incident update <estado>`"
    )

    # Invitar a las personas relevantes
    client.conversations_invite(
        channel=channel_id,
        users=get_oncall_and_leads()
    )

    return channel_id

2. Actualizaciones de página de estado

Mantené informados a los usuarios con una página de estado. No necesitás una herramienta sofisticada — un sitio simple de GitHub Pages que se actualice por tu bot de incidentes funciona perfecto:


# status-page/update.sh
#!/bin/bash
# Actualizar página de estado durante un incidente

STATUS=$1  # investigating | identified | monitoring | resolved
MESSAGE=$2
DATE=$(date -Iseconds)

# Agregar al archivo de incidentes
cat >> _data/incidents.yml <<EOF
- date: "$DATE"
  status: "$STATUS"
  message: "$MESSAGE"
  service: "tr-web"
EOF

git add _data/incidents.yml
git commit -m "status: $STATUS - $MESSAGE"
git push origin main

Herramientas MCP para gestión de incidentes

Construyendo sobre el servidor MCP del artículo anterior, podés agregar herramientas de gestión de incidentes que te permitan manejar incidentes a través de lenguaje natural:


#[derive(Tool)]
#[tool(name = "create_incident", description = "Crear un nuevo incidente con severidad y descripción")]
struct CreateIncident {
    title: String,
    severity: String,  // SEV-1, SEV-2, SEV-3
    description: String,
}

impl CreateIncident {
    async fn execute(&self) -> ToolResult {
        // Crear incidente en PagerDuty
        let pd_incident = pagerduty_create_incident(&self.title, &self.severity).await?;

        // Crear canal de Slack
        let channel = create_incident_channel(&self.title, &self.severity).await?;

        // Iniciar stub de postmortem si es SEV-1
        if self.severity == "SEV-1" {
            create_postmortem_stub(&self.title).await?;
        }

        ToolResult::text(format!(
            "Incidente creado:\n- PagerDuty: {}\n- Slack: #{}\n- Severidad: {}",
            pd_incident.id, channel.name, self.severity
        ))
    }
}

#[derive(Tool)]
#[tool(name = "incident_timeline", description = "Obtener la línea de tiempo del incidente activo")]
struct IncidentTimeline {
    incident_id: String,
}

impl IncidentTimeline {
    async fn execute(&self) -> ToolResult {
        let events = pagerduty_get_timeline(&self.incident_id).await?;
        let timeline = events.iter()
            .map(|e| format!("{} - {}", e.timestamp, e.description))
            .collect::<Vec<_>>()
            .join("\n");

        ToolResult::text(format!("Línea de tiempo del incidente:\n{}", timeline))
    }
}

#[derive(Tool)]
#[tool(name = "recent_deploys", description = "Listar deploys recientes que podrían haber causado el incidente")]
struct RecentDeploys {
    service: String,
    hours: u32,
}

impl RecentDeploys {
    async fn execute(&self) -> ToolResult {
        let deploys = argocd_get_history(&self.service, self.hours).await?;
        let summary = deploys.iter()
            .map(|d| format!("{} - {} por {} ({})", d.timestamp, d.revision[..8].to_string(), d.author, d.message))
            .collect::<Vec<_>>()
            .join("\n");

        ToolResult::text(format!("Deploys recientes para {} (últimas {}h):\n{}", self.service, self.hours, summary))
    }
}

Con estas herramientas podés pedir cosas como:


  • “Creá un incidente SEV-2 por alta latencia en tr-web”
  • “¿Qué deploys hubo en las últimas 6 horas?”
  • “Mostrá la línea de tiempo del incidente actual”

Esto es increíblemente útil durante un incidente porque no tenés que cambiar de contexto entre múltiples UIs. Te quedás en un lugar y dejás que la IA coordine entre sistemas.


Juntando todo

Acá hay un resumen del flujo completo de gestión de incidentes como código:


  1. Alertas basadas en SLOs detectan el problema (del artículo anterior)
  2. PagerDuty/OpsGenie contacta al guardia basándose en schedules manejados con Terraform
  3. Bot de incidentes crea un canal de Slack e inicia la línea de tiempo
  4. Runbooks guían al guardia por el diagnóstico y la mitigación
  5. Auto-remediación maneja problemas conocidos automáticamente (restart de pods, limpieza de cache)
  6. Herramientas MCP te permiten consultar sistemas y tomar acciones sin salir de tu terminal
  7. Bot de postmortem crea un documento a partir de un template cuando el incidente se resuelve
  8. Tracker de acciones asegura el seguimiento con reportes semanales

La idea clave es que cada paso puede ser automatizado o al menos templated. Nunca deberías arrancar de cero durante un incidente. Las herramientas, procesos y documentos deberían estar ahí esperándote.


Qué no hacer

Estuve en suficientes respuestas a incidentes para tener una lista de anti-patrones:


  • No culpes a personas en el postmortem. “Bob pusheó una config mala” es inútil. “Nuestro proceso de revisión de config no detectó un valor inválido” es accionable.
  • No te saltees el postmortem para incidentes chicos. Los incidentes chicos son las mejores oportunidades para aprender porque las apuestas son bajas.
  • No dejes que una persona esté de guardia para siempre. Rotá. Si tenés una sola persona que puede manejar incidentes, eso es un problema de bus factor, no un problema de guardias.
  • No alertes por todo. Más alertas no significa mejor monitoreo. Significa más ruido.
  • No trates los incidentes como fracasos. Los incidentes son inevitables en sistemas complejos. Son oportunidades para aprender y mejorar.

Notas finales

La gestión de incidentes no se trata solo de responder a incendios. Se trata de construir un sistema donde los incendios se detectan rápido, se responde eficientemente, se mitigan automáticamente cuando es posible, y se aprende de ellos siempre.


Las herramientas que cubrimos hoy — PagerDuty/Terraform para guardias, runbooks como código, auto-remediación con Jobs de Kubernetes, postmortems sin culpas con templates, y herramientas MCP para coordinación de incidentes — son todas cosas que podés empezar a implementar hoy. No necesitás hacer todo de una. Elegí lo que más te hubiera ayudado en tu último incidente y empezá por ahí.


En el próximo artículo podríamos explorar observabilidad en profundidad — trazabilidad distribuida con OpenTelemetry, agregación de logs, y construir dashboards que realmente ayuden durante incidentes. Pero eso es para otro día.


¡Espero que te haya resultado útil y lo hayas disfrutado! ¡Hasta la próxima!


Errata

Si encontrás algún error o tenés alguna sugerencia, por favor mandame un mensaje para que se corrija.

También podés revisar el código fuente y los cambios en las fuentes acá



$ Comentarios

Online: 0

Por favor inicie sesión para poder escribir comentarios.

2026-02-23 | Gabriel Garrido