Je telefoon trilt om 3 uur ’s nachts. Je checkt slaperig: “High CPU usage on node-worker-3.” Je kijkt naar de grafiek, ziet dat hij 10 minuten op 75% staat, en gaat weer slapen. Morgen, dezelfde alert. Volgende week check je niet meer.

Dit is alert fatigue, en het is gevaarlijk. Als alles alert, doet niets het. Echte incidenten verdwijnen in de ruis.

Ik ben aan beide kanten geweest — verdrinken in alerts, en systemen draaien waar pages zeldzaam zijn en altijd actionable. Het verschil is niet betere tools. Het is beter nadenken over wat aandacht verdient.

Het Probleem met Default Alerts

De meeste monitoring setups komen met default alerting regels. Prometheus Operator shipt er honderden. Ze alerten op alles:

  • CPU > 80%
  • Memory > 80%
  • Disk > 80%
  • Pod restarts > 0
  • Elke 5xx error

Deze defaults zijn goedbedoeld maar verschrikkelijk in de praktijk:

  1. Geen context — Is 80% CPU slecht? Hangt af van de workload.
  2. Geen impact — Beïnvloedt dit gebruikers? Onbekend.
  3. Geen actie — Wat moet ik doen? Ook onbekend.
  4. Te gevoelig — Korte spikes triggeren alerts die resolven voor je reageert.

Het resultaat: pages die geen mensen nodig hebben, mensen die pages niet meer vertrouwen.

De Alerting Filosofie

Goede alerts volgen principes:

1. Alert op Symptomen, Niet Oorzaken

Slecht: “Pod restarted” Goed: “Service error rate > 1%”

Gebruikers geven niet om of pods restarten. Ze geven om of de service werkt. Alert op wat gebruikers ervaren, onderzoek oorzaken daarna.

2. Alert op Wat Actie Vereist

Als niemand iets hoeft te doen, moet het niet pagen. Bewaar informatieve alerts voor dashboards of dagelijkse reviews.

Vraag: “Als ik deze alert krijg, wat doe ik dan?”

  • Als het antwoord is “onderzoeken en waarschijnlijk niets” → geen alert
  • Als het antwoord is “specifieke remediatie” → valide alert

3. Elke Alert Heeft een Runbook Nodig

Als de alert vuurt, wat doe je? Als je het niet kunt opschrijven, is de alert niet klaar.

annotations:
  runbook_url: https://wiki.example.com/runbooks/high-error-rate
  summary: "Error rate overschrijdt SLO"
  description: "Service {{ $labels.service }} error rate is {{ $value }}%"

4. Severity Moet Iets Betekenen

SeverityResponseVoorbeeld
criticalMaak iemand NU wakkerService down, data verlies dreigt
warningHandel af tijdens kantoorurenDisk vult, certificaat verloopt
infoReview in dagelijkse standupOngewoon patroon, niet urgent

Als critical alerts geen directe actie vereisen, moeten ze niet critical zijn.

SLO-Based Alerting

De beste alerts zijn direct gekoppeld aan Service Level Objectives:

# SLO: 99.9% beschikbaarheid
# Error budget: 0.1% = 43 minuten/maand

groups:
  - name: slo-alerts
    rules:
      # Fast burn: verbruikt error budget snel
      - alert: HighErrorRateFastBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[5m])) /
            sum(rate(http_requests_total[5m]))
          ) > 0.01
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Error rate boven 1% (fast burn)"
          description: "Met dit tempo is maandelijks error budget op in {{ $value | humanizeDuration }}"

      # Slow burn: verbruikt error budget gestaag
      - alert: HighErrorRateSlowBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h])) /
            sum(rate(http_requests_total[1h]))
          ) > 0.001
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "Error rate verhoogd (slow burn)"

De logica:

  • Fast burn: Direct probleem, maak iemand wakker
  • Slow burn: Trend naar SLO breach, handel af tijdens werkuren

Praktische Alert Voorbeelden

Beschikbaarheid Alerts

# Service is down
- alert: ServiceDown
  expr: up{job="my-service"} == 0
  for: 1m
  labels:
    severity: critical
  annotations:
    summary: "Service {{ $labels.job }} is down"
    runbook_url: https://wiki/runbooks/service-down

# Hoge error rate (user impact)
- alert: HighErrorRate
  expr: |
    sum by (service) (rate(http_requests_total{status=~"5.."}[5m])) /
    sum by (service) (rate(http_requests_total[5m])) > 0.05
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "{{ $labels.service }} error rate boven 5%"

Latency Alerts

# P99 latency te hoog
- alert: HighLatency
  expr: |
    histogram_quantile(0.99,
      sum by (le, service) (rate(http_request_duration_seconds_bucket[5m]))
    ) > 2
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "{{ $labels.service }} P99 latency boven 2s"

Capaciteit Alerts

# Disk vult (voorspellend)
- alert: DiskFillingUp
  expr: |
    predict_linear(node_filesystem_avail_bytes{fstype!="tmpfs"}[6h], 24*60*60) < 0
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "Disk {{ $labels.mountpoint }} vol binnen 24u"

# Certificaat verloopt
- alert: CertificateExpiringSoon
  expr: |
    certmanager_certificate_expiration_timestamp_seconds - time() < 7*24*60*60
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "Certificaat {{ $labels.name }} verloopt binnen 7 dagen"

Waar NIET Op Te Alerten

# NIET: Generiek resource gebruik
- alert: HighCPU
  expr: node_cpu_seconds_total > 0.8  # Slecht: geen context

# NIET: Verwacht gedrag
- alert: PodRestart
  expr: increase(kube_pod_container_status_restarts_total[1h]) > 0  # Slecht: restarts zijn normaal

