Développement#
+Outillage réservé au développement de two : il ne s’installe sur aucun hyperviseur et ne sert +pas à exploiter un cluster.
+diff --git a/main/_images/architecture-cluster-dark.svg b/main/_images/architecture-cluster-dark.svg new file mode 100644 index 0000000..483d0f0 --- /dev/null +++ b/main/_images/architecture-cluster-dark.svg @@ -0,0 +1,155 @@ + diff --git a/main/_images/architecture-cluster.svg b/main/_images/architecture-cluster.svg new file mode 100644 index 0000000..25028d6 --- /dev/null +++ b/main/_images/architecture-cluster.svg @@ -0,0 +1,155 @@ + diff --git a/main/_images/topologie-cluster-dark.svg b/main/_images/topologie-cluster-dark.svg deleted file mode 100644 index 48a3916..0000000 --- a/main/_images/topologie-cluster-dark.svg +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/main/_images/topologie-cluster.svg b/main/_images/topologie-cluster.svg deleted file mode 100644 index 858d18a..0000000 --- a/main/_images/topologie-cluster.svg +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/main/architecture/contraintes.html b/main/architecture/contraintes.html index 6106adf..2c9e2db 100644 --- a/main/architecture/contraintes.html +++ b/main/architecture/contraintes.html @@ -218,6 +218,13 @@ +
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
précédent
-Diagnostic
+Lab de test multi-nœud
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Topologie cible. Trait plein : plan de données. Trait pointillé : plan de contrôle.#
+Architecture cible. Trait bleu : plan de données (VXLAN). Tirets violets : plan de contrôle +(sessions BGP). Pointillé gris : interfaces créées par l’agent.#
Topologie cible. Trait plein : plan de données. Trait pointillé : plan de contrôle.#
+Architecture cible. Trait bleu : plan de données (VXLAN). Tirets violets : plan de contrôle +(sessions BGP). Pointillé gris : interfaces créées par l’agent.#
Deux plans distincts, à ne pas confondre au moment du diagnostic :
diff --git a/main/deploiement/image-qcow2.html b/main/deploiement/image-qcow2.html index 256e6d7..786b304 100644 --- a/main/deploiement/image-qcow2.html +++ b/main/deploiement/image-qcow2.html @@ -654,6 +654,13 @@ window.runMermaid = runMermaid;Développement
+Interne
detect-zeroes=un
aucun espace à l’host : les qcow2 ne se rétractent pas. L’activation reste utile pour le jour
où l’option sera ajoutée côté agent, mais ne pas compter dessus pour la place disque.
Note
-À vérifier — seedfrom sans barre oblique finale. cloud-init construit l’URL des
-documents en concaténant seedfrom avec meta-data et user-data. Confirmer sur une
-VM réelle que les deux documents sont bien récupérés, et corriger en
-http://169.254.169.254:80/ si ce n’est pas le cas.
Avertissement
+La barre oblique finale de seedfrom est indispensable avant cloud-init 23.1.
+cloud-init construit l’URL des documents en concaténant seedfrom avec meta-data,
+user-data et vendor-data (util.read_seeded). Jusqu’à la 22.4 — celle de Debian 12,
+22.4.2 —, sans barre oblique finale il demande http://169.254.169.254:80meta-data : la
+source échoue, la VM démarre en DataSourceNone, sans nom d’hôte ni user-data. À partir de
+23.1, un drapeau actif par défaut (NOCLOUD_SEED_URL_APPEND_FORWARD_SLASH) ajoute la barre
+oblique manquante, ce qui explique qu’une image récente fonctionne sans elle. Avec la barre
+oblique, la configuration fonctionne quelle que soit la version de cloud-init.
Note
diff --git a/main/deploiement/index.html b/main/deploiement/index.html index 83034bc..8d2cbd5 100644 --- a/main/deploiement/index.html +++ b/main/deploiement/index.html @@ -218,6 +218,13 @@ +Développement
+Interne
Développement
+Interne
Développement
+Interne
Il tourne lui-même en machine virtuelle, ce qui crée une dépendance circulaire à traiter explicitement : la VM qui porte le plan de contrôle du cluster est hébergée par le cluster.
+Le route reflector est sur le même segment L2 que les hyperviseurs, avec trois adresses :
+Adresse |
+Interface |
+Rôle |
+
|---|---|---|
une adresse de |
+interface principale |
+celle de n’importe quelle machine du segment ; les réponses aux hyperviseurs en partent |
+
|
+interface principale, adresse secondaire |
+le lien avec les routeurs de cluster ( |
+
|
+
|
+
|
+
Les hyperviseurs ne connaissent que la loopback : ils ouvrent leur session vers 10.255.255.1
+par leur passerelle (192.168.14.1), les routeurs l’ayant apprise du route reflector en eBGP.
+Aucune adresse d’hyperviseur n’est déclarée côté route reflector : il accepte toute session venant
+du segment (bgp listen range).
L’adresse du lien est une adresse secondaire de l’interface principale, pas une interface à
+part : même L2, même MAC sur le fil, une interface de moins qu’avec un ipvlan. Elle doit
+survivre aux renouvellements DHCP de l’adresse principale : la déclarer dans la configuration
+réseau du système, pas la poser à la main.
Note
-À rédiger. À documenter :
+Non vérifié sur l’image de production. Avec NetworkManager, la forme attendue est :
+nmcli connection modify <connexion> +ipv4.addresses 169.254.0.3/28
+nmcli connection add type dummy ifname lo1 con-name lo1 \
+ ipv4.method manual ipv4.addresses 10.255.255.1/32 ipv6.method disabled
+Ces commandes restent à valider sur l’image golden.
+bgpd et bfdd activés dans /etc/frr/daemons.
frr defaults traditional
+hostname rr1
+log syslog informational
+!
+ip prefix-list RR-LOOPBACK-OUT seq 10 permit 10.255.255.1/32
+!
+route-map NO-IN deny 999
+ description deny
+exit
+!
+router bgp 65000
+ no bgp default ipv4-unicast
+ bgp router-id 10.255.255.1
+ bgp cluster-id 10.255.255.1
+ neighbor CLUSTER peer-group
+ neighbor CLUSTER remote-as 65100
+ neighbor CLUSTER bfd
+ neighbor 169.254.0.1 peer-group CLUSTER
+ neighbor 169.254.0.1 description router-1
+ neighbor 169.254.0.2 peer-group CLUSTER
+ neighbor 169.254.0.2 description router-2
+ neighbor fabric peer-group
+ neighbor fabric remote-as 64600
+ neighbor fabric local-as 64600 no-prepend replace-as
+ neighbor fabric capability extended-nexthop
+ neighbor fabric update-source 10.255.255.1
+ bgp listen range 192.168.14.0/24 peer-group fabric
+ bgp listen limit 200
+ !
+ address-family ipv4 unicast
+ network 10.255.255.1/32
+ neighbor CLUSTER activate
+ neighbor CLUSTER prefix-list RR-LOOPBACK-OUT out
+ neighbor CLUSTER route-map NO-IN in
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor fabric activate
+ neighbor fabric route-reflector-client
+ exit-address-family
+ !
+exit
+!
+Ce qu’elle établit :
la création de la VM : ressources, subnet et mode utilisés, et s’il s’agit d’une VM créée -par l’agent comme les autres ou d’un cas particulier — l’image, elle, est l’image golden de -Construction de l’image qcow2 ;
son adressage, et comment les hyperviseurs le connaissent ;
la configuration du démon de routage qu’elle héberge ;
Routeurs de cluster (groupe CLUSTER) : eBGP vers l’AS 65100, avec BFD, en IPv4
+unicast. Le route reflector n’annonce que sa loopback (RR-LOOPBACK-OUT) et n’accepte
+rien (NO-IN) : il ne reçoit aucune route des routeurs.
Hyperviseurs (groupe fabric) : voisins dynamiques, toute session venant de
+192.168.14.0/24 étant acceptée, jusqu’à 200. local-as 64600 no-prepend replace-as fait
+que, du point de vue des hyperviseurs, la session est en iBGP dans l’AS 64600 — celui de leur
+configuration — alors que le route reflector est en AS 65000 face aux routeurs.
EVPN : seule famille activée vers les hyperviseurs, qui sont ses clients
+(route-reflector-client) : il réfléchit les routes EVPN de chacun vers tous les autres.
Avertissement
+Les tunnels ne survivent pas à la perte du route reflector. Avec un seul route reflector
+et sans graceful-restart, à l’arrêt de FRR sur le route reflector, la session EVPN des
+hyperviseurs tombe aussitôt, FRR retire les routes apprises et, avec elles, le VTEP distant et l’entrée d’inondation du VXLAN — plus
+aucun paquet ne passe entre hyperviseurs, à 30 s comme à 90 s. Au redémarrage de FRR sur le
+route reflector, VTEP distant et trafic reviennent 31 s plus tard. Le trafic entre VM d’un
+même hyperviseur n’est pas concerné. La redondance (deux route reflectors) ou
+graceful-restart sont les deux leviers ; ni l’un ni l’autre n’est encore qualifié.
Note
+À rédiger. Reste à documenter :
+la création de la VM : ressources, subnet et mode utilisés (bridge, pour pouvoir la
+lancer sur n’importe quel hyperviseur), et s’il s’agit d’une VM créée par l’agent comme les
+autres ou d’un cas particulier — l’image, elle, est l’image golden de Construction de l’image qcow2 ;
la redondance : une seule VM route reflector, ou deux, et sur quels hyperviseurs ;
la procédure de reconstruction, et l’état du cluster pendant que le route reflector est -absent — les tunnels déjà établis continuent-ils de fonctionner, et pendant combien de -temps ;
la procédure de reconstruction — l’état du cluster pendant l’absence du route reflector est +décrit ci-dessus : plus de trafic entre hyperviseurs ;
la procédure d’amorçage : ce qui fonctionne, et dans quel ordre, quand on démarre un cluster entier depuis zéro — le premier hyperviseur n’a pas de session FRR établie tant que cette VM n’existe pas, cf. Premier hyperviseur.
Développement
+Interne
terminent le routage vers l’extérieur.
Le routeur de cluster est la passerelle des hyperviseurs et le voisin eBGP du route reflector, dont
+il apprend la loopback : c’est par lui que chaque hyperviseur joint 10.255.255.1 pour ouvrir sa
+session EVPN (VM route reflector).
Adresse |
+Interface |
+Rôle |
+
|---|---|---|
|
+interface du segment des hyperviseurs |
+passerelle par défaut des hyperviseurs et du route reflector |
+
|
+même interface, adresse secondaire |
+lien avec le route reflector ( |
+
Le MTU minimal du segment, imposé par VXLAN, est donné plus bas.
+bgpd et bfdd activés dans /etc/frr/daemons.
frr defaults traditional
+hostname sw1
+log syslog informational
+!
+ip prefix-list RR-LOOPBACK-IN seq 10 permit 10.255.255.1/32
+!
+route-map RR-IN permit 10
+ match ip address prefix-list RR-LOOPBACK-IN
+exit
+!
+route-map NO-OUT deny 999
+exit
+!
+router bgp 65100
+ no bgp default ipv4-unicast
+ bgp router-id 169.254.0.1
+ neighbor 169.254.0.3 remote-as 65000
+ neighbor 169.254.0.3 description rr1
+ neighbor 169.254.0.3 bfd
+ !
+ address-family ipv4 unicast
+ neighbor 169.254.0.3 activate
+ neighbor 169.254.0.3 route-map RR-IN in
+ neighbor 169.254.0.3 route-map NO-OUT out
+ exit-address-family
+ !
+exit
+!
+Ce qu’elle établit :
+Route reflector : eBGP de l’AS 65100 vers l’AS 65000, avec BFD, en IPv4 unicast.
En entrée, seule la loopback du route reflector est acceptée (RR-IN) ; en sortie,
+rien n’est annoncé (NO-OUT). Symétrique de RR-LOOPBACK-OUT et NO-IN côté route
+reflector : chaque côté filtre, aucun ne dépend du filtre de l’autre.
Aucune session avec les hyperviseurs : le routeur leur sert de passerelle, pas de voisin BGP.
Le route reflector déclare aussi le second routeur (169.254.0.2). La redondance est l’objet de
+#54.
Note
-À rédiger. Cette page attend les éléments de terrain. Pour chacun des trois niveaux :
+Ce qui précède est le contrat du routeur de cluster : adresses, ASN, session, filtres. Sa +traduction dans la configuration d’un constructeur n’a pas sa place ici.
+Note
+À rédiger. Reste à documenter, faute d’éléments de terrain :
le matériel ou le logiciel employé, et la version de référence ;
la configuration de référence : interfaces, adressage, protocole de routage et numéros -d’AS ;
la redondance : combien d’équipements, quel mécanisme de bascule, quel comportement attendu -pendant une bascule ;
ce qui est annoncé et ce qui est filtré à chaque niveau ;
pour le routeur de cluster : le matériel employé et sa version de référence ; la redondance +— deux routeurs, mécanisme de bascule, comportement attendu pendant une bascule +(#54) ;
pour les routeurs de datacentre et de bordure : tout — équipement, configuration de +référence, ce qui est annoncé et filtré à chaque niveau, redondance ;
l’ordre de mise en service, et ce qui doit être opérationnel avant de préparer le premier hyperviseur.
Indépendamment des choix d’équipement, deux contraintes viennent de ce que fait l’agent.
@@ -445,6 +540,11 @@ joignable que depuis le réseau d’administration. Cf. diff --git a/main/developpement/index.html b/main/developpement/index.html new file mode 100644 index 0000000..83a4ced --- /dev/null +++ b/main/developpement/index.html @@ -0,0 +1,443 @@ + + + + + + + + + + +Documentation two 0.1.0
+ +Mise en œuvre
+ +Exploitation
+Développement
+ +Interne
+Outillage réservé au développement de two : il ne s’installe sur aucun hyperviseur et ne sert +pas à exploiter un cluster.
+Documentation two 0.1.0
+ +Mise en œuvre
+ +Exploitation
+Développement
+ +Interne
+Ce qui fait l’intérêt 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 passe inaperçue (voir +Architecture du cluster). Le lab reproduit la topologie du cluster — +hyperviseurs, route reflector, switch L3 — sous forme de VM, sur un serveur physique loué à +l’heure.
+Le pourquoi des choix (serveur physique plutôt que VM cloud, câbles QEMU, MTU 9000, versions) est +consigné sur le ticket #50. Cette page décrit +comment s’en servir.
+Note
+État actuel : le lab de #50 est livré — serveur loué à l’heure (scripts/lab-host.sh),
+topologie déclarative et plan déterministe (lab plan), VM rendues et lancées sur le
+serveur (lab render, lab up / status / down / ssh), rôles installés au
+démarrage (FRR sur le switch et le route reflector, two par deploy.sh sur les
+hyperviseurs) et scénarios versionnés (test/e2e/run.sh). La redondance du plan de
+contrôle — deux route reflectors, deux switchs — est l’objet de
+#54.
Un serveur Scaleway Elastic Metal, créé pour une campagne de tests puis supprimé.
+Offre |
+
|
+
Matériel |
+2 × Xeon E5-2620 v4 or equivalent, 256 Go, 2 × 1 To SSD |
+
Système |
+Debian 12, installé par Scaleway à la création |
+
Pourquoi Intel |
+lab3 est en Intel : two lance ses VM en |
+
Or equivalent n’est pas une clause de style : le premier serveur livré était un
+Xeon E5-2640 v3 (Haswell, la génération de lab3), pas le E5-2620 v4 annoncé. Relever
+lscpu au début de chaque campagne.
Un projet dédié au lab, séparé de toute autre ressource. lab-host.sh down supprime
+tout serveur de lab du projet : il ne doit rien y avoir d’autre.
Une clé d’API limitée à ce projet, avec les droits Elastic Metal et la lecture des clés SSH +du projet. Rien d’autre.
Les clés SSH publiques enregistrées dans le projet, injectées à l’installation : sans elle,
+le serveur serait facturé sans que personne puisse s’y connecter, et plan refuse de
+continuer.
Le quota Elastic Metal. L”EM-B212X-SSD exige un compte dont le moyen de paiement et
+l’identité sont validés ; le quota est alors de 2. Vérifier dans la console : Organisation →
+Quotas → Elastic Metal. Voir les quotas Scaleway.
Tout ce dont le script a besoin vit sous ~/.config/two-lab/, hors du dépôt.
~/.config/two-lab/scaleway.envIdentifiants Scaleway, une ligne CLÉ=valeur chacun :
SCW_SECRET_KEY=<clé secrète>
+SCW_DEFAULT_PROJECT_ID=<identifiant du projet de lab>
+SCW_DEFAULT_ZONE=fr-par-1
+le fichier doit être en 0600 : le script refuse de s’en servir s’il est lisible par
+d’autres que son propriétaire ;
il est lu, jamais exécuté — pas de source ; seules ces trois clés sont reconnues ;
une variable d’environnement du même nom l’emporte sur le fichier ;
la clé d’accès (SCW…) n’est pas nécessaire : l’API REST n’authentifie que par la clé
+secrète, dans l’en-tête X-Auth-Token.
Pour changer de clé, remplacer la ligne SCW_SECRET_KEY= ; rien d’autre à modifier.
~/.config/two-lab/ssh/lab_ed25519Clé SSH dédiée au lab, sans phrase de passe, pour que les sessions tournent sans
+intervention. Sa partie publique doit être enregistrée dans le projet. Quand elle existe, le
+script l’utilise seule (IdentitiesOnly, agent désactivé) ; sinon il retombe sur
+l’agent SSH.
Elle ne doit ouvrir que les serveurs éphémères du projet de lab : ne jamais l’installer sur +lab3 ni sur une machine durable. En cas de doute, la retirer du projet et en générer une +autre :
+mkdir -p ~/.config/two-lab/ssh && chmod 700 ~/.config/two-lab ~/.config/two-lab/ssh
+ssh-keygen -t ed25519 -N '' -C two-lab-automation -f ~/.config/two-lab/ssh/lab_ed25519
+~/.cache/two-lab/État de la session en cours : adresse et utilisateur du serveur, known_hosts dédié. Vidé
+par down.
usage: lab-host.sh <commande> [arguments]
+
+ plan résout l'offre horaire, l'OS et les clés SSH, affiche la requête de création
+ et le prix ; ne crée rien
+ up crée le serveur de lab, attend la fin de son installation et son SSH,
+ puis le prépare (voir prepare)
+ status liste les serveurs de lab du projet
+ ssh [commande] se connecte au serveur de lab ; avec une commande, un terminal n'est demandé
+ que si l'entrée standard en est un
+ prepare installe sur le serveur ce dont lab a besoin (qemu, genisoimage), vérifie
+ /dev/kvm et la virtualisation imbriquée ; lancé aussi par up
+ push <topologie> compile cmd/lab pour linux/amd64 et dépose sur le serveur ~/lab et le
+ répertoire de la topologie dans ~/topology/ (avec les fichiers qu'elle
+ référence) ; ensuite : ssh './lab up topology/<topologie>'
+ down supprime tous les serveurs de lab du projet et attend leur disparition
+ session [cmd] up, puis la commande distante (ou un shell), puis down quoi qu'il arrive
+plan et status sont gratuits ; up et session créent un serveur facturé.
Toujours commencer par plan. Il valide la clé d’API, le quota d’offre, l’OS et les clés SSH,
+et montre exactement ce qui serait commandé :
$ scripts/lab-host.sh plan
+== offre : EM-B212X-SSD (ddaf8ba6-b2b2-4279-8af3-51930fb602f8), facturation hourly, stock available
+== prix : 0.321 EUR HT par heure, frais de mise en service 0 EUR
+== os : Debian 12 (Bookworm) (83640d93-a0b8-45ad-9c9f-30cae48380a4), utilisateur root
+== clés : 2 clé(s) SSH du projet
+== requête : POST /baremetal/v1/zones/fr-par-1/servers
+session est la forme normale d’usage : le serveur est supprimé à la fin, que la commande
+réussisse, échoue, ou que la session soit interrompue (Ctrl-C, TERM, fermeture du terminal).
$ scripts/lab-host.sh session 'uname -a; lscpu | grep -E "Model name|^CPU\(s\)|Virtualization"; free -g | head -2; echo "nested=$(cat /sys/module/kvm_intel/parameters/nested)"; ls -l /dev/kvm'
+== création de two-lab (EM-B212X-SSD, 0.321 EUR/h HT)
+== serveur 2ecc1e6a-0de8-48c0-a198-93857eee5957 créé, facturé jusqu'à 'lab-host.sh down'
+== serveur 2ecc1e6a-0de8-48c0-a198-93857eee5957 : ordered, installation to_install
+== serveur 2ecc1e6a-0de8-48c0-a198-93857eee5957 : ready, installation installing
+…
+== serveur 2ecc1e6a-0de8-48c0-a198-93857eee5957 : ready, installation completed
+== SSH pas encore joignable, nouvel essai dans 20s
+…
+== prêt : root@<adresse>
+Linux two-lab 6.1.0-53-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.187-1 (2026-09-07) x86_64 GNU/Linux
+CPU(s): 32
+Model name: Intel(R) Xeon(R) CPU E5-2640 v3 @ 2.60GHz
+…
+Virtualization: VT-x
+ total used free shared buff/cache available
+Mem: 251 1 250 0 0 249
+nested=Y
+crw-rw---- 1 root kvm 10, 232 Oct 3 17:23 /dev/kvm
+== session terminée (code 0), suppression du serveur
+== suppression de 2ecc1e6a-0de8-48c0-a198-93857eee5957
+== aucun serveur de lab ne reste dans le projet
+Extrait de la première campagne (lignes répétées remplacées par …). Compter environ 15 minutes entre la création et le SSH disponible : 13 min 30 à la première
+campagne, suppression comprise. SSH ne répond pas tout de suite après la fin de l’installation —
+environ 100 secondes la première fois — d’où l’attente intégrée à up. Le code de sortie de
+session est celui de la commande distante.
up, ssh et down séparément servent au debug interactif — et laissent la suppression
+à la charge de l’utilisateur.
Une campagne sur le lab enchaîne ces commandes depuis le Mac ; lab s’exécute sur le serveur
+(voir Lancement des VM) :
scripts/lab-host.sh up
+scripts/lab-host.sh push test/e2e/topologies/evpn-2hv.yml
+scripts/lab-host.sh ssh './lab up topology/evpn-2hv.yml'
+scripts/lab-host.sh ssh './lab ssh hv1' # shell interactif sur hv1
+scripts/lab-host.sh ssh './lab ssh hv1 ip -br a' # commande, code de retour propagé
+scripts/lab-host.sh down
+push transfère par la connexion SSH du script — mêmes options, même clé, même
+known_hosts que ssh : le binaire par cat, le répertoire de la topologie par tar
+(sans les métadonnées macOS), chacun renommé une fois complet. Tout le répertoire part, pour que
+les fichiers que la topologie référence (frr/*.conf) arrivent avec elle.
Un lab est décrit par un fichier YAML : des nœuds (les VM) et des segments (des réseaux L2
+portés par un switch). Exemple livré, test/e2e/topologies/evpn-2hv.yml :
name: evpn-2hv
+
+images:
+ debian12:
+ url: https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
+ sums: https://cloud.debian.org/images/cloud/bookworm/latest/SHA512SUMS
+
+segments:
+ underlay:
+ switch: sw1
+ cidr: 192.168.14.0/24
+ mtu: 9000
+
+nodes:
+ sw1:
+ { role: switch, image: debian12, cpus: 2, memory: 1024,
+ secondary: { underlay: [169.254.0.1/28] }, frr: frr/sw1.conf }
+ rr1:
+ { role: rr, image: debian12, cpus: 1, memory: 1024, segments: [underlay],
+ addresses: { underlay: 192.168.14.2 }, secondary: { underlay: [169.254.0.3/28] },
+ loopback: 10.255.255.1/32, frr: frr/rr1.conf }
+ hv1:
+ { role: hypervisor, image: debian12, cpus: 4, memory: 16384, segments: [underlay],
+ addresses: { underlay: 192.168.14.11 }, frr: frr/hv1.conf, release: 0.2.0rc003,
+ agent: agent/two.yml }
+ hv2:
+ { role: hypervisor, image: debian12, cpus: 4, memory: 16384, segments: [underlay],
+ addresses: { underlay: 192.168.14.12 }, frr: frr/hv2.conf, release: 0.2.0rc003 }
+Chaque nœud non-switch est relié au switch de chacun de ses segments par un câble virtuel QEMU ; +le switch met ces câbles dans un bridge et porte la passerelle du segment.
+Ce que le fichier déclare :
+imagesurl de l’image qcow2 et sums du fichier de sommes à vérifier, tous deux en https://.
segmentsswitch (un nœud de rôle switch), cidr IPv4 entre /8 et /30, mtu
+facultatif — 9000 par défaut, entre 1280 et 9000. Nom : 12 caractères au plus, minuscules et
+chiffres, parce qu’il devient le nom d’interface dans les VM et, préfixé de br-, celui du
+bridge (15 caractères au plus sous Linux).
nodesrole (switch, rr ou hypervisor), image, cpus, memory en Mio
+(256 au moins), segments auxquels le nœud est relié, et addresses pour fixer
+l’adresse d’un nœud sur un segment (addresses: {underlay: 192.168.14.50}). Un switch ne
+déclare ni segments ni addresses : il porte ceux dont il est le switch.
Champs de rôle, facultatifs :
+secondary — des adresses supplémentaires par segment, avec leur longueur de préfixe
+(secondary: {underlay: [169.254.0.3/28]}), posées sur la même interface que l’adresse
+principale : même L2, même MAC. Elles doivent être hors du CIDR du segment, pour ne
+jamais croiser l’attribution automatique. Sur un switch, elles vont sur le bridge du
+segment ;
loopback — une adresse sur une interface dummy nommée lo1
+(loopback: 10.255.255.1/32) ;
frr — le chemin d’un frr.conf, relatif au fichier de topologie : FRR est installé
+au démarrage et la configuration déposée telle quelle (voir Rôles).
release — obligatoire pour un hyperviseur, refusé ailleurs : le tag de la release de
+two que deploy.sh installe (release: 0.2.0rc003). Sans lui, deploy.sh prendrait la
+dernière release, et le lab ne serait plus reproductible.
agent — pour un hyperviseur seulement : le chemin d’un agent.yml, relatif au fichier
+de topologie, déposé dans /etc/two/agent.yml avant deploy.sh. Sans lui, l’agent tourne
+avec sa configuration par défaut. L’exemple met hv1 sur le serveur DHCP intégré
+(test/e2e/topologies/agent/two.yml : dhcp.backend: two) et laisse hv2 sur dnsmasq.
mgmt0 et lo1 sont réservés : aucun segment ne peut porter ces noms.
Ce que l’outil en déduit, de façon déterministe — même fichier, même plan :
+Passerelle d’un segment |
+la première adresse du CIDR, portée par le switch sur |
+
Adresse d’un nœud |
+les suivantes, dans l’ordre de déclaration des nœuds ; une adresse fixée par
+ |
+
Câbles |
+un par couple (segment, nœud), segments puis nœuds dans l’ordre de déclaration ; le
+câble i utilise les ports UDP |
+
MAC |
+
|
+
Interfaces |
+côté nœud, le nom du segment ; côté switch, |
+
SSH d’administration |
+
|
+
Avertissement
+Réordonner les nœuds ou les segments dans le fichier change les adresses, les MAC et les
+ports. C’est assumé pour un lab ; lab plan montre le résultat avant tout lancement.
Limites : 1000 nœuds, 256 segments, et autant de câbles que la plage UDP le permet (22 768).
+lab plan valide le fichier et affiche le plan, sans rien lancer :
$ go run ./cmd/lab plan test/e2e/topologies/evpn-2hv.yml
+lab evpn-2hv: nodes 4, segments 1, cables 3
+
+nodes
+ name role image cpus memory ssh
+ sw1 switch debian12 2 1024 MiB 127.0.0.1:2200
+ rr1 rr debian12 1 1024 MiB 127.0.0.1:2201
+ hv1 hypervisor debian12 4 16384 MiB 127.0.0.1:2202
+ hv2 hypervisor debian12 4 16384 MiB 127.0.0.1:2203
+
+roles
+ name loopback secondary frr release agent
+ sw1 - underlay 169.254.0.1/28 sw1.conf - -
+ rr1 lo1 10.255.255.1/32 underlay 169.254.0.3/28 rr1.conf - -
+ hv1 - - hv1.conf 0.2.0rc003 two.yml
+ hv2 - - hv2.conf 0.2.0rc003 -
+
+segment underlay: 192.168.14.0/24, mtu 9000, switch sw1, bridge br-underlay, gateway 192.168.14.1
+ node interface address mac udp switch port mac udp
+ rr1 underlay 192.168.14.2/24 02:4c:00:01:00:00 20000 <-> sw1 p0 02:4c:00:01:00:01 20001
+ hv1 underlay 192.168.14.11/24 02:4c:00:02:00:00 20002 <-> sw1 p1 02:4c:00:02:00:01 20003
+ hv2 underlay 192.168.14.12/24 02:4c:00:03:00:00 20004 <-> sw1 p2 02:4c:00:03:00:01 20005
+Un fichier invalide est refusé avec toutes ses erreurs à la fois, et un code de sortie 1. Les +champs inconnus et les clés en double sont refusés aussi :
+$ lab plan cassee.yml
+lab: cassee.yml:
+segment underlay: rr1 is a rr, not a switch
+segment underlay: cidr 10.250.0.0/31 prefix length out of range [/8, /30]
+node sw1: switch carries no segment
+Les ASN, la loopback du route reflector, le lien 169.254.0.0/28 et le subnet des hyperviseurs
+de l’exemple sont ceux de la production (décision du 2026-10-04, #50) : les fichiers de
+test/e2e/topologies/ restent ainsi au plus près de ce qui tourne réellement. Toutes les adresses y sont
+fixées par addresses — le route reflector en .2, les hyperviseurs à partir de .11 —
+pour que le modèle se lise sans le plan et ne dépende pas de l’ordre de déclaration : le
+frr.conf d’un hyperviseur, écrit à la main, porte son adresse en router-id. Seul le switch
+n’en déclare pas : il porte toujours la passerelle, la première adresse du segment.
Les configurations FRR du lab vivent dans test/e2e/topologies/frr/, une par nœud, écrites à la main :
+ce sont les mêmes fichiers que la documentation de déploiement inclut, pour que le lab qualifie
+exactement ce qu’elle prescrit. Celle du route reflector :
frr defaults traditional
+hostname rr1
+log syslog informational
+!
+ip prefix-list RR-LOOPBACK-OUT seq 10 permit 10.255.255.1/32
+!
+route-map NO-IN deny 999
+ description deny
+exit
+!
+router bgp 65000
+ no bgp default ipv4-unicast
+ bgp router-id 10.255.255.1
+ bgp cluster-id 10.255.255.1
+ neighbor CLUSTER peer-group
+ neighbor CLUSTER remote-as 65100
+ neighbor CLUSTER bfd
+ neighbor 169.254.0.1 peer-group CLUSTER
+ neighbor 169.254.0.1 description router-1
+ neighbor 169.254.0.2 peer-group CLUSTER
+ neighbor 169.254.0.2 description router-2
+ neighbor fabric peer-group
+ neighbor fabric remote-as 64600
+ neighbor fabric local-as 64600 no-prepend replace-as
+ neighbor fabric capability extended-nexthop
+ neighbor fabric update-source 10.255.255.1
+ bgp listen range 192.168.14.0/24 peer-group fabric
+ bgp listen limit 200
+ !
+ address-family ipv4 unicast
+ network 10.255.255.1/32
+ neighbor CLUSTER activate
+ neighbor CLUSTER prefix-list RR-LOOPBACK-OUT out
+ neighbor CLUSTER route-map NO-IN in
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor fabric activate
+ neighbor fabric route-reflector-client
+ exit-address-family
+ !
+exit
+!
+Au premier démarrage, cloud-init installe FRR (frr-stable de deb.frrouting.org, sans les
+paquets recommandés), active bgpd — et bfdd sur le switch et le route reflector —, puis
+dépose le frr.conf du nœud et redémarre FRR. La mise à jour des index de paquets est réessayée
+pendant cinq minutes : un nœud peut démarrer avant que le switch, par lequel il sort, n’ait posé
+son NAT.
La clé du dépôt FRR n’est pas téléchargée au démarrage : elle est enregistrée dans lab
+(internal/lab/render/frrouting.gpg) et déposée par cloud-init. Elle a été récupérée le
+2026-10-04 sur deb.frrouting.org ; les empreintes de ses clés primaires sont publiées sous la
+même valeur sur keys.openpgp.org et keyserver.ubuntu.com :
3D99 68AC 9AE7 BE11 6928 8DDB 1FD5 8398 95F5 7FDA David Lamparter
+4A56 C773 8BB3 F815 95A8 05D2 A832 7699 08F1 3ED1 FRRouting Debian Repository
+A90F C36D 9429 4097 98E9 C2D8 74DE ED43 AB19 4DBF Jafar Al-Gharaibeh
+Une clé renouvelée par FRR fera échouer l’installation (signature inconnue) : remplacer le fichier +après avoir vérifié les nouvelles empreintes.
+Hyperviseurs. Ils se déploient comme en production, par deploy.sh — celui du dépôt,
+embarqué dans lab avec bootstrap_kvm.sh (paquet scripts) et déposé dans
+/opt/two/scripts/, où deploy.sh cherche d’abord bootstrap_kvm.sh :
deploy.sh --noup_script -i -u <segment> -t <release>
+--noup_script empêche l’auto-mise à jour de remplacer le script par celui de main : le lab
+teste les scripts de sa branche. L’uplink -u est l’interface qui porte la route par défaut —
+celle du premier segment de l’hyperviseur dans l’ordre de déclaration des segments — parce que
+deploy.sh y lit l’adresse et la passerelle qu’il déplace sur br-000000. deploy.sh
+télécharge la release sur git.g3e.fr sans réessayer : le lancement attend d’abord que le serveur
+réponde, à travers le switch. FRR est installé après : il démarre sur le réseau final.
Un seul script de provisionnement par nœud. cloud-init exécute runcmd comme un script
+sh sans set -e : seule la dernière commande compte, et un deploy.sh en échec suivi d’un
+FRR installé avec succès passerait pour un démarrage réussi. Chaque nœud reçoit donc
+/usr/local/sbin/lab-provision, en set -eu, qui enchaîne ses étapes ; runcmd n’appelle
+que lui, et la première étape en échec met cloud-init en erreur.
Ce que vérifie lab up, une fois cloud-init terminé sans erreur : agent.service actif
+sur chaque hyperviseur, frr actif sur chaque nœud qui en a un. Ce contrôle couvre ce que
+cloud-init ne voit pas — si la migration réseau échoue, deploy.sh arme un redémarrage de
+secours, et la VM redémarrée ne rejoue pas runcmd.
Avertissement
+Un hyperviseur du lab ne survit pas à un redémarrage. En production, la racine est en
+tmpfs et deploy.sh --bootstrap est rejoué à chaque démarrage ; dans le lab, -i n’est
+exécuté qu’au premier, et la migration réseau, qui n’est pas persistée, est perdue. Recréer le
+lab : lab down puis lab up.
Vérifié le 2026-10-04 sur le serveur de lab, topologie evpn-2hv, release 0.2.0rc002 :
$ scripts/lab-host.sh ssh './lab up -timeout 25m topology/evpn-2hv.yml'
+sw1: started
+rr1: started
+hv1: started
+hv2: started
+sw1: ready
+rr1: ready
+hv1: ready
+hv2: ready
+Vérification |
+Résultat |
+
|---|---|
|
+6 min 15 |
+
session switch ↔ route reflector, IPv4 unicast, BFD |
+Established, BFD up ; le switch reçoit la seule loopback du route reflector |
+
sessions EVPN des deux hyperviseurs vers la loopback du route reflector |
+Established, voisins dynamiques, stables |
+
réseau d’un hyperviseur après |
+adresse sur |
+
VPC, subnet |
+
|
+
DHCP et routes (option 121) servis par two à la VM |
+conformes, route |
+
métadonnées, image configurée selon Construction de l’image qcow2 |
+
|
+
VM ↔ VM entre les deux hyperviseurs, même subnet |
+échec : les VXLAN de two n’ont pas d’adresse VTEP locale, rien n’est annoncé en EVPN — +#51 ; avec l’adresse posée, ping et MTU 1500 +passent |
+
Note
+Le switch du lab est un routeur Linux avec FRR, par choix. Il joue le rôle générique de
+routeur de cluster : passerelle des hyperviseurs, session eBGP avec BFD vers le route reflector,
+dont il n’accepte que la loopback (test/e2e/topologies/frr/sw1.conf). L’équipement réel dépend de qui
+déploie l’infrastructure (MikroTik aujourd’hui ; Cisco, Juniper, Arista… demain) : il n’a besoin
+que de BGP et d’EVPN, et sa configuration propre au constructeur n’a pas sa place dans le lab.
lab render produit, pour chaque nœud, ce qu’il faut pour démarrer sa VM — sans rien lancer :
$ go run ./cmd/lab render -key ~/.config/two-lab/ssh/lab_ed25519.pub test/e2e/topologies/evpn-2hv.yml <répertoire>
+<répertoire>/<nœud>/ reçoit :
qemu.argsLes arguments de qemu-system-x86_64, un par ligne : rien à échapper, rien à
+interpréter par un shell.
meta-data, user-data, network-configLes trois fichiers NoCloud de cloud-init, à mettre dans une image de volume cidata.
Les chemins de la VM (disk.qcow2, seed.iso, console.log, qmp.sock, qemu.pid)
+sont ceux du répertoire du nœud ; -key peut être répété, et accepte un fichier
+authorized_keys (lignes vides et commentaires ignorés). Les fichiers sont créés en 0600.
Ce que contiennent les arguments QEMU d’un hyperviseur — extrait réel, côté réseau :
+-netdev
+user,id=mgmt0,restrict=on,ipv6=off,hostfwd=tcp:127.0.0.1:2202-:22
+-device
+virtio-net-pci,netdev=mgmt0,mac=02:4d:00:02:00:00,romfile=
+-netdev
+dgram,id=underlay,local.type=inet,local.host=127.0.0.1,local.port=20002,remote.type=inet,remote.host=127.0.0.1,remote.port=20003
+-device
+virtio-net-pci,netdev=underlay,mac=02:4c:00:02:00:00,host_mtu=9000,romfile=
+Les choix qui s’y lisent :
+machine q35, -accel kvm -cpu host — le KVM imbriqué des hyperviseurs du lab en
+dépend ; -nodefaults pour qu’aucun périphérique implicite ne s’ajoute ;
administration (mgmt0) : le NAT de QEMU, MAC 02:4d:<nœud>:<nœud>:00:00, SSH redirigé
+sur la boucle locale de l’hôte. restrict=on pour tous les nœuds sauf le switch : un
+nœud isolé ne joint ni l’hôte ni l’extérieur par là, seule la redirection SSH passe.
+ipv6=off partout (voir plus bas) ;
câbles : dgram sur 127.0.0.1, les deux extrémités d’un câble se répondent
+(port local de l’une = port distant de l’autre), host_mtu annonce le MTU du segment au
+guest ;
romfile= vide sur toutes les cartes : pas de ROM de démarrage réseau, donc pas de repli
+sur un démarrage PXE si le firmware ne trouve pas le disque. Pendant les essais de #50, une VM
+restée bloquée sans rien écrire sur sa console, CPU au repos, avait toutes les apparences de
+ce repli ; la cause n’a pas été isolée, l’option est une précaution.
Ce que fait cloud-init :
+toutes les VM : interfaces nommées d’après leur MAC (mgmt0, nom du segment, p<i>),
+dhcp4: false partout, mgmt0 en 10.0.2.15/24 sans passerelle ; connexion SSH par
+clé seulement, utilisateur debian, root désactivé, mot de passe refusé ;
un nœud : adresse sur chaque segment, MTU du segment, route par défaut et DNS
+(1.1.1.1, 8.8.8.8) sur son premier segment — la sortie Internet passe par le
+switch ;
le switch : route par défaut par mgmt0 ; un service lab-switch crée un bridge
+br-<segment> par segment (STP désactivé, MTU du segment), y branche ses ports, porte la
+passerelle, active le routage et masque (NAT nftables) les segments vers mgmt0. Le
+service est rejoué à chaque démarrage.
Le switch et le route reflector n’ont pas besoin de KVM imbriqué : sw1 et rr1 de
+l’exemple ont été démarrés sur un Mac, en émulation (TCG), avec Debian 12 generic et
+les fichiers produits par lab render.
Vérification |
+Résultat |
+
|---|---|
interfaces nommées et adressées, bridge |
+conforme |
+
service |
+conforme |
+
|
+passe |
+
|
+refusé : |
+
Internet depuis |
+passe, par |
+
|
+bloqué |
+
|
+bloqué |
+
Un défaut trouvé par cet essai, et corrigé : sans ipv6=off, le NAT de QEMU annonce un
+préfixe IPv6 et mgmt0 reçoit une route IPv6 par défaut — vers une impasse, puisque
+restrict=on bloque tout. Pas de fuite, mais chaque 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.
Note
+Un ping vers 10.0.2.2 n’est pas un test d’isolation : c’est la passerelle virtuelle de
+QEMU qui répond elle-même, restrict=on ou non. Seule une connexion vers un vrai service de
+l’hôte, avec un témoin qui y parvient, le prouve.
Reste à vérifier sur le serveur de lab : les hyperviseurs, qui exigent KVM imbriqué.
+lab s’exécute sur le serveur de lab. Il garde l’état du lab dans un répertoire
+(-run, par défaut ~/lab-run) : status, down et ssh n’ont donc pas besoin de la
+topologie.
lab up [-run dir] [-cache dir] [-timeout 20m] <topologie.yml>
+lab status [-run dir]
+lab down [-run dir]
+lab ssh [-run dir] <nœud> [commande…]
+lab uprefuse de continuer si un lab tourne déjà dans le répertoire ;
télécharge chaque image dans le cache (-cache, par défaut ~/.cache/two-lab) et la
+vérifie contre SHA512SUMS ; une image déjà présente et toujours conforme n’est pas
+retéléchargée, la liste des sommes est relue à chaque fois ;
génère une paire de clés SSH dans le répertoire du lab, si elle n’existe pas encore ;
pour chaque nœud : fichiers de lab render, disque neuf en overlay qcow2 sur
+l’image (qemu-img create -b, 20 Gio annoncés), image cidata (genisoimage) ;
démarre les QEMU, switchs d’abord, détachés (-daemonize) : ils survivent à la
+session SSH qui les a lancés ;
attend sur chaque nœud la fin de cloud-init (cloud-init status --wait par SSH),
+jusqu’au délai -timeout.
La topologie est copiée dans <run>/topology.yml. Un échec laisse les nœuds démarrés en
+place : lab status, puis lab down.
lab statusPour chaque nœud : rôle, état du processus QEMU, PID, port SSH sur la boucle locale.
+lab downArrête chaque QEMU par SIGTERM, puis SIGKILL au bout de 30 s. Les disques sont
+conservés jusqu’au prochain up, qui les recrée.
lab sshOuvre un shell sur un nœud, ou y exécute une commande, avec la clé générée par up. lab
+cède la place à ssh, dont le code de retour est donc celui de la commande. Un terminal
+n’est demandé (-t) que si l’entrée de lab en est un : depuis un script, ni
+pseudo-terminal ni \r\n dans la sortie.
Comme ssh, lab ssh recolle ses arguments par des espaces et les confie à un shell
+distant — et depuis le Mac, il y en a deux : celui du serveur, puis celui de la VM.
+Une commande qui contient elle-même des guillemets se passe en une seule chaîne :
$ echo | scripts/lab-host.sh ssh './lab ssh hv1 sh -c "exit 42"'; echo "rc 42=$?"
+rc 42=0
+$ echo | scripts/lab-host.sh ssh "./lab ssh hv1 'sh -c \"exit 42\"'"; echo "rc 42=$?"
+rc 42=42
+Dans le premier cas, la VM reçoit sh -c exit 42 : exit sans argument, 42 en
+$0. Pour plus d’une commande, passer un script sur l’entrée standard :
+scripts/lab-host.sh ssh "./lab ssh hv1 'sudo bash -s'" < script.sh.
Un processus n’est tenu pour celui d’un nœud que si son PID, lu dans qemu.pid, désigne un
+processus vivant dont la ligne de commande (/proc/<pid>/cmdline) contient -name <nœud>.
+Un PID réutilisé par un autre programme n’est donc jamais signalé.
Avertissement
+Le cache range une image sous son nom de fichier, et l’URL de Debian est latest : une
+nouvelle publication remplace le fichier, et les overlays existants pointeraient sur une
+base différente. up recrée toujours les disques, ce qui suffit avec un lab par serveur ;
+ne pas relancer un QEMU à la main à partir d’un qemu.args après un up ultérieur.
Une campagne réelle, de la création du serveur à la première commande sur une VM — sorties du +2026-10-04 :
+$ scripts/lab-host.sh up
+== création de two-lab (EM-B212X-SSD, 0.321 EUR/h HT)
+…
+== préparation du serveur : qemu, genisoimage, KVM imbriqué
+…
+qemu QEMU emulator version 7.2.22 (Debian 1:7.2+dfsg-7+deb12u18+b3), nested=Y
+== prêt : root@<adresse>
+$ scripts/lab-host.sh push test/e2e/topologies/evpn-2hv.yml
+== compilation de lab (linux/amd64)
+== déposés sur le serveur : ~/lab, ~/evpn-2hv.yml — ensuite : lab-host.sh ssh './lab up evpn-2hv.yml'
+$ scripts/lab-host.sh ssh './lab up evpn-2hv.yml'
+sw1: started
+rr1: started
+hv1: started
+hv2: started
+sw1: ready
+rr1: ready
+hv1: ready
+hv2: ready
+$ scripts/lab-host.sh ssh './lab status'
+node role state pid ssh
+sw1 switch running 5158 127.0.0.1:2200
+rr1 rr running 5171 127.0.0.1:2201
+hv1 hypervisor running 5182 127.0.0.1:2202
+hv2 hypervisor running 5196 127.0.0.1:2203
+lab up a pris 49 secondes, téléchargement et vérification de l’image (427 Mio) compris ;
+l’essentiel du temps d’une campagne est la livraison du serveur (environ 25 minutes avec
+prepare).
Le 2026-10-04, sur un Xeon E5-2640 v3, Debian 12 et QEMU 7.2 sur le serveur, topologie
+evpn-2hv :
Vérification |
+Résultat |
+
|---|---|
|
+passe |
+
|
+refusé : |
+
sortie Internet de hv1 |
+par |
+
hv1 vers un service TCP du serveur par |
+refusé |
+
hv1 vers Internet par |
+refusé |
+
|
+présent, |
+
racine de hv1 (overlay de 20 Gio) |
+20 Go : |
+
code de retour à travers |
+propagé jusqu’au Mac |
+
|
+shell interactif |
+
|
+conforme |
+
Les scénarios se lancent depuis le Mac, sur un lab démarré (up, push, ./lab up) :
test/e2e/run.sh s1 # un scénario
+test/e2e/run.sh s1 s3 # plusieurs
+test/e2e/run.sh all # tous, dans l'ordre
+Chacun affiche une ligne RÉUSSI ou ÉCHOUÉ par vérification — un échec porte la dernière
+ligne de la commande en cause —, des lignes INFO pour les mesures, puis son bilan. Le code de
+sortie vaut 1 si une vérification échoue ou si aucune n’a été faite.
Scénario |
+Ce qui doit être vrai |
+Hyperviseurs |
+
|---|---|---|
|
+backend DHCP |
+hv1 |
+
|
+route par défaut via |
+hv1 |
+
|
+deux VPC sur le même hyperviseur ne se joignent pas, en ICMP comme en TCP ; chaque VM +joint sa passerelle (témoin) |
+hv1 |
+
|
+adresse VTEP locale sur les VXLAN (#51), VTEP distant appris par EVPN, ping VM ↔ VM
+entre hyperviseurs, trame de 1472 octets en |
+hv1, hv2 |
+
|
+deux VPC de même plage, VNI différentes, sur deux hyperviseurs : la VM joint celle de sa +VPC sur l’autre hyperviseur (témoin) et pas celle de l’autre VPC — aucune résolution ARP |
+hv1, hv2 |
+
|
+FRR arrêté sur le route reflector, ping continu entre les VM de |
+hv1, hv2, rr1 |
+
s6 réutilise les VM de s4 : le lancer après.
Comment c’est fait. test/e2e/run.sh exécute chaque scénario sur le Mac ; un
+scénario envoie des blocs de shell aux nœuds par on <nœud> [VAR=valeur…] <<'NODE', précédés de
+test/e2e/lib/node.sh — appels à l’API de l’agent, attente des états, image Debian compatible two
+(préparée une fois par hyperviseur, seedfrom avec barre oblique finale), clé SSH des VM,
+check et vm_fails. Une vérification négative (« ne joint pas ») passe par vm_fails :
+elle n’est réussie que si le SSH vers la VM a fonctionné et que la commande y a échoué — un
+SSH en panne ne passe jamais pour une isolation.
Résultats du 2026-10-04 sur le serveur de lab, release 0.2.0rc003, hv1 sur le DHCP intégré et
+hv2 sur dnsmasq — toute la série en 8 minutes :
$ test/e2e/run.sh all | grep -E '^(=== s[0-9].* : |INFO)'
+INFO: MAC de sn-s1a : 00:22:33:00:00:0a 00:22:33:00:00:0b ; de sn-s1b : 00:22:33:00:00:0a 00:22:33:00:00:0b
+=== s1-dhcp-two : 37 réussi(s), 0 échoué(s)
+=== s2-gateway : 22 réussi(s), 0 échoué(s)
+=== s3-isolation-local : 12 réussi(s), 0 échoué(s)
+=== s4-evpn : 14 réussi(s), 0 échoué(s)
+=== s5-isolation-evpn : 13 réussi(s), 0 échoué(s)
+INFO: 30 s après l'arrêt du route reflector : 0 réponses dans les 10 dernières secondes (50 si le trafic passe intégralement)
+INFO: VTEP distant encore connu : 0 ; entrée d'inondation : 0
+INFO: 90 s après l'arrêt : 0 réponses dans les 10 dernières secondes
+INFO: retour du VTEP distant et du trafic 31 s après le redémarrage de FRR
+=== s6-rr-loss : 8 réussi(s), 0 échoué(s)
+Dans ce run, le contrôle « uniquement les MAC de son subnet » de s1 ne prouvait rien : two
+dérive la MAC du rang de l’IP, et les VM .10/.11 des deux subnets avaient les mêmes MAC.
+Vérifié à la main sur le lab, chaque serveur DHCP ne connaissait que les couples MAC/IP de son
+subnet ; le scénario compare désormais ces couples.
Les VM sont accessibles depuis le netns de leur VPC, sur l’hyperviseur, avec l’utilisateur
+syonad créé par les métadonnées de two :
scripts/lab-host.sh ssh "./lab ssh hv1 'sudo ip netns exec vp-s4 ssh -i /root/.ssh/lab-vm syonad@10.240.1.10'"
+Le lab sert à qualifier des comportements qui ne se voient qu’à plusieurs hyperviseurs — la
+campagne L3VNI de #41 en est le prochain exemple. Un
+scénario est un fichier test/e2e/scenarios/<n>-<nom>.sh, exécuté par scenario.sh sur le
+Mac ; il envoie des blocs aux nœuds :
on hv1 <<'NODE'
+two_image || { echo "ÉCHOUÉ: image compatible two"; exit 0; }
+KEY=$(vm_key)
+check "VPC vp-x" vpc_create vp-x 10.250.0.0/16
+check "subnet sn-x" subnet_create sn-x vp-x 2601 10.250.1.1 10.250.1.0/24
+check "VM x1" vm_create x1 sn-x 10.250.1.10 "${KEY}"
+check "x1 joignable" vm_wait vp-x 10.250.1.10
+check "x1 joint sa passerelle" vm_ssh vp-x 10.250.1.10 'ping -c 2 -W 2 10.250.1.1'
+check "x1 ne joint pas 10.251.1.10" vm_fails vp-x 10.250.1.10 'ping -c 2 -W 2 10.251.1.10'
+info "mesure : $(vtysh -c 'show evpn vni 2601' | grep -c 'flood')"
+NODE
+Les règles qui ont fait leurs preuves en E5 :
+une vérification par ligne, avec check — jamais un echo RÉUSSI écrit à la main ;
toute vérification négative a son témoin : avant « ne joint pas », une ligne qui prouve que +la cible est vivante et que le chemin du test fonctionne ;
``vm_fails`` pour le négatif, jamais ! devant un vm_ssh : un SSH en panne doit
+échouer, pas passer pour une isolation ;
une donnée qui distingue réellement les cas : two dérivant la MAC du rang de l’IP, deux +subnets ont les mêmes MAC — comparer des couples MAC/IP, pas des MAC ;
les mesures en ``INFO``, les attentes en check : un temps de reconvergence se mesure,
+il ne se décrète pas ;
chaque scénario crée ses propres VPC, plages et VNI, distinctes de celles des autres, pour que
+all les enchaîne sur le même lab ; un scénario qui dépend d’un autre le vérifie en tête
+(check "prérequis : …") ;
variables vers un nœud : on hv1 NOM=valeur <<'NODE' (valeurs échappées par
+scenario.sh) ;
bash test/e2e/run_test.sh vérifie la syntaxe de chaque bloc réellement envoyé — à
+lancer avant toute session.
Avertissement
+Un serveur Elastic Metal est facturé de sa création à sa suppression, éteint compris. +Éteindre ne suffit pas : il faut supprimer. La granularité n’est pas documentée par Scaleway — +compter chaque heure entamée comme une heure pleine.
+Ce que le script garantit :
+il ne commande jamais d’offre mensuelle : il exige une seule offre au nom demandé, en
+facturation horaire, en stock et sans frais de mise en service, sinon il refuse avant toute
+création. La CLI scw n’est pas utilisée pour cette raison : son server create type=…
+choisit l’offre par son seul nom et peut tomber sur la mensuelle, qui engage un mois ;
il refuse de créer un second serveur si un serveur de lab existe déjà ;
down agit sur tous les serveurs portant le tag two-lab dans le projet, et
+session y ajoute l’identifiant reçu à la création : un serveur créé juste avant une
+interruption est rattrapé ;
une suppression refusée pendant la livraison ou l’installation est réessayée tant qu’elle dure, +dans la limite du délai d’installation augmenté du délai de suppression ;
le serveur n’est déclaré supprimé qu’au 404 de l’API, jamais sur une erreur passagère ;
un échec de suppression se termine par SERVEUR(S) DE LAB TOUJOURS FACTURÉ(S) et un code
+d’erreur.
Ce qu’il ne peut pas garantir : un SIGKILL, une coupure de courant ou une mise en veille du
+poste qui lance la session. En cas de doute, toujours :
scripts/lab-host.sh status
+scripts/lab-host.sh down
+aucune clé SSH active dans le projetAucune clé SSH n’est enregistrée dans le projet de lab. En ajouter une dans la console (projet +→ Clés SSH).
+offre horaire EM-B212X-SSD : 0 correspondance(s)L’offre n’existe pas dans la zone en facturation horaire. Vérifier SCW_DEFAULT_ZONE.
création incertaineLa création a échoué ou n’a pas rendu d’identifiant. Le serveur a pu être créé malgré tout —
+cas typique : le quota (identité non validée), ou une réponse perdue. Le script indique s’il
+voit un serveur de lab ; dans tous les cas, lancer status puis down.
SSH injoignableL’installation est terminée mais SSH ne répond pas après 10 minutes. Avec une clé matérielle
+(Yubikey), chaque connexion demande un PIN ou un toucher : utiliser la clé dédiée du lab. Le
+serveur est toujours facturé : down.
SERVEUR(S) DE LAB TOUJOURS FACTURÉ(S)La suppression n’a pas abouti dans les délais. Relancer down ; si l’erreur persiste,
+supprimer depuis la console Scaleway.
HTTP 403 insufficient permissionsLa clé d’API est authentifiée mais n’a pas le droit demandé — en général la lecture des clés +SSH du projet. Compléter la politique de la clé, limitée au projet.
+La clé secrète n’apparaît ni dans les arguments des processus (elle est passée à curl par
+un descripteur de fichier), ni dans les journaux, ni dans l’environnement de ssh.
Ne jamais lancer le script sous bash -x : la trace afficherait la clé.
Le serveur n’expose que SSH, par clé. Le lab n’a aucune donnée personnelle ni secret de +production.
Une clé secrète qui a circulé ailleurs que dans scaleway.env (conversation, terminal
+partagé, capture d’écran) se régénère.
Clé SSH des VM : générée par lab up sur le serveur, c’est la seule clé autorisée dans
+les VM. Elle ne quitte jamais le serveur, n’ouvre que les VM du lab — qui n’écoutent qu’en
+boucle locale — et disparaît avec lui. La clé publique du Mac n’est jamais envoyée aux VM.
Clés d’hôte des VM non vérifiées par lab ssh (known_hosts jetable) : elles changent
+à chaque up. Acceptable uniquement parce que la connexion reste sur la boucle locale d’un
+serveur auquel on s’est authentifié.
Code exécuté sans épinglage par deploy.sh : la bibliothèque shflags est récupérée
+par curl sur la branche main d’un autre dépôt (H6N/tools) et exécutée par eval,
+sans vérification d’intégrité — dans le lab comme en production. Les artefacts de la release
+sont, eux, vérifiés contre SHA256SUMS.
Image : SHA512SUMS vient de la même origine que l’image, en HTTPS. La vérification
+protège contre la corruption, pas contre une origine compromise ; la signature GPG de Debian
+(SHA512SUMS.sign) n’est pas encore vérifiée.
bash scripts/lab-host_test.sh
+bash test/e2e/run_test.sh
+go test ./internal/lab/... ./cmd/lab/
+Environ une minute et demie, sans réseau : la suite remplace curl par une fausse API Scaleway
+qui se place dans le pire cas (offre mensuelle listée avant l’horaire, serveurs d’autres projets,
+suppressions refusées, erreurs 503, serveur qui tarde à disparaître) et ssh par un faux client.
+Elle tourne sous bash 5 comme sous le bash 3.2 de macOS.
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
suivant
-Concepts
+Développement
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne
Configuration, services, API de l’agent, métriques et diagnostic sur un nœud en service.
Outillage de développement de two, dont le lab de test multi-nœud sur serveur loué à l’heure.
+Comment les éléments fonctionnent entre eux : modèle de données, modes réseau, cycle de vie, metadata. À lire avant de diagnostiquer un comportement inattendu.
Développement
+Interne
Développement
+Interne
Développement
+Interne
Développement
+Interne