Le 2026-08-25, l'opérateur a cessé de réconcilier tous les FerrVaultSecret du cluster FerrLabs pendant plus de quatre heures. Aucun indicateur n'a bougé. La panne a été trouvée par hasard, en cherchant pourquoi une clé ajoutée dans un coffre n'arrivait pas.
La cause immédiate est corrigée par #240. Cette issue porte sur ce qui a rendu la panne invisible, et qui reste vrai après ce correctif.
Ce qu'on voyait pendant la panne
| Signal |
Valeur |
Vérité |
| Pod |
Running, Ready |
figé |
| CPU |
4m |
rien ne tourne |
| Sonde de vivacité |
verte |
le processus vit, mais ne travaille plus |
Conditions des FerrVaultSecret |
Ready=True |
dernier état connu, jamais réévalué |
status.lastSyncedAt |
11:12:57 |
dernière fois où le CONTENU a changé |
| Journaux |
erreurs de watch toutes les 30 s |
seul signal réel, noyé |
Aucun de ces signaux ne distingue « l'opérateur va bien et rien n'a changé » de « l'opérateur ne réconcilie plus rien ».
Les deux défauts, séparés
1. lastSyncedAt répond à la mauvaise question. Il n'est mis à jour qu'au changement de contenu. Un secret stable et un secret dont la synchro est morte présentent donc le même champ, figé au même endroit. Pendant le diagnostic, ce champ m'a fait conclure deux fois de suite, à tort, que la propagation fonctionnait.
Il manque un lastAttemptedAt (ou équivalent), écrit à chaque réconciliation aboutie, qu'elle ait changé quelque chose ou non. C'est ce champ, comparé à spec.refreshInterval, qui permet de dire « cette ressource aurait dû être revue il y a dix minutes ».
2. La sonde de vivacité ne teste que le processus. healthz répond tant que le binaire tourne. Or le mode de défaillance observé est précisément un binaire vivant dont la file de reconcile est bloquée. Une sonde qui échouerait quand aucune réconciliation n'a abouti depuis N fois l'intervalle le plus court aurait fait redémarrer le pod en quelques minutes, au lieu de quatre heures d'arrêt silencieux.
Pistes, à arbitrer
lastAttemptedAt dans le status, et une condition qui passe à False quand l'écart avec now dépasse un multiple de refreshInterval. C'est le minimum, et ça rend la panne visible dans un simple kubectl get.
- Une métrique
ferrvault_last_successful_reconcile_timestamp_seconds, par ressource ou globale. Le ServiceMonitor existe déjà dans le chart, donc l'alerte Prometheus suit naturellement.
- Une sonde de vivacité qui a un avis, adossée à cette même horloge, pour que le cas « bloqué » se répare tout seul.
- Un délai d'expiration sur le reconcile, pour qu'aucun appel ne puisse bloquer la file indéfiniment. C'est la protection générique : elle aurait couvert ce bug sans rien savoir des informers, et couvrira le prochain.
Ma préférence va au premier et au dernier : l'un rend la panne lisible, l'autre l'empêche de durer. Les deux autres sont utiles mais viennent après.
Critère d'acceptation
Reproduire le scénario — retirer list/watch sur apps et provoquer un changement de contenu sur un FerrVaultSecret déclarant un rolloutRestart — et vérifier que la panne devient visible sans lire les journaux, en moins de dix minutes.
Contexte
Le 2026-08-25, l'opérateur a cessé de réconcilier tous les
FerrVaultSecretdu cluster FerrLabs pendant plus de quatre heures. Aucun indicateur n'a bougé. La panne a été trouvée par hasard, en cherchant pourquoi une clé ajoutée dans un coffre n'arrivait pas.La cause immédiate est corrigée par #240. Cette issue porte sur ce qui a rendu la panne invisible, et qui reste vrai après ce correctif.
Ce qu'on voyait pendant la panne
Running,ReadyFerrVaultSecretReady=Truestatus.lastSyncedAtAucun de ces signaux ne distingue « l'opérateur va bien et rien n'a changé » de « l'opérateur ne réconcilie plus rien ».
Les deux défauts, séparés
1.
lastSyncedAtrépond à la mauvaise question. Il n'est mis à jour qu'au changement de contenu. Un secret stable et un secret dont la synchro est morte présentent donc le même champ, figé au même endroit. Pendant le diagnostic, ce champ m'a fait conclure deux fois de suite, à tort, que la propagation fonctionnait.Il manque un
lastAttemptedAt(ou équivalent), écrit à chaque réconciliation aboutie, qu'elle ait changé quelque chose ou non. C'est ce champ, comparé àspec.refreshInterval, qui permet de dire « cette ressource aurait dû être revue il y a dix minutes ».2. La sonde de vivacité ne teste que le processus.
healthzrépond tant que le binaire tourne. Or le mode de défaillance observé est précisément un binaire vivant dont la file de reconcile est bloquée. Une sonde qui échouerait quand aucune réconciliation n'a abouti depuis N fois l'intervalle le plus court aurait fait redémarrer le pod en quelques minutes, au lieu de quatre heures d'arrêt silencieux.Pistes, à arbitrer
lastAttemptedAtdans le status, et une condition qui passe àFalsequand l'écart avecnowdépasse un multiple derefreshInterval. C'est le minimum, et ça rend la panne visible dans un simplekubectl get.ferrvault_last_successful_reconcile_timestamp_seconds, par ressource ou globale. LeServiceMonitorexiste déjà dans le chart, donc l'alerte Prometheus suit naturellement.Ma préférence va au premier et au dernier : l'un rend la panne lisible, l'autre l'empêche de durer. Les deux autres sont utiles mais viennent après.
Critère d'acceptation
Reproduire le scénario — retirer
list/watchsurappset provoquer un changement de contenu sur unFerrVaultSecretdéclarant unrolloutRestart— et vérifier que la panne devient visible sans lire les journaux, en moins de dix minutes.Contexte