# NIET: Dingen die auto-resolven
- alert: PodPending
  expr: kube_pod_status_phase{phase="Pending"} == 1
  for: 1m  # Slecht: te kort, scheduler handelt het af

Alertmanager Configuratie

Route alerts gepast:

# alertmanager.yaml
global:
  resolve_timeout: 5m

route:
  receiver: 'default'
  group_by: ['alertname', 'severity']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
    # Critical: page direct
    - match:
        severity: critical
      receiver: 'pagerduty'
      continue: true

    # Warning: Slack tijdens werkuren
    - match:
        severity: warning
      receiver: 'slack-ops'
      active_time_intervals:
        - work_hours

    # Info: alleen loggen
    - match:
        severity: info
      receiver: 'null'

receivers:
  - name: 'pagerduty'
    pagerduty_configs:
      - service_key: '<key>'
        severity: '{{ .CommonLabels.severity }}'

  - name: 'slack-ops'
    slack_configs:
      - api_url: '<webhook>'
        channel: '#ops-alerts'
        title: '{{ .CommonAnnotations.summary }}'
        text: '{{ .CommonAnnotations.description }}'

  - name: 'null'

time_intervals:
  - name: work_hours
    time_intervals:
      - weekdays: ['monday:friday']
        times:
          - start_time: '09:00'
            end_time: '18:00'

Silencing en Inhibition

Silences voor Onderhoud

# Maak silence tijdens onderhoud
amtool silence add alertname=~".+" --duration=2h --comment="Gepland onderhoud"

# Silence specifieke service
amtool silence add service="api" --duration=1h --comment="Deployen v2.3.0"

Inhibition Rules

Als een alert een andere impliceert, onderdruk de ruis:

inhibit_rules:
  # Als cluster down is, alert niet op individuele services
  - source_match:
      alertname: 'ClusterDown'
    target_match_re:
      alertname: '.+'
    equal: ['cluster']

  # Als node down is, alert niet op pods op die node
  - source_match:
      alertname: 'NodeDown'
    target_match_re:
      alertname: 'Pod.+'
    equal: ['node']

Grafana Integratie

Visualiseer alert state in dashboards:

# Prometheus datasource query
ALERTS{alertstate="firing"}

Maak een alerts overzichtspanel die toont:

  • Momenteel vurende alerts
  • Recente alert geschiedenis
  • Alert trends over tijd

Link van alerts naar relevante dashboards:

annotations:
  dashboard_url: https://grafana/d/abc123?var-service={{ $labels.service }}

On-Call Best Practices

Rotatie Structuur

Primary    → Eerste responder, handelt alle pages af
Secondary  → Backup als primary niet reageert in 10 min
Escalation → Management, voor grote incidenten

Response Time SLAs

SeverityAcknowledgeStart Werk
Critical5 minuten15 minuten
Warning4 uur8 uur
InfoVolgende standupWanneer het uitkomt

Post-Incident Review

Elke critical alert moet een review triggeren:

  1. Was de alert noodzakelijk?
  2. Gaf het genoeg context?
  3. Was de runbook nuttig?
  4. Kon dit voorkomen worden?

Als dezelfde alert herhaaldelijk vuurt zonder actie, fix of verwijder hem.

Mijn Alerting Setup

# Alleen core SLO alerts
groups:
  - name: slo
    rules:
      # Beschikbaarheid: error rate
      - alert: HighErrorRate
        expr: error_rate > 0.01
        for: 5m
        severity: critical

      # Latency: P99
      - alert: HighLatency
        expr: p99_latency > 2
        for: 10m
        severity: warning

      # Capaciteit: voorspellend
      - alert: DiskFillingUp
        expr: disk_will_fill_24h
        severity: warning

      # Security: cert expiry
      - alert: CertExpiring
        expr: cert_expires_7d
        severity: warning

# Dat is het. ~10 alert regels totaal.

Belangrijke keuzes:

  • SLO-based — Alerts gekoppeld aan user impact
  • Minimale regels — Elke regel verdient zijn plek
  • Duidelijke severity — Critical betekent wakker maken, warning betekent werkuren
  • Elke alert heeft een runbook — Niet raden om 3 uur ’s nachts

Alert Fatigue Verminderen

Stappen om op te ruimen:

  1. Audit huidige alerts — Hoeveel vuurden afgelopen maand? Hoeveel waren actionable?
  2. Verwijder ongebruikte alerts — Als het in 90 dagen niet vuurde, waarschijnlijk niet nodig
  3. Verhoog thresholds — Als alerts altijd auto-resolven, is threshold te gevoelig
  4. Voeg for duration toe — Korte spikes moeten niet pagen
  5. Merge vergelijkbare alerts — Eén “high error rate” is beter dan per-endpoint alerts

Doel: Elke page moet menselijke actie vereisen.

Waarom Dit Ertoe Doet

Alert fatigue doodt betrouwbaarheid. Als on-call engineers alerts niet meer vertrouwen, worden echte incidenten gemist. Als iedereen voor alles gepaged wordt, neemt niemand verantwoordelijkheid.

Goede alerting is:

  • Respectvol voor menselijke aandacht
  • Actionable met duidelijke volgende stappen
  • Accuraat over impact
  • Minimaal in volume

Je observability stack met Loki en Tempo genereert enorme hoeveelheden data. Alerting is hoe je die data omzet in aandacht — gebruik het wijs.


De beste alert is degene die je niet hoeft te sturen omdat het systeem zichzelf healde. De op een na beste is degene die je precies vertelt wat er mis is en hoe je het fixt.