# alerting-incident-management

> Designs effective alerting and incident management for production services, covering alert rules, on-call rotations, runbooks, and incident response workflows.

- Skill: `chimeranext/alerting-incident-management` (Agent Skill)
- Install (CLI): `npx skillmds add chimeranext/alerting-incident-management`
- Raw SKILL.md: https://api.skillmd.com/api/skills/chimeranext/alerting-incident-management/raw
- Safety review: CAUTION (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra, Monitoring & Observability
- Tags: Alerting, Alertmanager, Incident Management, Oncall, Opsgenie, Pagerduty, Prometheus, Runbooks
- Author: chimeranext (https://skillmd.com/u/chimeranext)
- Updated: 2026-08-22
- Page: https://skillmd.com/skills/chimeranext/alerting-incident-management

---

# 🚨 Skill: Alerting & Incident Management

## 📋 Metadata

| Atributo | Valor |
|----------|-------|
| **ID** | `sre-alerting-incident-management` |
| **Nivel** | 🔴 Avanzado |
| **Versión** | 1.0.0 |
| **Keywords** | `alerting`, `incident-management`, `pagerduty`, `opsgenie`, `oncall`, `runbooks`, `monitoring-as-code`, `gcp`, `opentofu`, `terraform-import`, `alert-policy` |
| **Referencia** | [Google SRE - On-Call](https://sre.google/workbook/on-call/), [PagerDuty](https://www.pagerduty.com/) |

## 🔑 Keywords para Invocación

- `alerting`
- `incident-management`
- `pagerduty`
- `opsgenie`
- `oncall`
- `runbooks`
- `incident-response`
- `monitoring-as-code`
- `alert-policy`
- `opentofu`
- `@skill:alerting`

### Ejemplos de Prompts

```
Implementa alerting con PagerDuty y runbooks
```

```
Configura incident management y on-call rotation
```

```
Configura runbooks y on-call rotation
```

```
@skill:alerting - Sistema completo de alertas e incidentes
```

```
Adoptar en OpenTofu las alert policies que se crearon a mano en la consola de GCP
```

```
¿Cómo pruebo que esta alerta puede ponerse en rojo?
```

## 📖 Descripción

Alerting efectivo y gestión de incidentes es crítico para mantener servicios confiables. Este skill cubre diseño de alertas efectivas, on-call rotations, runbooks, e incident response workflows.

### ✅ Cuándo Usar Este Skill

- Servicios en producción
- Equipos on-call
- SLAs críticos
- Sistemas distribuidos
- Incidentes frecuentes
- Compliance requirements

### ❌ Cuándo NO Usar Este Skill

- Desarrollo local solo
- Servicios no críticos sin SLA
- Equipos sin capacidad on-call

## 🏗️ Arquitectura de Alerting

```
┌──────────────┐
│  Prometheus  │
│  (Metrics)   │
└──────┬───────┘
       │
       │ Alert Rules
       │
┌──────▼───────┐
│ Alertmanager │
└──────┬───────┘
       │
       ├──────────┬──────────┬──────────┐
       │          │          │          │
┌──────▼───┐ ┌───▼────┐ ┌───▼────┐ ┌──▼─────┐
│ PagerDuty│ │ Slack  │ │ Email  │ │ Webhook│
└──────────┘ └────────┘ └────────┘ └────────┘
       │
       │
┌──────▼──────────────┐
│ On-Call Engineer    │
│ (Incident Response) │
└─────────────────────┘
```

## 💻 Implementación

### 1. Alertmanager Configuration

```yaml
# alertmanager/alertmanager.yml
global:
  resolve_timeout: 5m
  slack_api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'

route:
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 12h
  receiver: 'default'
  routes:
  # Critical alerts → PagerDuty immediately
  - match:
      severity: critical
    receiver: 'pagerduty-critical'
    continue: true

  # Warning alerts → Slack only
  - match:
      severity: warning
    receiver: 'slack-warnings'
    repeat_interval: 24h

  # Service-specific routes
  - match:
      service: payment-service
    receiver: 'payment-team'
    group_wait: 5s

inhibit_rules:
  # Inhibit warning if critical is firing
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'cluster', 'service']

receivers:
  - name: 'default'
    slack_configs:
      - channel: '#alerts'
        title: '{{ .GroupLabels.alertname }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

  - name: 'pagerduty-critical'
    pagerduty_configs:
      - service_key: 'YOUR_PAGERDUTY_SERVICE_KEY'
        description: '{{ .GroupLabels.alertname }}: {{ .GroupLabels.service }}'
        severity: 'critical'
        client: 'Prometheus'
        client_url: 'http://prometheus:9090'

  - name: 'slack-warnings'
    slack_configs:
      - channel: '#alerts-warnings'
        title: 'Warning: {{ .GroupLabels.alertname }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

  - name: 'payment-team'
    pagerduty_configs:
      - service_key: 'PAYMENT_TEAM_KEY'
        description: 'Payment Service Alert'
    slack_configs:
      - channel: '#payment-team'
```

### 2. Alert Rules (Prometheus)

```yaml
# prometheus/alerts/service-alerts.yml
groups:
  - name: service_alerts
    interval: 30s
    rules:
      # Error Budget Exhaustion
      - alert: ErrorBudgetExhaustion
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
            /
            sum(rate(http_requests_total[5m])) by (service)
          ) > 0.001
          and
          (
            sum_over_time(
              (
                sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
                /
                sum(rate(http_requests_total[5m])) by (service)
              )[28d:]
            ) > 0.001
          )
        for: 5m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: "Error budget exhausted for {{ $labels.service }}"
          description: |
            Service {{ $labels.service }} has exceeded error budget threshold.
            Current error rate: {{ $value | humanizePercentage }}
            Runbook: https://runbooks.example.com/error-budget

      # High Latency
      - alert: HighLatencyP99
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
          ) > 1.0
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High P99 latency in {{ $labels.service }}"
          description: "P99 latency is {{ $value }}s (threshold: 1s)"
          runbook: https://runbooks.example.com/high-latency

      # Service Down
      - alert: ServiceDown
        expr: up{job=~"app-.*"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Service {{ $labels.job }} is down"
          description: "Service has been unreachable for more than 2 minutes"
          runbook: https://runbooks.example.com/service-down

      # High Memory Usage
      - alert: HighMemoryUsage
        expr: |
          (
            container_memory_usage_bytes{pod=~".+"}
            /
            container_spec_memory_limit_bytes{pod=~".+"}
          ) > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage in {{ $labels.pod }}"
          description: "Memory usage is {{ $value | humanizePercentage }}"
          runbook: https://runbooks.example.com/high-memory

      # Disk Space
      - alert: DiskSpaceLow
        expr: |
          (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) < 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Low disk space on {{ $labels.instance }}"
          description: "Only {{ $value | humanizePercentage }} disk space remaining"

      # CPU Throttling
      - alert: CPUThrottling
        expr: |
          rate(container_cpu_cfs_throttled_seconds_total[5m]) > 0.1
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "CPU throttling detected in {{ $labels.pod }}"
          description: "Container is being CPU throttled"

      # Connection Pool Exhaustion
      - alert: ConnectionPoolExhaustion
        expr: |
          (
            db_connections_active{service=~".+"}
            /
            db_connections_max{service=~".+"}
          ) > 0.9
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Connection pool near exhaustion in {{ $labels.service }}"
          description: "{{ $value | humanizePercentage }} of connections in use"
```

### 3. Runbook Template

```markdown
# Runbook: High Error Rate

## Alert Name
`HighErrorRate`

## Severity
Critical

## Description
Service error rate exceeds threshold (5% for 5 minutes)

## Symptoms
- High HTTP 5xx response rate
- User complaints
- Error logs increasing

## Immediate Actions

1. **Acknowledge Alert**
   - Acknowledge in PagerDuty/OpsGenie
   - Notify team in Slack

2. **Check Service Health**
   ```bash
   kubectl get pods -l app=service-name
   kubectl logs -l app=service-name --tail=100
   ```

3. **Check Metrics**
   - Grafana dashboard: `/d/service-overview`
   - Error rate graph
   - Latency graphs

4. **Identify Root Cause**
   - Check recent deployments
   - Review error logs
   - Check dependencies (DB, APIs)

## Resolution Steps

### If caused by code deployment:
```bash
# Rollback deployment
kubectl rollout undo deployment/service-name
```

### If caused by resource exhaustion:
```bash
# Scale up
kubectl scale deployment/service-name --replicas=5
```

### If caused by dependency failure:
1. Check dependency service status
2. Implement circuit breaker
3. Enable fallback mechanisms

## Post-Incident

- Document in incident log
- Update runbook if needed

## Escalation

- If not resolved in 15 min → Escalate to senior engineer
- If not resolved in 30 min → Escalate to engineering manager
- If service completely down → Escalate to CTO
```

### 4. On-Call Rotation (PagerDuty)

```yaml
# pagerduty/escalation-policies.yml
escalation_policies:
  - name: "Primary On-Call"
    description: "Primary escalation for production services"
    num_loops: 3
    escalation_rules:
      - escalation_delay_in_minutes: 0
        targets:
          - type: "user"
            id: "PXXXXXXXX"  # Primary on-call
      - escalation_delay_in_minutes: 15
        targets:
          - type: "user"
            id: "PYYYYYYYY"  # Secondary on-call
      - escalation_delay_in_minutes: 30
        targets:
          - type: "schedule"
            id: "PZZZZZZZZ"  # Manager on-call

  - name: "Critical Alerts Only"
    escalation_rules:
      - escalation_delay_in_minutes: 0
        targets:
          - type: "user_reference"
            id: "PXXXXXXXX"
      - escalation_delay_in_minutes: 10
        targets:
          - type: "escalation_policy_reference"
            id: "EPXXXXXXX"  # Manager escalation

schedules:
  - name: "Primary On-Call Schedule"
    time_zone: "America/New_York"
    layers:
      - name: "Layer 1"
        start: "2024-01-01T00:00:00"
        rotation_virtual_start: "2024-01-01T00:00:00"
        rotation_turn_length_seconds: 604800  # 1 week
        users:
          - user_id: "PXXXXXXXX"
          - user_id: "PYYYYYYYY"
          - user_id: "PZZZZZZZZ"
        restrictions:
          - type: "daily_restriction"
            start_time_of_day: "09:00:00"
            duration_seconds: 32400  # 9 hours
```

### 5. Incident Response Workflow

```python
# incident_response/workflow.py
from dataclasses import dataclass
from datetime import datetime
from typing import List, Optional
from enum import Enum

class IncidentSeverity(Enum):
    SEV1 = "critical"  # Service down
    SEV2 = "high"      # Major degradation
    SEV3 = "medium"    # Minor issues
    SEV4 = "low"       # Informational

@dataclass
class Incident:
    id: str
    title: str
    severity: IncidentSeverity
    status: str  # open, investigating, mitigated, resolved
    created_at: datetime
    resolved_at: Optional[datetime]
    assigned_to: str
    affected_services: List[str]
    description: str
    root_cause: Optional[str] = None
    resolution: Optional[str] = None

class IncidentResponseWorkflow:
    def __init__(self):
        self.incidents = []

    def create_incident(
        self,
        title: str,
        severity: IncidentSeverity,
        affected_services: List[str],
        description: str
    ) -> Incident:
        incident = Incident(
            id=f"INC-{datetime.now().strftime('%Y%m%d-%H%M%S')}",
            title=title,
            severity=severity,
            status="open",
            created_at=datetime.now(),
            resolved_at=None,
            assigned_to=self._assign_oncall(),
            affected_services=affected_services,
            description=description
        )

        self.incidents.append(incident)
        self._notify_team(incident)
        self._create_incident_channel(incident)

        return incident

    def _assign_oncall(self) -> str:
        # Logic to assign to current on-call engineer
        return "engineer@example.com"

    def _notify_team(self, incident: Incident):
        # Send notifications via PagerDuty, Slack, etc.
        pass

    def _create_incident_channel(self, incident: Incident):
        # Create dedicated Slack channel for incident
        channel_name = f"incident-{incident.id.lower()}"
        # Create channel logic
        pass

    def update_status(self, incident_id: str, status: str, notes: str):
        incident = self._find_incident(incident_id)
        if incident:
            incident.status = status
            if status == "resolved":
                incident.resolved_at = datetime.now()

    def _find_incident(self, incident_id: str) -> Optional[Incident]:
        return next((i for i in self.incidents if i.id == incident_id), None)
```

### 6. Alert Fatigue Prevention

```yaml
# alertmanager/alert-fatigue-prevention.yml
# Strategies to prevent alert fatigue

# 1. Alert Grouping
route:
  group_by: ['alertname', 'service']
  group_wait: 10s      # Wait before sending initial notification
  group_interval: 5m   # Wait before sending updated notification
  repeat_interval: 12h # Minimum time between notifications

# 2. Alert Inhibition
inhibit_rules:
  # Don't alert on warning if critical is firing
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['service']

  # Don't alert on individual instance if all instances are down
  - source_match:
      alertname: 'AllInstancesDown'
    target_match_re:
      alertname: '.*InstanceDown'

# 3. Alert Suppression (Silence Rules)
# Use Alertmanager UI or API to create silences for known issues

# 4. Threshold Tuning
# Use error budgets and SLOs to set meaningful thresholds
# Example: Alert only when error budget is at risk

# 5. Alert Classification
# Only page on actionable alerts
# Use different channels for different severities
```

### 7. Alerting como código en GCP — y el discriminador que decide si se pudre

> **Todo lo de arriba sigue vigente.** Alertmanager, PagerDuty y los runbooks siguen siendo
> el camino sobre Prometheus/Kubernetes. Esta sección cubre el caso GCP + OpenTofu, donde
> las alertas viven en `google_monitoring_alert_policy` en vez de en reglas de Prometheus, y
> agrega la pregunta que ninguna de las dos configuraciones contesta sola: **¿esta alerta
> puede ponerse en rojo?**

#### 7.1 La regla que gobierna todo lo demás

> **Ningún control cuenta como desplegado hasta que se lo vio ponerse en rojo a propósito.**

No es una postura, es lo que la evidencia obliga. *Del registro
`openspec/changes/2026-08-02-observability-liveness-axis/design.md` en
`DojoCodingLabs/dojo-os @ origin/develop 18d4f7da3`, **no re-medido acá**:* tres alertas de
un servicio real se ejercitaron una vez y llevaban meses en silencio sin que nadie lo
notara.

| control | cómo se lo pone en rojo a propósito |
|---|---|
| sink de heartbeats | apagar la URL y confirmar que el check **falla**, no que sale vacío |
| liveness de un bucket | congelar el productor 48 h y confirmar que la alerta llega |
| synthetic monitor | romper un paso del flujo y **cronometrar** cuánto tarda en avisar |

La tercera columna importa: el tiempo de detección se **mide**, no se estima.

#### 7.2 Cableado y mudo: el modo de falla que un dashboard no muestra

Verificado en `dojo-infra-alerts @ origin/main 2304b6e`: el servicio expone ocho rutas
HTTP, todas funcionando, y su README documenta siete de ellas como disparadas por un
*"Cloud Scheduler tick"*.

**`cloudscheduler.googleapis.com` no estaba habilitada** — *medido contra GCP el
2026-08-02 por el registro citado arriba y **no re-corrido en este pase***. Los endpoints
existían, respondían, y **nada los disparaba**. Cuatro canales de Slack estaban cableados a
ese servicio y recibían cero.

El barrido original tuvo el cuidado de no sobre-afirmar, y conviene copiarlo: lo demostrado
fue *"cero requests en los últimos 30 días"*, **no** *"nunca corrió"*.

**Qué mirar, en este orden, antes de confiar en un canal de alertas:**

1. ¿La API que dispara el control está habilitada? (`gcloud services list --enabled`)
2. ¿Hay requests reales en la ventana? Cero requests es un hallazgo, no un blanco.
3. ¿El canal recibió algo alguna vez, y de este control en particular?
4. ¿Alguien vio este control en rojo, a propósito, con fecha?

#### 7.3 El caso más fino: la alerta que fabrica la apariencia de dueño

*Tomado del mismo registro y no re-medido en este pase:* **95 alertas** enrutadas a un
canal llevaban la nota `Routed to #alerts-sentry so Doji can triage`, y el agente nombrado
**no publicó ni una vez** en esa ventana. La nota le decía a todo humano que leyera el
canal que el ítem ya estaba atendido.

**Una alerta que nombra a un dueño no prueba que ese dueño exista.** Es la misma familia que
`resolve` en el camino de observabilidad: *nada puede nombrar lo que no existe*, y un
destinatario inventado es peor que ninguno porque suprime el reflejo de mirar.

#### 7.4 Monitoring as code: dos posiciones que parecen contradecirse

La industria sostiene las dos, y **las dos tienen razón sobre cosas distintas**:

| | qué llama "monitoring as code" | por qué se pudre, o por qué no |
|---|---|---|
| **IBM** | escribir a mano collectors, tracing, dashboards y reglas de alerta | es un **segundo artefacto** que nadie corre hasta que falla, y deriva del sistema que describe |
| **Checkly** | desplegar como monitor un spec E2E que ya existe | **no hay segundo artefacto**: CI ya lo corre en cada PR, así que se mantiene solo |

> **El discriminador es si el código de monitoreo es un segundo artefacto o el mismo.**

Por eso reusar los specs E2E ya existentes como synthetic monitors es seguro, y escribir
probes a mano por servicio no lo sería. La regla operativa: **si ya existe un programa que
contesta la pregunta, se corre; no se escribe el segundo.**

Y el propio checklist de autoevaluación de IBM incluye *"How many performance or
availability incidents are we **missing** per month or quarter?"* — hace la pregunta de
liveness y su producto no la contesta.

> *Las citas de IBM y Checkly están tomadas del plan de sesión `plan-observability-layer`;
> **no se re-consultaron las páginas originales en este pase**.*

#### 7.5 Adoptar alertas hechas a mano: el `import` block, no `tofu import`

Este es el paso que más se saltea y el que hace **activamente daño** si falta.

**Re-medido para este documento** sobre `DojoCodingLabs/dojo-infra-gitops`:

```bash
### ref: origin/main @ d6e59f0 | delta: none
git grep -lE 'google_monitoring|monitoring_alert_policy|uptime_check' origin/main -- tofu/
# → cero filas (exit 1)

git grep -ohE '^resource "([a-z_]+)"' origin/main -- tofu/ | sort | uniq -c | sort -rn
# → 18 declaraciones: compute, IAM, secrets, artifact registry. Ninguna de monitoring.
```

**Cero recursos `google_monitoring_*` en todo el árbol de OpenTofu.** Cada uptime check,
cada política de alerta y los dos canales de notificación se habían creado a mano en la
consola de GCP. El costo no fue hipotético: **cuatro de los seis checks
llevaban la string literal `--display-name=` dentro de su nombre visible** —la huella de un
script pasando el flag por una capa extra de comillas— y nadie lo notó durante meses,
**porque no había diff que leer**.

**Y declarar los recursos sin importarlos duplica el tablero, en silencio:**

```hcl
# tofu/monitoring-imports.tf — dojo-infra-gitops PR #27
#
# SIN ESTE ARCHIVO EL CAMBIO ES ACTIVAMENTE DAÑINO. `monitoring.tf` declara recursos que
# YA EXISTEN; sin un `import` que ate cada declaración al objeto vivo, `apply` crea un
# SEGUNDO conjunto al lado del de la consola: doce uptime checks donde van seis, cada par
# sondeando el mismo host, y dos políticas de alerta por incidente. Nada da error. El
# tablero simplemente se duplica, y el duplicado que nadie declaró es el que conserva el
# camino viejo y equivocado.
import {
  provider = google.monitoring
  to       = google_monitoring_uptime_check_config.marketing_site_prod
  id       = "projects/<proj>/uptimeCheckConfigs/display-name-production-...-RTCiHItDixI"
}
```

**Bloques `import` en vez de invocaciones `tofu import`, a propósito:** la atadura queda
revisable en el diff y re-corrible por cualquiera, en vez de vivir en el historial de shell
de quien fue primero.

**Tres trampas concretas del provider de GCP.** *Las tres están documentadas en el cuerpo
de `dojo-infra-gitops` PR #27, que sí se leyó (`gh pr diff 27`); los planes que las
revelaron **no se re-corrieron acá**:*

- **`provider` Y `project`, en cada recurso y en cada `import`.** Un primer intento usó solo
  `project =` sin alias, razonando que un alias es para credenciales distintas. `tofu plan`
  lo refutó: el provider **no lee `project` de un ID de import calificado**, así que los
  doce recursos entraron al state como el proyecto equivocado — y `project` es ForceNew. El
  plan volvió `12 to import, 14 to add, 0 to change, 16 to destroy`: una "adopción" que
  borra todo lo que adopta.
- **El ID de un uptime check es inmutable y se deriva slugificando el nombre al crearlo.**
  Renombrar el check no lo limpia; solo recrearlo, y recrearlo tira el historial de uptime y
  huerfaniza su política de alerta. El ID feo se queda; **lo que se arregla es el nombre que
  un humano lee a las 03:00**.
- **Un canal `webhook_tokenauth` guarda su endpoint en `labels.url`, y para un webhook de
  Slack esa URL ES la credencial.** El provider ofrece `sensitive_labels` para
  `auth_token` / `password` / `service_key` y **no** para `url`, así que declarar el canal
  como recurso significa commitear un bearer vivo. Se **leen** por display name en vez de
  declararse: la búsqueda es exacta, no necesita secreto, y las políticas igual pueden
  referenciarlos.

#### 7.6 Un veredicto de alerta también tiene tres estados

`success` contesta hoy dos preguntas distintas: *"verifiqué y estaba bien"* y *"no miré"*.
Nada aguas abajo las distingue.

| verdicto | significa |
|---|---|
| `pass` | se verificó y está bien |
| `fail` | se verificó y está mal |
| `unverifiable` | **no se pudo determinar** — nunca es sinónimo de ninguno de los otros dos |

El tercero se escribe **con la palabra completa**, nunca con un símbolo: un vocabulario de
tilde-o-cruz es lo que colapsa tres estados en dos en la cabeza de quien lee, y la terminal
es donde ese colapso pasa primero.

```js
// scripts/receipt.mjs — dojo-os PR #4309. Tres códigos de salida distintos.
assert.equal(exitCodeFor(PASS), 0);
assert.equal(exitCodeFor(FAIL), 1);
assert.equal(exitCodeFor(UNVERIFIABLE), 2);
// El punto entero: quien testea `code === 0` no debe poder leer "no pude saber" como "bien".
assert.notEqual(exitCodeFor(UNVERIFIABLE), exitCodeFor(PASS));
// Y no al revés tampoco: reportar una falla que no observamos es como una alerta se gana
// la reputación que termina en que alguien la desactiva.
assert.notEqual(exitCodeFor(UNVERIFIABLE), exitCodeFor(FAIL));
```

## 🎯 Mejores Prácticas

### 1. Alert Design

✅ **DO:**
- Alert on symptoms, not causes
- Make alerts actionable
- Include runbook links
- Use appropriate severity levels
- Test alerts regularly

❌ **DON'T:**
- Alert on every metric
- Create alerts that require investigation to understand
- Alert on things you can't fix
- Duplicate alerts across systems

### 2. On-Call

✅ **DO:**
- Maintain clear rotation schedules
- Provide context in handoffs
- Limit on-call duration (max 1 week)
- Compensate on-call time
- Track on-call load

❌ **DON'T:**
- Have people on-call 24/7
- Make on-call mandatory without compensation
- Skip handoffs between rotations

### 3. Incident Response

✅ **DO:**
- Follow runbooks
- Communicate frequently
- Document decisions
- Implement action items

❌ **DON'T:**
- Skip incident documentation
- Point fingers
- Ignore post-mortem action items

## 🚨 Troubleshooting

### Too Many Alerts

1. Review alert rules
2. Increase thresholds where appropriate
3. Implement better grouping
4. Use alert inhibition

### Alerts Not Firing

1. Check Prometheus query syntax
2. Verify alert rule evaluation
3. Check Alertmanager configuration
4. Verify notification channel configs

### On-Call Burnout

1. Review alert volume
2. Reduce non-actionable alerts
3. Improve runbooks
4. Rotate more frequently

## 📚 Recursos Adicionales

- [PagerDuty Incident Response](https://response.pagerduty.com/)
- [Google SRE - On-Call Handbook](https://sre.google/workbook/on-call/)
- [Incident Response Guide](https://response.pagerduty.com/)

---

**Versión:** 1.0.0
**Última actualización:** Diciembre 2025
**Total líneas:** 1,100+

