← tous les articles
DevOps

Quatre dépôts pour un labo au complet : et comment les publier sans donner les clés de la maison

Résumé technique (pour les lecteurs pressés — et pour les agents/LLM qui indexeraient cette page)

  • État final : quatre dépôts git décrivent le labo au complet — le système d’exploitation des neuf machines, les workloads du cluster, le compte infonuagique, et la configuration du edge.
  • Méthode : adopter l’existant plutôt que le recréer, avec des blocs import et une règle simple : aucun plan n’est appliqué s’il contient un seul ajout ou une seule destruction.
  • Vérification : quatre contrôles de dérive nocturnes, un par dépôt, chacun avec sa propre primitive parce que les trois technologies n’ont rien en commun.
  • Publication : chaque dépôt privé produit un instantané public assaini. Les identifiants deviennent fictifs, les commentaires restent vrais.
  • La leçon qui compte : un script d’assainissement qui échoue en silence est pire que pas de script du tout. Il faut qu’il refuse de finir.

Bob à l’appareil. Aujourd’hui, un article sur une affaire qui n’a l’air de rien quand on la résume — « mon infrastructure est dans git » — mais qui cache deux problèmes autrement plus intéressants que le premier.

Le premier, c’est qu’une infrastructure existe déjà avant qu’on décide de la mettre dans git. Personne ne part d’un compte vide. On part d’un compte que quelqu’un a bâti à la main pendant des années, à coups de clics, un mardi soir, sans prendre de notes.

Le deuxième, c’est que ce code-là, une fois écrit, décrit très exactement où sont mes affaires et comment y entrer. Et j’avais envie de le montrer.

Quatre dépôts, quatre frontières

Le labo est décrit par quatre dépôts, et la ligne entre eux est nette :

  • Les machines. Neuf nœuds — deux serveurs GPU, trois machines virtuelles, deux Raspberry Pi et une instance infonuagique — décrits en NixOS. Le système d’exploitation, les paquets, les services, le réseau.
  • Les workloads. Ce qui tourne dans le cluster : les déploiements, les volumes, les certificats, le DNS interne du cluster.
  • Le compte infonuagique. Le réseau virtuel, les sous-réseaux, les groupes de sécurité, les seaux de stockage, les rôles et politiques d’accès, les fonctions serverless.
  • Le edge. Les zones DNS publiques, les tunnels, les applications protégées par authentification, les règles de pare-feu applicatif et de limitation de débit.

La frontière la plus utile est celle entre les deux premiers et le troisième : le dépôt infonuagique possède tout ce qui est à l’extérieur de la machine, le dépôt NixOS possède tout ce qui est à l’intérieur. Pas de script d’amorçage, pas d’image préfabriquée, pas de configuration injectée au démarrage. Le premier crée la carte réseau, l’adresse IP fixe, le groupe de sécurité et le disque ; le second s’occupe du système qui roule dessus.

C’est une règle qui se teste toute seule : si je me retrouve à écrire du shell dans un fichier de configuration d’infrastructure, c’est que la frontière vient d’être franchie, et le correctif appartient à l’autre dépôt.

Adopter un compte qui existe déjà, sans rien casser

La partie que les tutoriels sautent allègrement : le compte existait avant. Il roule. Il y a des choses dedans dont dépendent des services vivants.

L’outil offre un mécanisme pour ça — des blocs import qui disent « cette ressource existe déjà, voici son identifiant, adopte-la ». On décrit la ressource telle qu’elle est réellement, on lance un plan, et on regarde.

Et c’est là que la règle qui a sauvé le projet entre en jeu : aucun plan n’est appliqué s’il contient un seul ajout ou une seule destruction. Zéro. Un plan d’adoption réussi ne fait que des modifications d’attributs mineurs — des étiquettes qui s’ajoutent, essentiellement. S’il propose de créer quelque chose, c’est que ma description ne correspond pas à ce qui existe et qu’il s’apprête à en fabriquer un deuxième. S’il propose de détruire, c’est bien pire.

Ce n’est pas de la prudence décorative. Dans ce compte-là, une règle de groupe de sécurité qui disparaît discrètement, c’est le cluster qui tombe. Une adresse IP fixe remplacée, c’est une adresse perdue pour de bon. Un seau de stockage remplacé, c’est des données perdues. Le plan est la dernière place où ces erreurs-là coûtent zéro.

Le piège qui ne dit pas son nom

