Contexte : l’architecture de stockage deux niveaux (tiers critical/standard, replica, tagging de disques) est détaillée dans Stockage bloc distribué avec Longhorn.
Le symptôme : 12 GiB pour un pod qui “ne fait rien”
Le 25 avril 2026, un dashboard de monitoring affiche quelque chose d’anormal sur worker-1 : la RAM disponible est tombée à moins de 4 GiB sur 16 GiB. Rien d’inhabituel côté charge applicative. En creusant avec kubectl top pods -n longhorn-system, le coupable saute aux yeux immédiatement.
NAME CPU(cores) MEMORY(bytes)
instance-manager-e3a8b2c1d4f6... 12m 12426Mi
longhorn-manager-xk9p2 8m 87Mi
longhorn-csi-plugin-r4t7n 3m 42Mi
12,4 GiB dans un pod instance-manager. Un pod dont le rôle est de gérer les processus engine et replica de Longhorn sur ce nœud — rien qui justifie d’approcher la limite RAM du RPi 5. Le pod était en vie depuis 32 jours.
Le pod est redémarré manuellement, la mémoire retombe à une trentaine de mégaoctets. Incident clos… provisoirement.
Le 11 mai, même scénario : worker-1 de nouveau, 9,6 GiB après 15 jours. Le 17 mai, trois pods en même temps lors d’une vérification de routine :
| Node | RAM consommée | Uptime du pod | Charge |
|---|---|---|---|
| worker-1 | 3,0 Gi | 6 jours | répliques actives |
| master-3 | 2,4 Gi | 6 jours | engine vmsingle (I/O lourde) |
| worker-3 | 311 Mi | 70 jours | idle, zéro réplique |
Le dernier cas est révélateur : worker-3 n’héberge aucune réplique active, aucun trafic I/O — et il fuit quand même, juste plus lentement. Ce n’est pas une question de charge, c’est structurel.
Le contexte : qu’est-ce qu’un instance-manager ?
Longhorn est un système de stockage bloc distribué pour Kubernetes. Son architecture repose sur trois couches de processus qui interagissent ensemble pour présenter un PersistentVolume à un pod applicatif.
Le longhorn-manager tourne comme DaemonSet sur tous les nœuds. C’est le cerveau central : il orchestre la création des volumes, gère les StorageClasses, et surveille l’état global du cluster. Il n’accède jamais directement aux données.
Les processus engine et replica sont les vrais moteurs du stockage. Pour chaque volume monté :
- engine : expose le block device iSCSI/NVMe au pod applicatif, tourne sur le nœud où le pod est schedulé
- replica : écrit et lit les données réelles sur le disque local, un processus par nœud de réplication
Avec numberOfReplicas: 2, un volume a un engine et deux replica tournant sur deux nœuds différents. Les données sont synchronisées en temps réel entre les replica via le réseau.
Ces processus sont des binaires Go indépendants, mais ils ne tournent pas directement comme pods Kubernetes. Longhorn les pilote via un pod coordinateur par nœud : l’instance-manager.
L’instance-manager joue le rôle de supervisor local. Il démarre les processus engine et replica quand un volume doit être servi sur le nœud, surveille leur état de santé, et communique avec eux via gRPC pour remonter les métriques et les événements au longhorn-manager. Il est aussi responsable de leur redémarrage si l’un d’eux crashe.
Sur un cluster typique avec 15 volumes et 3 nœuds workers, chaque instance-manager gère donc potentiellement 10 à 30 processus enfants simultanément, avec un trafic gRPC constant.
Sur un homelab RPi hétérogène — 16 GiB sur worker-1 et worker-2, mais seulement 2 GiB sur worker-3 — RAM partagée entre le système, les pods applicatifs, et les processus Longhorn, un leak de plusieurs GiB dans ce seul pod coordinateur met directement en danger la stabilité du nœud entier. Le danger est proportionnellement pire sur worker-3, où quelques centaines de Mio suffisent déjà à peser lourd. Un OOM killer qui tue un pod applicatif est gênant. Un OOM killer qui tue des processus engine ou replica en cours d’écriture, c’est une corruption de données potentielle.
L’investigation : isoler la fuite
Face à une consommation mémoire anormale dans le namespace longhorn-system, il y a trois suspects naturels : le longhorn-manager lui-même, les processus engine/replica enfants, ou le binaire instance-manager. La méthode pour les distinguer est reproductible et vaut d’être documentée.
Étape 1 — Confirmer que c’est bien l’IM, pas les enfants
La première hypothèse naïve : ce sont les processus engine ou replica qui consomment. On entre dans le pod pour vérifier.
# Identifier le PID du processus instance-manager principal
kubectl -n longhorn-system exec -it instance-manager-e3a8b2c1d4f6 -- \
ps aux --sort=-%mem | head -20
PID USER %CPU %MEM VSZ RSS COMMAND
1 root 0.1 77.4 13107200 12654592 /usr/local/bin/longhorn-instance-manager
142 root 0.0 0.2 458240 32768 longhorn-engine --listen ...
143 root 0.0 0.1 368640 28672 longhorn-engine --listen ...
201 root 0.0 0.2 442368 30720 longhorn-replica --sync-agent ...
Clair : le binaire principal (longhorn-instance-manager, PID 1) absorbe toute la RAM. Les enfants engine et replica restent stables à ~30 MiB chacun.
Étape 2 — Distinguer RSS et VmData
RSS (Resident Set Size) peut inclure des pages partagées. Pour confirmer que c’est bien une fuite heap dans le binaire Go lui-même, on lit /proc/1/status directement :
kubectl -n longhorn-system exec -it instance-manager-e3a8b2c1d4f6 -- \
cat /proc/1/status | grep -E "VmRSS|VmData|VmHeap"
VmRSS: 12654592 kB
VmData: 12587008 kB
VmData représente le segment de données du processus — heap Go inclus. Avec 12,3 GiB là-dedans et seulement ~60 MiB d’autre chose, la conclusion est sans appel : le garbage collector Go ne récupère pas cette mémoire. C’est une fuite dans le binaire instance-manager lui-même.
Étape 3 — Chercher l’issue upstream
Le pattern est suffisamment spécifique (Go binary, gRPC, croît avec le temps et l’I/O) pour chercher directement dans le tracker Longhorn. La recherche instance-manager memory leak mène en quelques minutes à l’issue #12573 :
[BUG] Very high memory consumption in longhorn-instance-manager Severity: 1 — Regression introduced in v1.11.0
La cause confirmée : une régression dans la couche gRPC de l’instance-manager v1.11.0 provoque une accumulation de structures non libérées en heap Go. Le taux de fuite est proportionnel au volume de trafic gRPC (donc à l’activité I/O du nœud), mais présent même au repos — ce qui explique les 311 MiB sur worker-3 idle après 70 jours.
Le fix a été mergé et backporté en v1.11.1, puis intégré en v1.11.2.
Le contournement (historique, v1.11.0 uniquement)
Le workaround est chirurgical : supprimer le pod bloaté. Longhorn-manager le recrée en 15-30 secondes avec une mémoire fraîche. Les données sur disque ne bougent pas — le nouvel instance-manager remonte les répertoires replica existants depuis /var/lib/longhorn sans perte.
Mais supprimer un pod instance-manager n’est pas anodin. Il faut d’abord s’assurer que chaque volume hébergé sur ce nœud a bien au moins une réplique saine ailleurs.
Pre-flight checks obligatoires
# 1. Lister les engines actifs sur le nœud concerné
sudo kubectl -n longhorn-system get engines.longhorn.io \
-o custom-columns="NAME:.metadata.name,NODE:.spec.nodeID,STATE:.status.currentState" \
| grep worker-1
# Exemple de sortie attendue :
# pvc-abc123-e-0 worker-1 running
# pvc-def456-e-0 worker-1 running
# 2. Lister les répliques sur ce nœud
sudo kubectl -n longhorn-system get replicas.longhorn.io \
-o custom-columns="NAME:.metadata.name,VOL:.spec.volumeName,NODE:.spec.nodeID,STATE:.status.currentState" \
| grep worker-1
# Exemple :
# pvc-abc123-r-abc pvc-abc123 worker-1 running
# pvc-abc123-r-def pvc-abc123 worker-2 running ← réplique sur autre nœud OK
# pvc-def456-r-ghi pvc-def456 worker-1 running
# pvc-def456-r-jkl pvc-def456 worker-3 running ← réplique sur autre nœud OK
# 3. Pour chaque volume, confirmer ≥1 réplique healthy sur un autre nœud
sudo kubectl -n longhorn-system get volumes.longhorn.io pvc-abc123 \
-o jsonpath='{.status.robustness}'
# Attendu : healthy ou degraded (jamais faulted avant de continuer)
Le check essentiel : chaque volume doit avoir au moins une réplique en running sur un nœud autre que celui qu’on va redémarrer. Si un volume n’a qu’une seule réplique (sur ce nœud), le supprimer rendrait le volume indisponible — c’est la seule situation où il ne faut pas procéder.
L’action
# Identifier le nom exact du pod
sudo kubectl -n longhorn-system get pod -l app=longhorn-instance-manager \
-o wide | grep worker-1
# Supprimer avec grace period raisonnable
sudo kubectl -n longhorn-system delete pod instance-manager-e3a8b2c1d4f6 \
--grace-period=30
Ce qui se passe ensuite
t+0s Pod instance-manager-e3a8b2c1d4f6 : Terminating
t+5s Volumes concernés : transition vers Degraded (engine perd le gRPC local)
t+15s Pod instance-manager-f7c9d1e2b4a8 : Running (nouveau pod recréé)
t+25s Volumes : retour à Healthy (IM reconnecté aux répliques sur disque)
t+30s RAM instance-manager : ~25 Mi (idle)
Les applications ne voient rien pendant ce laps de temps : les I/O continuent via les répliques sur les autres nœuds. Le Longhorn CSI driver maintient le block device monté côté applicatif.
sur ~30 jours · heap Go jamais récupéré over ~30 days · Go heap never reclaimed
stables — données sur disque dans /var/lib/longhorn stable — data on disk in /var/lib/longhorn
t+0s
le pod instance-manager bloaté passe en Terminating the bloated instance-manager pod transitions to Terminating
t+5s
l’engine perd le gRPC local — I/O servies par les répliques distantes the engine loses its local gRPC — I/O served by the remote replicas
t+15s
longhorn-manager recrée l’instance-manager avec une mémoire fraîche longhorn-manager recreates the instance-manager with fresh memory
t+25s
l’IM se rattache aux répertoires replica déjà présents sur disque the IM reattaches to the replica directories already present on disk
t+30s
plan de contrôle stateless redémarré ; le plan de données n’a pas bougé stateless control plane restarted; the data plane hasn’t moved
/var/lib/longhorn et le nouvel instance-manager les remonte au démarrage.
Plan de contrôle stateless, plan de données intact.
Deleting the coordinator doesn't delete the replicas : the data lives in
/var/lib/longhorn and the new instance-manager remounts it at startup.
Stateless control plane, data plane untouched.
Quand ne pas appliquer le workaround
- Volume avec une seule réplique ET engine sur ce nœud → tolérance zéro, le volume devient indisponible
- Node en cours d’éviction Longhorn → attendre la fin avant d’intervenir
- Plusieurs nodes bloqués simultanément → traiter un nœud à la fois, vérifier le retour à
Healthyavant le suivant
Le fix permanent : upgrade vers v1.11.2
Le 18 mai 2026, upgrade du cluster via Helm :
# Vérifier la version actuelle
sudo helm list -n longhorn-system
# NAME NAMESPACE REVISION UPDATED STATUS CHART
# longhorn longhorn-system 7 2026-04-01 ... deployed longhorn-1.11.0
# Upgrade vers v1.11.2
sudo -E helm upgrade longhorn longhorn/longhorn \
-n longhorn-system \
--version 1.11.2 \
--reset-then-reuse-values \
--wait --timeout 15m
L’upgrade rolling remplace les pods instance-manager nœud par nœud. Après chaque remplacement, les volumes transitent brièvement par Degraded puis reviennent à Healthy — comportement attendu, identique au workaround manuel.
Ensuite, déclencher l’upgrade automatique des binaires engine (les processus enfants tournent encore avec le binaire v1.11.0 jusqu’à cette étape) :
sudo kubectl -n longhorn-system patch setting \
concurrent-automatic-engine-upgrade-per-node-limit \
--type=merge -p '{"value":"3"}'
Métriques post-upgrade (observées le 18 mai)
| Node | Rôle | RAM instance-manager |
|---|---|---|
| worker-3 | idle, zéro réplique | 11 Mi |
| worker-1 | 12 répliques actives | 250-330 Mi |
| worker-2 | 12 répliques actives | 260-310 Mi |
| master-1 | 1-2 engines | 13-43 Mi |
| master-3 | engine vmsingle (I/O lourde) | 43 Mi |
Le contraste est saisissant : worker-3 qui plafonnait à 311 MiB après 70 jours de leak lent est maintenant à 11 MiB stable. master-3 qui gérait l’engine vmsingle à 2,4 GiB est à 43 MiB.
Historique complet de l’incident
| Date | Action | Résultat |
|---|---|---|
| 2026-04-25 | Premier bloat documenté — worker-1 à 12,4 Gi après 32j — restart pod | Récupération de 12,2 Gi |
| 2026-05-11 | Récurrence — worker-1 à 9,6 Gi après 15j — restart pod | Récupération de ~9,5 Gi |
| 2026-05-17 | Cycle confirmé (6j après dernier fix) — worker-1 3 Gi, master-3 2,4 Gi, worker-3 311 Mi | Restart × 3 pods |
| 2026-05-18 | Upgrade v1.11.0 → v1.11.2 | Cause racine éliminée upstream |
Leçons retenues
1. Monitorer les pods système individuellement, pas seulement le nœud
kubectl top nodes montre la RAM totale du nœud — mais ne distingue pas un pod qui gonfle d’une charge applicative légitime. L’alerte qui aurait dû exister dès le premier jour :
# Alerte Prometheus à ajouter
- alert: LonghornInstanceManagerMemoryHigh
expr: |
container_memory_working_set_bytes{
namespace="longhorn-system",
pod=~"instance-manager-.*"
} > 2 * 1024 * 1024 * 1024
for: 10m
labels:
severity: warning
annotations:
summary: "Instance-manager {{ $labels.pod }} > 2 GiB RAM"
2. Sur un homelab, les Severity/1 latents restent longtemps invisibles
Dans un environnement de production managé, un Severity/1 déclenche des alertes, des war rooms, des patchs d’urgence dans les heures qui suivent. Sur un homelab, v1.11.0 a été installé et oublié. La fuite a mis 32 jours pour devenir suffisamment visible pour alerter — 32 jours pendant lesquels le nœud s’approchait silencieusement du seuil OOM.
Deux réflexes à prendre avant d’upgrader un composant de stockage :
- Consulter le CHANGELOG de la version cible et les issues ouvertes avec le label
kind/bugouseverity/1 - Traiter les releases
.0de composants critiques (stockage, réseau, PKI) avec la même prudence qu’une beta — attendre au minimum une.1ou quelques semaines de retour communautaire
Dans ce cas, v1.11.1 était disponible rapidement après le signalement de l’issue #12573. Rester sur v1.11.0 plus de quelques semaines après la sortie de v1.11.1 était évitable.
3. Les données survivent au restart de l’instance-manager — c’est du design
Le comportement le plus contre-intuitif de l’incident : supprimer le pod qui “gère” les répliques ne supprime pas les répliques. Les données sont sur le disque physique dans /var/lib/longhorn. L’instance-manager est un coordinateur stateless du point de vue des données — il sait où sont les répertoires replica sur le disque, il les remonte au démarrage, il ne les possède pas.
Cette séparation stricte entre le plan de contrôle (instance-manager) et le plan de données (fichiers sur disque) est un principe de design de Longhorn. Elle rend le workaround safe, et elle explique aussi pourquoi un crash OOM de l’instance-manager lui-même — sans kill des enfants — ne corrompt pas les données. Ce sont les processus engine et replica qui détiennent les verrous sur les blocs de données, pas leur superviseur.
4. Un runbook pré-écrit change tout
La différence entre “panique à 23h avec 500 MiB de RAM libre sur un worker” et “exécute la procédure en 3 minutes” tient à l’existence d’un runbook avec les pre-flight checks déjà rédigés. Le workaround lui-même prend moins d’une minute à exécuter. Ce qui prend du temps sans préparation, c’est la phase de décision : est-ce que c’est safe ? Est-ce que mes volumes ont bien des répliques ailleurs ? Est-ce que le timing est bon ?
Un runbook répond à ces questions avant qu’elles se posent en urgence. Il documente aussi les seuils de déclenchement (“à partir de combien de GiB on intervient ?”) et les critères d’échec (“si le volume ne revient pas à Healthy dans les 60 secondes, escalader vers X”). La prochaine personne à lire ce runbook — ou vous-même dans six mois — aura les mêmes garanties qu’aujourd’hui.
Références
- Issue upstream : longhorn/longhorn#12573 — Very high memory consumption in longhorn-instance-manager (Severity/1, v1.11.0)
- Fix backporté en v1.11.1, intégré en v1.11.2
- Runbook interne :
docs/runbooks/longhorn-instance-manager-bloat.md(marqué résolu au 2026-05-18)