Set-OPS-Public/docs/decisions-architecture.md
Daniel Allaire 199783f5fd D-80 : un tenant est agnostique de son underlay, a trois cles pres
Formulation de l'exploitant, meilleure que celle du depot. Le commentaire disait
« la fabric reste celle de l'hebergeur, quel que soit le tenant actif » — vrai,
mais centre sur l'HEBERGEUR. Le cadrage juste est centre sur le TENANT : les deux
symlinks composent deux axes INDEPENDANTS (instance = quel tenant, underlay.yml =
sur quelle fabric).

Tout l'adressage derive du seed index : le plan se deplace d'une fabric a l'autre
sans y toucher. Ce qui ne se deplace pas tient en TROIS CLES —
proxmox_clone_noeud, _stockage, _pont. Elles vivent cote tenant (c'est lui qui
choisit ou se poser) mais nomment des objets de l'hebergeur. Trois, pas trente :
c'est ce qui separe « portable » de « theoriquement portable ».

P37 confronte ces trois noms aux listes de proxmox-hebergeur.yml, trouve par
derivation du symlink underlay.yml. Un nom absent est un ecart STATIQUE (D-75) —
sans quoi une faute de frappe ne se decouvre qu'au premier clone, apres quarante
minutes. Eprouvee en negatif sur les trois cles.

Non verifie et dit comme tel : le gabarit (_vmid_modele) est un objet du cluster,
pas une liste declaree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 09:47:38 -04:00

35 KiB
Raw Blame History

Registre des décisions d'architecture

Pour qui : le mainteneur — pourquoi les choses sont ainsi, et ce qui a été écarté.

À quoi sert ce document. Les décisions sont écrites là où elles s'appliquent — frontiere-opnsense.md, sdn-evpn.md, migration-tenant.md, underlay.yml.example — et leur histoire vit dans le CHANGELOG. Ce registre ne les répète pas : il dit quelles décisions existent, pourquoi, et où lire le détail. Sans lui, « pourquoi le /29 et pas le /30 ? » demande de relire vingt entrées de journal.

Ce n'est pas la doctrine de conduite (AGENTS.md §Principes), ni le registre des affirmations prouvables (docs/audit/affirmations.md). Une décision peut n'être gardée par aucune preuve : elle reste une décision.

Qui décide. Toutes ces décisions sont celles de l'opérateur du dépôt. Plusieurs ont été prises sur recommandation — l'assistance propose et argumente, elle ne tranche pas. La distinction compte pour la suite : une décision se renverse par celui qui l'a prise, et savoir qu'elle a été choisie plutôt que héritée change ce qu'on s'autorise à en faire.

Les dates de la §5 sont celles de l'historique git, pas du moment de la conversation : ce sont les seules vérifiables.


1. Le réseau

