CrashLoopBackOff: waarom je Kubernetes-pod steeds herstart

CrashLoopBackOff is geen foutmelding. Het is een status die aangeeft dat een container in je pod steeds opstart, crasht en opnieuw gestart wordt met oplopende wachttijden. Dit artikel loopt door wat de status betekent, hoe je exit codes en logs leest, de meest voorkomende oorzaken, en hoe je elk probleem oplost.

Wat CrashLoopBackOff precies betekent

CrashLoopBackOff is een kubelet-displaystatus, geen officiële podfase. De daadwerkelijke podfase blijft Running of gaat naar Failed. Wat de status je vertelt: een container is gestart, gestopt, en de kubelet herstart hem met exponentieel oplopende wachttijden.

De backoff-reeks (Kubernetes 1.32 en eerder):

Restart-poging Wachttijd voor volgende poging
1 10 seconden
2 20 seconden
3 40 seconden
4 80 seconden
5 160 seconden
6+ 300 seconden (maximum)

Draait de container 10 minuten aaneengesloten zonder te crashen, dan reset de backoff naar 10 seconden.

Tijdens elke wachtperiode toont kubectl get pods de status CrashLoopBackOff. Zodra de kubelet een nieuwe poging doet, wisselt de status kort naar Running of ContainerCreating voordat de container opnieuw crasht.

Kubernetes 1.33 alpha-wijziging. De ReduceDefaultCrashLoopBackOffDecay feature gate (KEP-4603, opt-in, standaard uitgeschakeld) verlaagt de initiële wachttijd naar 1 seconde en kapt af op 60 seconden. Als je op 1.33 zit met deze gate ingeschakeld, herstellen je pods sneller van tijdelijke fouten, maar de diagnostische aanpak blijft gelijk.

De symptomen lezen

Drie commando's, in deze volgorde.

Stap 1: bevestig de crash loop

kubectl get pods -n <namespace>

Verwachte output voor een crashende pod:

NAME                      READY   STATUS             RESTARTS      AGE
payment-svc-7b9f6d-xk2p  0/1     CrashLoopBackOff   14 (2m ago)   47m

Hoog getal bij RESTARTS, 0/1 onder READY en CrashLoopBackOff onder STATUS bevestigen de loop.

Stap 2: lees exit codes en events

kubectl describe pod payment-svc-7b9f6d-xk2p -n payments

Zoek naar twee secties in de output.

Containerstatus:

Last State:    Terminated
  Reason:      Error
  Exit Code:   1
  Started:     Wed, 09 Apr 2026 14:03:10 +0000
  Finished:    Wed, 09 Apr 2026 14:03:11 +0000

De exit code is je eerste vertakking. Zie de exit code referentietabel hieronder.

Events:

Warning  BackOff  2m (x47 over 30m)  kubelet  Back-off restarting failed container

Zie je Liveness probe failed in de events? Dan crasht de container niet zelf. Kubernetes killt hem. Dat is een andere oorzaak.

Stap 3: lees de logs van de vorige container

kubectl logs payment-svc-7b9f6d-xk2p -n payments --previous

De --previous flag (of -p) haalt logs op van de laatst gestopte container-instantie. Zonder die flag krijg je de logs van de huidige container, die vaak leeg zijn omdat de container net is gestart.

Voor pods met meerdere containers:

kubectl logs payment-svc-7b9f6d-xk2p -n payments -c payment-worker --previous

Als --previous lege logs teruggeeft, crashte de container voordat er iets naar stdout/stderr geschreven werd. Dat wijst op een ontbrekend binary (exit code 127), een missende shared library, of een OOM kill in de eerste milliseconden. Ga naar debuggen als logs leeg zijn.

Exit code referentie

Exit codes verschijnen in kubectl describe pod onder Last State > Exit Code. De code vertelt je met welk type fout je te maken hebt.

