· · 12 min leestijd Advanced

Chaos engineering: je cluster kapot maken om het sterker te maken

Leer chaos engineering voor Kubernetes. Maak opzettelijk dingen kapot om zwakheden te ontdekken voor ze storingen worden. Praktische gids met Litmus Chaos.

Zij zwaait een sloopkogel naar haar eigen rack dat blijft branden, een wezen in pak bedekt zijn ogen

TL;DR Chaos engineering is je eigen cluster expres kapot maken, met de hypothese vooraf op papier en een rollback binnen handbereik. Ik draai het op de homelab met Litmus: een wekelijkse pod-delete, een maandelijkse node drain, elk kwartaal een game day. Tot nu toe heeft het vier dingen gevonden die het dashboard me nooit had laten zien.

M’n dashboard is een muur van groen. Pods draaien, replicas kloppen, CPU rustig, geen alert te bekennen. Ik kijk ernaar en krijg dat kleine shotje “alles is in orde.” En grotendeels klopt dat ook. Het cluster draait al weken en er is niks omgevallen.

Die muur is tegelijk het gevaarlijkste ding in m’n homelab. Hij zegt niks over wat er gebeurt als het misgaat. Alleen dat er, op dit moment, nog niks mis is.

Stel je nu de andere versie van die ochtend voor. Een node valt weg op precies het moment van de dag dat de load ertoe doet. Een DNS-lookup begint te timeouten en sleept elke readiness probe mee. Een service die ik acht maanden geleden heb neergezet en vergeten ben, vreet geheugen tot de OOM-killer van de kernel ingrijpt. Niks daarvan staat op een gezond dashboard. Het komt boven als ik slaap, en degene die dan zit te debuggen ben ik, slecht uitgerust en chagrijnig.

Dus hoe kom ik daar achter vóór productie het doet? Door het zelf kapot te maken. Op een doordeweekse middag, met koffie erbij en een rollback klaar. Dat is de hele pitch van chaos engineering, en ik vind m’n zwakke plekken liever op m’n eigen moment dan dat productie het moment kiest.

Wat expres dingen kapot maken betekent

Een gedistribueerd systeem faalt niet op één plek. Je kunt elke regel code lezen, het mooiste architectuurdiagram ter wereld tekenen, en nog steeds geen idee hebben hoe het ding zich gedraagt als een node halverwege een request wegvalt. Dat gedrag zit in de gaten tussen de componenten. De enige manier om het te zien is de failure veroorzaken en kijken.

Ik kom hier op deze blog niet voor niks steeds op terug. Ik wil begrijpen wat ik draai. Een groen dashboard vraagt me om vertrouwen, en iets wat ik nog nooit heb zien falen vertrouw ik niet helemaal. Na een chaos-experiment heb ik het zien herstellen, in plaats van aan te nemen dat het wel goed komt.

Even een verduidelijking, want de naam schrikt mensen af. Chaos engineering is niet willekeurig dingen slopen om te zien wat er gebeurt, en het is ook niet fault injection de prod in gooien zonder plan. Elk experiment heeft drie dingen aan zich hangen:

  • Een hypothese. “Als één node faalt, reschedulen pods binnen vijf minuten.” Je test iets wat je al gelooft.
  • Een blast radius die jij bepaalt. Bekend target, vaste duur, een rollback die je meteen kunt indrukken.
  • Iets wat je leert. Of de overtuiging houdt stand, of je hebt een bug die je nog niet kende. Allebei prima wat mij betreft.

Netflix maakte dit beroemd met Chaos Monkey, dat willekeurige instances doodde zodat engineers wel services moesten bouwen die dat overleefden. De tooling is sindsdien flink volwassen geworden. Het idee is niet veranderd. Je veroorzaakt de failure op een moment dat je zelf koos, in plaats van erop te wachten.

De chaos-engineering-cyclus

Chaos Engineering Cyclus

Definieer Hypothese

Ontwerp Experiment

Draai in Gecontroleerde Omgeving

Observeer & Meet

Leer & Verbeter

1. Schrijf de hypothese op

Schrijf op wat je gelooft dat waar is, vóór je iets aanraakt. Je legt je vast op een voorspelling, zodat het resultaat je 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 het experiment

Nu scopen. Vier vragen:

  • Blast radius. Wat kan er geraakt worden?
  • Duur. Hoe lang blijft de fault bestaan?
  • Rollback. Hoe stop je het nú?
  • Metrics. Waaraan zie je of het slaagde of faalde?

De rollback-vraag slaan mensen over. En dat is precies de vraag die voorkomt dat een experiment een incident wordt.

3. Draai het waar het geen kwaad kan

Begin klein. Eerst dev, dan staging, dan productie, en productie alleen met safeguards. Die volgorde heb ik precies één keer overgeslagen. Daarna niet meer.

4. Observeer en meet

Hier verdient je observability-stack zichzelf terug. Kun je niet zien wat er gebeurde, dan heb je alleen schade aangericht. Let op de voor de hand liggende dingen. Gingen de juiste alerts af? Bewoog de latency? Schoot de error rate omhoog? Hoe lang duurde het herstel, naast wat je had gegokt?

5. Leer en fix

Schrijf op wat je vond, fix de zwakke plek en werk het runbook bij, zodat de volgende niet vanaf nul begint. Die volgende ben ik meestal zelf, zes maanden later. Ontwerp dan het volgende experiment.

Litmus, chaos op de Kubernetes-manier

Litmus is een CNCF -project dat chaos engineering doet zoals Kubernetes alles doet. Je declareert een experiment als YAML en applyt het. Daarom koos ik het voor de homelab. Het experiment staat in Git naast de workload die het aanvalt, dus het is reviewbaar en reproduceerbaar, en het is geen 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

