Mijn dashboard is een muur van groen. Pods draaien, replicas kloppen, CPU comfortabel, geen alerts. Ik kijk ernaar en voel dat kleine dopamine-shotje van “alles is in orde.” En grotendeels klopt dat ook. De cluster draait al weken. Er is niks omgevallen.

Die groene muur is tegelijk het gevaarlijkste in mijn homelab, want hij vertelt me niks over wat er gebeurt als het mis gaat. Hij vertelt me alleen dat er, op dit moment, nog niks mis is gegaan.

Stel je nu de andere versie van die ochtend voor. Een node sterft op precies het moment dat er echte load is. Een connection pool raakt stilletjes uitgeput. Een of andere service die ik acht maanden geleden gedeployed en vergeten ben begint geheugen op te eten tot de OOM-killer ingrijpt. Niks daarvan staat op een gezond dashboard. Het laat zich zien om 3 uur ’s nachts, als ik slaap en de enige die staat te debuggen toekomst-ik is, oververmoeid en chagrijnig.

Er is een andere manier om daar achter te komen. Ik kan zelf dingen kapot maken, op een dinsdagmiddag, met een koffie in de hand en een rollback klaar. Dat is de hele pitch van chaos engineering: ik ontdek mijn zwakheden liever bij daglicht, op mijn voorwaarden, dan dat productie ze voor me ontdekt.

Wat opzettelijk dingen kapot maken echt betekent

Gedistribueerde systemen falen op gedistribueerde manieren. Je kunt elke regel code lezen en het mooiste architectuurdiagram ter wereld tekenen en nog steeds geen idee hebben hoe het ding zich gedraagt als een node halverwege een request wegvalt. Het gedrag onder failure leeft in de gaten tussen componenten, en de enige manier om het te zien is de failure veroorzaken en kijken.

Dit hangt direct samen met een van de waarden waar ik op deze blog steeds op terugkom: ik wil begrijpen wat ik draai. Een groen dashboard is een vorm van vertrouwen, en ik vertrouw dingen die ik nog nooit heb zien falen niet helemaal. Chaos engineering is hoe ik “ik denk dat het resilient is” verander in iets dat ik daadwerkelijk heb zien gebeuren.

Even een verduidelijking, want de naam schrikt mensen af. Chaos engineering betekent niet willekeurig dingen vernielen om te zien wat er gebeurt, en het betekent niet fault injection de prod in smijten zonder plan. Elk experiment heeft drie dingen eraan vast:

  • Een hypothese. “Als één node faalt, reschedulen pods binnen vijf minuten.” Je test een overtuiging, je gokt niet.
  • Een gecontroleerde blast radius. Bekend target, gedefinieerde duur, een rollback die je meteen kunt indrukken.
  • Iets dat je leert. Elke run bevestigt de overtuiging of geeft je een bug die je nog niet kende. Allebei winst.

Netflix maakte dit beroemd met Chaos Monkey, willekeurig instanties doden zodat engineers wel services moesten bouwen die het overleefden. De tooling is sindsdien een stuk volwassener geworden, maar het kernidee is hetzelfde: wacht niet op de failure, plan hem in.

Het chaos engineering proces

flowchart TD
    subgraph process["Chaos Engineering Cyclus"]
        H["Definieer Hypothese"]
        E["Ontwerp Experiment"]
        R["Draai in Gecontroleerde Omgeving"]
        O["Observeer & Meet"]
        L["Leer & Verbeter"]
    end

    H --> E --> R --> O --> L
    L --> H

1. Definieer hypothese

Schrijf op wat je gelooft dat waar is voor je iets aanraakt. Het punt is dat je je vastlegt op een voorspelling, zodat het resultaat je echt kan verrassen:

  • “Als één node faalt, zullen pods binnen 5 minuten reschedulen”
  • “Als de database traag wordt, zal de API graceful degraderen, niet crashen”
  • “Als DNS faalt, zullen gecachete responses requests bedienen voor 30 seconden”

2. Ontwerp experiment

Nu scope je het. Vier vragen:

  • Blast radius: Wat kan beïnvloed worden?
  • Duur: Hoe lang blijft de fault bestaan?
  • Rollback: Hoe stop je direct?
  • Metrics: Wat indiceert succes of falen?

De rollback-vraag is degene die mensen overslaan, en het is precies degene die voorkomt dat een chaos experiment een echt incident wordt.

3. Draai in gecontroleerde omgeving

Begin klein. Eerst dev, dan staging, en pas daarna productie, en zelfs dan alleen met safeguards. Ik heb deze regel precies één keer gebroken en geleerd waarom hij bestaat.