Exit code Signaal Wat het betekent Meest waarschijnlijke Kubernetes-oorzaak
0 geen Succesvolle exit Container heeft taak afgerond en stopt; restartPolicy: Always herstart hem steeds
1 geen Applicatiefout Onafgevangen exceptie, missende config, fatale startfout
126 geen Commando niet uitvoerbaar Binary bestaat maar heeft verkeerde permissies
127 geen Commando niet gevonden Binary ontbreekt in image; verkeerd pad in command
137 SIGKILL Geforceerde kill (128+9) OOMKilled door de kernel, of directe SIGKILL
139 SIGSEGV Segmentatiefout Geheugentoegangsfout in applicatiecode
143 SIGTERM Nette beeindiging (128+15) Liveness probe-falen; kubelet stuurt SIGTERM

De formule achter signaal-gebaseerde codes: 128 + signaalnummer. SIGKILL is signaal 9, dus 128 + 9 = 137.

Meest voorkomende oorzaken

Gerangschikt op hoe vaak ik ze tegenkom in productieclusters. De eerste vier dekken meer dan 90% van de gevallen.

Oorzaak 1: applicatiecrash (exit code 1)

De applicatie start, raakt een fatale fout en stopt. Logs tonen een stacktrace, panic of onafgevangen exceptie.

Typische logoutput:

  • Python: Exception: Database connection refused
  • Go: panic: runtime error: nil pointer dereference
  • Node.js: Error: Cannot find module './config'
  • Java: java.lang.NullPointerException

Oplossing. Lees de stacktrace via kubectl logs --previous. Reproduceer lokaal met dezelfde image en omgevingsvariabelen:

docker run --rm \
  -e DATABASE_URL=postgres://db-primary.internal:5432/payments \
  -e LOG_LEVEL=debug \
  registry.internal/payment-svc:3.1.4

Fix de bug. Bouw een nieuwe image met een unieke tag (niet latest). Roll hem uit:

kubectl set image deployment/payment-svc \
  payment-svc=registry.internal/payment-svc:3.1.5 \
  -n payments
kubectl rollout status deployment/payment-svc -n payments

Controleer: kubectl get pods -n payments toont 1/1 Running met 0 restarts.

Oorzaak 2: OOM kill (exit code 137)

De container overschreed zijn geheugenlimiet en de OOM killer van de Linux-kernel heeft hem beëindigd. Dit is een van de meest voorkomende oorzaken en heeft een duidelijke signatuur in kubectl describe pod:

Last State:    Terminated
  Reason:      OOMKilled
  Exit Code:   137

Waarom het gebeurt: resources.limits.memory staat lager dan het daadwerkelijke geheugengebruik van de applicatie. Of de applicatie heeft een geheugenlek.

Snelle fix: verhoog de geheugenlimiet.

kubectl patch deployment payment-svc -n payments -p \
  '{"spec":{"template":{"spec":{"containers":[{"name":"payment-svc","resources":{"limits":{"memory":"512Mi"},"requests":{"memory":"256Mi"}}}]}}}}'

Voordat je blindelings verhoogt, controleer of het geheugen oneindig blijft groeien (lek) of stabiliseert op een niveau boven de huidige limiet (te krap). kubectl top pod geeft een momentopname:

kubectl top pod payment-svc-7b9f6d-xk2p -n payments

Als je Prometheus met kube-state-metrics draait:

container_memory_working_set_bytes{container="payment-svc", namespace="payments"}

Een monotoon stijgende lijn betekent een lek. Fix de applicatie. Een lijn die stabiliseert boven de huidige limiet betekent dat de limiet te laag is. Verhoog hem.

Voor een dieper begrip van hoe requests en limits samenwerken, zie Kubernetes resource requests en limits.

Controleer: restart-teller stopt met oplopen, geen OOMKilled meer in kubectl describe pod.

Oorzaak 3: ontbrekende configuratie (ConfigMap, Secret, omgevingsvariabele)

De applicatie vereist een ConfigMap, Secret of omgevingsvariabele die niet bestaat in de namespace, een verkeerde naam heeft, of verwijderd is.

Hoe te bevestigen: logs tonen missing required env variable DATABASE_URL of config file not found: /etc/app/config.yaml. Soms start de pod helemaal niet en toont CreateContainerConfigError in plaats van CrashLoopBackOff.

# Controleer of de verwachte ConfigMap bestaat
kubectl get configmaps -n payments

# Controleer of het verwachte Secret bestaat
kubectl get secrets -n payments

