WAF++ WAF++
Back to WAF++ Homepage

Best Practice: Incident Response & Runbooks

Kontext

Ohne strukturierten Incident Response-Prozess verbringen On-Call-Engineers wertvolle Minuten damit, Severity-Einordnungen zu diskutieren, Kontakte zu suchen und Diagnose-Schritte zu rekonstruieren, die bereits einmal durchgeführt wurden. Jede Minute Unklarheit im Incident kostet MTTR – und damit Verfügbarkeit.

Häufige Probleme ohne strukturierten IR-Prozess:

  • SEV1-Incident wird als Minor behandelt, weil Severity-Kriterien unklar sind

  • Key-Engineer nicht erreichbar; kein Backup definiert

  • Gleicher Incident tritt zum dritten Mal auf, weil Post-Mortem-Actions nicht verfolgt wurden

  • Runbook existiert, ist aber 18 Monate alt und referenziert gelöschte Services

Zugehörige Controls

  • WAF-REL-060 – Incident Response & Runbook Readiness

Zielbild

  • Klar definierte Severity-Stufen mit objektiven Kriterien

  • On-Call-Rotation mit primärem und sekundärem Kontakt

  • Runbooks für die häufigsten 5 Alerts je Service – verlinkt aus Alert-Body

  • Blameless Post-Mortems für SEV1/SEV2 innerhalb 5 Werktage

  • MTTR als getracktes Reliability-Metric

Technische Umsetzung

Schritt 1: Severity-Definitionen

# docs/incident-response/severity-definitions.yml
severity_levels:
  SEV1:
    name: "Critical"
    description: "Complete service outage or data loss in production"
    criteria:
      - "Service unavailable for > 5% of users"
      - "Data loss confirmed or suspected"
      - "SLO error budget fully exhausted"
      - "Revenue-generating functionality completely unavailable"
    response_time_sla: "15 minutes"
    escalation:
      primary: "On-call Engineer"
      secondary: "Engineering Manager (after 20min)"
      executive: "VP Engineering (after 45min)"
    communication:
      internal: "Slack #incidents every 30min"
      external: "Status page update within 30min"

  SEV2:
    name: "High"
    description: "Major degradation, partial outage or SLO burn"
    criteria:
      - "Error rate > 5x normal"
      - "Latency > 3x p99 SLO"
      - "Error budget burn rate > 14x"
      - "Critical feature unavailable for < 50% of users"
    response_time_sla: "30 minutes"
    escalation:
      primary: "On-call Engineer"
      secondary: "Team Lead (after 45min)"

  SEV3:
    name: "Medium"
    description: "Non-critical feature degradation, slow burn"
    criteria:
      - "Non-critical feature unavailable"
      - "Error budget burn rate 6x–14x"
      - "Performance degradation noticed but SLO not at risk"
    response_time_sla: "4 hours"
    escalation:
      primary: "On-call Engineer (next business day if outside hours)"

  SEV4:
    name: "Low"
    description: "Cosmetic issue, monitoring noise, minor inconvenience"
    response_time_sla: "Next sprint"
    escalation:
      primary: "Development team via ticket"

Schritt 2: PagerDuty Konfiguration via Terraform

# terraform/monitoring/pagerduty.tf

resource "pagerduty_schedule" "primary" {
  name      = "payment-service-primary"
  time_zone = "Europe/Berlin"

  layer {
    name                         = "weekly-rotation"
    start                        = "2026-01-01T08:00:00+01:00"
    rotation_virtual_start       = "2026-01-06T08:00:00+01:00"
    rotation_turn_length_seconds = 604800  # 7 Tage

    users = [
      pagerduty_user.engineer1.id,
      pagerduty_user.engineer2.id,
      pagerduty_user.engineer3.id,
    ]
  }
}

