Lab virtuel multi-nœud : hyperviseurs en KVM imbriqué, route reflectors et switchs L3 en VM #50

Open
opened 2026-10-03 12:08:49 +00:00 by nicolas.boufideline · 11 comments

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 de
monter un cluster de test reproductible :

  • #46 : une validation lab reste ouverte (second client DHCP, systemd-networkd) et la cohabitation de N
    serveurs DHCP sur UDP/67 dans un même netns n'a jamais été rejouée de façon automatique ;
  • #41 : la campagne de qualification en lab est notée bloquante, avec en critère
    d'acceptation un test négatif inter-VPC ;
  • la liste P999 (netns, netif, ebtables, subnet, vpc, vm, watchdog…) n'a pas
    d'environnement Linux où tourner ;
  • #48 ajoutera les tests unitaires à la CI, mais pas de test d'intégration multi-nœud.

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.

Rôle Nombre Contenu
Hyperviseur ≥ 2 KVM imbriqué, agent two + FRR, déployé par deploy.sh ; lance lui-même les VM de test
Route reflector 1, puis 2 FRR, l2vpn evpn, peering dynamique
Switch L3 1, puis 2 FRR, underlay eBGP vers les hyperviseurs (rôle du routeur de cluster), puis border
VM de test n lancées par les agents des hyperviseurs du lab, via l'API two
                 ┌──────────── hôte physique du lab ─────────────┐
                 │                                                │
                 │   [switch-l3-1]  ←── eBGP underlay ──┐         │
                 │        │                             │         │
                 │   [rr-1] ←── iBGP evpn (local-as) ───┤         │
                 │                                      │         │
                 │   [hv-1]  ═══ VXLAN ═══  [hv-2]  ────┘         │
                 │    ├ vm-a                 ├ vm-b               │
                 │    └ vm-c                 └ vm-d               │
                 └────────────────────────────────────────────────┘

Questions à trancher

  1. Qui lance les VM de premier niveau (hyperviseurs, RR, switchs) ?
    • two lui-même sur l'hôte du lab — on éprouve le produit à deux niveaux, mais la topologie
      du lab hérite de ses limites (cf. MTU ci-dessous) ;
    • un outil à part (script qemu, libvirt…) — topologie libre, mais une seconde pile à maintenir.
  2. MTU et double encapsulation. L'agent fige le MTU à 1500 et VXLAN exige ≥ 1550 sur
    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).
  3. Prérequis de l'hôte : virtualisation imbriquée activée (kvm_intel nested=1 /
    kvm_amd nested=1), -cpu host pour les hyperviseurs du lab. Les performances des VM de
    second niveau ne sont pas représentatives : le lab qualifie le comportement, pas le débit.
  4. Hyperviseurs sans état : en production la racine est en tmpfs et deploy.sh --bootstrap
    est 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).
  5. Description de la topologie : déclarative (un fichier → un lab), pour pouvoir monter,
    détruire et remonter à l'identique.
  6. Forme des scénarios et intégration éventuelle à la CI (#48) — qui suppose un runner avec
    KVM imbriqué.

Premiers scénarios visés

  • reprise des validations lab restantes de #46 (DHCP two sur deux subnets d'un même VPC,
    systemd-networkd) ;
  • convergence EVPN entre deux hyperviseurs : une VM de hv-1 joint une VM de hv-2 sur le même
    subnet ;
  • MTU de bout en bout (ping -M do pleine taille) ;
  • test négatif d'isolation inter-VPC, prérequis de #41 ;
  • perte et retour du route reflector.

Découpage proposé

  1. Topologie minimale montée à la main : 2 hyperviseurs, 1 RR, 1 switch L3 — valider le KVM
    imbriqué et le MTU avant d'outiller quoi que ce soit.
  2. Description déclarative + montage / destruction automatisés.
  3. Scénarios automatisés, sortie lisible (succès / échec par scénario).
  4. Redondance : 2 RR, 2 switchs.

Analyse de risque

  • Fuite vers la production : le lab fait tourner du BGP. Ses sessions ne doivent jamais
    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.
  • API agent sans authentification : exposée sur les hyperviseurs du lab ; l'hôte du lab ne
    doit pas la rendre joignable au-delà du réseau de lab.
  • Images : pas de clé ni de mot de passe réutilisé de la production dans les images du lab.
  • Ressources : un lab complet consomme CPU et RAM de l'hôte ; à dimensionner pour ne pas
    dégrader un hyperviseur réel s'il est partagé.
  • Données : aucune donnée personnelle ni secret de production — données d'infrastructure
    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.

## 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 de monter un cluster de test reproductible : - **#46** : une validation lab reste ouverte (second client DHCP, systemd-networkd) et la cohabitation de N serveurs DHCP sur UDP/67 dans un même netns n'a jamais été rejouée de façon automatique ; - **#41** : la campagne de qualification en lab est notée **bloquante**, avec en critère d'acceptation un test négatif inter-VPC ; - la liste P999 (`netns`, `netif`, `ebtables`, `subnet`, `vpc`, `vm`, `watchdog`…) n'a pas d'environnement Linux où tourner ; - **#48** ajoutera les tests unitaires à la CI, mais pas de test d'intégration multi-nœud. ## 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. | Rôle | Nombre | Contenu | |---|---|---| | Hyperviseur | ≥ 2 | KVM **imbriqué**, agent two + FRR, déployé par `deploy.sh` ; lance lui-même les VM de test | | Route reflector | 1, puis 2 | FRR, `l2vpn evpn`, peering dynamique | | Switch L3 | 1, puis 2 | FRR, underlay eBGP vers les hyperviseurs (rôle du routeur de cluster), puis border | | VM de test | n | lancées **par les agents** des hyperviseurs du lab, via l'API two | ``` ┌──────────── hôte physique du lab ─────────────┐ │ │ │ [switch-l3-1] ←── eBGP underlay ──┐ │ │ │ │ │ │ [rr-1] ←── iBGP evpn (local-as) ───┤ │ │ │ │ │ [hv-1] ═══ VXLAN ═══ [hv-2] ────┘ │ │ ├ vm-a ├ vm-b │ │ └ vm-c └ vm-d │ └────────────────────────────────────────────────┘ ``` ## Questions à trancher 1. **Qui lance les VM de premier niveau** (hyperviseurs, RR, switchs) ? - two lui-même sur l'hôte du lab — on éprouve le produit à deux niveaux, mais la topologie du lab hérite de ses limites (cf. MTU ci-dessous) ; - un outil à part (script qemu, libvirt…) — topologie libre, mais une seconde pile à maintenir. 2. **MTU et double encapsulation.** L'agent fige le MTU à 1500 et VXLAN exige ≥ 1550 sur 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). 3. **Prérequis de l'hôte** : virtualisation imbriquée activée (`kvm_intel nested=1` / `kvm_amd nested=1`), `-cpu host` pour les hyperviseurs du lab. Les performances des VM de second niveau ne sont pas représentatives : le lab qualifie le comportement, pas le débit. 4. **Hyperviseurs sans état** : en production la racine est en tmpfs et `deploy.sh --bootstrap` est 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). 5. **Description de la topologie** : déclarative (un fichier → un lab), pour pouvoir monter, détruire et remonter à l'identique. 6. **Forme des scénarios** et intégration éventuelle à la CI (#48) — qui suppose un runner avec KVM imbriqué. ## Premiers scénarios visés - reprise des validations lab restantes de #46 (DHCP `two` sur deux subnets d'un même VPC, systemd-networkd) ; - convergence EVPN entre deux hyperviseurs : une VM de hv-1 joint une VM de hv-2 sur le même subnet ; - MTU de bout en bout (`ping -M do` pleine taille) ; - test négatif d'isolation inter-VPC, prérequis de #41 ; - perte et retour du route reflector. ## Découpage proposé 1. Topologie minimale montée à la main : 2 hyperviseurs, 1 RR, 1 switch L3 — valider le KVM imbriqué et le MTU avant d'outiller quoi que ce soit. 2. Description déclarative + montage / destruction automatisés. 3. Scénarios automatisés, sortie lisible (succès / échec par scénario). 4. Redondance : 2 RR, 2 switchs. ## Analyse de risque - **Fuite vers la production** : le lab fait tourner du BGP. Ses sessions ne doivent **jamais** 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. - **API agent sans authentification** : exposée sur les hyperviseurs du lab ; l'hôte du lab ne doit pas la rendre joignable au-delà du réseau de lab. - **Images** : pas de clé ni de mot de passe réutilisé de la production dans les images du lab. - **Ressources** : un lab complet consomme CPU et RAM de l'hôte ; à dimensionner pour ne pas dégrader un hyperviseur réel s'il est partagé. - **Données** : aucune donnée personnelle ni secret de production — données d'infrastructure 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.
Author
Owner

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 :

  • two lui-même : la topologie du lab hériterait de ses limites, à commencer par le MTU figé à
    1500 (cf. Q2).
  • Terraform : il faudrait deux providers et deux topologies, un par plateforme, ce qui ne
    donne aucun lab unique. dmacvicar/libvirt est mûr (v0.9.9, 2026-08-30) mais réservé à
    Linux. Le seul provider Lima (guidoiaquinti/lima) est communautaire, à mainteneur unique, et
    sa couverture est partielle : risque de chaîne d'approvisionnement pour un gain faible.
  • Lima : sur Apple Silicon il fournit des VM arm64, alors que two lance
    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 dgram sur UDP en loopback

Chaque lien de la topologie est un câble point à point entre deux processus QEMU, sans bridge,
sans root et sans libvirt.

  • dgram plutôt que stream : pas de rôle serveur ni client, donc une VM peut redémarrer
    sans casser le lien.
  • UDP plutôt que socket Unix : sous Linux les deux conviennent ; UDP est retenu pour que la
    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 à 9216
    octets (net.inet.udp.maxdgram) ; une trame de MTU 9000 en fait 9014 (9018 avec un tag VLAN).
  • Réseau isolé par construction : un câble dgram ne mène qu'à l'autre extrémité. Aucune
    sortie 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=9000 annonce 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.sh ne pose aucun MTU, et br-000000 hérite de
celui 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é :

Test Résultat
Hyperviseur x86_64 émulé (TCG), -cpu qemu64,+svm kvm_amd se charge, /dev/kvm présent (TCG émule SVM, pas VMX)
VM KVM minimale par les ioctl (10 octets, mode réel) s'exécute
VM Linux de second niveau en KVM, -cpu host puis -cpu qemu64 firmware, GRUB et code de démarrage OK, puis reset du CPU au passage en mode protégé/long, au bout de 2 s
VM Linux de second niveau en TCG pur pas de crash, mais pas d'invite de connexion au bout de 45 min — inutilisable

kvm_amd signale Nested Paging disabled : TCG n'émule pas la pagination imbriquée. Rendre
l'accélérateur de two configurable (kvm / tcg) a été envisagé puis écarté pour l'instant : le
TCG 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.

  • Pourquoi du physique et pas une VM cloud : sur une VM cloud, les VM de test des hyperviseurs
    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.
  • Pourquoi celui-ci : Intel, comme lab3 (Core i3-4130T, Haswell). two lance ses VM en
    -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é.
  • « Or equivalent » : le processeur exact n'est pas garanti d'une location à l'autre. Relever
    lscpu au 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.

  • Livraison et OS en un seul appel d'API : moins de 15 min annoncées par Scaleway.
  • Le script de campagne crée le serveur et le supprime dans un trap … EXIT, y compris sur un
    échec ou une interruption.
  • Une commande lab-cleanup supprime tout serveur du projet de lab, à lancer en cas de doute.
  • Un projet Scaleway dédié au lab, et une clé d'API limitée à ce projet : jamais dans le dépôt
    ni dans les issues.
  • Exposition : SSH par clé uniquement. API de l'agent et BGP du lab restent sur les câbles
    dgram internes.