# Décision Pourquoi Détail Garde
D-01 OPNsense est une frontière nord/sud, pas la passerelle des zones le routage inter-zone reste au débit ligne ; la bordure ne voit pas l'est-ouest frontiere-opnsense.md §1
D-02 Le lien de transit vit dans l'underlay, pas dans un tenant la frontière route vers tous les tenants par le même saut : il ne peut dériver d'aucun index frontiere-opnsense.md §6 P23
D-04 Un point de routage porte le même dernier octet sur tous ses sous-réseaux — dans l'adressage dérivé des tenants seulement on retient une adresse, pas treize. Dans l'underlay, l'adressage est dicté par les participants d'un lien physique : la règle y imposait un seuil arbitraire et empruntait sa valeur à la nomenclature d'un tenant frontiere-opnsense.md §4 P20
D-52 Dans l'underlay, une passerelle doit être l'adresse d'un hôte déclaré sur ce réseau remplace le comptage d'octets par une règle plus forte : elle attrape les passerelles fantômes underlay.py P23
D-49 Aucun commutateur ne route. La frontière est le seul équipement L3 l'EVPN a retiré les VLAN tenants du fil, puis la fusion du lien de sortie a rendu les nœuds de sortie adjacents à la frontière : plus un seul flux à router underlay.yml P23
D-50 passerelle = la passerelle du sous-réseau, où qu'elle vive ; le SVI se dérive du rôle de son porteur la clé signifiait « adresse du SVI du switch » — une hypothèse, pas une donnée. Un même moteur sert désormais les deux postures devis_reseau.py P23
D-51 Deux commutateurs en paire, pas trois en étoile le centre d'une étoile est sur tous les chemins : point de panne unique du plan de données. Deux liens vers deux commutateurs donnent la redondance qu'une étoile ne peut pas donner underlay.yml
D-06 Les réseaux déclarent leur fabric ; un devis ne parle que de la sienne le stockage jumbo vit sur ses propres commutateurs — un devis est une configuration, pas un inventaire sdn-evpn.md §4 P23
D-07 Pas d'ACL sur cette fabric (acl_inter_tenant: false) le matériel ne sait pas lier une ACL à un SVI ; des règles jamais liées auraient l'air d'isoler frontiere-opnsense.md §1
D-43 Les objets SDN d'un tenant (zone, VNets, sous-réseaux) sont dérivés du seed et émis par make devis-sdn ajouter un tenant implique 13 objets de cluster ; les créer à la main dans un dépôt où tout dérive était la dernière lacune sdn-evpn.md §2 P30
D-44 Le nommage SDN dérive du tenant : zone <PRÉFIXE><index>, VNet <préfixe><index><zone> se lit sans table de correspondance ; les zones faites à la main (VRF0017) sont remplacées tant qu'elles sont vides. Seule contrainte conservée : 8 caractères, imposée par Proxmox sdn-evpn.md §2 P30
D-08 Le routage tenant passe au SDN EVPN — une zone par tenant une zone EVPN est un VRF : l'isolation devient structurelle, pas réglementaire sdn-evpn.md P23 (MTU)
D-09 Le filtrage inter-zone d'un tenant se fait au même niveau (EVPN) là où le routage a lieu sdn-evpn.md §3
D-31 Le filtrage est-ouest est appliqué deux fois : hyperviseur puis nftables d'hôte défense en profondeur ; les deux dérivent du même registre par les mêmes fonctions, donc ne peuvent pas diverger sdn-evpn.md §3 P25
D-32 Tout ce qui entre ou sort d'un tenant passe par la frontière un VRF n'a qu'une sortie ; conséquence : la frontière devient un prérequis de déploiement sdn-evpn.md §3
D-10 L'inter-tenant passe obligatoirement par la frontière il sort du VRF, donc traverse une bordure en block par défaut : il ne peut plus être oublié sdn-evpn.md §3
D-11 La sortie générale est déclarée dans le registre des flux block out est un vrai default-deny ; un besoin oublié ne se manifeste pas par un refus clair frontiere-opnsense.md §8 P09
D-29 L'overlay EVPN plafonne à 1450 ; le transport doit donc dépasser 1500 choix d'exploitation ; l'encapsulation VXLAN coûte 50 octets sdn-evpn.md §5 P23 (MTU du transport)
D-30 L'ICMP « fragmentation nécessaire » est déclaré, dans les deux sens à 1450, tout ce qui traverse la frontière dépend de la découverte de MTU de chemin ; une bordure en default-deny la casse en silence roles/serveur_debian/meta/flux.yml P09
D-64 La politique DROP du pare-feu Proxmox se pose sur chaque VM tenant, jamais au datacenter au datacenter, policy_in est la politique par défaut de toute VM dont le pare-feu s'active — un parc hérité sans règles tomberait d'un bloc. Par VM : même isolation, sans falaise, et le reste du cluster garde sa politique devis_proxmox_fw.py §0 P25
D-62 La sortie d'un VRF tenant emprunte un seul saut (nexthop-vrf default vers la frontière), jamais la table principale import vrf default ferait hériter au tenant le défaut de l'hyperviseur — sortie contournant la frontière — et lui donnerait au passage le transport VXLAN, la gestion et les VLAN hérités. Un saut emprunté n'est pas une table héritée devis_sdn.py §4 P30
D-63 Tout supernet routé doit être traduit : le NAT sortant est dérivé, pas laissé en « automatique » le mode automatique ne couvre que les réseaux directement attachés ; un supernet joint par route statique en sort sans avertissement. Panne muette : le filtre passe, un état s'ouvre, rien ne revient devis_opnsense.py §2bis P24
D-61 L'interface d'une règle se dérive de l'attachement réel de sa source, jamais du sens du flux une règle OPNsense est toujours in sur l'interface d'arrivée ; posée ailleurs elle ne peut jamais correspondre, et le devis affirme alors un accès qui n'existe pas frontiere-opnsense.md §7 P24
D-60 Nommage : tout l'ensemble de bordure est bifrost-N, et N est son dernier octet on retient un chiffre au lieu d'une table ; le nom ne dit plus le type de la machine, c'est le champ role qui le fait — et lui seul pilote le devis underlay.yml