resource "pagerduty_escalation_policy" "payment" {
  name      = "payment-service-escalation"
  num_loops = 2

  rule {
    escalation_delay_in_minutes = 15

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

  rule {
    escalation_delay_in_minutes = 30

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

resource "pagerduty_service" "payment_api" {
  name              = "Payment API – Production"
  escalation_policy = pagerduty_escalation_policy.payment.id

  incident_urgency_rule {
    type    = "use_support_hours"
    urgency = "high"

    during_support_hours { type = "constant", urgency = "high" }
    outside_support_hours { type = "constant", urgency = "low" }
  }
}

Schritt 3: Runbook-Struktur

# Runbook: Payment API – High Error Rate

**Alert ID:** payment-api-error-rate-sev2
**Severity:** SEV2
**Owner:** payments-team
**Last Updated:** 2026-03-01

## Symptom
CloudWatch Alarm `slo-payment-api-fast-burn` ausgelöst.
Error Rate > 2% über 5 Minuten.

## Hypothesen (nach Häufigkeit sortiert)
1. Downstream Payment Gateway degradiert
2. Database Connection Pool erschöpft
3. Ungültiges Deployment ausgerollt

## Diagnose

### 1. Dashboard öffnen
https://grafana.example.com/d/payment-api-slo

### 2. Fehlertypen prüfen
```
# CloudWatch Insights
fields @timestamp, @message
| filter statusCode >= 500
| stats count(*) by bin(1m), statusCode
| sort @timestamp desc
| limit 20
```

### 3. Payment Gateway Status prüfen
- Status Page: https://status.payment-gateway.example.com
- Circuit Breaker State: `curl https://api.payment.internal/actuator/circuitbreaker`

### 4. DB Connection Pool prüfen
```
SELECT count(*), state FROM pg_stat_activity GROUP BY state;
SELECT * FROM pg_stat_database WHERE datname = 'payment_db';
```

## Remediation

### A: Payment Gateway degradiert
1. Circuit Breaker manuell öffnen wenn nicht automatisch: `POST /admin/circuitbreaker/open`
2. Queued-Payment-Mode aktivieren: `POST /admin/features/queued-payments/enable`
3. Status-Page aktualisieren

### B: DB Connection Pool erschöpft
1. `kubectl get pods -n payment | grep -E 'Terminating|Error'` – hängende Pods finden
2. Hängende Pods löschen: `kubectl delete pod <pod-name> -n payment --grace-period=0`
3. DB Connection Limit prüfen: `SHOW max_connections;` in DB

## Eskalation
Nach 20 Minuten ohne Fortschritt: @engineering-manager via Slack
Nach 45 Minuten: SEV1 erwägen, VP Engineering informieren

## Post-Mortem Template
https://wiki.example.com/post-mortem-template
resource "aws_cloudwatch_metric_alarm" "api_error_rate" {
  alarm_name          = "payment-api-error-rate-sev2"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 5
  datapoints_to_alarm = 4

  metric_name = "5XXError"
  namespace   = "AWS/ApiGateway"
  period      = 60
  statistic   = "Sum"
  threshold   = 10

  # Runbook-URL im Alert-Body: direkt navigierbar für On-Call
  alarm_description = jsonencode({
    severity  = "SEV2"
    service   = "payment-api"
    runbook   = "https://wiki.example.com/runbooks/payment-api-error-rate"
    dashboard = "https://grafana.example.com/d/payment-api-slo"
  })

  alarm_actions = [aws_sns_topic.oncall_sev2.arn]
  ok_actions    = [aws_sns_topic.oncall_sev2.arn]

  tags = var.mandatory_tags
}

Post-Mortem Template

# Post-Mortem: [Service] – [Kurzbeschreibung]

**Datum:** YYYY-MM-DD
**Severity:** SEV1/SEV2
**Duration:** X Stunden Y Minuten
**Impacted Users:** ~N users / % traffic
**Author:** @name

## Timeline

| Zeit | Ereignis |
|------|----------|
| HH:MM | Alarm ausgelöst |
| HH:MM | On-Call antwortet |
| HH:MM | Root Cause identifiziert |
| HH:MM | Mitigation deployed |
| HH:MM | Service fully restored |

## Root Cause
[Einstufiger Satz: "Der Ausfall wurde verursacht durch..."]

## Contributing Factors
- [Faktor 1]
- [Faktor 2]

## What Went Well
- [Positiv 1]
- [Positiv 2]

## Action Items

| Priorität | Aktion | Owner | Due Date |
|-----------|--------|-------|----------|
| P1 | ... | @name | YYYY-MM-DD |
| P2 | ... | @name | YYYY-MM-DD |

Typische Fehlmuster

  • Runbook veraltet: Runbook referenziert Service-Namen oder Endpoints, die nicht mehr existieren

  • Keine OK-Action: Alarm löst aus wenn es schlimm wird, aber kein Signal wenn es besser wird → False Recovery

  • Post-Mortem ohne Action Items: Reviews ohne konkrete Tasks verhindern keine Wiederholung

  • Severity-Eskalation zu früh: Führungskräfte werden unnötig oft für SEV3 paginiert

Metriken

  • MTTR: Mittlere Zeit von Alarm bis Service-Restore (Ziel: < 30 Minuten für SEV2)

  • MTTD: Zeit von erstem Fehler bis Alarm ausgelöst (Ziel: < 5 Minuten)

  • Post-Mortem Compliance Rate: % der SEV1/SEV2 Incidents mit dokumentiertem Post-Mortem (Ziel: 100%)

  • Action Item Closure Rate: % der Post-Mortem Action Items innerhalb des Zieldatums abgeschlossen

Reifegrad

Level 1 – Ad-hoc Incident Response, kein Prozess
Level 2 – Severity definiert, On-Call konfiguriert, Basis-Runbooks
Level 3 – Alle Critical-Alerts mit Runbook-Link; MTTR getrackt; Post-Mortems für SEV1/SEV2
Level 4 – Automatisierte Diagnose-Datensammlung; Runbooks teilweise automatisiert
Level 5 – AIOps-Incident-Correlation; MTTR < 5 Minuten für bekannte Fehlerklassen