4. Observeer en meet

Hier verdient je observability stack zijn bestaan. Als je niet kunt zien wat er gebeurde, heb je geen experiment gedraaid maar gewoon schade aangericht. Let op de voor de hand liggende dingen: vuurden de juiste alerts, bewoog latency, stegen error rates, en hoe lang duurde recovery echt versus wat je gokte.

5. Leer en verbeter

Schrijf op wat je vond, fix de zwakke plek, update de runbook zodat de volgende persoon (waarschijnlijk jij, over zes maanden) niet vanaf nul begint. Ontwerp dan het volgende experiment.

Litmus Chaos: Kubernetes-native chaos

Litmus is een CNCF project dat chaos engineering op de Kubernetes-manier doet: je declareert experimenten als YAML en applyt ze net als al het andere. Die declaratieve insteek is precies waarom ik het voor de homelab koos. Het experiment leeft in Git naast de workload die het aanvalt, wat betekent dat het inspecteerbaar, reviewbaar en reproduceerbaar is in plaats van een slim commando dat iemand ooit één keer draaide en nooit opschreef.

Litmus installeren

# Voeg Litmus helm repo toe
helm repo add litmuschaos https://litmuschaos.github.io/litmus-helm/
helm repo update

# Installeer Litmus
helm install litmus litmuschaos/litmus \
  --namespace litmus \
  --create-namespace \
  --set portal.frontend.service.type=ClusterIP

Litmus architectuur

flowchart TD
    subgraph litmus["Litmus Componenten"]
        subgraph control["Control Plane"]
            Portal["Litmus Portal<br/>(UI/API)"]
            Server["Litmus Server"]
        end
        subgraph exec["Execution Plane"]
            Sub["Subscriber"]
            Runner["Chaos Runner"]
            Exp["Chaos Experiments"]
        end
    end

    Portal --> Server
    Server --> Sub
    Sub --> Runner
    Runner --> Exp
    Exp --> Target["Target Workload"]

ChaosEngine: experimenten declareren

apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: nginx-chaos
  namespace: default
spec:
  appinfo:
    appns: 'default'
    applabel: 'app=nginx'
    appkind: 'deployment'
  engineState: 'active'
  chaosServiceAccount: litmus-admin
  experiments:
    - name: pod-delete
      spec:
        components:
          env:
            - name: TOTAL_CHAOS_DURATION
              value: '30'
            - name: CHAOS_INTERVAL
              value: '10'
            - name: FORCE
              value: 'false'

Dit doodt nginx pods elke 10 seconden voor 30 seconden totaal. Klein, contained en makkelijk te volgen, precies waar je wilt beginnen.

De experimenten die de moeite waard zijn

Je hebt deze niet allemaal nodig op dag één. Maar het helpt om het menu te kennen, want elk experiment test een andere aanname die je stilletjes maakt over je cluster.

Pod-level chaos

Pod Delete: Dood pods om restart gedrag te testen

experiments:
  - name: pod-delete
    spec:
      components:
        env:
          - name: TOTAL_CHAOS_DURATION
            value: '60'
          - name: CHAOS_INTERVAL
            value: '10'
          - name: PODS_AFFECTED_PERC
            value: '50'  # Dood 50% van de pods

Container Kill: Dood specifieke containers binnen pods

experiments:
  - name: container-kill
    spec:
      components:
        env:
          - name: TARGET_CONTAINER
            value: 'sidecar'
          - name: CHAOS_INTERVAL
            value: '10'

Node-level chaos

Node Drain: Simuleer node onderhoud

apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: node-drain-chaos
spec:
  engineState: 'active'
  auxiliaryAppInfo: ''
  chaosServiceAccount: litmus-admin
  experiments:
    - name: node-drain
      spec:
        components:
          env:
            - name: TOTAL_CHAOS_DURATION
              value: '60'
            - name: TARGET_NODE
              value: 'worker-1'

Node CPU Hog: Simuleer CPU druk

experiments:
  - name: node-cpu-hog
    spec:
      components:
        env:
          - name: TOTAL_CHAOS_DURATION
            value: '60'
          - name: NODE_CPU_CORE
            value: '2'  # Verbruik 2 cores

Network chaos

Pod Network Loss: Simuleer network partities

experiments:
  - name: pod-network-loss
    spec:
      components:
        env:
          - name: NETWORK_INTERFACE
            value: 'eth0'
          - name: NETWORK_PACKET_LOSS_PERCENTAGE
            value: '100'  # Totaal verlies
          - name: TOTAL_CHAOS_DURATION
            value: '30'