2. Qui possède quoi

# Décision Pourquoi Détail Garde
D-13 Un hébergeur sert plusieurs tenants et a son tenant par défaut Chezlepro est les deux à la fois, ce qui masquait la distinction frontiere-opnsense.md §2
D-14 underlay.yml appartient à l'hébergeur, monté par symlink ce sont ses commutateurs, ses câbles ; le moteur est générique, un tenant n'en possède pas sdn-evpn.md, underlay.yml.example
D-15 Ce symlink ne suit pas make instance-utiliser basculer le tenant actif ne change pas la fabric frontiere-opnsense.md §2
D-16 Les intrants de la frontière se lisent chez l'hébergeur un hébergeur n'a qu'une frontière pour tous ses tenants frontiere-opnsense.md §2
D-17 L'hébergeur n'est pas déclaré : le symlink le désigne une seconde déclaration ouvrirait deux valeurs contradictoires frontiere-opnsense.md §2
D-35 Le cluster Proxmox appartient à l'hébergeur ; seuls le golden template et les défauts de placement restent au tenant recopié chez chaque tenant, l'inventaire du cluster avait déjà divergé — deux listes de stockages contradictoires pour le même matériel config-proxmox.md P27
D-36 Le panneau nomme le propriétaire de chaque section d'intrants éditer une section « hébergeur » vaut pour tous ses tenants ; l'écran ne le disait pas scripts/inventory_gui.py (INTRANTS_SCHEMA)
D-37 Chaque tenant a son pool Proxmox ; les noms courts de VM restent identiques d'un tenant à l'autre 11 serveurs sur 14 sont homonymes — c'est la preuve que la nomenclature est un gabarit ; le coût est humain (la console affiche le nom), et le pool le corrige sans rien renommer devis_proxmox_pools.py P28
D-45 L'affinité de VM (garder un groupe sur le même hyperviseur) attend Proxmox 9 ; tenue à la main d'ici là les resource affinity rules n'existent qu'en 9 ; en 8.4 seuls les groupes HA épinglent à des nœuds, pas des VM entre elles. Sans ressource HA déclarée, rien ne déplace ni ne sépare les VM — le sujet ne devient réel qu'en activant la HA sdn-evpn.md
D-46 Un hébergeur porte trois catégories, pas deux : son tenant, ses opérations, le plan de contrôle Chezlepro est hébergeur ET tenant, ce qui masquait des besoins n'appartenant à aucun tenant hebergeur-exploitation.md §2
D-47 Les services d'exploitation de l'hébergeur vivent dans son dépôt, et leurs VM se rattachent à un pont VLAN, jamais un VNet un service qui observe la fabric ne peut pas dépendre d'elle : l'EVPN tombe, et la supervision tombe avec la raison de la panne hebergeur-exploitation.md §1, §4
D-48 Les hyperviseurs sont gérables par Ansible ; « hors flotte » ne vaut que pour les commutateurs et la frontière ce sont des Debian joignables en SSH ; c'est la seule façon d'y poser un exportateur de métriques hebergeur-exploitation.md §5
D-55 Le dépôt réseau porte une interface normalisée vers les tenants de l'Alliance, et abstrait le matériel en les encapsulant dans des zones EVPN un tenant qui ne nomme aucun équipement se déplace d'un hébergeur à l'autre sans rien changer ; le VRF borne ce qu'il a le droit de connaître hebergeur-exploitation.md §7
D-56 Le VNet d'une VM est dérivé (index + zone), jamais déclaré ; l'étiquette VLAN est vide en SDN déclaré, il faisait naître les VM sur vmbr1 avec un tag — l'ancien monde, à rebrancher une par une instancier.py P02, P03
D-53 Le réseau et l'underlay de l'hébergeur méritent leur propre dépôt, séparé de son tenant underlay.yml et le cluster décrivent une infrastructure ; le dépôt de tenant décrit une organisation. Les mêler oblige à trancher qui possède quoi à chaque commit hebergeur-exploitation.md §7
D-54 10.0.0.0/24 est réservé à l'IPAM, la gestion des équipements et l'OOB — accès sysadmin aucune VM, aucun trafic tenant ; c'est la raison d'être des VLAN 11 et 40 underlay.yml
D-57 L'interface sysadmin d'un hyperviseur (vmbr0) n'a pas de route par défaut ; celle-ci vit sur vlan40, vers la frontière on n'atteint l'administration que depuis son propre domaine de diffusion — un accès distant doit être ouvert explicitement, il ne peut pas exister par accident. Et le trafic tenant ne touche plus la carte d'administration underlay.yml
D-58 Un hôte déclare par quelle interface (via) chaque réseau lui arrive ; le devis en dérive un port par interface et son type un hyperviseur a plusieurs pattes ; les grouper remettait la gestion sur le trunk du transport devis_reseau.py P23
D-59 Un VLAN qui ne porte que des adresses d'hôte n'a pas besoin de pont un pont sert à brancher des invités ; vide, il coûte une table MAC et un saut de plus sur le lien qui porte tout le trafic tenant underlay.yml
D-18 Chaque tenant a un responsable désigné sans lui, « qui peut décider de déménager cette organisation ? » se pose au pire moment migration-tenant.md §3