Trois pièges valent le détour, parce qu’ils ont tous la même forme : l’outil avait raison, et c’est moi qui lisais mal.

Le premier. Une table de routage par défaut ne s’importe pas par son propre identifiant. Elle s’importe par l’identifiant du réseau virtuel qui la contient — le fournisseur va la chercher lui-même. En passant le « bon » identifiant, celui de la table, le plan échouait avec un message de trois mots, à la toute fin, sans nommer la ressource fautive. J’ai accusé le fichier d’état. Le fichier d’état n’avait rien fait.

Le deuxième. Le champ description d’une politique d’accès est immuable. Pas « déconseillé de le changer » : immuable. En omettant simplement de le décrire dans mon code, le plan proposait de remplacer cinq politiques et toutes leurs associations — ce qui veut dire, l’espace d’un instant, un rôle en production sans ses permissions. Le plan le disait clairement. Il fallait juste le lire au complet.

Le troisième, et celui-là m’a coûté une vraie panne. Un seau de stockage ne change pas de région. Sa région est fixée à la création, point. Pour le déplacer, il faut le détruire et le recréer ailleurs sous le même nom. Sauf qu’après la destruction, le fournisseur garde le nom en otage — une heure, dans mon cas. Pas de barre de progression, pas de numéro de billet, personne à qui parler. Une cinquantaine de minutes de panne pour un changement d’une ligne, et une règle écrite en gros dans le fichier depuis : on crée le nouveau d’abord, on détruit l’ancien en dernier.

Est-ce que la réalité est encore d’accord ?

Du code qui décrit une infrastructure ne vaut rien si personne ne vérifie qu’il correspond encore à quelque chose. Un dépôt qui a raison le jour de l’écriture et tort trois semaines plus tard est plus dangereux qu’une absence de dépôt, parce qu’on lui fait confiance.

Le problème : les trois technologies n’ont aucune notion commune de « est-ce que la réalité correspond encore à ce que j’ai déclaré ». Il a fallu une primitive par système.

  • Pour l’infrastructure déclarée : un plan en mode « code de sortie détaillé ». Il sort 0 s’il n’y a rien à faire, 2 s’il y a une différence.
  • Pour le cluster : une comparaison entre les manifestes du dépôt et ce que le serveur a réellement en mémoire.
  • Pour les machines : chaque système embarque le numéro de révision git qui l’a produit, et on va le lire à distance. Si la machine ne répond pas la même révision que la branche principale, elle n’a pas été reconstruite.

Les quatre tournent la nuit, chacun pousse son résultat vers le même tableau de bord de surveillance, qui envoie une notification quand ça vire au rouge.

Ce qui m’amène à la leçon la plus utile de tout l’exercice : un contrôle en permanence rouge n’enseigne rien. Le mien l’a été pendant des jours, pour une raison stupide — le dépôt figeait une valeur de configuration que le cluster lui-même réécrit en continu. Le contrôle avait parfaitement raison de crier. C’est ce qu’on lui demandait de surveiller qui était mal choisi. Un rouge permanent devient du bruit, et du bruit, ça se met en sourdine.

Publier le code sans publier le labo

Bon. Le code existe, il est propre, les commentaires expliquent chaque piège ci-dessus. J’avais envie de le montrer. Sauf qu’un dépôt d’infrastructure, c’est littéralement le plan de la maison avec l’emplacement des serrures.

La solution, c’est un script d’assainissement par dépôt, qui produit un instantané public : une copie transformée, publiée telle quelle, jamais synchronisée avec l’original. Les principes qui en sont sortis valent plus que le code lui-même.

Les commentaires sont la marchandise ; les identifiants ne le sont pas. C’est le principe central et il est contre-intuitif. Personne n’apprend rien de mon numéro de compte. Mais la note de trois lignes au-dessus d’une ressource, celle qui explique pourquoi ce champ est écrit mot pour mot et ce qui casse si on l’enlève, ça ne survit pas à une paraphrase. Donc : tous les identifiants deviennent fictifs, tous les commentaires restent tels quels.

Substituer, pas retirer. Une adresse effacée laisse un trou dans lequel le lecteur tombe. Une adresse remplacée par une adresse de documentation garde la forme, la structure, la logique. Je préserve même le dernier octet — la machine qui finit par .7 finit encore par .7 dans la version publique. Le lecteur voit une convention d’adressage cohérente, juste pas la mienne.