Pod Network Latency: Injecteer latency

experiments:
  - name: pod-network-latency
    spec:
      components:
        env:
          - name: NETWORK_LATENCY
            value: '300'  # 300ms latency
          - name: JITTER
            value: '100'  # 100ms jitter

Storage chaos

Disk Fill: Test disk druk afhandeling

experiments:
  - name: disk-fill
    spec:
      components:
        env:
          - name: FILL_PERCENTAGE
            value: '90'
          - name: TOTAL_CHAOS_DURATION
            value: '60'

Je eerste experiment draaien

Genoeg menu-lezen. Hier een compleet experiment tegen een wegwerp-app, van begin tot eind, zodat je het kunt kopiëren en echt iets kunt zien breken.

1. Deploy test applicatie

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
  labels:
    app: demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 5

2. Maak ServiceAccount voor Litmus

apiVersion: v1
kind: ServiceAccount
metadata:
  name: litmus-admin
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: litmus-admin
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/exec", "pods/log", "events"]
    verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: litmus-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: litmus-admin
subjects:
  - kind: ServiceAccount
    name: litmus-admin
    namespace: default

3. Draai pod delete experiment

apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: demo-chaos
  namespace: default
spec:
  appinfo:
    appns: 'default'
    applabel: 'app=demo'
    appkind: 'deployment'
  engineState: 'active'
  chaosServiceAccount: litmus-admin
  experiments:
    - name: pod-delete
      spec:
        components:
          env:
            - name: TOTAL_CHAOS_DURATION
              value: '30'
            - name: CHAOS_INTERVAL
              value: '10'
            - name: FORCE
              value: 'false'
        probe:
          - name: "check-endpoint"
            type: "httpProbe"
            httpProbe/inputs:
              url: "http://demo-app.default.svc:80"
              insecureSkipVerify: false
              method:
                get:
                  criteria: "=="
                  responseCode: "200"
            mode: "Continuous"
            runProperties:
              probeTimeout: 5
              interval: 2
              retry: 2

4. Observeer resultaten

# Watch chaos engine status
kubectl get chaosengine demo-chaos -w

# Check chaos result
kubectl get chaosresult demo-chaos-pod-delete -o yaml

# Watch pod gedrag
kubectl get pods -l app=demo -w

Game days: oefenen voor echte incidenten

Hier komt het deel dat de YAML niet kan fixen. Een chaos experiment vertelt je of het systeem het overleeft. Het zegt niks over of de mensen erachter weten wat ze moeten doen als de pager afgaat. De eerste keer dat iemand een runbook aanraakt zou niet tijdens een echte storing moeten zijn, en een game day is hoe je dat voorkomt.

Dus je neemt hetzelfde idee van gecontroleerde failure en richt het op het team in plaats van op de cluster.

Een game day plannen

  1. Definieer scope: Welke systemen, welke failure scenarios
  2. Stel team samen: Engineers die op echte incidenten zouden reageren
  3. Bereid runbooks voor: Documenteer verwachte responses
  4. Stel succescriteria: Hoe ziet “goed afgehandeld” eruit?
  5. Plan tijd: Dedicated tijd, niet gepropt tussen meetings

Game day structuur

09:00 - Briefing: Leg scope uit, regels, hoe af te breken
09:15 - Injecteer failure (team weet niet welke)
09:15 - Team reageert alsof echt incident
10:00 - Failure onthuld, extra scenarios geïntroduceerd
11:00 - Debrief: Wat werkte, wat niet, wat te verbeteren

Voorbeeld scenarios

ScenarioWat Je Leert
Dood primaire databaseFailover snelheid, data integriteit
DNS storingCaching gedrag, timeout handling
Certificaat verlooptMonitoring gaps, renewal proces
Cloud regio onbeschikbaarMulti-regio readiness
Secrets manager downApplicatie gedrag zonder secrets

Het in Git houden

Omdat de experimenten gewoon YAML zijn, horen ze in version control bij al het andere. Ik beheer de mijne met ArgoCD, gedefinieerd maar slapend, klaar om te activeren wanneer ik ze wil draaien:

# chaos-experiments/pod-delete.yaml (in Git)
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: scheduled-pod-delete
  namespace: chaos-testing
spec:
  engineState: 'stop'  # Handmatig geactiveerd
  # ... experiment config

De flow is dezelfde die ik voor al het andere gebruik: definieer experimenten in Git, sync met ArgoCD, zet engineState op active wanneer ik er één wil draaien, lees de resultaten, commit dan wat voor fixes of bevindingen eruit kwamen. Het experiment en zijn historie leven op dezelfde plek als de workload die het test, en dat is het hele punt.