| D-38 | Toute authentification web passe par Keycloak ; LDAP est la source unique des comptes | une identité, un mot de passe ; aucun service ne tient son propre répertoire d'humains | authentification.md §1-2 | — | | D-39 | Les protocoles qui ne parlent pas OIDC (IMAP, SMTP) se lient directement à LDAP | le chemin varie, la source ne varie pas | authentification.md §2 | — | | D-40 | L'accès de secours passe par sudo sur l'hôte, pas par un compte web permanent | service → Keycloak → LDAP est en série : sans secours, une panne exclut tout le monde, y compris pour réparer | authentification.md §4 | — | | D-41 | Le formulaire de connexion locale n'est pas proposé au repos (<rôle>_connexion_locale: false) | il contourne la politique de mot de passe, le MFA et surtout la révocation centrale ; sudo est le mécanisme de réouverture | authentification.md §3 | — |

| D-65 | Les groupes LDAP portent l'autorisation ; Keycloak les projette, il ne les détient pas | Dovecot et Postfix ne savent pas lire un rôle Keycloak : l'y loger rendrait la moitié courriel aveugle et imposerait au sysadmin deux modèles de permissions — donc leur divergence. Un seul endroit à administrer | autorisation.md §4 | — | | D-66 | Un service nomme un groupe, jamais une personne | c'est ce qui rend la reprise possible : révoquer quelqu'un ne demande pas un déploiement. Nommer une personne crée une dette qu'on découvre le jour du départ, service par service | autorisation.md §5 | — | | D-67 | Les appartenances aux groupes ne sont jamais réconciliées : Set-OPS amorce un accès sysadmin, puis se retire | contredit délibérément la doctrine du dépôt, où un écart est un défaut. Ici l'écart est l'exploitation : réconcilier effacerait le compte créé la veille pour un nouvel employé. Idempotence par existence, pas par conformité | autorisation.md §2-3 | — | | D-42 | Chaque rôle déclare sa position d'authentification (meta/authentification.yml), gardée par une preuve | une directive qu'aucune garde ne vérifie finit par ne plus être vraie — c'était le cas des 28 lignes d'intégration recopiées | authentification.md §5 | P29 |

3. Les secrets

# Décision Pourquoi Détail Garde
D-19 Une seule voûte par instance (group_vars/all/vault.yml) un mot de passe, un endroit config-proxmox.md P18
D-20 La liste des secrets se recense, elle ne s'écrit pas trois copies manuelles ont existé, toutes ont divergé intrants-communs.md §H P18
D-21 L'empreinte du root CA n'est pas un secret : elle se dérive à chaud un from-zero régénère l'AC ; figée en voûte, elle serait périmée roles/client_pki/README.md
D-22 Migrer un tenant, c'est révoquer, pas transmettre sinon l'ancien hébergeur garde à vie l'accès aux secrets d'un client parti migration-tenant.md §6

4. La méthode