Rôle Architecture sur l'hôte Linux x86_64
Hyperviseur x86_64, KVM + KVM imbriqué — le chemin de code de production
Route reflector x86_64 natif
Switch L3 x86_64 natif

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

  • Debian 12 bookworm partout, comme lab3. Noyau de la série 6.1 ; lab3 est en 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 : la dernière stable, frr-stable sur 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.
  • Le dépôt ne conserve que la dernière version de chaque branche : 10.5.0 et 10.7.0 ne sont plus
    téléchargeables. La version installée doit donc être relevée et consignée à chaque
    construction d'image.

Questions encore ouvertes

  • Premier essai sur l'EM-B212X-SSD, moins d'une heure : nested à Y, une VM de second
    niveau jusqu'à l'invite de connexion, deux VM reliées par dgram en MTU 9000.
  • Q4 (hyperviseurs sans état), Q5 (format de description de la topologie) et Q6 (CI).
  • Le plan d'adressage du lab : ASN et plages dédiés, distincts de 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 : - **two lui-même** : la topologie du lab hériterait de ses limites, à commencer par le MTU figé à 1500 (cf. Q2). - **Terraform** : il faudrait deux providers et deux topologies, un par plateforme, ce qui ne donne aucun lab unique. `dmacvicar/libvirt` est mûr (v0.9.9, 2026-08-30) mais réservé à Linux. Le seul provider Lima (`guidoiaquinti/lima`) est communautaire, à mainteneur unique, et sa couverture est partielle : risque de chaîne d'approvisionnement pour un gain faible. - **Lima** : sur Apple Silicon il fournit des VM arm64, alors que two lance `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 dgram` sur UDP en loopback Chaque lien de la topologie est un câble point à point entre deux processus QEMU, sans bridge, sans root et sans libvirt. - **`dgram` plutôt que `stream`** : pas de rôle serveur ni client, donc une VM peut redémarrer sans casser le lien. - **UDP plutôt que socket Unix** : sous Linux les deux conviennent ; UDP est retenu pour que la 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 à 9216 octets (`net.inet.udp.maxdgram`) ; une trame de MTU 9000 en fait 9014 (9018 avec un tag VLAN). - **Réseau isolé par construction** : un câble `dgram` ne mène qu'à l'autre extrémité. Aucune sortie 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=9000` annonce 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.sh` ne pose aucun MTU, et `br-000000` hérite de celui 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é : | Test | Résultat | |---|---| | Hyperviseur x86_64 émulé (TCG), `-cpu qemu64,+svm` | `kvm_amd` se charge, `/dev/kvm` présent (TCG émule SVM, pas VMX) | | VM KVM minimale par les ioctl (10 octets, mode réel) | s'exécute | | VM Linux de second niveau en KVM, `-cpu host` puis `-cpu qemu64` | firmware, GRUB et code de démarrage OK, puis **reset du CPU au passage en mode protégé/long**, au bout de 2 s | | VM Linux de second niveau en TCG pur | pas de crash, mais **pas d'invite de connexion au bout de 45 min** — inutilisable | `kvm_amd` signale `Nested Paging disabled` : TCG n'émule pas la pagination imbriquée. Rendre l'accélérateur de two configurable (`kvm` / `tcg`) a été envisagé puis écarté pour l'instant : le TCG 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. - **Pourquoi du physique et pas une VM cloud** : sur une VM cloud, les VM de test des hyperviseurs 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. - **Pourquoi celui-ci** : Intel, comme lab3 (Core i3-4130T, Haswell). two lance ses VM en `-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é. - **« Or equivalent »** : le processeur exact n'est pas garanti d'une location à l'autre. Relever `lscpu` au 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. - Livraison et OS en un seul appel d'API : moins de 15 min annoncées par Scaleway. - Le script de campagne crée le serveur et le **supprime dans un `trap … EXIT`**, y compris sur un échec ou une interruption. - Une commande `lab-cleanup` supprime tout serveur du projet de lab, à lancer en cas de doute. - **Un projet Scaleway dédié au lab**, et une clé d'API limitée à ce projet : jamais dans le dépôt ni dans les issues. - Exposition : SSH par clé uniquement. API de l'agent et BGP du lab restent sur les câbles `dgram` internes. | Rôle | Architecture sur l'hôte Linux x86_64 | |---|---| | Hyperviseur | x86_64, KVM + KVM imbriqué — le chemin de code de production | | Route reflector | x86_64 natif | | Switch L3 | x86_64 natif | **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 - **Debian 12 bookworm** partout, comme lab3. Noyau de la série 6.1 ; lab3 est en `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 : la dernière stable**, `frr-stable` sur 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. - Le dépôt ne conserve que la dernière version de chaque branche : 10.5.0 et 10.7.0 ne sont plus téléchargeables. La version installée doit donc être relevée et consignée à chaque construction d'image. ### Questions encore ouvertes - Premier essai sur l'EM-B212X-SSD, moins d'une heure : `nested` à `Y`, une VM de second niveau jusqu'à l'invite de connexion, deux VM reliées par `dgram` en MTU 9000. - Q4 (hyperviseurs sans état), Q5 (format de description de la topologie) et Q6 (CI). - Le plan d'adressage du lab : ASN et plages dédiés, distincts de la production.
Author
Owner

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, pas genericcloud : generic porte le noyau
linux-image-amd64, celui de lab3 (6.1.0-33-amd64), alors que genericcloud porte le noyau
cloud-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 SHA512SUMS au 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 dgram relie 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-000000 dans scripts/bootstrap_kvm.sh.

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: 10.250.0.0/24
    mtu: 9000

nodes:
  sw1: { role: switch,     image: debian12, cpus: 2, memory: 1024 }
  rr1: { role: rr,         image: debian12, cpus: 1, memory: 1024, segments: [underlay] }
  hv1: { role: hypervisor, image: debian12, cpus: 4, memory: 16384, segments: [underlay] }
  hv2: { role: hypervisor, image: debian12, cpus: 4, memory: 16384, segments: [underlay] }

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

Élément Règle
Adresse d'un nœud sur un segment la passerelle prend la première adresse du CIDR, les nœuds les suivantes dans l'ordre de déclaration ; surcharge possible par nœud
Ports UDP des câbles une paire de ports fixes sur 127.0.0.1 par câble, dérivée de la position du nœud et du segment
MAC des interfaces dérivée du nœud et du segment, préfixe localement administré
Nom des interfaces dans le guest cloud-init (configuration réseau v2) associe chaque MAC à un nom stable — un nom qui flotte d'un démarrage à l'autre casse tout, cf. la carte PCI de two (#36)
Port SSH d'administration 127.0.0.1:22xx sur l'hôte, un par nœud

Le rang de déclaration détermine ces valeurs : réordonner le fichier change les adresses. C'est
assumé pour un lab, et lab plan affiche 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 SSH
redirigé 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é.

Nœud Interface d'administration Sortie Internet
switch NAT QEMU sans restriction directe ; il masque (NAT) ses segments
hyperviseurs, route reflector NAT QEMU en restrict=on par la passerelle du switch, sur le segment

restrict=on isole la VM — ni l'hôte ni l'extérieur ne sont joignables par cette interface — sans
toucher aux redirections déclarées (qemu-options.hx : « This option does not affect any
explicitly 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 par
dé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/lab en Go, exécuté sur l'hôte du lab

Mac ── scripts/lab-host.sh (CLI scw) ──► crée le serveur Scaleway
Mac ── SSH ──► serveur : copie `lab` (linux/amd64) et le fichier de topologie, lance `lab up`
                 └─ lab : génère les seeds cloud-init, lance les QEMU, attend que chaque VM réponde
Mac ── SSH (ProxyJump par le serveur) ──► 127.0.0.1:22xx ──► une VM, pour les scénarios et le debug
Mac ── scripts/lab-host.sh ──► trap … EXIT : suppression du serveur
  • Go dans ce dépôt pour la topologie : lecture du YAML, calcul du plan, génération des
    arguments QEMU et des seeds cloud-init. Testable sur Mac, et aucune dépendance nouvelle : le YAML
    est déjà dans l'arbre via viper.
  • Bash et la CLI scw pour le cycle de vie du serveur : le SDK Scaleway n'entre pas dans le
    module de l'agent, actif critique. Même raisonnement que pour le plugin Packer.
  • Configuration des VM par cloud-init, pas par SSH : adresses, MTU, bridge et NAT du switch,
    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.

Étape Contenu Vérification
E0 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 un trap … EXIT sur Scaleway, moins d'une heure : serveur joignable, lscpu relevé, /sys/module/kvm_intel/parameters/nested à Y, puis scw baremetal server list vide après down
E1 cmd/lab : lecture et validation de la topologie, calcul du plan déterministe ; lab plan tests sur Mac : nœud ou segment inconnu, nom en double, CIDR trop petit, stabilité du plan d'une exécution à l'autre
E2 Génération des arguments QEMU (câbles dgram, host_mtu=9000, NAT d'administration, restrict=on) et des seeds cloud-init tests sur Mac sur les arguments et les fichiers produits, valeurs attendues écrites en littéral
E3 lab up / down / status sur l'hôte : processus QEMU, attente du SSH sur Scaleway : ping -M do -s 8972 de hv1 à hv2 à travers le switch ; un hyperviseur sort sur Internet par le switch ; il ne sort pas par son interface d'administration
E4 Rôles : FRR frr-stable sur switch et route reflector (version relevée), two sur les hyperviseurs via deploy.sh sur Scaleway : agent actif, une VM créée par l'API de hv1 atteint son invite de connexion en KVM imbriqué
E5 Premiers scénarios, lancés à la main : validation lab restante de #46 (DHCP two avec systemd-networkd) sortie réussi / échoué par scénario

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.rst la marque « À rédiger ». Elle sera écrite une seule
fois 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

  • Sortie Internet du lab : les VM sortent par le NAT du switch, puis par celui de QEMU. Elles
    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.
  • Serveur exposé : SSH par clé uniquement ; API de l'agent, BGP et SSH des VM restent sur la
    boucle locale ou sur les câbles internes.
  • Clé d'API Scaleway : projet dédié, clé limitée à ce projet, lue depuis l'environnement,
    jamais affichée, jamais dans le dépôt ni dans les issues.
  • Coût : facturation jusqu'à la suppression, éteint compris. trap … EXIT et cleanup sont
    la protection, pas la vigilance.
  • Données : aucune donnée personnelle ni secret de production.
  • Code assisté par IA : outillage de test, mais il qualifiera des changements destinés à la
    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.
## 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`**, pas `genericcloud` : `generic` porte le noyau `linux-image-amd64`, celui de lab3 (`6.1.0-33-amd64`), alors que `genericcloud` porte le noyau `cloud-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 `SHA512SUMS` au 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 `dgram` relie 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-000000` dans `scripts/bootstrap_kvm.sh`. ```yaml 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: 10.250.0.0/24 mtu: 9000 nodes: sw1: { role: switch, image: debian12, cpus: 2, memory: 1024 } rr1: { role: rr, image: debian12, cpus: 1, memory: 1024, segments: [underlay] } hv1: { role: hypervisor, image: debian12, cpus: 4, memory: 16384, segments: [underlay] } hv2: { role: hypervisor, image: debian12, cpus: 4, memory: 16384, segments: [underlay] } ``` 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 | Élément | Règle | |---|---| | Adresse d'un nœud sur un segment | la passerelle prend la première adresse du CIDR, les nœuds les suivantes dans l'ordre de déclaration ; surcharge possible par nœud | | Ports UDP des câbles | une paire de ports fixes sur `127.0.0.1` par câble, dérivée de la position du nœud et du segment | | MAC des interfaces | dérivée du nœud et du segment, préfixe localement administré | | Nom des interfaces dans le guest | cloud-init (configuration réseau v2) associe chaque MAC à un nom stable — un nom qui flotte d'un démarrage à l'autre casse tout, cf. la carte PCI de two (#36) | | Port SSH d'administration | `127.0.0.1:22xx` sur l'hôte, un par nœud | Le rang de déclaration détermine ces valeurs : réordonner le fichier change les adresses. C'est assumé pour un lab, et `lab plan` affiche 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 SSH redirigé 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é. | Nœud | Interface d'administration | Sortie Internet | |---|---|---| | switch | NAT QEMU sans restriction | directe ; il masque (NAT) ses segments | | hyperviseurs, route reflector | NAT QEMU en **`restrict=on`** | par la passerelle du switch, sur le segment | `restrict=on` isole la VM — ni l'hôte ni l'extérieur ne sont joignables par cette interface — sans toucher aux redirections déclarées (`qemu-options.hx` : *« This option does not affect any explicitly 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 par dé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/lab` en Go, exécuté sur l'hôte du lab ``` Mac ── scripts/lab-host.sh (CLI scw) ──► crée le serveur Scaleway Mac ── SSH ──► serveur : copie `lab` (linux/amd64) et le fichier de topologie, lance `lab up` └─ lab : génère les seeds cloud-init, lance les QEMU, attend que chaque VM réponde Mac ── SSH (ProxyJump par le serveur) ──► 127.0.0.1:22xx ──► une VM, pour les scénarios et le debug Mac ── scripts/lab-host.sh ──► trap … EXIT : suppression du serveur ``` - **Go dans ce dépôt pour la topologie** : lecture du YAML, calcul du plan, génération des arguments QEMU et des seeds cloud-init. Testable sur Mac, et aucune dépendance nouvelle : le YAML est déjà dans l'arbre via viper. - **Bash et la CLI `scw` pour le cycle de vie du serveur** : le SDK Scaleway n'entre pas dans le module de l'agent, actif critique. Même raisonnement que pour le plugin Packer. - **Configuration des VM par cloud-init, pas par SSH** : adresses, MTU, bridge et NAT du switch, 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. | Étape | Contenu | Vérification | |---|---|---| | **E0** | `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 un `trap … EXIT` | sur Scaleway, moins d'une heure : serveur joignable, `lscpu` relevé, `/sys/module/kvm_intel/parameters/nested` à `Y`, puis `scw baremetal server list` vide après `down` | | **E1** | `cmd/lab` : lecture et validation de la topologie, calcul du plan déterministe ; `lab plan` | tests sur Mac : nœud ou segment inconnu, nom en double, CIDR trop petit, stabilité du plan d'une exécution à l'autre | | **E2** | Génération des arguments QEMU (câbles `dgram`, `host_mtu=9000`, NAT d'administration, `restrict=on`) et des seeds cloud-init | tests sur Mac sur les arguments et les fichiers produits, valeurs attendues écrites en littéral | | **E3** | `lab up` / `down` / `status` sur l'hôte : processus QEMU, attente du SSH | sur Scaleway : `ping -M do -s 8972` de hv1 à hv2 à travers le switch ; un hyperviseur sort sur Internet par le switch ; il **ne sort pas** par son interface d'administration | | **E4** | Rôles : FRR `frr-stable` sur switch et route reflector (version relevée), two sur les hyperviseurs via `deploy.sh` | sur Scaleway : agent actif, une VM créée par l'API de hv1 atteint son invite de connexion en KVM imbriqué | | **E5** | Premiers scénarios, lancés à la main : validation lab restante de #46 (DHCP `two` avec systemd-networkd) | sortie réussi / échoué par scénario | **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.rst` la marque « À rédiger ». Elle sera écrite **une seule fois 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 - **Sortie Internet du lab** : les VM sortent par le NAT du switch, puis par celui de QEMU. Elles 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. - **Serveur exposé** : SSH par clé uniquement ; API de l'agent, BGP et SSH des VM restent sur la boucle locale ou sur les câbles internes. - **Clé d'API Scaleway** : projet dédié, clé limitée à ce projet, lue depuis l'environnement, jamais affichée, jamais dans le dépôt ni dans les issues. - **Coût** : facturation jusqu'à la suppression, éteint compris. `trap … EXIT` et `cleanup` sont la protection, pas la vigilance. - **Données** : aucune donnée personnelle ni secret de production. - **Code assisté par IA** : outillage de test, mais il qualifiera des changements destinés à la 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.
Author
Owner

E0 — fait le 2026-10-03 (28ce00c, branche feature-50)

scripts/lab-host.sh crée, surveille et supprime le serveur de lab ; scripts/lab-host_test.sh
le teste ; docs/developpement/lab.rst documente l'usage.

Écart au plan : l'API REST, pas la CLI scw

Le plan prévoyait la CLI scw. Son code montre que scw baremetal server create type=… résout
l'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 une
offre unique, en facturation horaire, en stock, sans frais de mise en service.

Commandes

Commande Effet Coût
plan résout offre, OS, clés SSH ; affiche prix et requête aucun
status liste les serveurs de lab du projet aucun
up / ssh / down création, connexion, suppression, séparément facturé jusqu'à down
session [cmd] up, la commande, puis down quoi qu'il arrive facturé

Garanties contre la surfacturation

  • suppression dans un trap EXIT ; INT, TERM et HUP pendant la commande distante, et un second
    signal pendant le nettoyage n'interrompt pas celui-ci ;
  • down agit par tag et projet, et session y ajoute l'identifiant reçu à la création ;
  • suppression réessayée tant que le serveur est en livraison ou en installation, avec un plafond ;
  • serveur déclaré supprimé uniquement sur un 404, jamais sur une erreur passagère ;
  • délais sur chaque appel HTTP ; erreurs 4xx (hors 429) traitées comme définitives ;
  • échec de suppression : 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

Durée totale 13 min 30, création → suppression confirmée (une heure entamée, 0,321 € HT)
Processeur livré Xeon E5-2640 v3 (Haswell, comme lab3), et non le E5-2620 v4 annoncé — or equivalent
KVM imbriqué nested=Y, VT-x, /dev/kvm présent
Mémoire 251 Gio
Noyau Debian 12, 6.1.0-53-amd64 (6.1.187)
SSH ~100 s après la fin de l'installation : l'attente de SSH intégrée à up était nécessaire

Contrô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

  • Quota : l'EM-B212X-SSD exige un compte dont le moyen de paiement et l'identité sont
    validés (quota de 2 ensuite).
  • Clé d'API limitée au projet de lab : Elastic Metal, et lecture des clés SSH du projet.
  • Clés SSH du projet : sans elle, plan refuse de continuer — un serveur facturé où personne
    ne peut entrer.
  • Seule la clé secrète sert : l'API n'authentifie que par X-Auth-Token.

Fichiers locaux, hors dépôt

  • ~/.config/two-lab/scaleway.env : identifiants, en 0600 ; 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 jamais
    installer 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é.

## E0 — fait le 2026-10-03 (`28ce00c`, branche `feature-50`) `scripts/lab-host.sh` crée, surveille et supprime le serveur de lab ; `scripts/lab-host_test.sh` le teste ; `docs/developpement/lab.rst` documente l'usage. ### Écart au plan : l'API REST, pas la CLI `scw` Le plan prévoyait la CLI `scw`. Son code montre que `scw baremetal server create type=…` résout l'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 une offre unique, en facturation horaire, en stock, sans frais de mise en service. ### Commandes | Commande | Effet | Coût | |---|---|---| | `plan` | résout offre, OS, clés SSH ; affiche prix et requête | aucun | | `status` | liste les serveurs de lab du projet | aucun | | `up` / `ssh` / `down` | création, connexion, suppression, séparément | facturé jusqu'à `down` | | `session [cmd]` | `up`, la commande, puis `down` quoi qu'il arrive | facturé | ### Garanties contre la surfacturation - suppression dans un `trap EXIT` ; INT, TERM et HUP pendant la commande distante, et un second signal pendant le nettoyage n'interrompt pas celui-ci ; - `down` agit par tag et projet, et `session` y ajoute l'identifiant reçu à la création ; - suppression réessayée tant que le serveur est en livraison ou en installation, avec un plafond ; - serveur déclaré supprimé uniquement sur un 404, jamais sur une erreur passagère ; - délais sur chaque appel HTTP ; erreurs 4xx (hors 429) traitées comme définitives ; - échec de suppression : `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 | | | |---|---| | Durée totale | **13 min 30**, création → suppression confirmée (une heure entamée, 0,321 € HT) | | Processeur livré | **Xeon E5-2640 v3** (Haswell, comme lab3), et non le E5-2620 v4 annoncé — *or equivalent* | | KVM imbriqué | `nested=Y`, VT-x, `/dev/kvm` présent | | Mémoire | 251 Gio | | Noyau | Debian 12, `6.1.0-53-amd64` (6.1.187) | | SSH | **~100 s** après la fin de l'installation : l'attente de SSH intégrée à `up` était nécessaire | Contrô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 - **Quota** : l'`EM-B212X-SSD` exige un compte dont le moyen de paiement *et* l'identité sont validés (quota de 2 ensuite). - **Clé d'API** limitée au projet de lab : Elastic Metal, et lecture des clés SSH du projet. - **Clés SSH du projet** : sans elle, `plan` refuse de continuer — un serveur facturé où personne ne peut entrer. - Seule la **clé secrète** sert : l'API n'authentifie que par `X-Auth-Token`. ### Fichiers locaux, hors dépôt - `~/.config/two-lab/scaleway.env` : identifiants, en `0600` ; **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 jamais installer 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é.
Author
Owner

E1 — fait le 2026-10-04 (a706e24, branche feature-50)

internal/lab/topology lit et valide la topologie et calcule le plan ; cmd/lab l'expose par
lab 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 inclut
directement 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 map Go 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.Node qui relève l'ordre des clés. go.yaml.in/yaml/v3 passe de
dépendance indirecte (via viper) à directe : aucun module nouveau.

Ce que l'outil déduit

Élément Règle
Passerelle première adresse du CIDR, portée par le switch sur br-<segment>
Adresse d'un nœud les suivantes, dans l'ordre de déclaration ; une adresse fixée par addresses est réservée d'abord et sautée par l'attribution
Câbles un par couple (segment, nœud) ; le câble i prend les ports UDP 20000 + 2i (nœud) et 20001 + 2i (switch)
MAC 02:4c:<nœud>:<nœud>:<segment>:<côté> — localement administrée, rang du nœud sur deux octets, 00 côté nœud, 01 côté switch
Interfaces côté nœud, le nom du segment ; côté switch, p<i>
SSH 127.0.0.1:<2200 + rang du nœud> sur l'hôte du lab

Choix faits à l'écriture, à confirmer

  • Un segment s'appelle en 12 caractères au plus (minuscules et chiffres) : il devient le nom
    d'interface dans les VM, et br-<segment> celui du bridge — 15 caractères au plus sous Linux.
  • Un switch ne déclare ni segments ni addresses : il porte ceux dont il est le switch.
    Relier deux switchs entre eux, pour simuler plusieurs racks, viendra avec ce besoin.
  • Limites : 1000 nœuds, 256 segments, 22 768 câbles (la plage UDP 20000–65535).
  • Toutes les erreurs sont rendues d'un coup, dans un ordre stable, avec un code de sortie 1.
  • Réordonner le fichier change les adresses : assumé pour un lab, lab plan le montre avant tout
    lancement.

Vérification

65 tests, -race propre ; couverture 94,9 % (topology), 85 % (cmd/lab). Les valeurs attendues
sont é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/amd64 en CGO_ENABLED=0, documentation
construite avec sphinx-build -W sans avertissement.

Au passage

go mod tidy ajoutait à go.sum trois 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 tidy
de 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, NAT
d'administration avec restrict=on) et les seeds cloud-init. Testable sur Mac, sans serveur loué.

## E1 — fait le 2026-10-04 (`a706e24`, branche `feature-50`) `internal/lab/topology` lit et valide la topologie et calcule le plan ; `cmd/lab` l'expose par `lab 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 inclut directement 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 `map` Go 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.Node` qui relève l'ordre des clés. `go.yaml.in/yaml/v3` passe de dépendance indirecte (via viper) à directe : aucun module nouveau. ### Ce que l'outil déduit | Élément | Règle | |---|---| | Passerelle | première adresse du CIDR, portée par le switch sur `br-<segment>` | | Adresse d'un nœud | les suivantes, dans l'ordre de déclaration ; une adresse fixée par `addresses` est réservée d'abord et sautée par l'attribution | | Câbles | un par couple (segment, nœud) ; le câble *i* prend les ports UDP `20000 + 2i` (nœud) et `20001 + 2i` (switch) | | MAC | `02:4c:<nœud>:<nœud>:<segment>:<côté>` — localement administrée, rang du nœud sur deux octets, `00` côté nœud, `01` côté switch | | Interfaces | côté nœud, le nom du segment ; côté switch, `p<i>` | | SSH | `127.0.0.1:<2200 + rang du nœud>` sur l'hôte du lab | ### Choix faits à l'écriture, à confirmer - **Un segment s'appelle en 12 caractères au plus** (minuscules et chiffres) : il devient le nom d'interface dans les VM, et `br-<segment>` celui du bridge — 15 caractères au plus sous Linux. - **Un switch ne déclare ni `segments` ni `addresses`** : il porte ceux dont il est le `switch`. Relier deux switchs entre eux, pour simuler plusieurs racks, viendra avec ce besoin. - **Limites** : 1000 nœuds, 256 segments, 22 768 câbles (la plage UDP 20000–65535). - **Toutes les erreurs sont rendues d'un coup**, dans un ordre stable, avec un code de sortie 1. - Réordonner le fichier change les adresses : assumé pour un lab, `lab plan` le montre avant tout lancement. ### Vérification 65 tests, `-race` propre ; couverture 94,9 % (`topology`), 85 % (`cmd/lab`). Les valeurs attendues sont é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/amd64` en `CGO_ENABLED=0`, documentation construite avec `sphinx-build -W` sans avertissement. ### Au passage `go mod tidy` ajoutait à `go.sum` trois 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 tidy` de 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`, NAT d'administration avec `restrict=on`) et les seeds cloud-init. Testable sur Mac, sans serveur loué.
Author
Owner

E2 — fait le 2026-10-04 (99ecb59, branche feature-50)

internal/lab/render transforme le plan en ce qu'il faut pour démarrer chaque VM ; cmd/lab l'expose
par lab render -key <clé.pub> <topologie> <répertoire>, qui écrit pour chaque nœud qemu.args (un
argument par ligne) et les trois fichiers NoCloud meta-data, user-data, network-config. Rien
n'est lancé : l'image cidata et 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, disque virtio,
    seed.iso en cdrom, console dans un fichier, QMP sur socket Unix, -pidfile.
  • Administration mgmt0 : NAT de QEMU, MAC 02:4d:<nœud>:<nœud>:00:00, SSH redirigé sur
    127.0.0.1:<2200+n>. restrict=on pour tous les nœuds sauf le switch ; ipv6=off partout.
  • Câbles : -netdev dgram sur 127.0.0.1, les deux extrémités croisées, host_mtu = MTU du
    segment. 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: false et ssh_pwauth: false sont écrits explicitement, jamais omis.

  • toutes les VM : interfaces nommées d'après leur MAC, mgmt0 en 10.0.2.15/24 sans
    passerelle
    , utilisateur debian, SSH par clé seulement, root désactivé ;
  • un nœud : adresse et MTU par segment ; route par défaut et DNS (1.1.1.1, 8.8.8.8) sur son
    premier segment, donc sortie Internet par le switch ;
  • le switch : route par défaut par mgmt0 ; un service lab-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 : mgmt0 est réservé. Un segment de ce nom aurait donné deux
interfaces et deux netdev homonymes 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, kvm
remplacé par tcg faute 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é : sw1 et rr1
de l'exemple ont été démarrés sur un Mac, en émulation, avec Debian 12 generic et les fichiers de
lab render.

Vérification Résultat
interfaces nommées et adressées, br-underlay en 10.250.0.1/24 conforme
service lab-switch actif, y compris après redémarrage conforme
ping -M do -s 8972 de rr1 au switch (MTU 9000, sans fragmentation) passe
ping -M do -s 8973 (MTU 9001) refusé : message too long, mtu=9000
Internet depuis rr1 en IPv4 passe, par 10.250.0.1
rr1 vers un service TCP de l'hôte par mgmt0 — sw1, témoin, y parvient bloqué
rr1 vers Internet par mgmt0 bloqué

Défaut trouvé par cet essai, corrigé. Sans ipv6=off, le NAT de QEMU annonce un préfixe IPv6 :
mgmt0 recevait une route IPv6 par défaut vers une impasse (restrict=on bloque 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 passerelle
virtuelle 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, -race propre ; couverture 95,2 % (render), 96,3 % (topology), 85,2 % (cmd/lab).
22 mutations, toutes détectées. Compilation linux/amd64, documentation construite avec
sphinx-build -W sans avertissement.

Au passage, #47 est sorti pendant une passe de la suite complète, dans
internal/dispatcher/agent, non modifié par E2. Reproduit sur main sans 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 / status sur l'hôte : disques en overlay sur l'image vérifiée, image
cidata, lancement des QEMU, attente du SSH. Vérification sur le serveur Scaleway, avec un coût
d'environ une heure entamée.

## E2 — fait le 2026-10-04 (`99ecb59`, branche `feature-50`) `internal/lab/render` transforme le plan en ce qu'il faut pour démarrer chaque VM ; `cmd/lab` l'expose par `lab render -key <clé.pub> <topologie> <répertoire>`, qui écrit pour chaque nœud `qemu.args` (un argument par ligne) et les trois fichiers NoCloud `meta-data`, `user-data`, `network-config`. Rien n'est lancé : l'image `cidata` et 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`, disque `virtio`, `seed.iso` en cdrom, console dans un fichier, QMP sur socket Unix, `-pidfile`. - **Administration** `mgmt0` : NAT de QEMU, MAC `02:4d:<nœud>:<nœud>:00:00`, SSH redirigé sur `127.0.0.1:<2200+n>`. `restrict=on` pour tous les nœuds **sauf le switch** ; `ipv6=off` partout. - **Câbles** : `-netdev dgram` sur `127.0.0.1`, les deux extrémités croisées, `host_mtu` = MTU du segment. 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: false` et `ssh_pwauth: false` sont écrits explicitement, jamais omis. - toutes les VM : interfaces nommées d'après leur MAC, `mgmt0` en `10.0.2.15/24` **sans passerelle**, utilisateur `debian`, SSH par clé seulement, `root` désactivé ; - un nœud : adresse et MTU par segment ; route par défaut et DNS (`1.1.1.1`, `8.8.8.8`) sur son **premier** segment, donc sortie Internet par le switch ; - le switch : route par défaut par `mgmt0` ; un service `lab-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 : **`mgmt0` est réservé**. Un segment de ce nom aurait donné deux interfaces et deux `netdev` homonymes 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, `kvm` remplacé par `tcg` faute 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é : `sw1` et `rr1` de l'exemple ont été démarrés sur un Mac, en émulation, avec Debian 12 `generic` et les fichiers de `lab render`. | Vérification | Résultat | |---|---| | interfaces nommées et adressées, `br-underlay` en `10.250.0.1/24` | conforme | | service `lab-switch` actif, y compris après redémarrage | conforme | | `ping -M do -s 8972` de `rr1` au switch (MTU 9000, sans fragmentation) | passe | | `ping -M do -s 8973` (MTU 9001) | refusé : `message too long, mtu=9000` | | Internet depuis `rr1` en IPv4 | passe, par `10.250.0.1` | | `rr1` vers un service TCP de l'hôte par `mgmt0` — `sw1`, témoin, y parvient | bloqué | | `rr1` vers Internet par `mgmt0` | bloqué | **Défaut trouvé par cet essai, corrigé.** Sans `ipv6=off`, le NAT de QEMU annonce un préfixe IPv6 : `mgmt0` recevait une route IPv6 par défaut vers une impasse (`restrict=on` bloque 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 passerelle virtuelle 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, `-race` propre ; couverture 95,2 % (`render`), 96,3 % (`topology`), 85,2 % (`cmd/lab`). **22 mutations, toutes détectées.** Compilation `linux/amd64`, documentation construite avec `sphinx-build -W` sans avertissement. Au passage, **#47** est sorti pendant une passe de la suite complète, dans `internal/dispatcher/agent`, non modifié par E2. Reproduit sur `main` sans 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` / `status` sur l'hôte : disques en overlay sur l'image vérifiée, image `cidata`, lancement des QEMU, attente du SSH. Vérification sur le serveur Scaleway, avec un coût d'environ une heure entamée.
Author
Owner

E3 — plan, décidé le 2026-10-04

lab up / down / status / ssh sur le serveur de lab, et ce qu'il faut côté Mac pour les y
lancer.

Frontière entre les deux outils

  • lab-host.sh (Mac) : le serveur Scaleway et le transport. Il ne lit jamais la topologie et ne
    connaî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 ssh qui existe déjà :

./lab-host.sh up                              # serveur + paquets + contrôle KVM
./lab-host.sh push conf/lab/evpn-2hv.yml      # binaire lab (linux/amd64) et topologie
./lab-host.sh ssh './lab up evpn-2hv.yml'
./lab-host.sh ssh './lab ssh hv1'             # shell interactif sur hv1
./lab-host.sh ssh                             # session longue sur le serveur
./lab-host.sh down

Décisions

Sujet Choix Écarté
Survie des QEMU à la fin de la session SSH -daemonize ; le pidfile et la socket QMP d'E2 suffisent à status / down unit systemd-run --user (linger, nommage, nettoyage) ; lab up au premier plan (session à garder ouverte)
Image cidata genisoimage -volid cidata -joliet -rock, la méthode documentée par cloud-init ; les arguments QEMU d'E2 ne changent pas vvfat de QEMU (chemin peu utilisé) ; ISO 9660 écrite en Go
Préparation du serveur fonction prepare appelée par up et disponible seule : qemu-system-x86, qemu-utils, genisoimage, contrôle de /dev/kvm et de nested —
Accès aux VM lab ssh <nœud> [cmd], exécuté sur le serveur un ssh_config généré sur le Mac avec ProxyJump
Clé SSH des VM une paire ed25519 générée par lab up sur le serveur, seule clé autorisée dans les VM. Elle n'en sort jamais et disparaît avec lui transfert d'agent (ssh -A) : exposerait l'agent du Mac à root sur le serveur
Clé publique du Mac jamais envoyée : on entre dans les VM depuis le serveur, elle n'a aucun usage —
VM prêtes lab up attend cloud-init status --wait par SSH, possible grâce à la clé du serveur lecture du message de fin dans la console série

lab render -key garde son option : c'est la commande hors ligne d'E2, pour générer et inspecter
les fichiers sur le Mac.

Terminal interactif

Chaque maillon décide d'après sa propre entrée standard, comme ssh lui-même :
lab-host.sh ssh ajoute -t si son entrée est un terminal, lab ssh fait de même avant de céder
la place à ssh par exec. 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

Lot Contenu Vérification
E3a image téléchargée en cache et vérifiée contre SHA512SUMS, disque en overlay qemu-img, seed.iso, paire de clés du lab tests sur Mac, exécuteur factice, valeurs attendues en littéral
E3b lab up (switch d'abord), status (pidfile recoupé avec /proc/<pid>/cmdline), down (quit QMP via internal/qmp), ssh ; up repart toujours de disques neufs tests sur Mac, fausse socket QMP
E3c lab-host.sh : prepare, push, -t sur ssh ; documentation une session Scaleway, voir ci-dessous

Vérification d'E3c sur Scaleway :

  • ping -M do -s 8972 de hv1 à hv2 à travers le switch ;
  • un hyperviseur sort sur Internet par le switch, et ne sort pas par mgmt0 : connexion TCP vers
    un vrai service du serveur, avec sw1 comme témoin qui y parvient ;
  • /dev/kvm présent dans hv1, préalable à E4 ;
  • ./lab-host.sh ssh './lab ssh hv1' donne un shell interactif, et
    echo | ./lab-host.sh ssh './lab ssh hv1 false' rend 1 sans pseudo-terminal ;
  • lab down puis suppression du serveur confirmée.

Analyse de risque — compléments

  • Clé privée du lab sur le serveur : générée sur place, elle n'ouvre que les VM du lab, qui
    n'écoutent qu'en boucle locale, et disparaît avec le serveur.
  • Clés d'hôte des VM : elles changent à chaque up, donc lab ssh ne les vérifie pas
    (known_hosts jetable). Acceptable seulement parce que la connexion reste sur la boucle locale
    d'un serveur auquel on s'est authentifié.
  • Intégrité de l'image : SHA512SUMS est 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, correctif gateway livré pendant #34) : vérification
qui 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_ip au lieu d'être
fabriquée par dnsmasq — et les routes metadata et VPC sont présentes.

## E3 — plan, décidé le 2026-10-04 `lab up` / `down` / `status` / `ssh` sur le serveur de lab, et ce qu'il faut côté Mac pour les y lancer. ### Frontière entre les deux outils - **`lab-host.sh`** (Mac) : le serveur Scaleway et le transport. Il ne lit jamais la topologie et ne connaî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 `ssh` qui existe déjà : ``` ./lab-host.sh up # serveur + paquets + contrôle KVM ./lab-host.sh push conf/lab/evpn-2hv.yml # binaire lab (linux/amd64) et topologie ./lab-host.sh ssh './lab up evpn-2hv.yml' ./lab-host.sh ssh './lab ssh hv1' # shell interactif sur hv1 ./lab-host.sh ssh # session longue sur le serveur ./lab-host.sh down ``` ### Décisions | Sujet | Choix | Écarté | |---|---|---| | Survie des QEMU à la fin de la session SSH | `-daemonize` ; le pidfile et la socket QMP d'E2 suffisent à `status` / `down` | unit `systemd-run --user` (linger, nommage, nettoyage) ; `lab up` au premier plan (session à garder ouverte) | | Image `cidata` | `genisoimage -volid cidata -joliet -rock`, la méthode documentée par cloud-init ; les arguments QEMU d'E2 ne changent pas | `vvfat` de QEMU (chemin peu utilisé) ; ISO 9660 écrite en Go | | Préparation du serveur | fonction `prepare` appelée par `up` et disponible seule : `qemu-system-x86`, `qemu-utils`, `genisoimage`, contrôle de `/dev/kvm` et de `nested` | — | | Accès aux VM | `lab ssh <nœud> [cmd]`, exécuté **sur le serveur** | un `ssh_config` généré sur le Mac avec `ProxyJump` | | Clé SSH des VM | une paire ed25519 **générée par `lab up` sur le serveur**, seule clé autorisée dans les VM. Elle n'en sort jamais et disparaît avec lui | transfert d'agent (`ssh -A`) : exposerait l'agent du Mac à root sur le serveur | | Clé publique du Mac | **jamais envoyée** : on entre dans les VM depuis le serveur, elle n'a aucun usage | — | | VM prêtes | `lab up` attend `cloud-init status --wait` par SSH, possible grâce à la clé du serveur | lecture du message de fin dans la console série | `lab render -key` garde son option : c'est la commande hors ligne d'E2, pour générer et inspecter les fichiers sur le Mac. ### Terminal interactif Chaque maillon décide d'après **sa propre** entrée standard, comme `ssh` lui-même : `lab-host.sh ssh` ajoute `-t` si son entrée est un terminal, `lab ssh` fait de même avant de céder la place à `ssh` par `exec`. 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 | Lot | Contenu | Vérification | |---|---|---| | **E3a** | image téléchargée en cache et vérifiée contre `SHA512SUMS`, disque en overlay `qemu-img`, `seed.iso`, paire de clés du lab | tests sur Mac, exécuteur factice, valeurs attendues en littéral | | **E3b** | `lab up` (switch d'abord), `status` (pidfile recoupé avec `/proc/<pid>/cmdline`), `down` (`quit` QMP via `internal/qmp`), `ssh` ; `up` repart toujours de disques neufs | tests sur Mac, fausse socket QMP | | **E3c** | `lab-host.sh` : `prepare`, `push`, `-t` sur `ssh` ; documentation | une session Scaleway, voir ci-dessous | Vérification d'E3c sur Scaleway : - `ping -M do -s 8972` de hv1 à hv2 à travers le switch ; - un hyperviseur sort sur Internet par le switch, et **ne sort pas** par `mgmt0` : connexion TCP vers un vrai service du serveur, avec `sw1` comme témoin qui y parvient ; - `/dev/kvm` présent dans hv1, préalable à E4 ; - `./lab-host.sh ssh './lab ssh hv1'` donne un shell interactif, et `echo | ./lab-host.sh ssh './lab ssh hv1 false'` rend 1 sans pseudo-terminal ; - `lab down` puis suppression du serveur confirmée. ### Analyse de risque — compléments - **Clé privée du lab sur le serveur** : générée sur place, elle n'ouvre que les VM du lab, qui n'écoutent qu'en boucle locale, et disparaît avec le serveur. - **Clés d'hôte des VM** : elles changent à chaque `up`, donc `lab ssh` ne les vérifie pas (`known_hosts` jetable). Acceptable seulement parce que la connexion reste sur la boucle locale d'un serveur auquel on s'est authentifié. - **Intégrité de l'image** : `SHA512SUMS` est 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, correctif `gateway` livré pendant #34) : vérification qui 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_ip` au lieu d'être fabriquée par dnsmasq — et les routes metadata et VPC sont présentes.
Author
Owner

E3 — fait le 2026-10-04 (d1cd943, 1146d13, 90d8128, branche feature-50)

lab démarre, surveille et arrête les VM sur le serveur de lab ; lab-host.sh prépare le serveur
et y dépose lab. Validé sur Scaleway le 2026-10-04.

Trois lots

Lot Contenu
E3a internal/lab/provision : image en cache vérifiée contre SHA512SUMS (écriture à côté puis renommage : rien en cache sur un échec), disque en overlay qcow2 de 20 Gio, seed.iso par genisoimage, paire de clés générée sur le serveur
E3b internal/lab/machine et lab up / status / down / ssh : QEMU en -daemonize, switchs d'abord, attente de cloud-init status --wait ; état dans ~/lab-run
E3c lab-host.sh : prepare (lancé par up), push <topologie>, -t seulement si l'entrée est un terminal

Écarts au plan

  • Arrêt par SIGTERM puis SIGKILL, pas par quit QMP : internal/qmp.Send lit ses réponses ligne à ligne sans filtrer les événements asynchrones, et un arrêt propre du système invité n'apporte rien quand up recrée les disques.
  • Un PID n'est retenu que si /proc/<pid>/cmdline contient -name <nœud> : un PID réutilisé n'est jamais signalé. stop refuse en outre tout PID ≤ 0 (kill(0) vise le groupe, kill(-1) tous les processus de l'utilisateur).
  • Détection du terminal par l'ioctl termios (TCGETS / TIOCGETA) : os.ModeCharDevice prenait /dev/null pour un terminal.
  • prepare installe QEMU sans les paquets recommandés : la première version tirait GTK, Pango et des codecs, inutiles en -display none.
  • lab-host.sh et son test sont désormais exécutables (mode 100755) ; ils étaient enregistrés en 100644 depuis E0.

Résultat sur Scaleway

Serveur EM-B212X-SSD, Xeon E5-2640 v3, Debian 12 ; QEMU 7.2.22 (contre 11.0 sur le Mac en E2)
up + prepare ~27 min, dont l'installation de QEMU avec ses recommandations — raccourci depuis
lab up, 4 VM prêtes 49 s, téléchargement et vérification de l'image compris
Durée de vie du serveur 15:15 → 16:22, deux heures entamées, ~0,64 € HT ; projet vide vérifié après down
Vérification Résultat
ping -M do -s 8972 hv1 → hv2 à travers le switch passe ; -s 8973 refusé (mtu=9000)
sortie Internet de hv1 par le switch, HTTPS 200, aucune route IPv6 globale
hv1 vers un service TCP du serveur par mgmt0, sw1 en témoin (200) refusé
hv1 vers Internet par mgmt0, route forcée, sw1 en témoin refusé
/dev/kvm, nested, racine dans hv1 présent, Y, 20 Go (growpart)
code de retour à travers lab-host.sh ssh + lab ssh, sans terminal propagé ; deux shells distants, donc deux niveaux de quoting (documenté)
shell interactif lab-host.sh ssh './lab ssh hv1' validé
lab down puis lab up d'une autre topologie conforme

Essais au-delà d'E3, pour lever les inconnues d'E4 — manuels, rien dans le dépôt

  • A — KVM de second niveau : une Debian genericcloud lancée à la main dans hv1 en -accel kvm -cpu host atteint login: 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.
  • B — deploy.sh -i -u underlay sur hv1 (release 0.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-000000 hérite du MTU 9000 de l'uplink. Deux enseignements : le lab devra passer -u <segment> ; la migration ne touche pas mgmt0, l'administration survit à un -i raté.
    deploy.sh rend 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.
  • D — FRR : frr-stable donne 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 (gpg absent de l'image) : à faire en E4.
  • Configuration BGP de production, transposée au lab (ASN et loopback repris, plages adaptées, valeurs non reproduites ici) : session switch ↔ route reflector en IPv4 unicast avec BFD, la loopback du RR seule annoncée ; sessions EVPN des deux hyperviseurs vers cette loopback, voisins dynamiques du RR (bgp listen range), en iBGP grâce au local-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 interface dummy.
    La configuration du switch était un bouche-trou écrit pour l'essai — celle des routeurs reste à fournir.
  • C — image Rocky 10 : écartée pour cette session, le débit de téléchargement imposait plus d'une heure. Une autre voie est à trouver avant le critère d'E4.

Erreurs de méthode, corrigées en route

  • Une campagne de mutation a fait appeler 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é.
  • Fichiers attribués à tort à une autre session : ils venaient de cette session, d'un appel lancé pendant sa mise en arrière-plan et absent de la reprise. Vérifié par la transcription.
  • Premier essai A sans 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é. Le Fetcher de lab suit les redirections.

Vérification

Go : 169 cas pour le lab, sous-tests compris (cmd/lab 15, topology 63, render 23, provision 38, machine 30), -race propre ; 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 avec sphinx-build -W sans avertissement, exemples tirés de la session réelle.

Pour la suite

  • E4 : rôles automatisés (FRR sur switch et RR, adresses du lien et loopback du RR, 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.
  • Préalable à E5 : la configuration FRR des hyperviseurs face aux netns de two, écrite une fois pour les pages « À rédiger » et pour le lab.
  • Évolutions notées : plusieurs serveurs dans lab-host.sh (un lab par serveur) ; vérification de la signature GPG de SHA512SUMS.
  • Image des VM de test : l'image Rocky 10 de l'équipe, une fois une voie de transfert trouvée.
## E3 — fait le 2026-10-04 (`d1cd943`, `1146d13`, `90d8128`, branche `feature-50`) `lab` démarre, surveille et arrête les VM sur le serveur de lab ; `lab-host.sh` prépare le serveur et y dépose `lab`. Validé sur Scaleway le 2026-10-04. ### Trois lots | Lot | Contenu | |---|---| | E3a | `internal/lab/provision` : image en cache vérifiée contre `SHA512SUMS` (écriture à côté puis renommage : rien en cache sur un échec), disque en overlay qcow2 de 20 Gio, `seed.iso` par `genisoimage`, paire de clés générée sur le serveur | | E3b | `internal/lab/machine` et `lab up` / `status` / `down` / `ssh` : QEMU en `-daemonize`, switchs d'abord, attente de `cloud-init status --wait` ; état dans `~/lab-run` | | E3c | `lab-host.sh` : `prepare` (lancé par `up`), `push <topologie>`, `-t` seulement si l'entrée est un terminal | ### Écarts au plan - **Arrêt par `SIGTERM` puis `SIGKILL`, pas par `quit` QMP** : `internal/qmp.Send` lit ses réponses ligne à ligne sans filtrer les événements asynchrones, et un arrêt propre du système invité n'apporte rien quand `up` recrée les disques. - **Un PID n'est retenu que si `/proc/<pid>/cmdline` contient `-name <nœud>`** : un PID réutilisé n'est jamais signalé. `stop` refuse en outre tout PID ≤ 0 (`kill(0)` vise le groupe, `kill(-1)` tous les processus de l'utilisateur). - **Détection du terminal par l'ioctl termios** (`TCGETS` / `TIOCGETA`) : `os.ModeCharDevice` prenait `/dev/null` pour un terminal. - `prepare` installe QEMU **sans les paquets recommandés** : la première version tirait GTK, Pango et des codecs, inutiles en `-display none`. - `lab-host.sh` et son test sont désormais exécutables (mode `100755`) ; ils étaient enregistrés en `100644` depuis E0. ### Résultat sur Scaleway | | | |---|---| | Serveur | EM-B212X-SSD, Xeon E5-2640 v3, Debian 12 ; **QEMU 7.2.22** (contre 11.0 sur le Mac en E2) | | `up` + `prepare` | ~27 min, dont l'installation de QEMU avec ses recommandations — raccourci depuis | | `lab up`, 4 VM prêtes | **49 s**, téléchargement et vérification de l'image compris | | Durée de vie du serveur | 15:15 → 16:22, deux heures entamées, ~0,64 € HT ; projet vide vérifié après `down` | | Vérification | Résultat | |---|---| | `ping -M do -s 8972` hv1 → hv2 à travers le switch | passe ; `-s 8973` refusé (`mtu=9000`) | | sortie Internet de hv1 | par le switch, HTTPS 200, aucune route IPv6 globale | | hv1 vers un service TCP du serveur par `mgmt0`, sw1 en témoin (200) | refusé | | hv1 vers Internet par `mgmt0`, route forcée, sw1 en témoin | refusé | | `/dev/kvm`, `nested`, racine dans hv1 | présent, `Y`, 20 Go (`growpart`) | | code de retour à travers `lab-host.sh ssh` + `lab ssh`, sans terminal | propagé ; deux shells distants, donc deux niveaux de quoting (documenté) | | shell interactif `lab-host.sh ssh './lab ssh hv1'` | validé | | `lab down` puis `lab up` d'une autre topologie | conforme | ### Essais au-delà d'E3, pour lever les inconnues d'E4 — manuels, rien dans le dépôt - **A — KVM de second niveau** : une Debian `genericcloud` lancée à la main dans hv1 en `-accel kvm -cpu host` atteint `login:` 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. - **B — `deploy.sh -i -u underlay` sur hv1** (release `0.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-000000` hérite du MTU 9000 de l'uplink. Deux enseignements : le lab devra passer `-u <segment>` ; la migration ne touche pas `mgmt0`, l'administration survit à un `-i` raté. **`deploy.sh` rend 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. - **D — FRR** : `frr-stable` donne **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 (`gpg` absent de l'image) : à faire en E4. - **Configuration BGP de production, transposée au lab** (ASN et loopback repris, plages adaptées, valeurs non reproduites ici) : session switch ↔ route reflector en IPv4 unicast avec BFD, la loopback du RR seule annoncée ; sessions EVPN des deux hyperviseurs vers cette loopback, voisins dynamiques du RR (`bgp listen range`), en iBGP grâce au `local-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 interface `dummy`. La configuration du **switch** était un bouche-trou écrit pour l'essai — celle des routeurs reste à fournir. - **C — image Rocky 10** : écartée pour cette session, le débit de téléchargement imposait plus d'une heure. Une autre voie est à trouver avant le critère d'E4. ### Erreurs de méthode, corrigées en route - Une campagne de mutation a fait appeler `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é. - Fichiers attribués à tort à une autre session : ils venaient de cette session, d'un appel lancé pendant sa mise en arrière-plan et absent de la reprise. Vérifié par la transcription. - Premier essai A sans `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é. Le `Fetcher` de `lab` suit les redirections. ### Vérification Go : 169 cas pour le lab, sous-tests compris (`cmd/lab` 15, `topology` 63, `render` 23, `provision` 38, `machine` 30), `-race` propre ; 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 avec `sphinx-build -W` sans avertissement, exemples tirés de la session réelle. ### Pour la suite - **E4** : rôles automatisés (FRR sur switch et RR, adresses du lien et loopback du RR, `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. - **Préalable à E5** : la configuration FRR des hyperviseurs face aux netns de two, écrite une fois pour les pages « À rédiger » et pour le lab. - **Évolutions notées** : plusieurs serveurs dans `lab-host.sh` (un lab par serveur) ; vérification de la signature GPG de `SHA512SUMS`. - **Image des VM de test** : l'image Rocky 10 de l'équipe, une fois une voie de transfert trouvée.
Author
Owner

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

Sujet Choix Écarté
two sur les hyperviseurs la release publiée, par deploy.sh -i -u <segment> -t <tag> : le lab qualifie ce qui est livré, par le chemin de la production binaires compilés depuis la branche — viendront si un scénario l'exige
deploy.sh utilisé celui du dépôt, déposé par lab, avec --noup_script : la version testée est celle de la branche celui de main, que l'auto-mise à jour imposerait
Configuration des rôles des frr.conf écrits à la main dans conf/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 » une configuration générée par lab depuis la topologie : elle n'existerait que dans le code Go, et doc et lab pourraient diverger
Valeurs BGP et plages celles de la production — ASN, loopback du RR, lien 169.254.0.0/28, subnet des hyperviseurs — pour que les fichiers de conf/lab/ soient au plus près de la production ; noms d'hôtes propres au lab valeurs dédiées au lab (décision du 2026-10-03, revue)
Clé du dépôt FRR enregistrée dans le dépôt, empreinte vérifiée, plutôt que téléchargée à chaque démarrage téléchargement au démarrage (confiance au premier usage)
Image de la VM de test Debian genericcloud, vérifiée par SHA512SUMS l'image Rocky 10 de l'équipe, en attente d'une voie de transfert — elle la remplacera

Validé 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 une
interface dummy ; le switch porte la sienne sur le bridge du segment.

Découpage

Lot Contenu Vérification
E4a topologie : par nœud, frr (chemin d'un frr.conf de conf/lab/), adresses secondaires par segment, loopback ; rendu : adresses et dummy, installation de FRR (frr-stable, clé du dépôt), démons selon le rôle, frr.conf déposé après le paquet tests sur Mac, valeurs attendues en littéral
E4b hyperviseurs : deploy.sh du dépôt lancé par cloud-init (--noup_script -i -u <segment> -t <tag>, tag dans la topologie), puis FRR ; lab up attend la fin, un échec remonte tests sur Mac
E4c route-reflector.rst inclut le frr.conf du RR ; une session Scaleway sessions BGP Established, BFD up ; agent actif sur les deux hyperviseurs ; VPC, subnet et VM Debian genericcloud créés par l'API de hv1 : la VM atteint login: en KVM imbriqué

Reste en attente

  • La configuration des routeurs (rôle du switch) : à fournir. D'ici là, le switch du lab garde
    la configuration minimale de la session E3, documentée comme telle, et routeurs.rst reste
    « À rédiger ».
  • La configuration FRR des hyperviseurs face aux netns de two — préalable à E5, inchangé.

Analyse de risque — compléments

  • Publication des valeurs de production : ASN, loopback et plages entrent dans un dépôt et une
    documentation publics. Choix assumé ; aucun nom d'hôte ni adresse d'équipement de production
    n'y est écrit.
  • Chaîne d'approvisionnement : FRR vient d'un dépôt tiers, authentifié par une clé enregistrée
    dans le dépôt et vérifiée ; deploy.sh vérifie les artefacts de la release contre SHA256SUMS.
  • Le lab reste isolé : les sessions BGP ne vivent que sur les câbles dgram.
## 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 | Sujet | Choix | Écarté | |---|---|---| | two sur les hyperviseurs | la **release publiée**, par `deploy.sh -i -u <segment> -t <tag>` : le lab qualifie ce qui est livré, par le chemin de la production | binaires compilés depuis la branche — viendront si un scénario l'exige | | `deploy.sh` utilisé | celui **du dépôt**, déposé par `lab`, avec `--noup_script` : la version testée est celle de la branche | celui de `main`, que l'auto-mise à jour imposerait | | Configuration des rôles | des `frr.conf` **écrits à la main dans `conf/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 » | une configuration générée par `lab` depuis la topologie : elle n'existerait que dans le code Go, et doc et lab pourraient diverger | | Valeurs BGP et plages | **celles de la production** — ASN, loopback du RR, lien `169.254.0.0/28`, subnet des hyperviseurs — pour que les fichiers de `conf/lab/` soient au plus près de la production ; noms d'hôtes propres au lab | valeurs dédiées au lab (décision du 2026-10-03, revue) | | Clé du dépôt FRR | **enregistrée dans le dépôt**, empreinte vérifiée, plutôt que téléchargée à chaque démarrage | téléchargement au démarrage (confiance au premier usage) | | Image de la VM de test | Debian `genericcloud`, vérifiée par `SHA512SUMS` | l'image Rocky 10 de l'équipe, en attente d'une voie de transfert — elle la remplacera | Validé 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 une interface `dummy` ; le switch porte la sienne sur le bridge du segment. ### Découpage | Lot | Contenu | Vérification | |---|---|---| | **E4a** | topologie : par nœud, `frr` (chemin d'un `frr.conf` de `conf/lab/`), adresses secondaires par segment, `loopback` ; rendu : adresses et `dummy`, installation de FRR (`frr-stable`, clé du dépôt), démons selon le rôle, `frr.conf` déposé après le paquet | tests sur Mac, valeurs attendues en littéral | | **E4b** | hyperviseurs : `deploy.sh` du dépôt lancé par cloud-init (`--noup_script -i -u <segment> -t <tag>`, tag dans la topologie), puis FRR ; `lab up` attend la fin, un échec remonte | tests sur Mac | | **E4c** | `route-reflector.rst` inclut le `frr.conf` du RR ; une session Scaleway | sessions BGP Established, BFD up ; agent actif sur les deux hyperviseurs ; VPC, subnet et VM Debian `genericcloud` créés par l'API de hv1 : la VM atteint `login:` en KVM imbriqué | ### Reste en attente - **La configuration des routeurs** (rôle du switch) : à fournir. D'ici là, le switch du lab garde la configuration minimale de la session E3, documentée comme telle, et `routeurs.rst` reste « À rédiger ». - La configuration FRR des hyperviseurs face aux netns de two — préalable à E5, inchangé. ### Analyse de risque — compléments - **Publication des valeurs de production** : ASN, loopback et plages entrent dans un dépôt et une documentation publics. Choix assumé ; aucun nom d'hôte ni adresse d'équipement de production n'y est écrit. - **Chaîne d'approvisionnement** : FRR vient d'un dépôt tiers, authentifié par une clé enregistrée dans le dépôt et vérifiée ; `deploy.sh` vérifie les artefacts de la release contre `SHA256SUMS`. - Le lab reste isolé : les sessions BGP ne vivent que sur les câbles `dgram`.
Author
Owner

E4 — fait le 2026-10-04 (b348652, 4410f30, bc05fd9, branche feature-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ère
d'E4 est atteint.

Trois lots

Lot Contenu
E4a (b348652) topologie : par nœud secondary (adresses supplémentaires, hors du CIDR du segment), loopback (lo1, dummy), frr (un frr.conf de conf/lab/frr/) ; FRR frr-stable installé avec la clé du dépôt enregistrée dans lab ; push envoie tout le répertoire de la topologie
E4b (4410f30) hyperviseurs : deploy.sh et bootstrap_kvm.sh du dépôt, embarqués dans lab (paquet scripts), lancés par deploy.sh --noup_script -i -u <uplink> -t <release> ; release obligatoire dans la topologie ; lab up vérifie agent et frr
E4c (bc05fd9) route-reflector.rst : adressage et configuration FRR, incluse depuis conf/lab/frr/rr1.conf

Valeurs de production (ASN, loopback, lien 169.254.0.0/28, subnet des hyperviseurs) dans
conf/lab/, toutes les adresses fixées par addresses — route reflector en .2, hyperviseurs à
partir de .11.

Trouvé en route

  • cloud-init exécute runcmd sans set -e (vérifié dans shellify, 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, en set -eu.
  • Course au démarrage : un nœud installe FRR à travers le NAT du switch, qui peut ne pas être
    prêt ; mise à jour des paquets réessayée cinq minutes avec --error-on=any (sans quoi
    apt-get update rend 0 même sur un dépôt injoignable).
  • push n'envoyait pas les frr.conf référencés ; il envoie maintenant le répertoire, sans les
    métadonnées macOS.
  • L'uplink de deploy.sh est l'interface qui porte la route par défaut : celle du premier segment
    dans 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)

Vérification Résultat
up + prepare 15 min (27 en E3 : QEMU sans les paquets recommandés)
lab up avec rôles et deploy.sh 6 min 15, vérifications d'agent et de frr passées
switch ↔ route reflector, IPv4 unicast, BFD Established, BFD up ; seule la loopback est annoncée ; session vers le second routeur en attente (attendu)
hyperviseurs → loopback du route reflector, EVPN Established, voisins dynamiques, stables
réseau d'un hyperviseur après deploy.sh adresse sur br-000000, MTU 9000, API de l'agent OK
critère d'E4 : VPC, subnet vxlan, VM Debian genericcloud par l'API de hv1 login: en 20 s, en KVM imbriqué
DHCP et option 121 servis par two conformes, route /32 vers 169.254.169.254 comprise
métadonnées échec avec seedfrom sans barre oblique finale ; OK avec (DataSourceNoCloudNet, nom d'hôte appliqué)
VM ↔ VM entre hyperviseurs, même subnet vxlan échec — VTEP local absent, voir ci-dessous

Deux constats sur le produit

seedfrom de image-qcow2.rst : jusqu'à cloud-init 22.4 (Debian 12 : 22.4.2), l'URL est
concaténée sans barre oblique et devient http://169.254.169.254:80meta-data ; à partir de 23.1, un
drapeau 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 ne
l'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_iface du subnet), l'adresse suffit : routes EVPN de
type 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-vni
les voit. Le préalable « configuration FRR des hyperviseurs face aux netns » se réduit à #51.

Erreurs de méthode, corrigées en route

  • Une première lecture « les sessions EVPN flappent » : elles venaient de s'établir, FRR étant
    installé en dernier sur les hyperviseurs. Vérifié par l'uptime croissant et les journaux.
  • Scripts de session : un heredoc imbriqué refermé trop tôt, une clé publique non quotée, un ssh
    qui 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.sh 60/60 sous bash 5 et 3.2, documentation construite
avec sphinx-build -W, sorties réelles dans lab.rst.

Corrections en cours de commit sur feature-50

  • image-qcow2.rst : seedfrom avec 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

  • E5 : scénarios lancés à la main — DHCP two avec systemd-networkd (#46), gateway /
    public_ip (#31) ; puis, une fois #51 intégré, convergence EVPN, MTU de bout en bout, test
    négatif inter-VPC (#41), perte et retour du route reflector.
  • En attente : la configuration des routeurs (rôle du switch, routeurs.rst) ; une voie de
    transfert pour l'image Rocky 10.
  • Ticket proposé : deploy.sh exécute shflags par eval, depuis un autre dépôt, sans
    épinglage.
## E4 — fait le 2026-10-04 (`b348652`, `4410f30`, `bc05fd9`, branche `feature-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ère d'E4 est atteint. ### Trois lots | Lot | Contenu | |---|---| | E4a (`b348652`) | topologie : par nœud `secondary` (adresses supplémentaires, hors du CIDR du segment), `loopback` (`lo1`, `dummy`), `frr` (un `frr.conf` de `conf/lab/frr/`) ; FRR `frr-stable` installé avec la clé du dépôt **enregistrée dans `lab`** ; `push` envoie tout le répertoire de la topologie | | E4b (`4410f30`) | hyperviseurs : `deploy.sh` et `bootstrap_kvm.sh` **du dépôt**, embarqués dans `lab` (paquet `scripts`), lancés par `deploy.sh --noup_script -i -u <uplink> -t <release>` ; `release` obligatoire dans la topologie ; `lab up` vérifie `agent` et `frr` | | E4c (`bc05fd9`) | `route-reflector.rst` : adressage et configuration FRR, incluse depuis `conf/lab/frr/rr1.conf` | Valeurs de production (ASN, loopback, lien `169.254.0.0/28`, subnet des hyperviseurs) dans `conf/lab/`, toutes les adresses fixées par `addresses` — route reflector en `.2`, hyperviseurs à partir de `.11`. ### Trouvé en route - **cloud-init exécute `runcmd` sans `set -e`** (vérifié dans `shellify`, 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`, en `set -eu`. - **Course au démarrage** : un nœud installe FRR à travers le NAT du switch, qui peut ne pas être prêt ; mise à jour des paquets réessayée cinq minutes avec `--error-on=any` (sans quoi `apt-get update` rend 0 même sur un dépôt injoignable). - `push` n'envoyait pas les `frr.conf` référencés ; il envoie maintenant le répertoire, sans les métadonnées macOS. - L'uplink de `deploy.sh` est l'interface qui porte la route par défaut : celle du premier segment dans 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) | Vérification | Résultat | |---|---| | `up` + `prepare` | 15 min (27 en E3 : QEMU sans les paquets recommandés) | | `lab up` avec rôles et `deploy.sh` | **6 min 15**, vérifications d'`agent` et de `frr` passées | | switch ↔ route reflector, IPv4 unicast, BFD | Established, BFD up ; seule la loopback est annoncée ; session vers le second routeur en attente (attendu) | | hyperviseurs → loopback du route reflector, EVPN | Established, voisins dynamiques, stables | | réseau d'un hyperviseur après `deploy.sh` | adresse sur `br-000000`, MTU 9000, API de l'agent OK | | **critère d'E4** : VPC, subnet `vxlan`, VM Debian `genericcloud` par l'API de hv1 | **`login:` en 20 s, en KVM imbriqué** | | DHCP et option 121 servis par two | conformes, route `/32` vers `169.254.169.254` comprise | | métadonnées | **échec** avec `seedfrom` sans barre oblique finale ; **OK** avec (`DataSourceNoCloudNet`, nom d'hôte appliqué) | | VM ↔ VM entre hyperviseurs, même subnet `vxlan` | **échec** — VTEP local absent, voir ci-dessous | ### Deux constats sur le produit **`seedfrom` de `image-qcow2.rst`** : jusqu'à cloud-init 22.4 (Debian 12 : 22.4.2), l'URL est concaténée sans barre oblique et devient `http://169.254.169.254:80meta-data` ; à partir de 23.1, un drapeau 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 ne l'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_iface` du subnet), l'adresse suffit : routes EVPN de type 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-vni` les voit. Le préalable « configuration FRR des hyperviseurs face aux netns » se réduit à #51. ### Erreurs de méthode, corrigées en route - Une première lecture « les sessions EVPN flappent » : elles venaient de s'établir, FRR étant installé en dernier sur les hyperviseurs. Vérifié par l'uptime croissant et les journaux. - Scripts de session : un heredoc imbriqué refermé trop tôt, une clé publique non quotée, un `ssh` qui 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.sh` 60/60 sous bash 5 et 3.2, documentation construite avec `sphinx-build -W`, sorties réelles dans `lab.rst`. ### Corrections en cours de commit sur `feature-50` - `image-qcow2.rst` : `seedfrom` avec 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 - **E5** : scénarios lancés à la main — DHCP `two` avec systemd-networkd (#46), gateway / `public_ip` (#31) ; puis, une fois #51 intégré, convergence EVPN, MTU de bout en bout, test négatif inter-VPC (#41), perte et retour du route reflector. - **En attente** : la configuration des routeurs (rôle du switch, `routeurs.rst`) ; une voie de transfert pour l'image Rocky 10. - **Ticket proposé** : `deploy.sh` exécute `shflags` par `eval`, depuis un autre dépôt, sans épinglage.
Author
Owner

E5 — fait le 2026-10-04 (b4660c9, 909b411, branche feature-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), hv2
sur dnsmasq.

Outillage

  • scripts/lab/scenario.sh <scénario>… | all (Mac) envoie à chaque nœud, par lab-host.sh ssh, la
    bibliothèque scripts/lab/node.sh et un bloc du scénario ; une ligne RÉUSSI / ÉCHOUÉ (avec la
    raison) / INFO par vérification, code 1 si une vérification échoue ou si aucune n'a été faite.
  • Vérifications négatives par vm_fails : réussie seulement 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.
  • Image Debian compatible two préparée une fois par hyperviseur (seedfrom avec barre oblique
    finale, cf. E4).
  • Topologie : champ agent par hyperviseur (/etc/two/agent.yml posé avant deploy.sh).
  • Tests : 11 (lanceur et bibliothèque, faux lab-host.sh qui vérifie la syntaxe de chaque bloc
    réellement envoyé), bash 5 et 3.2 ; mutation : 9 mutants, un équivalent (code simplifié), un vrai
    trou fermé (une ressource en error doit arrêter l'attente immédiatement, pas au bout de 3 min).

Résultats

Scénario Vérifications Ce qui est établi
s1-dhcp-two (#46) 37/37 backend 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 /32 vers les métadonnées via interface_ip ; chaque serveur ne connaît que son subnet
s2-gateway (#31) 22/22 route par défaut via interface_ip, ou via gateway avec default_route ; route vers la VPC toujours via interface_ip ; identique après suppression et recréation du subnet
s3-isolation-local 12/12 deux VPC sur un hyperviseur : ni ICMP ni TCP entre elles, chaque VM joint sa passerelle (témoin)
s4-evpn (#51) 14/14 local posé 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és
s5-isolation-evpn 13/13 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), pas celle de l'autre VPC — aucune résolution ARP
s6-rr-loss 8/8 voir ci-dessous

Perte du route reflector — mesuré

Un seul route reflector, FRR 10.7.1, 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, 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-restart sont nécessaires avant la production. route-reflector.rst
le 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 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 — un mélange serait passé inaperçu. Repéré dans la ligne INFO du run ; vérifié à la main
sur 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 (up 12 min, lab up 6 min 26, scénarios 8 min) : une heure entamée,
~0,32 € HT ; projet vide vérifié. push extrait désormais sans conserver l'UID du Mac.

Pour la suite

  • Redondance (4ᵉ étape du découpage d'origine) : deux route reflectors, deux switchs — et
    rejouer s6 pour mesurer le comportement avec un route reflector restant.
  • graceful-restart : à qualifier comme alternative ou complément.
  • Les routeurs réels (rôle du switch, routeurs.rst) et l'image Rocky 10 restent en attente.
  • Ticket à ouvrir : deploy.sh exécute shflags par eval, depuis un autre dépôt, sans épinglage.
  • #51 : validé en release 0.2.0rc003 par s4 et s5 ; reste sa revue technique et son
    intégration à main.
## E5 — fait le 2026-10-04 (`b4660c9`, `909b411`, branche `feature-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`), hv2 sur dnsmasq. ### Outillage - `scripts/lab/scenario.sh <scénario>… | all` (Mac) envoie à chaque nœud, par `lab-host.sh ssh`, la bibliothèque `scripts/lab/node.sh` et un bloc du scénario ; une ligne `RÉUSSI` / `ÉCHOUÉ` (avec la raison) / `INFO` par vérification, code 1 si une vérification échoue **ou si aucune n'a été faite**. - Vérifications négatives par `vm_fails` : réussie seulement 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. - Image Debian compatible two préparée une fois par hyperviseur (`seedfrom` avec barre oblique finale, cf. E4). - Topologie : champ `agent` par hyperviseur (`/etc/two/agent.yml` posé avant `deploy.sh`). - Tests : 11 (lanceur et bibliothèque, faux `lab-host.sh` qui vérifie la syntaxe de chaque bloc réellement envoyé), bash 5 et 3.2 ; mutation : 9 mutants, un équivalent (code simplifié), un vrai trou fermé (une ressource en `error` doit arrêter l'attente immédiatement, pas au bout de 3 min). ### Résultats | Scénario | Vérifications | Ce qui est établi | |---|---|---| | `s1-dhcp-two` (#46) | 37/37 | backend `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 `/32` vers les métadonnées via `interface_ip` ; chaque serveur ne connaît que son subnet | | `s2-gateway` (#31) | 22/22 | route par défaut via `interface_ip`, ou via `gateway` avec `default_route` ; route vers la VPC toujours via `interface_ip` ; identique après suppression et recréation du subnet | | `s3-isolation-local` | 12/12 | deux VPC sur un hyperviseur : ni ICMP ni TCP entre elles, chaque VM joint sa passerelle (témoin) | | `s4-evpn` (#51) | 14/14 | `local` posé 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és | | `s5-isolation-evpn` | 13/13 | 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), pas celle de l'autre VPC — aucune résolution ARP | | `s6-rr-loss` | 8/8 | voir ci-dessous | ### Perte du route reflector — mesuré Un seul route reflector, FRR 10.7.1, 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, 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-restart` sont nécessaires avant la production. `route-reflector.rst` le 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 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 — un mélange serait passé inaperçu. Repéré dans la ligne `INFO` du run ; vérifié à la main sur 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 (`up` 12 min, `lab up` 6 min 26, scénarios 8 min) : une heure entamée, ~0,32 € HT ; projet vide vérifié. `push` extrait désormais sans conserver l'UID du Mac. ### Pour la suite - **Redondance** (4ᵉ étape du découpage d'origine) : deux route reflectors, deux switchs — et rejouer `s6` pour mesurer le comportement avec un route reflector restant. - `graceful-restart` : à qualifier comme alternative ou complément. - Les routeurs réels (rôle du switch, `routeurs.rst`) et l'image Rocky 10 restent en attente. - Ticket à ouvrir : `deploy.sh` exécute `shflags` par `eval`, depuis un autre dépôt, sans épinglage. - **#51** : validé en release `0.2.0rc003` par `s4` et `s5` ; reste sa revue technique et son intégration à `main`.
Author
Owner

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, de 28ce00c à
ddcbd2b.

Ce qui est livré

Étape Livrable
E0 scripts/lab-host.sh : serveur Scaleway Elastic Metal loué à l'heure — plan, up (avec prepare), ssh, push, down, session ; suppression garantie (trap EXIT, down par tag et par identifiant)
E1 lab plan : topologie YAML déclarative, plan déterministe (adresses, MAC, ports, câbles)
E2 lab render : arguments QEMU (câbles dgram en MTU 9000, administration NAT isolée en restrict=on) et seeds cloud-init
E3 lab up / status / down / ssh sur le serveur : image vérifiée, disques neufs à chaque up, attente de cloud-init et des services de chaque rôle
E4 rôles au démarrage : FRR (frr-stable, clé épinglée) sur le switch et le route reflector, two par deploy.sh du dépôt puis FRR sur les hyperviseurs ; configurations FRR dans conf/lab/frr/, incluses par la documentation
E5 six scénarios (scripts/lab/scenario.sh) : DHCP two, gateway, isolation entre VPC sur un et deux hyperviseurs, EVPN et MTU, perte du route reflector — 106/106

Documentation : 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 FRR
validés dans le lab, perte du route reflector mesurée), docs/deploiement/image-qcow2.rst
(seedfrom).

Ce que le lab a déjà trouvé

  • #51 — les VXLAN de two n'avaient pas d'adresse VTEP locale : EVPN inopérant entre deux
    hyperviseurs two. Patch validé en release 0.2.0rc003 (feature-51) ; revue et merge à faire.
  • seedfrom sans barre oblique finale (image-qcow2.rst) : cassé avec cloud-init < 23.1
    (Debian 12). Corrigé dans la doc.
  • deploy.sh rendait 1 même en cas de succès : corrigé (32940c5).
  • Perte du route reflector : avec un seul route reflector, plus aucun trafic entre hyperviseurs
    pendant son absence ; retour 31 s après lui. D'où #54.
  • #46 validé en lab (systemd-networkd, un serveur DHCP par subnet) et #31 (gateway) vérifié
    — résultats sur chaque ticket.

Décisions qui ont changé en route

  • CLI scw remplacée par l'API REST (résolution d'offre non fiable, risque d'engagement mensuel).
  • Valeurs BGP et plages : celles de la production, dans un dépôt public (choix assumé) ; noms
    d'hôtes et adresses d'équipements de production exclus.
  • Routeur du lab : un Linux avec FRR, par choix — l'équipement réel n'a besoin que de BGP et
    d'EVPN.
  • Image des VM de test : Debian genericcloud rendue compatible two, en attendant #53.

Tickets issus de ce travail

  • #51 — VTEP local des VXLAN (patch prêt, en 0.2.0rc003).
  • #52 — hyperviseurs en Rocky 10, puis Rocky diskless (next_feature).
  • #53 — fournir au lab l'image Rocky 10 des VM (0.2.0).
  • #54 — redondance : deux route reflectors, deux switchs (0.2.0), 4ᵉ étape du découpage
    d'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 :

  • revue technique du code de feature-50 (Go cmd/lab, internal/lab/*, scripts
    lab-host.sh, scripts/lab/*) avant merge vers main ;
  • revue sécurité : isolation du lab (câbles dgram, restrict=on vé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 ;
  • tests : faits et décrits dans chaque bilan (suites Go et shell, campagnes de mutation,
    sessions réelles) ;
  • traçabilité : l'usage de l'IA est retracé étape par étape dans les commentaires de ce
    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 »).

## 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`, de `28ce00c` à `ddcbd2b`. ### Ce qui est livré | Étape | Livrable | |---|---| | E0 | `scripts/lab-host.sh` : serveur Scaleway Elastic Metal loué à l'heure — `plan`, `up` (avec `prepare`), `ssh`, `push`, `down`, `session` ; suppression garantie (`trap EXIT`, `down` par tag et par identifiant) | | E1 | `lab plan` : topologie YAML déclarative, plan déterministe (adresses, MAC, ports, câbles) | | E2 | `lab render` : arguments QEMU (câbles `dgram` en MTU 9000, administration NAT isolée en `restrict=on`) et seeds cloud-init | | E3 | `lab up` / `status` / `down` / `ssh` sur le serveur : image vérifiée, disques neufs à chaque `up`, attente de cloud-init et des services de chaque rôle | | E4 | rôles au démarrage : FRR (`frr-stable`, clé épinglée) sur le switch et le route reflector, two par `deploy.sh` du dépôt puis FRR sur les hyperviseurs ; configurations FRR dans `conf/lab/frr/`, incluses par la documentation | | E5 | six scénarios (`scripts/lab/scenario.sh`) : DHCP `two`, gateway, isolation entre VPC sur un et deux hyperviseurs, EVPN et MTU, perte du route reflector — 106/106 | Documentation : `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 FRR validés dans le lab, perte du route reflector mesurée), `docs/deploiement/image-qcow2.rst` (`seedfrom`). ### Ce que le lab a déjà trouvé - **#51** — les VXLAN de two n'avaient pas d'adresse VTEP locale : EVPN inopérant entre deux hyperviseurs two. Patch validé en release `0.2.0rc003` (`feature-51`) ; revue et merge à faire. - **`seedfrom` sans barre oblique finale** (`image-qcow2.rst`) : cassé avec cloud-init < 23.1 (Debian 12). Corrigé dans la doc. - **`deploy.sh` rendait 1 même en cas de succès** : corrigé (`32940c5`). - **Perte du route reflector** : avec un seul route reflector, plus aucun trafic entre hyperviseurs pendant son absence ; retour 31 s après lui. D'où #54. - **#46** validé en lab (systemd-networkd, un serveur DHCP par subnet) et **#31** (gateway) vérifié — résultats sur chaque ticket. ### Décisions qui ont changé en route - CLI `scw` remplacée par l'API REST (résolution d'offre non fiable, risque d'engagement mensuel). - Valeurs BGP et plages : celles de la production, dans un dépôt public (choix assumé) ; noms d'hôtes et adresses d'équipements de production exclus. - Routeur du lab : un Linux avec FRR, par choix — l'équipement réel n'a besoin que de BGP et d'EVPN. - Image des VM de test : Debian `genericcloud` rendue compatible two, en attendant #53. ### Tickets issus de ce travail - #51 — VTEP local des VXLAN (patch prêt, en `0.2.0rc003`). - #52 — hyperviseurs en Rocky 10, puis Rocky diskless (`next_feature`). - #53 — fournir au lab l'image Rocky 10 des VM (`0.2.0`). - #54 — redondance : deux route reflectors, deux switchs (`0.2.0`), 4ᵉ étape du découpage d'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 : - **revue technique** du code de `feature-50` (Go `cmd/lab`, `internal/lab/*`, scripts `lab-host.sh`, `scripts/lab/*`) avant merge vers `main` ; - **revue sécurité** : isolation du lab (câbles `dgram`, `restrict=on` vé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 ; - **tests** : faits et décrits dans chaque bilan (suites Go et shell, campagnes de mutation, sessions réelles) ; - **traçabilité** : l'usage de l'IA est retracé étape par étape dans les commentaires de ce 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 »).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
syonad/two#50
No description provided.