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