Veilig blijven terwijl je dit doet

Ik wil eerlijk zijn over de trade-off hier, want chaos engineering dat slordig gedaan wordt is gewoon een storing die je zelf hebt veroorzaakt. De discipline is wat de twee scheidt.

Een paar regels die ik echt volg. Begin in non-productie en blijf daar tot je het experiment vertrouwt. Heb de rollback klaar voor je begint, niet nadat het misgaat. Kijk live naar het experiment in plaats van het te starten en weg te lopen. Vertel iedereen die gepaged kan worden dat je dit gaat doen. En schrijf op wat je vond, want een ongedocumenteerd experiment is gewoon chaos met extra stappen.

De fouten die mensen bijten zijn even voorspelbaar: draaien tijdens je echte piekvenster terwijl je geen piekgedrag probeerde te testen, failure injecteren zonder de on-call te waarschuwen, de hypothese overslaan zodat je signaal niet van ruis kunt onderscheiden, en dan de bevinding negeren omdat de cluster “wel goed genoeg” herstelde.

Het belangrijkste: bepaal je afbreek-condities vooraf. Error rate voorbij een drempel, latency voorbij een plafond, echte gebruikers die klagen, of eerlijk gezegd elk gedrag dat je niet voorspeld had. Litmus kan een deel hiervan voor je afdwingen met een probe die het experiment automatisch afbreekt:

# Afbreken bij hoge error rate
probe:
  - name: "error-rate-check"
    type: "promProbe"
    promProbe/inputs:
      endpoint: "http://prometheus:9090"
      query: "rate(http_requests_total{status=~'5..'}[1m])"
      comparator:
        type: "float"
        criteria: "<"
        value: "0.1"  # Afbreken als > 10% errors

Waarom niet iedereen dit al doet

Als chaos engineering zo overduidelijk nuttig is, waarom is het dan niet overal? De obstakels zijn echt en het is de moeite om ze te benoemen, anders klinkt het hele ding als een makkelijke verkoop die het niet is.

De grote is angst, en het is rationele angst. Een systeem waar je verantwoordelijk voor bent bewust kapot maken voelt de eerste keer krankzinnig, zeker als je het nog nooit veilig hebt zien falen. “Het werkt” is een oprecht krachtig argument tegen “laten we het expres gaan breken.” Er is ook de tijdkost. Een echt experiment ontwerpen, het bekijken, documenteren, fixen wat het vond, niks daarvan is gratis, en het concurreert met features shippen. En er is een cultuurprobleem: in een team waar schuld rondgaat is expres een failure veroorzaken carrièrerisico, zelfs als het het verantwoordelijke is om te doen.

Geen van die verdwijnen door ze te negeren. Ze verdwijnen door zo klein te beginnen dat de angst nergens kan staan.

Hoe ik dit echt in de homelab draai

Dus hier is de haalbare versie, degene die ik op mijn eigen cluster draai. Niks heroïsch. Een wekelijkse pod-delete tegen willekeurige pods in non-kritieke namespaces, een maandelijkse node drain om het verliezen van een machine te oefenen, en ongeveer eens per kwartaal een echte game day met een paar gestapelde failures.

Die kleine cadans heeft zichzelf al terugbetaald. Een paar dingen die het me leerde:

  • Longhorn handelt een verdwijnende node een stuk rustiger af dan ik verwachtte, fijn om te weten in plaats van te hopen.
  • Meerdere van mijn workloads hadden een nutteloze terminationGracePeriod en werden halverwege een request hard-killed zonder dat ik het ooit merkte in normaal bedrijf.
  • Mijn alerting vuurt consequent later dan zou moeten. Fijn om dat op een dinsdag te vinden in plaats van tijdens een echt incident.
  • Recovery duurt altijd langer dan mijn optimistische mentale model zegt.

Elk van die was een toekomstige 3-uur-’s-nachts page die ik inruilde voor een middag van milde, gecontroleerde ongemak. Dat is de deal die chaos engineering biedt, en nadat ik het een tijd heb gedraaid vind ik het een hele goede deal.

Productie gaat je systemen testen of je het nou leuk vindt of niet. De test zelf eerst draaien is de enige versie waarin je het moment mag kiezen, de rollback vasthoudt, en iets leert in plaats van alleen overleven.


Maak dingen expres kapot, bij daglicht, met een koffie en een rollback. Het alternatief is ze zelf laten breken om 3 uur ’s nachts op de drukste dag van het jaar. Een van die ochtenden is een stuk beter dan de andere.