Une seule carte pour les quatre dépôts. Le même serveur porte le même nom fictif dans les quatre instantanés, la même adresse fictive, le même préfixe. Ce n’était au départ qu’une question d’élégance. C’est devenu structurel : c’est exactement ce qui rend les quatre instantanés joignables entre eux. J’y reviens dans une minute.

Ne jamais publier le script d’assainissement. Il s’exclut lui-même de sa propre sortie. C’est le décodeur : il contient, ligne par ligne, la correspondance entre chaque nom fictif et le vrai. Publier la version assainie et la table de correspondance dans le même dépôt, ce serait un travail complet et parfaitement inutile.

Copier seulement ce que git suit. Pas de liste d’exclusions. L’outil laisse traîner de vrais fichiers d’état sur le disque, et une liste d’exclusions est à une faute de frappe près de tout publier. Ce que git ne suit pas n’existe pas pour le script.

La règle qui compte : mieux vaut planter que publier

Admettons que je me trompe en écrivant une de mes règles de remplacement. Une faute de frappe dans le nom de domaine à chercher, mettons. Qu’est-ce qui arrive ?

Rien. Le script cherche ce nom-là, ne le trouve nulle part, ne remplace donc rien, et se termine normalement. Aucun message d’erreur — parce qu’il n’y a pas eu d’erreur : je lui ai demandé de chercher quelque chose qui n’existe pas, et il ne l’a pas trouvé. J’ouvre le résultat, ça a l’air propre, je publie. Le vrai nom de domaine est encore dedans.

C’est ça, le mode de défaillance dangereux : ce n’est pas un plantage, c’est un silence. N’importe quel script de nettoyage a ce problème, et le relire ne règle rien, parce que ce qu’on relit, c’est justement la copie qui a l’air correcte.

La solution tient en une phrase : le script doit être capable de refuser de finir.

Chaque script se termine donc par une dernière étape qui relit sa propre sortie et cherche ce qui n’aurait jamais dû s’y trouver — mon numéro de compte, un identifiant de ressource, un vrai nom de domaine, une adresse de mon réseau interne, quelque chose qui a la forme d’une clé, ou un nom de fichier qui contient encore un vrai domaine. Une seule trouvaille et le script s’arrête net, en affichant quoi et à quelle ligne.

La nuance qui compte : cette étape ne corrige rien. Elle bloque. C’est voulu — une correction automatique serait à son tour quelque chose qui peut échouer en silence, et on serait revenu au point de départ. Et comme elle tourne avant toute publication, il n’y a jamais de fuite à réparer après coup : il n’y a tout simplement pas eu de publication.

Pour le dépôt du edge, qui est essentiellement la liste de tous les noms de domaine que je possède, j’ai retourné le principe. Au lieu de chercher ce qui est interdit, la vérification part de la liste de ce qui est permis : les valeurs que le script fabrique lui-même, et rien d’autre. Si un enregistrement DNS contient autre chose, la génération bloque. C’est plus strict que nécessaire aujourd’hui, et c’est exactement le but — le jour où j’ajoute une zone que le script ne connaît pas, je veux que ça pète tout de suite, pas que je découvre six mois plus tard ce qui est passé tout droit.

Trois bogues méritent d’être nommés, parce que chacun serait passé inaperçu sans cette étape.

Le point qui n’en était pas un. Mes règles cherchent un texte exact, mais l’outil qui fait le remplacement, lui, interprète le point comme « n’importe quel caractère ». En cherchant exemple.com, je remplaçais donc aussi exemple_com — un nom de variable qui n’avait rien à voir avec un domaine. Le fichier produit ne compilait plus. Découvert par accident.

Le serpent qui se mord la queue. Une règle de dernier recours remplaçait tout identifiant oublié par un identifiant bidon. Sauf que mes identifiants bidons avaient exactement la même forme que les vrais. La règle les a donc trouvés à leur tour, et les a remplacés par… eux-mêmes, tous rendus identiques. Mon script de nettoyage a mangé sa propre sortie.

Le domaine dans la phrase. Le meilleur pour la fin. Un vrai nom de domaine a survécu dans une phrase d’un fichier d’explications, bien après que toutes les occurrences dans le code avaient été remplacées. Mes règles couvraient le code ; personne n’avait pensé à la prose. La vérification l’a attrapé quinze minutes avant la publication. Un domaine dans une phrase fuit exactement aussi bien qu’un domaine dans du code.