# Bekijk wat de deployment verwacht
kubectl get deployment payment-svc -n payments -o yaml | grep -A 20 "env:"

Veelvoorkomende scenario's:

  • ConfigMap verwijderd maar de Deployment verwijst er nog naar
  • Secret bestaat in de default namespace maar de pod draait in payments
  • Typfout in valueFrom.secretKeyRef.key (de key binnen het Secret, niet de Secretnaam)

Oplossing. Maak de ontbrekende resource aan of corrigeer de referentie:

kubectl create secret generic payment-db-creds -n payments \
  --from-literal=DB_PASSWORD=changeme-in-production

Controleer: pod start zonder config-gerelateerde fouten in kubectl logs.

Oorzaak 4: verkeerd geconfigureerde liveness probe

De container is gezond, maar Kubernetes blijft hem killen omdat de liveness probe faalt voordat de applicatie klaar is met opstarten. Dit is de meest misleidende oorzaak, want er is niks mis met de applicatie zelf.

Signatuur: kubectl describe pod toont Liveness probe failed in events. Exit code is 137 (SIGKILL van kubelet) of 143 (SIGTERM). De Last State van de container laat hele korte draaitijden zien (1–5 seconden).

Het meest voorkomende patroon: initialDelaySeconds is korter dan de opstarttijd van de applicatie. Een Java-service die 45 seconden nodig heeft om de Spring-context te laden, de database te verbinden en caches op te warmen, faalt een probe die begint bij initialDelaySeconds: 5.

Andere probe-misconfiguraties:

  • Verkeerd path: probe raakt /healthz maar de applicatie serveert /health
  • Verkeerde port: probe checkt 8080 maar de applicatie bindt op 8081
  • timeoutSeconds: 1 terwijl het endpoint 2 seconden nodig heeft onder load
  • failureThreshold: 1: een enkele timeout triggert een restart

Snelle test om te bevestigen. Verwijder de liveness probe tijdelijk:

kubectl patch deployment payment-svc -n payments --type json \
  -p '[{"op": "remove", "path": "/spec/template/spec/containers/0/livenessProbe"}]'

Stoppen de restarts? Dan was de probe het probleem.

Goede fix: voeg een startup probe toe. In plaats van te vechten met initialDelaySeconds, gebruik een startup probe die de liveness probe blokkeert totdat de applicatie klaar is:

startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  failureThreshold: 30      # 30 x 10s = 300s maximale opstarttijd
  periodSeconds: 10
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

Voor een volledig overzicht van probetypes, timingparameters en endpointdesign, zie Kubernetes health probes configureren.

Controleer: pod blijft Running tijdens het opstarten. Geen Liveness probe failed-events in kubectl describe pod.

Oorzaak 5: verkeerd entrypoint of ontbrekend binary (exit code 127/128)

De podspec definieert een command of args die niet bestaat in de image. De container stopt meteen met exit code 127 (niet gevonden) of 126 (niet uitvoerbaar). Logs zijn meestal leeg.

Belangrijk onderscheid: in Kubernetes overschrijft command Docker's ENTRYPOINT en args overschrijft Docker's CMD. Als je command zet in de podspec, wordt het ENTRYPOINT van de image volledig genegeerd.

Oplossing. Inspecteer de image om het juiste pad te vinden:

docker inspect registry.internal/payment-svc:3.1.4 \
  | jq '.[0].Config.Entrypoint, .[0].Config.Cmd'

Of draai een tijdelijke pod om het bestandssysteem te verkennen:

kubectl run debug-payment --rm -it \
  --image=registry.internal/payment-svc:3.1.4 \
  --restart=Never -- /bin/sh

# In de container:
find / -name payment-svc 2>/dev/null

Corrigeer command of args in de Deploymentspec.

Controleer: container start en produceert logoutput.

Oorzaak 6: container eindigt succesvol (exit code 0)

De container stopt met code 0 (succes), maar restartPolicy: Always (de default voor Deployments) herstart hem telkens opnieuw. Dit gebeurt als een batch-job-image als Deployment uitgerold wordt in plaats van als Job.