Litmus Componenten

Execution Plane

Control Plane

Litmus Portal
(UI/API)

Litmus Server

Subscriber

Chaos Runner

Chaos Experiments

Target Workload

Een experiment declareren met ChaosEngine

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, 30 seconden lang. Klein, afgebakend, makkelijk te volgen. Begin hier.

De experimenten die je moet kennen

Je hebt ze niet allemaal nodig op dag één. Het menu kennen helpt wel, want elk experiment test een andere aanname die je stilletjes over je cluster maakt.

Chaos op pod-niveau

Pod delete. Doodt 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. Doodt één container binnen een pod.

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

Chaos op node-niveau

Node drain. Simuleert 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. Simuleert 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

Netwerk-chaos

Pod network loss. Simuleert een netwerkpartitie.

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. Injecteert 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 hoe de workload omgaat met een volle disk.

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

Je eerste experiment draaien

Genoeg menu gelezen. Hier een compleet experiment tegen een wegwerp-app, van begin tot eind, zodat je het kunt kopiëren en iets kapot ziet gaan.

1. Deploy een testapplicatie

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 een 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 het 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. Bekijk de 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, hetzelfde idee gericht op het team

Hier komt het deel dat de YAML niet oplost. 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 openslaat hoort niet tijdens een echte storing te zijn. Een game day is hoe je dat voorkomt.

Je neemt dezelfde gecontroleerde failure en richt hem op het team in plaats van op het cluster.

Een game day plannen

  1. Scope. Welke systemen, welke failure-scenario’s.
  2. Team. De engineers die op het incident zelf zouden reageren.
  3. Runbooks. Vooraf opgeschreven, anders valt er niks te testen.
  4. Succescriteria. Hoe ziet “goed afgehandeld” eruit?
  5. Tijd. Een blok in de agenda, niet een gaatje tussen twee meetings.

Opbouw van een game day

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

Voorbeeldscenario’s

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

In Git houden

De experimenten zijn YAML, dus ze horen in version control bij al het andere. Ik beheer de mijne met ArgoCD, gedefinieerd maar slapend, klaar voor als ik er één 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 als voor al het andere. Experiment in Git, ArgoCD synct het, engineState op active als ik het wil draaien, resultaat lezen, en de fix of de bevinding committen. Het experiment en z’n historie staan naast de workload die het test.

Veilig blijven terwijl je dit doet

Slordig gedaan is chaos engineering een storing die je zelf hebt veroorzaakt. Het verschil zit in discipline, dus hier is de mijne.

Begin buiten productie en blijf daar tot je het experiment vertrouwt. Heb de rollback klaar vóór je begint, niet als het al misgaat. Kijk live mee in plaats van starten en weglopen. Zeg het tegen iedereen die gepaged kan worden. En schrijf op wat je vond, want over een maand weet niemand meer wat het vond.

De fouten zijn net zo voorspelbaar. Draaien in je piekuur terwijl je helemaal geen piekgedrag wilde testen. Een failure injecteren zonder de on-call te waarschuwen. De hypothese overslaan, zodat je signaal niet van ruis kunt onderscheiden. De bevinding negeren omdat het cluster “goed genoeg” herstelde.

Het belangrijkste van allemaal, bepaal vooraf wanneer je afbreekt. Error rate boven een drempel, latency boven een plafond, echte gebruikers die klagen, of welk gedrag dan ook dat je niet had voorspeld. Litmus kan een deel daarvan afdwingen met een probe die het experiment voor je 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 nuttig is, waarom zie je het dan niet overal? De obstakels bestaan, en als ik ze niet benoem leest dit als een makkelijke verkoop. Dat is het niet.

De grootste is angst, en dat is terechte angst. Een systeem waar jij verantwoordelijk voor bent expres kapot maken voelt de eerste keer krankzinnig, zeker als je het nog nooit veilig hebt zien falen. “Het werkt” is een sterk argument tegen “laten we het gaan slopen.” Dan is er de tijd. Een goed experiment ontwerpen, ernaar kijken, documenteren, fixen wat het vond, niks daarvan is gratis en het concurreert met features shippen. En er is cultuur. In een team waar de schuldvraag rondgaat, is expres een failure veroorzaken een carrièrerisico, ook als het het verantwoordelijke is om te doen.

Geen van die drie verdwijnt vanzelf. Wat bij mij werkte was zo klein beginnen dat er niks was om bang voor te zijn, een pod-delete tegen een wegwerp-nginx in een dev-namespace.

Wat ik in de homelab draai

Dus hier is de versie die ik op m’n eigen cluster draai. Niks heldhaftigs. Een wekelijkse pod-delete tegen willekeurige pods in niet-kritieke namespaces, een maandelijkse node drain om het verlies van een machine te oefenen, en ongeveer eens per kwartaal een game day met een paar gestapelde failures.

Dat kleine ritme heeft zichzelf al terugverdiend. Wat het me leerde:

  • Longhorn vangt een verdwijnende node een stuk rustiger op dan ik verwachtte. Dat hoopte ik altijd. Nu heb ik het gezien.
  • Meerdere van m’n workloads hadden een nutteloze terminationGracePeriod en werden halverwege een request hard-killed, en dat was me in normaal bedrijf nooit opgevallen.
  • M’n alerting gaat consequent later af dan zou moeten. Dat leer ik liever op een doordeweekse middag dan tijdens een incident.
  • Herstel duurt langer dan ik gok. Altijd.

Elk van die vier was een page die ik midden in de nacht had gekregen, ingeruild voor een middag licht ongemak. Die ruil doe ik zo weer.

Het dashboard is vanochtend weer groen. Ik vertrouw het iets meer dan eerst, omdat ik het op m’n eigen moment rood heb zien worden en weer terug.