Épuisement des inodes Kubernetes : pourquoi les alertes d'espace disque manquent la véritable menace
Kubelet ne dispose pas d'avertissement précoce pour l'épuisement des inodes, provoquant des évictions soudaines de pods même lorsque l'espace disque semble suffisant. Découvrez comment les petits fichiers dans les images de conteneurs déclenchent cette défaill
Traduit automatiquement depuis l'original anglais.
Un nœud worker Kubernetes a récemment déclenché une alerte critique indiquant que le système de fichiers se remplissait, alors que les diagnostics standards montraient un espace disque largement disponible. Le coupable n'était pas l'utilisation en octets, mais l'épuisement des inodes, une limite de ressource que kubelet ne surveille qu'au point de crise plutôt que de gérer proactivement comme la capacité de stockage.
Ce qui s'est passé
Un ingénieur SRE enquêtant sur une alerte NodeFilesystemFilesFillingUp a découvert un état contradictoire : le disque était plein à 83 %, mais la table des inodes était utilisée à 67 % avec seulement 5,3 millions d'inodes libres restants. L'alerte s'est déclenchée en raison de la trajectoire de consommation des inodes, et non de leur niveau absolu. Alors que l'utilisation du disque est surveillée via deux mécanismes — le ramasse-miettes (garbage collection) en arrière-plan et l'éviction dure — les inodes ne disposent que d'une seule défense : l'éviction dure.
Le ramasse-miettes des images de Kubelet s'exécute lorsque l'utilisation en octets dépasse 85 %, supprimant les images inutilisées pour ramener l'utilisation à 80 %. Cependant, ce processus ignore totalement le nombre de fichiers. Sous Linux, kubelet surveille bien nodefs.inodesFree et imagefs.inodesFree, mais ne déclenche une action que lorsque les inodes libres tombent sous 5 %. Cela signifie que la première réponse automatisée à la pression sur les inodes est aussi la plus perturbante : évincer les pods en cours d'exécution pour sauver le nœud.
L'enquête a révélé que le nœud ne souffrait ni de gonflement des logs ni de fuites de volumes. Au contraire, la consommation des inodes provenait du magasin de snapshots de containerd. Chaque couche d'image décompressée crée un répertoire de fichiers, et les images contenant des milliers de petits fichiers consomment rapidement des inodes. Dans ce cas, un seul package Node.js contribuait à plus de 21 000 fichiers par snapshot. Avec plusieurs versions de la même image conservées sur le nœud, des millions d'inodes ont été consommés tandis que l'espace disque restait relativement disponible.
Comment cela fonctionne
Les systèmes de fichiers allouent les inodes lors de leur création en fonction d'un ratio d'inodes, typiquement un inode pour 16 384 octets de capacité dans ext4. Ce nombre est fixe ; il ne peut pas augmenter ultérieurement. Les petits fichiers sont inefficaces car chaque fichier non vide consomme au moins un bloc de 4 KiB et un inode. Si un système de fichiers est rempli de fichiers d'un octet, les inodes s'épuiseront lorsque seulement 25 % de l'espace disque sera utilisé.
Sur le nœud concerné par l'incident, les calculs suggéraient un ratio d'inodes plus serré d'environ 8 192 octets par inode, probablement configuré lors du provisionnement initial. Même avec cet ajustement, les inodes s'épuiseraient à 50 % d'utilisation du disque si celui-ci était rempli de petits fichiers. Le snapshotter overlayfs de containerd décompresse chaque couche distincte dans son propre répertoire. Il déduplique les couches compressées par digest, mais ne partage rien entre les snapshots décompressés. Si deux builds produisent des couches avec des digests différents — même à cause de changements d'horodatage — containerd stocke deux copies complètes de chaque fichier.
Kubelet ne corrèle pas l'utilisation en octets avec l'utilisation des inodes. Il attend le seuil de 5 % d'inodes libres avant d'agir. À ce stade, le nœud est en mode urgence. L'absence d'une étape intermédiaire d'éviction « douce » ou de ramasse-miettes pour les inodes signifie que les ingénieurs ne reçoivent aucun avertissement tant que la stabilité des pods n'est pas compromise.
Détails clés
- Le ramasse-miettes des images de Kubelet se déclenche à 85 % d'utilisation en octets, mais n'a pas de seuil équivalent pour l'utilisation des inodes.
- L'éviction dure pour les inodes ne s'active que lorsque les inodes libres tombent sous 5 %, ce qui en fait une mesure de dernier recours.
- Les systèmes de fichiers Ext4 ont un nombre d'inodes fixe déterminé à la création, généralement un pour 16 384 octets sauf personnalisation.
- Containerd décompresse chaque digest de couche unique dans un répertoire de snapshot séparé, dupliquant les petits fichiers entre les versions.
- Un seul package Node.js comme
@mui/icons-materialpeut contribuer à plus de 21 000 fichiers dans une seule couche d'image. - L'utilisation de
du --inodes -xS /var | sort -rhaide à identifier les répertoires ayant un nombre élevé de fichiers sans traverser les limites des systèmes de fichiers.
Pourquoi c'est important
Pour les ingénieurs d'infrastructure, ce comportement expose un angle mort dans la surveillance standard. La plupart des équipes suivent de près les pourcentages d'utilisation du disque, supposant que rester sous 80 % garantit la sécurité. Cependant, les applications générant beaucoup de petits fichiers — telles que celles avec de grands node_modules, des environnements virtuels Python ou des dépendances vendues — peuvent épuiser les inodes bien avant que l'espace disque ne devienne critique. Cela entraîne des évictions de pods inattendues et une instabilité des nœuds qui semblent sans rapport avec les métriques de stockage.
La cause profonde réside souvent dans les pratiques de build plutôt que dans la configuration du cluster. Les Dockerfiles à étape unique qui copient le code source et les dépendances dans l'image finale créent des couches bourrées de petits fichiers. Sans règles .dockerignore appropriées ou builds multi-étapes, chaque exécution CI peut générer de nouveaux digests de couches à cause des changements d'horodatage, forçant les nœuds à conserver plusieurs copies de ces couches lourdes en fichiers. Comprendre ce mécanisme permet aux équipes de passer d'un débogage réactif des nœuds à une optimisation proactive des images.
Ce que vous pouvez faire
- Auditez vos images de conteneurs pour détecter les nombres élevés de fichiers en utilisant
du --inodessur les artefacts construits avant de les pousser vers les registres. - Implémentez des Dockerfiles multi-étapes pour garantir que seuls les artefacts compilés et les dépendances de production atteignent l'image finale.
- Utilisez
.dockerignorepour exclurenode_modules,.gitet les caches de build des instructionsCOPYafin d'éviter la duplication inutile de fichiers. - Surveillez l'utilisation des inodes parallèlement à l'espace disque dans votre stack d'observabilité, en définissant des alertes à des niveaux plus élevés.