Opties:

  • Schakel de workload om naar een Job of CronJob als het een batchtaak is
  • Fix het containercommando zodat het een persistent proces draait (exec payment-svc serve in plaats van payment-svc run-once)
  • Zet restartPolicy: Never als de workload echt niet hoeft te herstarten (alleen geldig in losse pods, niet in Deployments)

Debuggen als logs leeg zijn

Als kubectl logs --previous niets teruggeeft, crashte de container voordat er iets naar stdout geschreven werd. Twee technieken om toch binnen te komen.

Ephemeral containers (Kubernetes 1.23+)

Ephemeral containers koppelen een debugcontainer aan een draaiende (of crashende) pod. Ze delen de procesnamespace met de doelcontainer.

kubectl debug -it payment-svc-7b9f6d-xk2p -n payments \
  --image=busybox:1.36 \
  --target=payment-svc

Eenmaal binnen kun je het bestandssysteem en de processen van de doelcontainer inspecteren:

# Controleer of het binary bestaat
ls -la /proc/1/root/usr/local/bin/

# Controleer shared library-afhankelijkheden
ldd /proc/1/root/usr/local/bin/payment-svc

# Bekijk omgevingsvariabelen
cat /proc/1/environ | tr '\0' '\n'

Copy-and-override debugging

Maak een kopie van de crashende pod met een ander commando dat hem in leven houdt:

kubectl debug payment-svc-7b9f6d-xk2p -it \
  --copy-to=payment-debug \
  -n payments \
  -- sleep infinity

Exec er dan in en draai het originele commando handmatig om de fout te zien:

kubectl exec -it payment-debug -n payments -- /bin/sh
# Draai het originele entrypoint
/usr/local/bin/payment-svc serve --config /etc/payment/config.yaml

Ruim de debug-pod op als je klaar bent: kubectl delete pod payment-debug -n payments.

Monitoring en alerting

Als je kube-state-metrics met Prometheus draait, kun je op CrashLoopBackOff alerteren voordat iemand het handmatig opmerkt.

CrashLoopBackOff direct detecteren:

kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"} == 1

Alertregel op hoge restart-rate (vangt loops eerder op):

# Prometheus alerting rule
- alert: PodCrashLooping
  expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "Pod / herstartte >5 keer in 1u"

De restart-rate query viert voordat de backoff het 5-minutenmaximum bereikt, dus je krijgt eerder een waarschuwing.

Herhaling voorkomen

  • Pin image-tags. payment-svc:3.1.4, niet payment-svc:latest. Gepinde tags maken rollbacks deterministisch met kubectl rollout undo.
  • Zet altijd memory requests en limits. Geen limiet betekent geen geheugengrens; de kernel killt je pod zonder waarschuwing als de node krap zit.
  • Test containers lokaal eerst. docker run met dezelfde omgevingsvariabelen vangt de meeste opstartfouten voordat ze het cluster bereiken.
  • Gebruik startup probes voor trage applicaties. Rek initialDelaySeconds niet op naar 120 seconden. Daar zijn startup probes voor.
  • Verifieer dat dependencies bestaan voor het deployen. kubectl get configmaps,secrets -n payments voordat je kubectl apply -f deployment.yaml draait.
  • Zet JVM-heap en GOMAXPROCS expliciet. Java- en Go-processen gebruiken standaard al het beschikbare geheugen en alle cores van de node, niet die van de container. De JVM respecteert -XX:MaxRAMPercentage sinds Java 10. Go respecteert de GOMEMLIMIT omgevingsvariabele sinds Go 1.19.

Wanneer escaleren

Als geen van de bovenstaande oorzaken past, of als fixes de loop niet oplossen, verzamel dan deze informatie voordat je hulp vraagt:

  • Volledige output van kubectl describe pod <pod-name> -n <namespace>
  • Logs van de vorige container: kubectl logs <pod-name> -n <namespace> --previous
  • Namespace-events: kubectl get events -n <namespace> --sort-by=.metadata.creationTimestamp
  • Node-resourcedruk: kubectl top node en kubectl describe node <node-name>
  • Kubernetes-versie: kubectl version
  • Het Deployment-manifest (zonder secrets)
  • Of het probleem consistent of intermitterend is
  • Of dezelfde image werkt in een andere namespace of cluster

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.