Kubernetes: FreeDiskSpaceFailed – Kubelet kann Images nicht bereinigen
Symptom
Im Event-Log eines Kubernetes-Nodes erscheint folgende Warnung wiederholt:
Warning FreeDiskSpaceFailed kubelet (combined from similar events):
Failed to garbage collect required amount of images.
Attempted to free 2015210700 bytes, but only found 0 bytes eligible to free.
Die Meldung tritt auf, obwohl auf dem Node noch ausreichend Speicherplatz vorhanden ist (z. B. 25 % frei).
Ursache
Der Kubelet-Prozess führt regelmäßig eine Image Garbage Collection durch, um ungenutzten Speicher auf der Image-Partition freizugeben. Dabei gelten zwei zentrale Schwellenwerte:
- imageGCHighThresholdPercent (Standard: 85 %): Ab dieser Belegung startet die Bereinigung.
- imageGCLowThresholdPercent (Standard: 80 %): Bis zu diesem Wert wird versucht, Speicher freizuräumen.
Die Fehlermeldung entsteht, wenn Kubelet versucht, Speicher freizugeben, aber keine löschbaren Images findet. Ein Image gilt nur dann als löschbar, wenn es von keinem Container referenziert wird – auch nicht von gestoppten (Exited) Containern.
Häufigste Ursachen im Detail
1. Exited Container referenzieren Images Gestoppte Container bleiben auf dem Node bestehen und halten eine Referenz auf ihr Image. Solange der Container existiert, ist das zugehörige Image vor dem Garbage Collector geschützt. Bei Nodes mit vielen Workloads sammeln sich über Wochen Dutzende gestoppter Container an.
2. Kubelet Container-GC ist nicht aggressiv genug konfiguriert Standardmäßig ist die Container-GC bei einigen Distributionen (z. B. K3s) konservativ eingestellt. Alte Container werden nicht automatisch entfernt, was dazu führt, dass auch deren Images nicht bereinigt werden können.
3. Viele verschiedene Workloads auf einem Node Wenn nahezu alle vorhandenen Images von laufenden Pods referenziert werden, gibt es schlicht nichts zu löschen. Der GC meldet trotzdem einen Fehler, weil er sein Ziel (Speicher unter den Low-Threshold bringen) nicht erreicht.
4. Dangling Images und Build-Artefakte
Untagged Images (<none>) aus fehlgeschlagenen oder abgebrochenen Pulls belegen Speicher, werden aber vom normalen GC-Zyklus nicht immer erfasst.
5. Logs und Volumes belegen Speicherplatz Wenn Container-Logs oder emptyDir-Volumes die Disk füllen, kann die Gesamtbelegung den Threshold überschreiten. Der Image-GC kann aber nur Images löschen, keine Logs – daher findet er nichts zum Freigeben.
Diagnose
Alle folgenden Befehle werden direkt auf dem betroffenen Node ausgeführt.
1. Gestoppte Container prüfen
crictl ps -a --state exited
Zeigt alle gestoppten Container mit Alter und zugehörigem Pod. Wenn hier Einträge stehen, die Wochen oder Monate alt sind, ist das die wahrscheinlichste Ursache.
2. Image-Belegung ermitteln
crictl imagefsinfo
crictl images | sort -k3 -h
Zeigt die Gesamtbelegung der Image-Partition und listet alle Images sortiert nach Größe.
3. Untagged/Dangling Images identifizieren
crictl images | grep "<none>"
Diese Images haben keinen Tag mehr und sind Kandidaten für die Bereinigung.
4. Speicherverbrauch nach Verzeichnis prüfen
du -sh /var/lib/containerd/* 2>/dev/null
du -sh /var/lib/rancher/* 2>/dev/null
du -sh /var/lib/kubelet/*
du -sh /var/log/pods/* | sort -h | tail -20
Damit lässt sich feststellen, ob die Belegung tatsächlich von Images stammt oder ob Logs oder andere Daten den Speicher belegen.
5. Kubelet-GC-Konfiguration prüfen
cat /var/lib/kubelet/config.yaml | grep -A5 -i "image\|container\|eviction"
Lösung
Sofortmaßnahme: Gestoppte Container und ungenutzte Images entfernen
# Alle gestoppten Container entfernen
crictl rm $(crictl ps -a -q --state exited)
# Nicht mehr referenzierte Images bereinigen
crictl rmi --prune
Nach dem Entfernen der gestoppten Container verlieren deren Images ihre Referenz und werden durch crictl rmi --prune oder den nächsten GC-Zyklus automatisch gelöscht.
Dauerhafte Lösung: Kubelet Container-GC konfigurieren
Die Kubelet-Konfiguration steuert, wie aggressiv alte Container entfernt werden.
Für Standard-Kubernetes (/var/lib/kubelet/config.yaml):
imageGCHighThresholdPercent: 80
imageGCLowThresholdPercent: 70
imageMinimumGCAge: 2m
evictionHard:
imagefs.available: "15%"
Für K3s (/etc/rancher/k3s/config.yaml):
kubelet-arg:
- "maximum-dead-containers-per-container=1"
- "maximum-dead-containers=20"
- "minimum-container-ttl-duration=1m"
Nach der Änderung den Kubelet- bzw. K3s-Dienst neu starten:
# K3s
sudo systemctl restart k3s # Server-Node
sudo systemctl restart k3s-agent # Agent-Node
# Standard-Kubernetes
sudo systemctl restart kubelet
Optional: Regelmäßige Bereinigung per CronJob
Als zusätzliche Absicherung kann ein systemd-Timer oder Kubernetes-CronJob gestoppte Container und ungenutzte Images regelmäßig entfernen:
# /etc/cron.d/container-cleanup
0 3 * * * root crictl rm $(crictl ps -a -q --state exited) 2>/dev/null; crictl rmi --prune 2>/dev/null
Alternativ als Kubernetes DaemonSet, das auf allen Nodes läuft und regelmäßig aufräumt.
Zusammenfassung
| Schritt | Aktion | Befehl |
|---|---|---|
| Diagnose | Gestoppte Container auflisten | crictl ps -a --state exited |
| Diagnose | Image-Belegung prüfen | crictl imagefsinfo |
| Diagnose | Speicherverbrauch prüfen | du -sh /var/lib/containerd/* |
| Sofort | Container aufräumen | crictl rm $(crictl ps -a -q --state exited) |
| Sofort | Images aufräumen | crictl rmi --prune |
| Dauerhaft | Kubelet-GC konfigurieren | maximum-dead-containers-per-container=1 |
| Optional | Cron-basierte Bereinigung | Cron-Job oder DaemonSet einrichten |
Verwandte Themen
- Kubernetes Dokumentation: Garbage Collection
- Kubelet Konfiguration: Kubelet Configuration (v1beta1)