La poubelle qu'on avait déjà sans le savoir
Comment on a évité une migration de 4 heures en admettant que le projet qu'on s'apprêtait à créer existait déjà — il fallait juste réparer sa description.
Le 15 mai 2026, j'ai voulu créer un nouveau projet dans l'écosystème : hub-shared. L'idée était simple — regrouper les briques mutualisées de la plateforme (postgres, redis, npm frontal, wireguard) dans un repo dédié, avec son propre chart Helm, son cycle de vie indépendant. Une « poubelle système » assumée, à côté des projets applicatifs.
Sauf qu'on a fini par ne rien créer. Et le problème a quand même été résolu. C'est l'histoire d'une refonte qui n'a jamais eu lieu — et de ce que ça apprend sur la valeur des descriptions.
Le piège invisible
Quand un service applicatif comme knowledge-api veut une base de données, il pointe sur postgres.semantic-hub.svc.cluster.local. Une URL. Une chaîne de connexion. Ce postgres tourne quelque part — mais où, exactement ? Dans l'écosystème home-ai, il vit dans un chart Helm appelé infra-shared, hébergé dans le repo hub-deploy.
hub-deploy, dans la description officielle du registre interne, c'est « le chef d'orchestre du cluster ». Un repo qui ne contient ni image, ni pod : juste versions.yaml (la liste des versions en prod) et une pipeline GitHub Actions qui applique des charts Helm. Du GitOps, au sens strict. Élégant.
Sauf que c'est faux. Ou plutôt : c'est partiel. hub-deploy contient AUSSI le chart infra-shared, qui héberge depuis le jour 1 : un Deployment postgres, des PersistentVolumeClaims partagés, du RBAC pour le ServiceAccount du dashboard, un ConfigMap d'initialisation de la base, et le namespace security où vit wireguard. Quatre services qui tournent en cluster, sous le pilotage d'un repo qui dit « je n'ai ni image ni pod ».
Pendant des mois, j'ai vécu avec cette description sans en interroger l'angle mort. Le code disait une chose, la fiche projet en disait une autre, et personne ne percevait la divergence — j'étais le seul lecteur.
La fausse bonne idée d'extraire
L'évidence apparente : il faut extraire ces briques. Créer un projet hub-shared avec son repo, son chart, ses propres helm releases. postgres devient le sien. Authentik-redis aussi. Les consommateurs pointeront vers eux explicitement. Architecture propre, séparation des concerns, chaque projet a une responsabilité unique.
J'ai commencé à planifier. Un nouveau repo hub-shared. Chart multi-deploy à la hub-zulip (qui contient déjà 5 deploys dans un chart). Migration de postgres avec downtime estimé à 30 secondes — le Service Kubernetes DNS reste, le PVC keep annoté survit au helm uninstall infra-shared. Plan en trois étapes : scaffold du repo + chart, fiche projet créée dans le registre, puis extraction du postgres en chirurgie contrôlée.
3 à 4 heures de travail effectif. Une migration réelle, avec un risque réel — chaque consumer (knowledge-api, MCP-Unified, authentik-server, authentik-worker) dépend de ce postgres. Un downtime, même bref, ça se planifie.
Et c'est là qu'est venu le déclic.
La question qui change tout
J'étais en train de lister les briques à extraire, à voix presque haute. postgres, mais où est-ce qu'il tourne déjà ? Dans infra-shared, hébergé par hub-deploy. Et le redis d'authentik ? Ah, là aussi. Et wireguard ? Là encore. Quatre briques, toutes au même endroit.
Et c'est venu d'un coup : mais en fait, c'est déjà là, ma poubelle. hub-deploy EST déjà cette poubelle système. Il l'est depuis le premier commit. La description disait juste autre chose.
Le réflexe avait été de faire matcher le code à la description (« hub-deploy est un orchestrateur pur, donc déplaçons ce qui n'est pas de l'orchestration ») au lieu de l'inverse (« hub-deploy fait deux choses, donc disons-le »). Le premier mouvement, c'est de la migration. Le second, c'est de l'honnêteté.
L'honnêteté est gratuite.
Le piège de la description-vérité
C'est un mécanisme insidieux. La description officielle d'un projet — celle qu'on lit en passant, celle qui apparaît dans la liste, celle qu'on cite dans une réunion — finit par avoir plus de poids que la réalité du code. On la lit. Le code, on l'ouvre quand on doit. La description installe un modèle mental qui s'inscrit en dur ; le code, lui, se modifie chaque jour, par petites couches dont aucune ne déclenche de relecture de la description.
Tant que la divergence reste petite, personne ne la perçoit. Et tant que personne ne la perçoit, elle continue de grandir. Un jour, on veut « faire bien » — et on découvre qu'on s'apprête à refaire ce qui existe déjà sous un autre nom.
Le bug n'est pas dans le code. Le bug est dans la fiche.
L'admission comme refactoring
Le « refactoring » qu'on a effectivement fait, c'est :
- Une mise à jour de la description du projet hub-deploy dans le registre, pour évoquer explicitement la double identité : « GitOps + briques mutualisées de la plateforme ».
- Une mise à jour du rôle : de « GitOps — déploiements k3s & promotion des versions » à « GitOps + briques mutualisées — promotion versions & infra partagée ».
- Cinq entrées
ProjectServiceajoutées sous hub-deploy, marquéesis_shared=true, pour rendre les briques (postgres, authentik-redis, dashboard-redis, npm-frontal, wireguard) visibles dans le registre. - Neuf dépendances câblées via une API REST, depuis chaque consumer vers la brique qu'il consomme.
Vingt minutes. Zéro downtime. Zéro repo créé. Zéro chart à scaffolder.
L'inventaire technique du cluster, lui, n'a pas bougé d'un octet. C'est uniquement la couche de représentation qui s'est mise en phase avec la couche d'exécution.
Le graphe inversé
L'effet de bord positif est arrivé en cascade. Avant, le registre interne avait une table project_service_dependencies (livrée la veille) — un graphe orienté qui modélisait « cette brique consomme cette autre brique ». Mais cette table était vide : aucune brique mutualisée n'était déclarée comme ProjectService, donc aucune cible à pointer.
Une fois les cinq briques de hub-deploy entrées dans le registre, le graphe a pris vie d'un coup. Le dashboard affiche désormais, sur la fiche authentik-server, deux « briques virtuelles » côte à côte avec ses briques réelles : postgres @hub-deploy et authentik-redis @hub-deploy. Cliquables, lien direct vers la fiche productrice. La visualisation graphique du graphe (/api/projects/dependencies/graph) montre 9 arêtes là où elle en montrait 0 deux heures plus tôt.
Et l'effet inattendu : le bloc dependencies: dans versions.yaml (maintenu à la main depuis des mois, avec des noms qui ne matchaient même pas les org_slug du registre) peut maintenant disparaître. Le dashboard consommera le graphe en BD, qui est nominatif et cohérent.
Quand l'extraction réelle redevient pertinente
Cette histoire ne dit pas qu'il ne faut jamais extraire. Elle dit qu'il faut une raison concrète. Le test, posé honnêtement, c'est : « ai-je une contrainte technique qui m'oblige à extraire maintenant ? »
Pour les briques mutualisées dans hub-deploy : non. Aucun consumer ne bloque. Aucune limite de scale. Aucune mutation de hub-deploy en vue. La double identité ne coûte rien — juste une description plus longue dans la fiche.
Si demain hub-deploy doit muter — devenir un autre service, être déprécié, fusionner avec autre chose — alors et seulement alors on extraira. Pas avant. Pas par esthétique.
Ce que ça a appris
J'ai trois prises de conscience à garder.
La description-vérité est un risque architectural. Une description fausse ne déclenche pas d'alerte parce qu'elle ne casse rien — elle juste désaligne ce que les gens lisent de ce qui tourne. Et ce désalignement génère du faux travail.
La poubelle assumée est un design pattern. Concentrer les briques sans propriétaire applicatif sous une fiche dédiée, en assumant le mot, c'est un statut. Le contraire (les laisser éparpillées comme « détails d'infra » dans le projet d'à côté) est un anti-pattern qui contraint les consumers sans le dire.
Le « refactoring » le moins cher est souvent l'admission. Le code qui marche, on ne le touche pas. La description qui ment, on la corrige.
La trame complète de cette session est documentée dans l'ADR 0002 du projet hub-projects. Le câblage des cinq briques et de leurs dépendances est public dans l'API du dashboard.
Voir aussi