# Décision Pourquoi Détail Garde
D-23 Les cibles hors flotte reçoivent un devis, pas un rôle commutateurs, frontière et SDN sont des objets de cluster ; un rôle boucle sur des hôtes sdn-evpn.md §7
D-24 Un devis n'écrit rien : il se relit, puis s'applique proportionné au risque — une config ratée partitionne tout un cluster frontiere-opnsense.md §7
D-25 Le dépôt n'affirme pas que ses devis s'appliquent, il affirme qu'ils dérivent leur syntaxe dépend d'un matériel que le dépôt ne possède pas audit/affirmations.md §10
D-26 Un modèle peut porter un underlay ; tous les hébergeurs n'ont pas le même matériel générique public, étoffés en privé exemples/modeles/socle/README.md P17
D-27 Migration : le receveur est prouvé prêt avant tout gel l'interruption se réduit au delta et à la propagation DNS migration-tenant.md §4
D-28 Le mandat de migration est signé par le tenant, pas convenu entre hébergeurs il n'y a pas de registre central pour arbitrer ; une organisation n'est pas la propriété de son hébergeur migration-tenant.md §2
D-33 Une intégration universelle est déclarée par le rôle, jamais recopiée par serveur 28 des 57 lignes du plan disaient oui à ce qui vaut pour tous : elles n'existaient que pour être oubliées — et quatre l'avaient été integrations-vm.md §Politique P26
D-34 Une exemption se dérive du service rendu (sauf_role), jamais d'un nom d'hôte l'AC ne s'enrôle pas auprès d'elle-même ; l'exemption doit suivre step-ca si on le déplace roles/client_pki/meta/integration.yml P26
D-68 On écrit, puis on relit et on compare — quelle que soit l'interface ; on choisit celle dont le chemin de lecture parle le même langage que le chemin d'écriture « toujours préférer l'API » n'aurait prédit aucune des pannes du 2026-08-08 : sur six familles de défauts, deux venaient d'un CLI, une d'un module Ansible (ldap_entry crée sans jamais modifier), une d'un grep de fichier, une de la précédence Ansible, une de mon comparateur. Le facteur commun est d'avoir écrit sans relire. Et la plupart de la flotte n'a pas d'API — Postfix, Dovecot, nginx, slapd, nftables : postconf -h / postconf -e sont symétriques, c'est tout ce qu'on demande devis-services.md les 5 devis
D-69 Sur Keycloak : l'API pour toute map ou collection (smtpServer, attributes, config), kcadm pour les scalaires et les créations kcadm -s sur une map accepte la commande, sort en succès et n'écrit rien — mesuré deux fois le 2026-08-08 (smtpServer resté vide après deux déploiements verts, puis post.logout.redirect.uris). Le CLI reste préféré ailleurs : c'est le vocabulaire de la documentation du produit, donc lisible sans IA roles/serveur_keycloak/tasks/ make identite-plan
D-72 Un assert de rôle est un contrat d'intrant, et l'instance doit l'honorer — vérifié hors ligne la première reconstruction from-zero s'est arrêtée sur amorcage_acces_courriel : obligatoire depuis le matin, déclaré par aucun tenant, et invisible parce que le compte existait déjà — la garde n'avait jamais eu l'occasion de se déclencher. Un intrant est satisfait par un défaut non vide, un set_fact de résolveur, ou une déclaration de l'inventaire (fichier hosts.yml compris) scripts/verifier_intrants.py, make intrants-verifier P32
D-73 Tout port lié se déclare, et deux rôles co-localisés ne peuvent pas revendiquer le même un port n'appartient à personne : le premier démarré le prend, l'autre échoue — parfois en silence. Sur infra-mail-01, le SASL de Dovecot et l'interface d'Alloy se disputaient le 12345 depuis le premier jour, et c'est Dovecot qui perdait sans que rien ne le dise. Le contrôle n'était possible qu'une fois le port d'Alloy — un défaut amont subi — déclaré. partage: true distingue « j'ouvre cette écoute » de « je décris celle d'un autre » scripts/verifier_ports.py, make ports-verifier P33
D-70 La documentation dit et explique tout ce que le dépôt fait — et l'exigence est outillée, pas seulement énoncée une exigence qu'on n'outille pas pourrit en silence : la carte annonçait « 28 décisions » quand il y en avait 66, et disait les accès « non construits » alors qu'ils tournaient en production. P31 garde le couvert — chaque script s'explique et reste atteignable, chaque cible make porte son aide (sauf les internes préfixées _), chaque rôle a son README. Elle ne garde pas la qualité du « pourquoi » : ça se juge en revue, et ça vit dans CHANGELOG.md et ici devis-services.md, CHANGELOG.md P31
D-75 Un ecart lisible dans le plan se prouve dans le plan, pas au deploiement l'assert de resoudre_base fait son travail — mais il s'est declenche a la 92e tache de collab-01, apres quarante minutes, pour une application declaree sans base au registre. L'information etait entierement statique. P35 derive les roles qui exigent une base (ceux qui incluent resoudre_base) et le groupe qu'ils reclament (le DEFAUT de la variable passee, jamais leur nom : serveur_icingaweb2 reclame la base d'serveur_icinga). Meme raisonnement que D-72/P32 pour les intrants : une garde de deploiement gagne a etre doublee d'une preuve statique scripts/prouver.py, plan/bases-donnees.yml P35
D-74 Chaque document déclare son lecteur en tête — un lecteur et sa situation, pas une catégorie de sujet la documentation était rangée par SUJET, ce qui est juste pour de la référence — mais personne n'arrive avec un sujet, on arrive avec une situation. Symptôme exact : autorisation.md portait le runbook de reprise le plus utile du dépôt, enfoui au §6, parce que son sujet est l'autorisation ; personne n'allait l'y chercher. Un document qui déclare son lecteur se range tout seul, et un intrus s'y voit. Deux exemptions, dérivées et non listées : un document qui s'annonce généré, et un fragment sans titre README.md, wiki/Reprendre-l-écosystème.md, docs/carte-set-ops.md P34
D-71 Une PKI et un DNS fonctionnels avant toute chose ; puis, par VM : socle → enrôlement PKI → enregistrement DNS (A et PTR) deployer-tout déroule par COUCHES — correct, mais chaque VM réclame alors un certificat à une autorité pas encore debout, et l'échec se lit comme un défaut du rôle et non d'ordre. Les deux hôtes d'amorçage se dérivent de applications.<step_ca|powerdns>.hote : déplacer l'autorité déplace l'amorçage. Deux exceptions structurelles assumées — l'AC s'auto-signe, le DNS pose son propre enregistrement Makefile _amorcer-socle, scripts/socle_amorcage.py
D-76 Le gabarit doit se FABRIQUER par le dépôt, pas se façonner à la main — décidée le 2026-08-12, volontairement différée après Technolibre tout dérive d'un plan et se prouve ; le gabarit est la seule pièce faite à la main — 853 lignes de procédure manuelle — et il est en amont des quatorze VM. Le 2026-08-09 l'a démontré : l'ancien portait une clé privée d'hôte SSH et un /etc/resolv.conf figé, recopiés dans chaque clone, et il a fallu le recapturer. La preuve de reconstruction s'arrête donc un cran trop tôt : on reconstruit la flotte, pas ce dont elle est clonée. Cible : image cloud Debian officielle → signature vérifiée contre une empreinte épinglée (verifier_signature.py, déjà écrit) → import, réglages, conversion. Coût honnête : la recette doit être COMPARÉE au gabarit courant avant bascule — un réglage oublié serait hérité par les quatorze VM, et découvert loin de sa cause. En attendant, un gabarit se transporte d'un cluster à l'autre par vzdump / qmrestore docs/procedure-template-debian13-proxmox.md, scripts/verifier_signature.py (à construire)

| D-77 | L'underlay d'un site dérive du même index que son tenant, dans la bande basse 10.<index>.015.x ; les réseaux de stockage en sortent et sont identiques partout, en 192.168.<vlan>.0/24 | tant qu'il n'existait qu'un site, 10.0.x.x suffisait. Deux sites qui doivent se joindre — reprise mutuelle, sauvegardes croisées, exploitation à distance — ne peuvent pas porter les mêmes plages : chaque routeur croit que le réseau est chez lui. Un « numéro de site » a d'abord été proposé : rejeté, c'était un SECOND SEED à tenir et à synchroniser, alors que tout dérive déjà d'index (D-17). La bande basse est sûre par la règle, pas par chance : les zones valent 10.<index>.(15+categorie).0/24, donc troisième octet ≥ 16 — les octets 0 à 15 ne sont jamais alloués. Les troisièmes et derniers octets de l'underlay actuel sont ainsi préservés : seul le deuxième change, et l'invariant .1 (D-04) survit intact. Le stockage en est sorti parce qu'il est jumbo, non routé, et ne quitte jamais son site : il n'a aucun besoin d'être unique, et l'aligner sur le VLAN (192.168.20.x ↔ VLAN 20) fait dire son VLAN à l'adresse. Contrepartie assumée : ces réseaux ne pourront jamais traverser un lien inter-sites — sans conséquence, puisque ce qui voyage entre deux sites est l'état, par le dépôt de sauvegarde, pas le stockage bloc | underlay.yml (index), scripts/underlay.py, docs/preparer-un-site-hebergeur.md | P23 |

| D-80 | Un tenant est agnostique de son underlay. Les deux symlinks composent deux axes INDEPENDANTS : instance designe le tenant, underlay.yml la fabric qui l'accueille. Seule exception : une liaison de placement de trois cles | le commentaire disait « la fabric reste celle de l'hebergeur, quel que soit le tenant actif » — vrai, mais centre sur l'hebergeur. Le cadrage juste est centre sur le TENANT : tout son adressage derive du seed index, donc son plan se deplace d'une fabric a l'autre sans y toucher. Ce qui ne se deplace pas, c'est le placementproxmox_clone_noeud, _stockage, _pont — qui vit cote tenant parce que c'est lui qui choisit ou se poser, mais qui NOMME des objets de l'hebergeur. Trois cles, pas trente : c'est ce qui separe « portable » de « theoriquement portable ». Le gabarit (_vmid_modele) s'y ajoute, mais il n'est pas verifiable statiquement — c'est un objet du cluster, pas une liste declaree | underlay.py, inventories/*/group_vars/proxmox.yml, proxmox-hebergeur.yml | P37 | | D-79 | Le filtrage que l'hyperviseur applique aux VM tourne encore sur iptables legacy. Le passer à proxmox-firewall (nftables natif) est une dette à rembourser, pas une option — et c'est le même geste qui rend le pare-feu de VNet opérant. Décidée le 2026-08-12, différée après la reconstruction | mesuré sur asgard : iptables v1.8.9 (legacy) — pas (nf_tables) ; nftables v1.0.6 installé mais inutilisé par Proxmox ; proxmox-firewall 0.7.1 installé et absent des services en cours ; seul pve-firewall 5.1.3 tourne. Le pare-feu de VNet est entièrement expressif (type/action/proto/source/dest/dport, et policy_forwardACCEPT,DROP — donc un vrai default-deny inter-zone) : une règle d'essai a été créée, relue, puis retirée. Mais il n'est implémenté QUE par le moteur nftables : posée aujourd'hui, une règle serait acceptée, stockée, visible dans l'interface, et n'appliquerait rien. Trois gains dans le même geste — le moteur rejoint la doctrine (« tout est nftables »), le pare-feu de VNet devient réel, et ses règles survivent à la reconstruction là où les groupes par VM doivent être ré-attachés à chaque cycle | devis_proxmox_fw.py, docs/sdn-evpn.md | P25 (sur le mécanisme actuel) |

Ce qui n'a PAS été établi, et qu'il faudra éprouver. nft list tables exige root et le compte ansible ne l'a pas sur les hyperviseurs : le jeu de règles réellement chargé n'a pas été inspecté. Et aucune VM n'était en service au moment de la mesure : aucun blocage n'a été prouvé, ni avec l'ancien moteur ni avec le nouveau. La conclusion sur l'inertie du pare-feu de VNet repose sur l'état du service, pas sur un paquet refusé.

Une supposition erronée, gardée ici parce qu'elle est instructive. L'assistance avait avancé que sur Debian 12 iptables est iptables-nft, donc que les paquets passaient déjà par nf_tables. C'est faux ici : Proxmox force l'alternative sur iptables-legacy, parce que pve-firewall dépend de comportements de l'ancien sous-système. Le défaut d'une distribution n'est pas une mesure.

Séquence retenue : après la reconstruction, quand make valider et les sept devis donnent une référence propre — activer nftables sur un seul nœud, vérifier que le filtrage par VM tient toujours, puis poser un policy_forward: DROP sur un VNet et prouver qu'il bloque, avec son contrôle négatif.

| D-78 | Un réseau d'underlay est soit une destination, soit un chemin. Les destinations gardent l'adressage dérivé (10.<index>.015.x) ; les chemins sortent de cet espace en 192.168.<vlan>.0/24, identiques à tous les sites — décidée le 2026-08-12, appliquée au déplacement physique | D-77 sortait déjà le stockage, mais par une exception nommée plutôt que par une règle. Le critère n'est pas « y a-t-il des VM dedans » — le VLAN de gestion n'en a pas non plus — c'est : ce réseau est-il jamais une destination, ou seulement un chemin ? Transit (40) : rien que des prochains sauts, deux extrémités adjacentes en L2. Transport VXLAN (VLAN 50) : VTEP ↔ VTEP, destination de rien. Stockage (20/30/31) : baie ↔ hyperviseurs. Gestion (10) : le poste de l'exploitant, un VPN, demain le lien inter-sites doivent l'atteindre — elle reste dérivée et unique. Effet : sur six réseaux, cinq cessent d'exiger la moindre coordination entre deux hébergeurs, et « unique » redevient signifiant — seul ce qui doit l'être l'est | underlay.yml, docs/preparer-un-site-hebergeur.md | P23 (la borne de bande basse ne s'applique qu'aux destinations) |

Ce que D-78 coûte, et ce qui la renverserait. Le jour où l'un de ces liens devrait franchir un site, il redeviendrait une destination et exigerait une adresse unique. Concrètement : un EVPN multi-sites (un VTEP en peering avec le VTEP d'un autre hébergeur), ou deux frontières échangeant des routes par le tunnel au lieu de routes statiques. Ni l'un ni l'autre n'est au plan — la fabric EVPN est mono-site par hébergeur, et le tunnel relie les frontières, pas les liens de transit. C'est néanmoins la seule chose qui forcerait un retour en arrière, et elle est écrite ici pour qu'on la reconnaisse si elle arrive.

Un site NEUF se monte directement au schéma final : il n'a aucune transition à subir. Seuls les sites existants ont un déplacement à faire.

Pourquoi le transport VXLAN passe du VLAN 11 au VLAN 50. Sous la règle 192.168.<vlan>, le VLAN 11 aurait produit 192.168.11.0/24déjà occupé par la gestion des hyperviseurs sur vmbr0 (passerelle .254). Collision frontale. Le VLAN 11 se libérera bien lorsque cette gestion rejoindra 10.<index>.0.x, mais faire dépendre un plan d'adressage de l'ORDRE d'une migration est exactement le genre de dette qui se paie un an plus tard. Le VLAN 50 est libre, son 192.168.50.0/24 aussi, et il ne dépend de rien.

Ce que D-77 rend possible, et ce qu'elle coûte. Elle est la condition de la reprise mutuelle : sans plages distinctes, aucun lien entre deux sites ne peut router. Elle est gratuite pour un site neuf et coûteuse pour un site vivant — renuméroter, c'est toucher la frontière, les commutateurs, l'IPMI, vmbr0. D'où le calendrier retenu : appliquée immédiatement là où elle est gratuite, différée à froid ailleurs. Rien ne presse, puisque 10.0.x.x et 10.21.x.x ne se chevauchent pas entre-temps.

Ce que D-76 retourne. Le gabarit était tenu pour un « actif central, jamais jetable ». C'est exactement le problème : un artefact qu'on ne sait pas refaire est un artefact qu'on ne peut pas se permettre de perdre. Le rendre reproductible le fait passer d'irremplaçable à régénérable — c'est plus fort, pas moins.


5. Décisions renversées

Les garder évite de refaire le chemin, et explique pourquoi le code porte encore des branches qui semblent inutiles.

Décision Tenue du … au … Renversée par Pourquoi Ce qu'il en reste
L'isolation inter-tenant est portée par des ACL de commutateur 2026-07-07 → 08-03 059d76a le matériel ne sait pas lier une ACL à une interface de routage acl_inter_tenant: true reste le défaut : une autre fabric peut en être capable
Le routage inter-zone est porté par les commutateurs L3 2026-07-07 → 08-03 e5ce2b9 sans ACL, l'isolation devenait déclarative ; EVPN la rend structurelle routage_tenants: switch reste le défaut et reste généré
L'underlay.yml vit à la racine du moteur, gitignoré 2026-07-24 → 08-02 0eae97c consommé par deux générateurs, validé par une preuve, versionné nulle part SETOPS_UNDERLAY permet toujours de le pointer ailleurs
D-05 — un seul commutateur route, les autres en L2 pur 2026-07-07 → 08-04 2026-08-04 deux décisions séparées (EVPN, puis fusion du lien de sortie) ont vidé le rôle sans qu'on regarde leur effet cumulé : les trois SVI n'avaient plus aucun consommateur le moteur sait toujours émettre des SVI — le modèle public s'en sert
D-03 — le /29 de transit, SVI en .6, .3 réservée CARP 2026-07-24 → 08-04 2026-08-04 élargi en /24 pour y loger les nœuds de sortie ; l'exemption d'invariant sous le /24 tombe alors, et le .1 revient à la passerelle le plan .1 passerelle / .2 .3 pare-feux survit, décalé d'un cran
D-12bifrost aux frontières, sleipnir à la fabric 2026-07-24 → 08-04 2026-08-04 le nom portait le type de la machine, ce que le champ role fait déjà et mieux ; et sleipnir ne plaisait pas l'idée qu'un nom doit se retenir survit — mais elle passe par le numéro, aligné sur l'adresse

Ce que ces renversements ont en commun. Aucun ne vient d'un changement d'avis : les trois viennent d'un fait découvert après la décision — une commande absente de l'aide du matériel, une capacité manquante, une dizaine de modifications irrécupérables. C'est l'argument le plus fort pour éprouver avant de figer.