Lab virtuel multi-nœud : hyperviseurs en KVM imbriqué, route reflectors et switchs L3 en VM #50
Labels
No labels
bug fix
feature implementation
new feature
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
syonad/two#50
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Contexte
Tout ce qui fait la valeur de two ne se voit qu'à partir de deux hyperviseurs :
sur un nœud isolé le trafic reste sur le bridge local, et l'absence de plan de contrôle ne se
remarque pas (cf.
docs/deploiement/architecture-cluster.rst). Or aujourd'hui rien ne permet demonter un cluster de test reproductible :
serveurs DHCP sur UDP/67 dans un même netns n'a jamais été rejouée de façon automatique ;
d'acceptation un test négatif inter-VPC ;
netns,netif,ebtables,subnet,vpc,vm,watchdog…) n'a pasd'environnement Linux où tourner ;
Objectif
Un lab virtuel : un ensemble de VM qui reproduit la topologie cible du cluster, sur lequel
on déploie two comme en production et on rejoue des scénarios.
deploy.sh; lance lui-même les VM de testl2vpn evpn, peering dynamiqueQuestions à trancher
du lab hérite de ses limites (cf. MTU ci-dessous) ;
l'underlay. Si les liens entre hyperviseurs du lab sont eux-mêmes des VXLAN two, l'underlay
des hyperviseurs ne dispose que de 1450 : les paquets pleine taille des VM imbriquées sont
perdus, avec le symptôme trompeur décrit dans la doc (le ping passe, TLS échoue). Il faut
soit des liens de premier niveau hors VXLAN avec un MTU ≥ 1600, soit un MTU paramétrable,
soit qualifier explicitement en MTU réduit. À trancher avant de choisir la réponse à (1).
kvm_intel nested=1/kvm_amd nested=1),-cpu hostpour les hyperviseurs du lab. Les performances des VM desecond niveau ne sont pas représentatives : le lab qualifie le comportement, pas le débit.
deploy.sh --bootstrapest rejoué à chaque démarrage. Les hyperviseurs du lab doivent démarrer de la même façon,
sinon le lab teste autre chose que la production. Image construite par la même chaîne que
docs/deploiement/image-qcow2.rst(voire par le plugin Packer).détruire et remonter à l'identique.
KVM imbriqué.
Premiers scénarios visés
twosur deux subnets d'un même VPC,systemd-networkd) ;
subnet ;
ping -M dopleine taille) ;Découpage proposé
imbriqué et le MTU avant d'outiller quoi que ce soit.
Analyse de risque
pouvoir s'établir avec les RR ou les routeurs réels — réseau de lab isolé, ASN et plages
d'adresses dédiés, aucune route vers l'underlay de production. Une annonce EVPN du lab reçue
par un RR réel serait une fuite silencieuse.
doit pas la rendre joignable au-delà du réseau de lab.
dégrader un hyperviseur réel s'il est partagé.
fictives uniquement.
Périmètre
Outillage de test, aucun changement du code produit — sauf si la question 2 conduit à rendre
le MTU paramétrable, auquel cas ce sera un ticket séparé.
L'outillage étant conçu avec assistance IA, il suit les règles internes : revue technique, revue
sécurité (isolation du lab), tests et traçabilité de l'usage de l'IA avant de servir à
qualifier quoi que ce soit destiné à la production.
Décisions du 2026-10-03
Q1 — Qui lance les VM de premier niveau : la machine qui crée le lab, en QEMU nu
Ni two, ni libvirt, ni Terraform. La machine qui monte le lab lance directement des processus
qemu-system-*, sur macOS comme sur Linux.Écartés, après vérification :
1500 (cf. Q2).
donne aucun lab unique.
dmacvicar/libvirtest mûr (v0.9.9, 2026-08-30) mais réservé àLinux. Le seul provider Lima (
guidoiaquinti/lima) est communautaire, à mainteneur unique, etsa couverture est partielle : risque de chaîne d'approvisionnement pour un gain faible.
qemu-system-x86_64 -enable-kvm -cpu host(internal/qemu/start_linux.go) et n'est publiéqu'en amd64 — et l'émulation x86_64 sur Mac ne suffit pas non plus (cf. Q3).
Liens : des câbles virtuels QEMU,
-netdev dgramsur UDP en loopbackChaque lien de la topologie est un câble point à point entre deux processus QEMU, sans bridge,
sans root et sans libvirt.
dgramplutôt questream: pas de rôle serveur ni client, donc une VM peut redémarrersans casser le lien.
topologie reste portable sur macOS, si un Mac doit un jour faire tourner les VM réseau (route
reflector, switch). macOS plafonne les datagrammes Unix à 2048 octets
(
net.local.dgram.maxdgram), ce qui exclut un MTU de 9000, et les datagrammes UDP à 9216octets (
net.inet.udp.maxdgram) ; une trame de MTU 9000 en fait 9014 (9018 avec un tag VLAN).dgramne mène qu'à l'autre extrémité. Aucunesortie vers un réseau réel, ce qui traite le risque principal de ce ticket (des sessions BGP du
lab qui atteindraient la production).
Q2 — MTU : underlay à 9000
virtio-net-pci,host_mtu=9000annonce le MTU au noyau invité, et l'interface démarre à 9000.Les VXLAN de two, figés à 1500, ont ainsi toute la marge nécessaire.
Point relevé au passage :
scripts/bootstrap_kvm.shne pose aucun MTU, etbr-000000hérite decelui de l'interface physique. Le MTU de l'underlay en production dépend donc de la configuration
de l'OS, hors de two. À vérifier sur lab3 avant d'affirmer que le lab reproduit la production.
Q3 — Hôte du lab : Linux x86_64, avec la virtualisation imbriquée
Les hyperviseurs du lab exigent un hôte Linux x86_64 avec KVM imbriqué. Un Mac Apple Silicon ne
peut pas les héberger ; vérifié le 2026-10-03 sur un M4 Pro, QEMU 11.0.0 sur le Mac, QEMU 10.0.13
et Debian 13 dans l'hyperviseur émulé :
-cpu qemu64,+svmkvm_amdse charge,/dev/kvmprésent (TCG émule SVM, pas VMX)-cpu hostpuis-cpu qemu64kvm_amdsignaleNested Paging disabled: TCG n'émule pas la pagination imbriquée. Rendrel'accélérateur de two configurable (
kvm/tcg) a été envisagé puis écarté pour l'instant : leTCG imbriqué ne produit pas de VM utilisable dans un délai raisonnable.
Le Mac reste un poste de pilotage du lab.
Hôte retenu : un serveur physique loué à l'heure, Scaleway Elastic Metal EM-B212X-SSD
(PAR-1, 0,321 €/h HT, sans engagement en facturation horaire) : 2 × Xeon E5-2620 v4 « or
equivalent » (2 × 8C/16T), 256 Go, 2 × 1 To SSD.
du lab seraient au troisième niveau d'imbrication, que les fournisseurs ne garantissent pas (GCP
et AWS ne promettent que le second). Sur du physique, l'hôte est la vraie machine, les
hyperviseurs du lab ont un vrai KVM, et leurs VM tournent au second niveau.
-cpu host, et KVM a deux implémentations distinctes (kvm_intel/kvm_amd) : même fabricant,même chemin de code. 256 Go laissent la place de simuler un datacenter complet, la RAM étant la
ressource qui limite un lab imbriqué.
lscpuau début de chaque campagne.Discipline de facturation. Un serveur Elastic Metal est facturé de sa création à sa
suppression, éteint compris ; la granularité (heure entamée ou minute) n'est pas documentée,
on compte l'heure entamée.
trap … EXIT, y compris sur unéchec ou une interruption.
lab-cleanupsupprime tout serveur du projet de lab, à lancer en cas de doute.ni dans les issues.
dgraminternes.Une VM par rôle, pas de cumul route reflector + switch L3 : la topologie doit pouvoir grandir
vers la simulation d'un datacenter complet (plusieurs réseaux, plusieurs switchs).
Versions
6.1.0-33(6.1.133). La version exacte, obtenue via snapshot.debian.org, n'est visée que sur les
hyperviseurs du lab, qui portent VXLAN et EVPN.
frr-stablesur deb.frrouting.org, soit 10.7.1 aujourd'hui,publiée en amd64 comme en arm64. Le lab sert à valider la montée de version avant de l'appliquer
à lab3, qui est en 10.5.0.
téléchargeables. La version installée doit donc être relevée et consignée à chaque
construction d'image.
Questions encore ouvertes
nestedàY, une VM de secondniveau jusqu'à l'invite de connexion, deux VM reliées par
dgramen MTU 9000.Décisions du 2026-10-03 (suite) — Q4, Q5, Q6 et plan
Q4 — Hyperviseurs : Debian 12 standard, pour l'instant
Les hyperviseurs du lab sont des Debian 12 installées normalement, pas des systèmes sans état.
L'image de départ est
debian-12-generic-amd64, pasgenericcloud:genericporte le noyaulinux-image-amd64, celui de lab3 (6.1.0-33-amd64), alors quegenericcloudporte le noyaucloud-amd64, allégé. Même saveur de noyau que la production, donc mêmes modules (KVM, VXLAN,bridge). Les images sont vérifiées contre
SHA512SUMSau téléchargement.Hors périmètre, et c'est précisément ce que ce lab doit permettre de qualifier plus tard : passer
les hyperviseurs en Rocky 10, puis générer un noyau et un initrd sur lesquels démarrer les VM.
Q6 — Lancement manuel depuis ce dépôt
Le lab se lance à la main, depuis ce dépôt. Pas de CI pour l'instant.
Q5 — Description de la topologie
Des segments L2 portés par le switch, pas des liens
Chaque câble
dgramrelie un nœud à un port du switch, et le switch met ses ports dans un bridge :tous les éléments qui doivent se parler le font en L2, sur un segment. Le switch porte la passerelle
du segment sur ce bridge et route entre ses segments et sa sortie Internet. Le rôle L3 reste donc
réel : il sert dès aujourd'hui pour la sortie Internet, et demain entre plusieurs segments, quand on
simulera plusieurs racks.
Bridge et ports en MTU 9000, STP désactivé, comme
br-000000dansscripts/bootstrap_kvm.sh.Ajouter un hyperviseur, un segment ou un switch se fait en ajoutant des lignes, sans toucher à
l'outil.
Ce que l'outil déduit, de façon déterministe
127.0.0.1par câble, dérivée de la position du nœud et du segment127.0.0.1:22xxsur l'hôte, un par nœudLe rang de déclaration détermine ces valeurs : réordonner le fichier change les adresses. C'est
assumé pour un lab, et
lab planaffiche le résultat avant tout lancement.Plages et ASN du lab : dédiés et distincts de la production, à définir plus tard. Les
valeurs de l'exemple ne sont que des valeurs de travail.
Administration et sortie Internet
Chaque VM a une seconde interface, d'administration, en NAT QEMU (
-netdev user), avec le SSHredirigé sur la boucle locale de l'hôte. Cette interface est séparée des segments : le trafic
d'administration ne se mélange jamais au réseau testé.
restrict=onrestrict=onisole la VM — ni l'hôte ni l'extérieur ne sont joignables par cette interface — sanstoucher aux redirections déclarées (
qemu-options.hx: « This option does not affect anyexplicitly set forwarding rules »). L'interface d'administration reçoit une adresse statique
(
10.0.2.15/24, l'adresse attendue par la redirection), sans passerelle : la seule route pardéfaut d'un hyperviseur passe par le switch, comme en production. Une adresse statique plutôt qu'un
DHCP dont on ignorerait la route : selon le moteur de rendu réseau de l'image, l'option qui ignore
la route du DHCP n'est pas toujours prise en charge.
L'outil :
cmd/laben Go, exécuté sur l'hôte du labarguments QEMU et des seeds cloud-init. Testable sur Mac, et aucune dépendance nouvelle : le YAML
est déjà dans l'arbre via viper.
scwpour le cycle de vie du serveur : le SDK Scaleway n'entre pas dans lemodule de l'agent, actif critique. Même raisonnement que pour le plugin Packer.
paquets du rôle. Déclaratif, rejoué à l'identique à chaque montage. SSH ne sert qu'aux scénarios et
au debug, et les ports n'écoutent que sur la boucle locale du serveur : rien n'est exposé.
Plan
Chaque étape laisse le dépôt compilable, les tests verts, et se vérifie autrement qu'en relisant
le code.
scripts/lab-host.sh:up(création EM-B212X en facturation horaire, OS en un appel),ssh,down,cleanup(supprime tout serveur du projet de lab) ; suppression dans untrap … EXITlscpurelevé,/sys/module/kvm_intel/parameters/nestedàY, puisscw baremetal server listvide aprèsdowncmd/lab: lecture et validation de la topologie, calcul du plan déterministe ;lab plandgram,host_mtu=9000, NAT d'administration,restrict=on) et des seeds cloud-initlab up/down/statussur l'hôte : processus QEMU, attente du SSHping -M do -s 8972de hv1 à hv2 à travers le switch ; un hyperviseur sort sur Internet par le switch ; il ne sort pas par son interface d'administrationfrr-stablesur switch et route reflector (version relevée), two sur les hyperviseurs viadeploy.shtwoavec systemd-networkd)Prérequis non résolu pour aller au-delà d'E5 : la configuration FRR des hyperviseurs (EVPN,
session vers le route reflector) n'est écrite nulle part dans two —
docs/deploiement/premier-hyperviseur.rstla marque « À rédiger ». Elle sera écrite une seulefois pour deux usages — la configuration des hyperviseurs du lab et la documentation — afin que
le lab qualifie exactement ce que la doc prescrit. Sujet à reprendre au moment d'E5 ; les scénarios
EVPN entre deux hyperviseurs viendront avec elle.
Analyse de risque — compléments
joignent l'Internet public, pas la production, qui n'est pas routable depuis Scaleway. Aucune
session BGP du lab ne peut s'établir hors du lab.
boucle locale ou sur les câbles internes.
jamais affichée, jamais dans le dépôt ni dans les issues.
trap … EXITetcleanupsontla protection, pas la vigilance.
production. Revue technique, revue sécurité (isolation du lab, gestion de la clé d'API), tests et
traçabilité de l'usage de l'IA, conformément aux règles internes.
E0 — fait le 2026-10-03 (
28ce00c, branchefeature-50)scripts/lab-host.shcrée, surveille et supprime le serveur de lab ;scripts/lab-host_test.shle teste ;
docs/developpement/lab.rstdocumente l'usage.Écart au plan : l'API REST, pas la CLI
scwLe plan prévoyait la CLI
scw. Son code montre quescw baremetal server create type=…résoutl'offre par son seul nom, sans période de facturation, et prend la première trouvée — possiblement
l'offre mensuelle, qui engage un mois (~116 € pour la B212X). Le script appelle donc l'API REST
directement (
curl+jq), chemins et champs tirés du SDK Go officiel, et exige côté client uneoffre unique, en facturation horaire, en stock, sans frais de mise en service.
Commandes
planstatusup/ssh/downdownsession [cmd]up, la commande, puisdownquoi qu'il arriveGaranties contre la surfacturation
trap EXIT; INT, TERM et HUP pendant la commande distante, et un secondsignal pendant le nettoyage n'interrompt pas celui-ci ;
downagit par tag et projet, etsessiony ajoute l'identifiant reçu à la création ;SERVEUR(S) DE LAB TOUJOURS FACTURÉ(S)et code d'erreur.Une relecture par un sous-agent a trouvé sept défauts dans la première version, tous corrigés
avec un test chacun.
Résultat sur la vraie API
nested=Y, VT-x,/dev/kvmprésent6.1.0-53-amd64(6.1.187)upétait nécessaireContrôle indépendant après la session : aucun serveur dans le projet, filtre de tag compris ou non.
Mise en place côté Scaleway, apprise en route
EM-B212X-SSDexige un compte dont le moyen de paiement et l'identité sontvalidés (quota de 2 ensuite).
planrefuse de continuer — un serveur facturé où personnene peut entrer.
X-Auth-Token.Fichiers locaux, hors dépôt
~/.config/two-lab/scaleway.env: identifiants, en0600; lu, jamais exécuté ; refusés'il est lisible par d'autres ; l'environnement l'emporte.
~/.config/two-lab/ssh/lab_ed25519: clé SSH dédiée au lab, sans phrase de passe, utilisée seule(
IdentitiesOnly, agent désactivé) pour que les sessions tournent sans Yubikey. À ne jamaisinstaller sur lab3 ni sur une machine durable.
Vérification
48 tests contre une fausse API qui se place dans le pire cas, sous bash 5 et sous le bash 3.2 de
macOS. Campagnes de mutation : chaque protection cassée fait échouer un test, sauf les traps
explicites INT et HUP, dont la suppression est équivalente (bash lance déjà le trap EXIT et sort
en 128+n). Doc construite avec
sphinx-build -W, sans avertissement.Décision de méthode
La documentation accompagne désormais chaque étape, dans le même commit que le code, au lieu
d'arriver au dernier lot. Les exemples qu'elle montre sont des sorties réelles.
Prochaine étape
E1 —
cmd/lab: lecture et validation de la topologie, calcul du plan déterministe,lab plan.Entièrement sur Mac, sans serveur loué.
E1 — fait le 2026-10-04 (
a706e24, branchefeature-50)internal/lab/topologylit et valide la topologie et calcule le plan ;cmd/labl'expose parlab plan <fichier>. Exemple livré :conf/lab/evpn-2hv.yml(1 switch, 1 route reflector,2 hyperviseurs). Documentation : section « Topologie » de
docs/developpement/lab.rst, qui inclutdirectement le fichier d'exemple — doc et exemple ne peuvent pas diverger.
L'ordre de déclaration, et pourquoi deux lectures
Les adresses, MAC et ports dépendent de l'ordre de déclaration ; une
mapGo n'en garde aucun.Le fichier est donc lu deux fois : une lecture stricte (champs inconnus et clés en double
refusés) et une lecture en
yaml.Nodequi relève l'ordre des clés.go.yaml.in/yaml/v3passe dedépendance indirecte (via viper) à directe : aucun module nouveau.
Ce que l'outil déduit
br-<segment>addressesest réservée d'abord et sautée par l'attribution20000 + 2i(nœud) et20001 + 2i(switch)02:4c:<nœud>:<nœud>:<segment>:<côté>— localement administrée, rang du nœud sur deux octets,00côté nœud,01côté switchp<i>127.0.0.1:<2200 + rang du nœud>sur l'hôte du labChoix faits à l'écriture, à confirmer
d'interface dans les VM, et
br-<segment>celui du bridge — 15 caractères au plus sous Linux.segmentsniaddresses: il porte ceux dont il est leswitch.Relier deux switchs entre eux, pour simuler plusieurs racks, viendra avec ce besoin.
lab planle montre avant toutlancement.
Vérification
65 tests,
-racepropre ; couverture 94,9 % (topology), 85 % (cmd/lab). Les valeurs attenduessont écrites en littéral dans les tests, jamais tirées des constantes du code.
25 mutations, toutes détectées. Deux vrais trous trouvés et fermés — aucun test ne dépassait
la limite de nœuds ni celle des ports UDP — plus un test ajouté pour la limite de segments. Deux
mutations d'abord classées « survivantes » étaient des échecs de compilation, réécrites.
Suite complète du dépôt verte, compilation
linux/amd64enCGO_ENABLED=0, documentationconstruite avec
sphinx-build -Wsans avertissement.Au passage
go mod tidyajoutait àgo.sumtrois modules sans rapport avec E1 (mdlayher/packet,mdlayher/socket,x/sync), dépendances de test de la bibliothèque DHCP de #46 jamais« tidyées ». Retirés du commit : E1 n'en a pas besoin. Ils reviendront au prochain
go mod tidyde qui que ce soit — à intégrer dans un commit à part, sans lien avec ce ticket.
Prochaine étape
E2 — à partir du plan, générer les arguments QEMU (câbles
dgram,host_mtu=9000, NATd'administration avec
restrict=on) et les seeds cloud-init. Testable sur Mac, sans serveur loué.E2 — fait le 2026-10-04 (
99ecb59, branchefeature-50)internal/lab/rendertransforme le plan en ce qu'il faut pour démarrer chaque VM ;cmd/labl'exposepar
lab render -key <clé.pub> <topologie> <répertoire>, qui écrit pour chaque nœudqemu.args(unargument par ligne) et les trois fichiers NoCloud
meta-data,user-data,network-config. Rienn'est lancé : l'image
cidataet le démarrage relèvent d'E3. Documentation : section « Rendu des VM »de
docs/developpement/lab.rst.Arguments QEMU
q35,-accel kvm -cpu host(KVM imbriqué des hyperviseurs),-nodefaults, disquevirtio,seed.isoen cdrom, console dans un fichier, QMP sur socket Unix,-pidfile.mgmt0: NAT de QEMU, MAC02:4d:<nœud>:<nœud>:00:00, SSH redirigé sur127.0.0.1:<2200+n>.restrict=onpour tous les nœuds sauf le switch ;ipv6=offpartout.-netdev dgramsur127.0.0.1, les deux extrémités croisées,host_mtu= MTU dusegment. Syntaxe tirée de
qemu-options.hx, pas de mémoire.romfile=vide sur toutes les cartes : pas de repli PXE. Précaution — la cause du blocage observépendant les essais de virtualisation imbriquée n'a pas été isolée.
cloud-init
YAML produit par marshalling de structures Go, pas par gabarit texte. Leçon de #46 appliquée :
dhcp4: falseetssh_pwauth: falsesont écrits explicitement, jamais omis.mgmt0en10.0.2.15/24sanspasserelle, utilisateur
debian, SSH par clé seulement,rootdésactivé ;1.1.1.1,8.8.8.8) sur sonpremier segment, donc sortie Internet par le switch ;
mgmt0; un servicelab-switch(rejoué à chaque démarrage)crée
br-<segment>(STP désactivé, MTU du segment), y branche ses ports, porte la passerelle,active le routage et masque les segments vers
mgmt0(nftables, remplacement atomique de la table).Ajout à la validation de la topologie :
mgmt0est réservé. Un segment de ce nom aurait donné deuxinterfaces et deux
netdevhomonymes dans la même VM.Vérifié avec les vrais outils
Un vrai QEMU (11.0.0) accepte la ligne de commande des quatre nœuds — lancés en pause,
kvmremplacé par
tcgfaute de KVM sur Mac — et lie les six ports UDP des câbles.Deux vraies VM. Le switch et le route reflector n'ont pas besoin de KVM imbriqué :
sw1etrr1de l'exemple ont été démarrés sur un Mac, en émulation, avec Debian 12
genericet les fichiers delab render.br-underlayen10.250.0.1/24lab-switchactif, y compris après redémarrageping -M do -s 8972derr1au switch (MTU 9000, sans fragmentation)ping -M do -s 8973(MTU 9001)message too long, mtu=9000rr1en IPv410.250.0.1rr1vers un service TCP de l'hôte parmgmt0—sw1, témoin, y parvientrr1vers Internet parmgmt0Défaut trouvé par cet essai, corrigé. Sans
ipv6=off, le NAT de QEMU annonce un préfixe IPv6 :mgmt0recevait une route IPv6 par défaut vers une impasse (restrict=onbloque tout). Pas de fuite,mais tout programme qui tente l'IPv6 d'abord — le DNS renvoie d'abord des adresses IPv6 — attend un
délai avant de se rabattre sur l'IPv4. Corrigé côté QEMU plutôt que dans la VM : la route est apprise
au démarrage, avant que la configuration réseau ne s'applique. Essai rejoué : plus de route.
Erreur de méthode en route : mon premier test d'isolation pingait
10.0.2.2, qui est la passerellevirtuelle de QEMU et répond toujours. Seule une connexion vers un vrai service de l'hôte, avec un
témoin qui y parvient, prouve l'isolation.
Vérification
91 tests,
-racepropre ; couverture 95,2 % (render), 96,3 % (topology), 85,2 % (cmd/lab).22 mutations, toutes détectées. Compilation
linux/amd64, documentation construite avecsphinx-build -Wsans avertissement.Au passage, #47 est sorti pendant une passe de la suite complète, dans
internal/dispatcher/agent, non modifié par E2. Reproduit surmainsans aucun changement(1 passe sur 6) : c'est le défaut connu, pas une régression.
Reste à vérifier sur le serveur de lab
Les hyperviseurs, qui exigent KVM imbriqué : c'est l'objet d'E3.
Prochaine étape
E3 —
lab up/down/statussur l'hôte : disques en overlay sur l'image vérifiée, imagecidata, lancement des QEMU, attente du SSH. Vérification sur le serveur Scaleway, avec un coûtd'environ une heure entamée.
E3 — plan, décidé le 2026-10-04
lab up/down/status/sshsur le serveur de lab, et ce qu'il faut côté Mac pour les ylancer.
Frontière entre les deux outils
lab-host.sh(Mac) : le serveur Scaleway et le transport. Il ne lit jamais la topologie et neconnaît ni QEMU ni les ports des VM.
cmd/lab(serveur) : tout ce qui concerne les VM.Un seul point d'entrée depuis le Mac, la commande
sshqui existe déjà :Décisions
-daemonize; le pidfile et la socket QMP d'E2 suffisent àstatus/downsystemd-run --user(linger, nommage, nettoyage) ;lab upau premier plan (session à garder ouverte)cidatagenisoimage -volid cidata -joliet -rock, la méthode documentée par cloud-init ; les arguments QEMU d'E2 ne changent pasvvfatde QEMU (chemin peu utilisé) ; ISO 9660 écrite en Goprepareappelée parupet disponible seule :qemu-system-x86,qemu-utils,genisoimage, contrôle de/dev/kvmet denestedlab ssh <nœud> [cmd], exécuté sur le serveurssh_configgénéré sur le Mac avecProxyJumplab upsur le serveur, seule clé autorisée dans les VM. Elle n'en sort jamais et disparaît avec luissh -A) : exposerait l'agent du Mac à root sur le serveurlab upattendcloud-init status --waitpar SSH, possible grâce à la clé du serveurlab render -keygarde son option : c'est la commande hors ligne d'E2, pour générer et inspecterles fichiers sur le Mac.
Terminal interactif
Chaque maillon décide d'après sa propre entrée standard, comme
sshlui-même :lab-host.sh sshajoute-tsi son entrée est un terminal,lab sshfait de même avant de céderla place à
sshparexec. Depuis un terminal on obtient un vrai shell ; depuis un scénario àl'entrée redirigée, ni pseudo-terminal ni
\r\n, et le code de retour remonte jusqu'au Mac.Découpage
SHA512SUMS, disque en overlayqemu-img,seed.iso, paire de clés du lablab up(switch d'abord),status(pidfile recoupé avec/proc/<pid>/cmdline),down(quitQMP viainternal/qmp),ssh;uprepart toujours de disques neufslab-host.sh:prepare,push,-tsurssh; documentationVérification d'E3c sur Scaleway :
ping -M do -s 8972de hv1 à hv2 à travers le switch ;mgmt0: connexion TCP versun vrai service du serveur, avec
sw1comme témoin qui y parvient ;/dev/kvmprésent dans hv1, préalable à E4 ;./lab-host.sh ssh './lab ssh hv1'donne un shell interactif, etecho | ./lab-host.sh ssh './lab ssh hv1 false'rend 1 sans pseudo-terminal ;lab downpuis suppression du serveur confirmée.Analyse de risque — compléments
n'écoutent qu'en boucle locale, et disparaît avec le serveur.
up, donclab sshne les vérifie pas(
known_hostsjetable). Acceptable seulement parce que la connexion reste sur la boucle localed'un serveur auquel on s'est authentifié.
SHA512SUMSest récupéré en HTTPS depuis la même origine que l'image.Cela protège contre la corruption, pas contre une origine compromise ; la vérification de la
signature GPG de Debian (
SHA512SUMS.sign) reste à faire.Ajout à E5
Scénario gateway /
public_ip(cf. #31, correctifgatewaylivré pendant #34) : vérificationqui devait se faire à la main sur lab3. Après recréation d'un subnet, la route par défaut réapparaît
dans le guest — c'est attendu, elle est désormais explicite vers
interface_ipau lieu d'êtrefabriquée par dnsmasq — et les routes metadata et VPC sont présentes.
E3 — fait le 2026-10-04 (
d1cd943,1146d13,90d8128, branchefeature-50)labdémarre, surveille et arrête les VM sur le serveur de lab ;lab-host.shprépare le serveuret y dépose
lab. Validé sur Scaleway le 2026-10-04.Trois lots
internal/lab/provision: image en cache vérifiée contreSHA512SUMS(écriture à côté puis renommage : rien en cache sur un échec), disque en overlay qcow2 de 20 Gio,seed.isopargenisoimage, paire de clés générée sur le serveurinternal/lab/machineetlab up/status/down/ssh: QEMU en-daemonize, switchs d'abord, attente decloud-init status --wait; état dans~/lab-runlab-host.sh:prepare(lancé parup),push <topologie>,-tseulement si l'entrée est un terminalÉcarts au plan
SIGTERMpuisSIGKILL, pas parquitQMP :internal/qmp.Sendlit ses réponses ligne à ligne sans filtrer les événements asynchrones, et un arrêt propre du système invité n'apporte rien quanduprecrée les disques./proc/<pid>/cmdlinecontient-name <nœud>: un PID réutilisé n'est jamais signalé.stoprefuse en outre tout PID ≤ 0 (kill(0)vise le groupe,kill(-1)tous les processus de l'utilisateur).TCGETS/TIOCGETA) :os.ModeCharDeviceprenait/dev/nullpour un terminal.prepareinstalle QEMU sans les paquets recommandés : la première version tirait GTK, Pango et des codecs, inutiles en-display none.lab-host.shet son test sont désormais exécutables (mode100755) ; ils étaient enregistrés en100644depuis E0.Résultat sur Scaleway
up+preparelab up, 4 VM prêtesdownping -M do -s 8972hv1 → hv2 à travers le switch-s 8973refusé (mtu=9000)mgmt0, sw1 en témoin (200)mgmt0, route forcée, sw1 en témoin/dev/kvm,nested, racine dans hv1Y, 20 Go (growpart)lab-host.sh ssh+lab ssh, sans terminallab-host.sh ssh './lab ssh hv1'lab downpuislab upd'une autre topologieEssais au-delà d'E3, pour lever les inconnues d'E4 — manuels, rien dans le dépôt
genericcloudlancée à la main dans hv1 en-accel kvm -cpu hostatteintlogin:en 21 s. Le blocage observé sur Mac (reset au passage en mode protégé) n'existe pas ici : le critère d'E4 est atteignable.deploy.sh -i -u underlaysur hv1 (release0.2.0rc002) : paquets, noyau, migration réseau (underlay→br-000000, rollback désarmé après le ping de la passerelle), agent actif, API qui répond.br-000000hérite du MTU 9000 de l'uplink. Deux enseignements : le lab devra passer-u <segment>; la migration ne touche pasmgmt0, l'administration survit à un-iraté.deploy.shrend 1 même en cas de succès : la seconde garde de fin de fichier, fausse en lancement direct, fixe le code de sortie. Défaut connu, à corriger hors de ce ticket — E4 en dépend.frr-stabledonne 10.7.1 (10.7.1-0~deb12u1) sur bookworm. L'empreinte de la clé du dépôt n'a pas été relevée (gpgabsent de l'image) : à faire en E4.bgp listen range), en iBGP grâce aulocal-as … replace-as. Tout est Established. Aucune route EVPN échangée : rien à annoncer tant que two n'a pas créé de VXLAN.Le RR porte son adresse du lien switch en adresse secondaire sur son interface principale, sans
ipvlan: même L2, même MAC sur le fil, une interface de moins. En production, la déclarer dans le profil NetworkManager pour qu'elle survive aux renouvellements DHCP. La loopback est une interfacedummy.La configuration du switch était un bouche-trou écrit pour l'essai — celle des routeurs reste à fournir.
Erreurs de méthode, corrigées en route
kill(0, SIGTERM)et s'est tuée avec le script qui la menait, laissant un mutant dans le source — repéré, restauré. Le harnais isole désormais les tests dans leur propre session et vérifie l'intégrité des sources ; le test de la garde passe par un faux qui échoue s'il est appelé.curl -L: la page de redirection a été téléchargée à la place de l'image ; la vérification SHA-512 l'a arrêté. LeFetcherdelabsuit les redirections.Vérification
Go : 169 cas pour le lab, sous-tests compris (
cmd/lab15,topology63,render23,provision38,machine30),-racepropre ; couverture 87,5 % (provision), 89 % (machine), 76,6 % (cmd/lab).lab-host.sh: 60 tests, sous bash 5 et bash 3.2. Mutation : 29 (E3a), 26 (E3b) et 10 (E3c) mutants, tous détectés hormis un mutant équivalent, dont le code a été retiré. Documentation construite avecsphinx-build -Wsans avertissement, exemples tirés de la session réelle.Pour la suite
deploy.sh -i -u <segment>puis FRR sur les hyperviseurs) ; à trancher en ouverture : où décrire ces adresses (topologie ou gabarits par rôle), et les ASN et plages dédiés au lab.lab-host.sh(un lab par serveur) ; vérification de la signature GPG deSHA512SUMS.E4 — plan, décidé le 2026-10-04
Les rôles du lab, installés au démarrage par cloud-init : FRR sur le switch et le route reflector,
two (par
deploy.sh) puis FRR sur les hyperviseurs.Décisions
deploy.sh -i -u <segment> -t <tag>: le lab qualifie ce qui est livré, par le chemin de la productiondeploy.shutilisélab, avec--noup_script: la version testée est celle de la branchemain, que l'auto-mise à jour imposeraitfrr.confécrits à la main dansconf/lab/, copiés tels quels par cloud-init et inclus dans la doc (literalinclude) : une seule source pour le lab et les pages « À rédiger »labdepuis la topologie : elle n'existerait que dans le code Go, et doc et lab pourraient diverger169.254.0.0/28, subnet des hyperviseurs — pour que les fichiers deconf/lab/soient au plus près de la production ; noms d'hôtes propres au labgenericcloud, vérifiée parSHA512SUMSValidé pendant la session E3 et repris tel quel : le RR porte l'adresse du lien switch en
adresse secondaire sur son interface principale (pas d'
ipvlan), sa loopback sur uneinterface
dummy; le switch porte la sienne sur le bridge du segment.Découpage
frr(chemin d'unfrr.confdeconf/lab/), adresses secondaires par segment,loopback; rendu : adresses etdummy, installation de FRR (frr-stable, clé du dépôt), démons selon le rôle,frr.confdéposé après le paquetdeploy.shdu dépôt lancé par cloud-init (--noup_script -i -u <segment> -t <tag>, tag dans la topologie), puis FRR ;lab upattend la fin, un échec remonteroute-reflector.rstinclut lefrr.confdu RR ; une session Scalewaygenericcloudcréés par l'API de hv1 : la VM atteintlogin:en KVM imbriquéReste en attente
la configuration minimale de la session E3, documentée comme telle, et
routeurs.rstreste« À rédiger ».
Analyse de risque — compléments
documentation publics. Choix assumé ; aucun nom d'hôte ni adresse d'équipement de production
n'y est écrit.
dans le dépôt et vérifiée ;
deploy.shvérifie les artefacts de la release contreSHA256SUMS.dgram.E4 — fait le 2026-10-04 (
b348652,4410f30,bc05fd9, branchefeature-50)Les rôles du lab sont installés au démarrage par cloud-init : FRR sur le switch et le route
reflector, two (par
deploy.sh) puis FRR sur les hyperviseurs. Validé sur Scaleway ; le critèred'E4 est atteint.
Trois lots
b348652)secondary(adresses supplémentaires, hors du CIDR du segment),loopback(lo1,dummy),frr(unfrr.confdeconf/lab/frr/) ; FRRfrr-stableinstallé avec la clé du dépôt enregistrée danslab;pushenvoie tout le répertoire de la topologie4410f30)deploy.shetbootstrap_kvm.shdu dépôt, embarqués danslab(paquetscripts), lancés pardeploy.sh --noup_script -i -u <uplink> -t <release>;releaseobligatoire dans la topologie ;lab upvérifieagentetfrrbc05fd9)route-reflector.rst: adressage et configuration FRR, incluse depuisconf/lab/frr/rr1.confValeurs de production (ASN, loopback, lien
169.254.0.0/28, subnet des hyperviseurs) dansconf/lab/, toutes les adresses fixées paraddresses— route reflector en.2, hyperviseurs àpartir de
.11.Trouvé en route
runcmdsansset -e(vérifié dansshellify, 22.4.2) : une étape enéchec suivie d'une étape réussie passait pour un démarrage réussi. Chaque nœud a désormais un seul
script,
lab-provision, enset -eu.prêt ; mise à jour des paquets réessayée cinq minutes avec
--error-on=any(sans quoiapt-get updaterend 0 même sur un dépôt injoignable).pushn'envoyait pas lesfrr.confréférencés ; il envoie maintenant le répertoire, sans lesmétadonnées macOS.
deploy.shest l'interface qui porte la route par défaut : celle du premier segmentdans l'ordre de déclaration des segments, pas de la liste du nœud.
Résultat sur Scaleway (session de 17:57 à 18:44, une heure entamée, ~0,32 € HT)
up+preparelab upavec rôles etdeploy.shagentet defrrpasséesdeploy.shbr-000000, MTU 9000, API de l'agent OKvxlan, VM Debiangenericcloudpar l'API de hv1login:en 20 s, en KVM imbriqué/32vers169.254.169.254compriseseedfromsans barre oblique finale ; OK avec (DataSourceNoCloudNet, nom d'hôte appliqué)vxlanDeux constats sur le produit
seedfromdeimage-qcow2.rst: jusqu'à cloud-init 22.4 (Debian 12 : 22.4.2), l'URL estconcaténée sans barre oblique et devient
http://169.254.169.254:80meta-data; à partir de 23.1, undrapeau actif par défaut l'ajoute — d'où une image récente qui fonctionne sans. La doc écrit
désormais
'http://169.254.169.254:80/', valable quelle que soit la version ; le point« À vérifier » est remplacé par l'explication et la vérification.
VTEP local des VXLAN → #51. two crée ses VXLAN sans adresse
local: FRR voit la VNI mais nel'annonce pas, et deux hyperviseurs two ne se voient pas. Posée à la main, puis par un patch de
l'agent (adresse IPv4 primaire du
local_ifacedu subnet), l'adresse suffit : routes EVPN detype 3 échangées, VTEP distant appris, ping et MTU 1500 entre VM de deux hyperviseurs. Le patch
complet (diff et tests) est dans #51. Confirmé hors du lab : sur lab1, le trafic ne passait que
parce que l'autre extrémité (un routeur de test) avait, elle, un VTEP.
Conséquence pour E5 : la configuration FRR des hyperviseurs (celle de lab3, reprise dans
conf/lab/frr/hv*.conf) fonctionne telle quelle avec les VXLAN de two —advertise-all-vniles voit. Le préalable « configuration FRR des hyperviseurs face aux netns » se réduit à #51.
Erreurs de méthode, corrigées en route
installé en dernier sur les hyperviseurs. Vérifié par l'uptime croissant et les journaux.
sshqui consommait l'entrée standard du script — tous repérés avant d'agir, rien de faux n'a tourné.
Vérification
Mutation : 26 (E4a) et 18 (E4b) mutants, tous détectés (un vrai trou fermé en E4a : la loopback
d'un switch). Suite complète verte,
lab-host.sh60/60 sous bash 5 et 3.2, documentation construiteavec
sphinx-build -W, sorties réelles danslab.rst.Corrections en cours de commit sur
feature-50image-qcow2.rst:seedfromavec barre oblique finale, et l'explication vérifiée.push: extraction avec--no-same-owner(les fichiers gardaient l'UID du Mac).lab.rst: résultats d'E4 sur le serveur.Pour la suite
twoavec systemd-networkd (#46), gateway /public_ip(#31) ; puis, une fois #51 intégré, convergence EVPN, MTU de bout en bout, testnégatif inter-VPC (#41), perte et retour du route reflector.
routeurs.rst) ; une voie detransfert pour l'image Rocky 10.
deploy.shexécuteshflagspareval, depuis un autre dépôt, sansépinglage.
E5 — fait le 2026-10-04 (
b4660c9,909b411, branchefeature-50)Six scénarios versionnés, lancés depuis le Mac sur le lab : 106 vérifications sur 106, en
8 minutes, release
0.2.0rc003(qui contient #51), hv1 sur le DHCP intégré (backend: two), hv2sur dnsmasq.
Outillage
scripts/lab/scenario.sh <scénario>… | all(Mac) envoie à chaque nœud, parlab-host.sh ssh, labibliothèque
scripts/lab/node.shet un bloc du scénario ; une ligneRÉUSSI/ÉCHOUÉ(avec laraison) /
INFOpar vérification, code 1 si une vérification échoue ou si aucune n'a été faite.vm_fails: réussie seulement si le SSH vers la VM a fonctionné etque la commande y a échoué — un SSH en panne ne passe jamais pour une isolation.
seedfromavec barre obliquefinale, cf. E4).
agentpar hyperviseur (/etc/two/agent.ymlposé avantdeploy.sh).lab-host.shqui vérifie la syntaxe de chaque blocréellement envoyé), bash 5 et 3.2 ; mutation : 9 mutants, un équivalent (code simplifié), un vrai
trou fermé (une ressource en
errordoit arrêter l'attente immédiatement, pas au bout de 3 min).Résultats
s1-dhcp-two(#46)two, une VPC, deux subnets, VM démarrées seules puis simultanément : chaque VM a l'adresse de son subnet, bail tenu par systemd-networkd, route par défaut, route vers la VPC et/32vers les métadonnées viainterface_ip; chaque serveur ne connaît que son subnets2-gateway(#31)interface_ip, ou viagatewayavecdefault_route; route vers la VPC toujours viainterface_ip; identique après suppression et recréation du subnets3-isolation-locals4-evpn(#51)localposé par two sur les VXLAN des deux hyperviseurs, VTEP distant appris par EVPN, ping VM ↔ VM entre hyperviseurs, 1472 octets en-M do, 1473 refuséss5-isolation-evpns6-rr-lossPerte du route reflector — mesuré
Un seul route reflector, FRR 10.7.1, sans
graceful-restart. À l'arrêt de FRR sur le routereflector, la session EVPN des hyperviseurs tombe aussitôt ; FRR retire les routes apprises, avec
elles le VTEP distant et l'entrée d'inondation : 0 paquet entre hyperviseurs à 30 s comme à
90 s. Au redémarrage de FRR, VTEP distant et trafic reviennent 31 s plus tard.
Les tunnels ne survivent pas à la perte du route reflector : la redondance (deux route
reflectors) ou
graceful-restartsont nécessaires avant la production.route-reflector.rstle dit désormais.
Erreur de méthode
Le contrôle « chaque serveur DHCP ne connaît que les MAC de son subnet » de
s1était vert mais neprouvait rien : two dérive la MAC du rang de l'IP, et les VM
.10/.11des deux subnets avaient lesmêmes MAC — un mélange serait passé inaperçu. Repéré dans la ligne
INFOdu run ; vérifié à la mainsur le lab encore en marche (chaque serveur ne connaissait que les couples MAC/IP de son subnet) ;
le scénario compare désormais ces couples.
Session
Serveur de 20:44 à 21:13 (
up12 min,lab up6 min 26, scénarios 8 min) : une heure entamée,~0,32 € HT ; projet vide vérifié.
pushextrait désormais sans conserver l'UID du Mac.Pour la suite
rejouer
s6pour mesurer le comportement avec un route reflector restant.graceful-restart: à qualifier comme alternative ou complément.routeurs.rst) et l'image Rocky 10 restent en attente.deploy.shexécuteshflagspareval, depuis un autre dépôt, sans épinglage.0.2.0rc003pars4ets5; reste sa revue technique et sonintégration à
main.Clôture — 2026-10-04
Le lab multi-nœud est livré : une infrastructure de test reproductible, montée et démontée en une
commande, sur laquelle two est déployé comme en production et où des scénarios versionnés qualifient
ce qui ne se voit qu'à plusieurs hyperviseurs. 16 commits sur
feature-50, de28ce00càddcbd2b.Ce qui est livré
scripts/lab-host.sh: serveur Scaleway Elastic Metal loué à l'heure —plan,up(avecprepare),ssh,push,down,session; suppression garantie (trap EXIT,downpar tag et par identifiant)lab plan: topologie YAML déclarative, plan déterministe (adresses, MAC, ports, câbles)lab render: arguments QEMU (câblesdgramen MTU 9000, administration NAT isolée enrestrict=on) et seeds cloud-initlab up/status/down/sshsur le serveur : image vérifiée, disques neufs à chaqueup, attente de cloud-init et des services de chaque rôlefrr-stable, clé épinglée) sur le switch et le route reflector, two pardeploy.shdu dépôt puis FRR sur les hyperviseurs ; configurations FRR dansconf/lab/frr/, incluses par la documentationscripts/lab/scenario.sh) : DHCPtwo, gateway, isolation entre VPC sur un et deux hyperviseurs, EVPN et MTU, perte du route reflector — 106/106Documentation :
docs/developpement/lab.rst(usage, topologie, rôles, scénarios et comment enécrire, sorties réelles),
docs/deploiement/route-reflector.rst(adressage et configuration FRRvalidés dans le lab, perte du route reflector mesurée),
docs/deploiement/image-qcow2.rst(
seedfrom).Ce que le lab a déjà trouvé
hyperviseurs two. Patch validé en release
0.2.0rc003(feature-51) ; revue et merge à faire.seedfromsans barre oblique finale (image-qcow2.rst) : cassé avec cloud-init < 23.1(Debian 12). Corrigé dans la doc.
deploy.shrendait 1 même en cas de succès : corrigé (32940c5).pendant son absence ; retour 31 s après lui. D'où #54.
— résultats sur chaque ticket.
Décisions qui ont changé en route
scwremplacée par l'API REST (résolution d'offre non fiable, risque d'engagement mensuel).d'hôtes et adresses d'équipements de production exclus.
d'EVPN.
genericcloudrendue compatible two, en attendant #53.Tickets issus de ce travail
0.2.0rc003).next_feature).0.2.0).0.2.0), 4ᵉ étape du découpaged'origine, sortie de ce ticket.
Avant de qualifier quoi que ce soit destiné à la production
Conformément aux règles internes, l'outillage ayant été écrit avec assistance IA :
feature-50(Gocmd/lab,internal/lab/*, scriptslab-host.sh,scripts/lab/*) avant merge versmain;dgram,restrict=onvérifié avec témoin),gestion de la clé d'API Scaleway, clés SSH générées sur le serveur jetable, clé du dépôt FRR
épinglée ;
sessions réelles) ;
ticket.
Coût total des sessions Scaleway : 5 heures entamées (E0 1 h, E3 2 h, E4 1 h, E5 1 h), ~1,61 € HT.
Suite
Le lab sert maintenant aux tests complexes : prochain candidat, la campagne L3VNI de #41, à écrire
comme un scénario (
lab.rst, « Écrire un scénario »).