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:
- Geen context — Is 80% CPU slecht? Hangt af van de workload.
- Geen impact — Beïnvloedt dit gebruikers? Onbekend.
- Geen actie — Wat moet ik doen? Ook onbekend.
- 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
| Severity | Response | Voorbeeld |
|---|---|---|
| critical | Maak iemand NU wakker | Service down, data verlies dreigt |
| warning | Handel af tijdens kantooruren | Disk vult, certificaat verloopt |
| info | Review in dagelijkse standup | Ongewoon 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
| Severity | Acknowledge | Start Werk |
|---|---|---|
| Critical | 5 minuten | 15 minuten |
| Warning | 4 uur | 8 uur |
| Info | Volgende standup | Wanneer het uitkomt |
Post-Incident Review
Elke critical alert moet een review triggeren:
- Was de alert noodzakelijk?
- Gaf het genoeg context?
- Was de runbook nuttig?
- 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:
- Audit huidige alerts — Hoeveel vuurden afgelopen maand? Hoeveel waren actionable?
- Verwijder ongebruikte alerts — Als het in 90 dagen niet vuurde, waarschijnlijk niet nodig
- Verhoog thresholds — Als alerts altijd auto-resolven, is threshold te gevoelig
- Voeg
forduration toe — Korte spikes moeten niet pagen - 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.