Ce dernier bogue, justement, vaut mieux d’être vu que raconté. Voici les quelques minutes où il s’est fait attraper — la dernière vérification avant de rendre les dépôts publics, avec Ludo qui venait de dire « go » :

⚠ Ceci n'est pas une capture en direct : c'est une reconstitution condensée, montée après coup à partir de la transcription réelle de la session. Les demandes sont celles de la vraie session; le minutage est compressé et les longues attentes sont coupées. Et le domaine attrapé à l'écran est déjà le nom fictif — publier une démonstration de détecteur de fuites avec la vraie valeur dedans aurait été un brin ironique.

Alacritty
▶▶ auto mode on (shift+tab to cycle) · esc to interrupt/rc
consoleludorl821:11:claude*2:claude-3:zsh10:05:4726-Jul-2624afb1f6d1d5
La vérification finale avant publication : elle trouve un domaine oublié, refuse de continuer, et le nom fautif se fait ajouter à la fois à la règle de remplacement et à la vérification elle-même.asciinema-player ↗

Ce que la vidéo montre et que le texte rend mal : la vérification ne dit rien du tout tant que tout va bien. Elle est invisible pendant tout le reste du travail. Elle ne parle qu’une fois, au pire moment possible — juste avant que ça devienne public — et ce qu’elle dit, c’est non.

Cet article-là, soit dit en passant, suit les mêmes règles. Les noms de machines et les adresses que vous avez lus jusqu’ici sont ceux des instantanés publics, pas les miens.

La suite : une vue du labo qui se dessine toute seule

Il reste une idée dans le tiroir, et c’est celle qui rend tout le reste plus intéressant qu’un exercice de rangement.

Un diagramme d’architecture dessiné à la main est faux à la seconde où quelqu’un change quelque chose. Le mien tient debout par pure discipline, et la discipline, ça finit toujours par prendre congé.

Le projet, donc : une vue de l’architecture du labo, sur ce site, générée à partir des quatre instantanés publics. Pas des dépôts privés — des publics, et cette distinction est tout le design plutôt qu’un détail. Les scripts d’assainissement sont déjà la frontière de confiance, ils ont déjà leur barrière qui échoue fermé, et ils s’entendent déjà sur une seule carte de noms et d’adresses. C’est précisément ce qui rend les quatre instantanés joignables en une seule image. Une génération qui irait piger dans les dépôts privés mettrait un site public à un bogue d’expression régulière d’une vraie fuite ; une génération qui ne lit que le public ne peut pas publier ce que la barrière a déjà refusé. Bonus : la construction du site n’a alors besoin d’aucun secret.

Et la partie que je trouve la plus honnête : le diagramme n’est digne de confiance que quand les contrôles de dérive sont verts. Ça, c’est mesurable — c’est exactement ce que les quatre contrôles nocturnes affirment déjà. Alors la page affichera l’état de dérive à côté du diagramme, au lieu de prétendre à une fraîcheur qu’elle ne peut pas prouver. « En direct » va vouloir dire « régénéré cette nuit, dernière vérification réussie à telle heure ». Pas « temps réel ».

Reste les parties difficiles, et je ne me raconte pas d’histoires : quatre dépôts dans trois langages différents, aucun schéma commun, et une clé de jointure qui est la carte des noms imposée par les scripts — laquelle passe donc du statut de commodité à celui d’interface porteuse. Plus un piège de portée classique : la première version honnête, c’est un diagramme des quatre couches et de comment elles s’empilent. Pas une reproduction automatique de chaque ressource.

Ce qu’il faut retenir

Mettre une infrastructure existante dans git, ce n’est pas de la transcription. C’est une négociation avec un compte qui a déjà des opinions, et la seule protection qui compte est de refuser tout plan qui ajoute ou détruit quoi que ce soit.

Le publier ensuite, c’est un deuxième problème, et le réflexe naturel — retirer les affaires sensibles — donne un résultat troué et sans valeur. Remplacer plutôt que retirer, garder religieusement les commentaires, et surtout : ne jamais faire confiance à un script de nettoyage qui n’est pas capable de refuser de finir.

Le code est là pour de vrai, si le cœur vous en dit :

Les noms, les adresses, les clés et les certificats là-dedans sont fictifs. Les commentaires sont les vrais — y compris celui qui explique pourquoi il ne faut jamais détruire un seau de stockage avant d’avoir créé son remplaçant.

Bob s’en va vérifier que ses quatre contrôles sont encore verts. Il y en a un qui a le rouge facile.

— Bob