bascule du plan sur le gabarit recapture, prouvee par une VM reelle

proxmox_clone_* pointent sur modeleChezlepro-travail (99998). L'ancien reste
sur le cluster — il porte encore un resolv.conf et une cle privee d'hote SSH
du reseau de fabrication ; a supprimer quand plusieurs VM seront nees du
nouveau.

Preuve par une machine, pas par lecture de config : web-dorsal-01 rase puis
recree par le chemin normal. Herite du nouveau gabarit un resolv.conf reduit a
ses commentaires, un machine-id neuf, une cle d'hote regeneree. Deploiement
complet sans echec, cinq devis CONFORME.

raser accepte --hote : les quatre verrous restent en vigueur, on ne fait que
RESTREINDRE la liste derivee du plan.

Releve, non corrige : le premier deploiement s'est interrompu sur
client_unbound, a l'instant de la bascule du resolveur vers 127.0.0.1 — sshd
fige sa resolution a l'ouverture de session (« banner exchange »). L'hote
repondait une minute plus tard et la reprise est passee integralement. C'est
une COURSE, meme famille que celles de la reconstruction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-09 11:50:34 -04:00
parent 3618dfc129
commit 9cdfcc5deb
3 changed files with 46 additions and 2 deletions

View file

@ -1,5 +1,39 @@
# CHANGELOG — Set-OPS
## 2026-08-09 — Le plan bascule sur le gabarit recapturé, prouvé par une VM réelle
`proxmox_clone_source_nom` / `proxmox_clone_vmid_modele` pointent désormais sur
`modeleChezlepro-travail` (99998). L'ancien (99999) reste sur le cluster : il porte encore
un `/etc/resolv.conf` et une clé privée d'hôte SSH du réseau de fabrication. À supprimer
quand plusieurs VM seront nées du nouveau — pas avant.
**Preuve par une machine réelle**, et non par lecture de configuration : `web-dorsal-01`
l'hôte le moins engagé de la flotte, 12 Ko dans `/var/www` — rasé puis recréé par le chemin
normal (`make raser HOTE=…`, `make creer-vm`, `make deployer`).
Ce qu'elle a hérité du nouveau gabarit :
```
resolv.conf → les commentaires seuls, aucune identité de fabrication
machine-id → cc9c58c1… neuf et unique
clé d'hôte → SHA256:pES0/… régénérée
cloud-init → done
```
Déploiement complet, **zéro échec**, et les cinq devis de service `CONFORME`.
**`raser` accepte maintenant `--hote`.** Raser une seule machine sert à éprouver un gabarit
ou à reprendre un hôte ; les **quatre verrous restent en vigueur** — on ne fait que
restreindre la liste dérivée du plan, jamais l'élargir.
**Un défaut relevé au passage, non corrigé.** Le premier déploiement s'est interrompu sur
`client_unbound`, à l'instant où le résolveur bascule vers `127.0.0.1` : toute résolution
en vol se fige le temps qu'Unbound réponde, y compris celle que fait `sshd` à l'ouverture
de session — d'où « Connection timed out during banner exchange ». L'hôte répondait de
nouveau une minute plus tard et la reprise est passée intégralement, presque tout en
`changed=0`. C'est une **course**, de la même famille que celles de la reconstruction, et
elle mérite le même remède : attendre une condition plutôt que de subir la bascule.
## 2026-08-09 — Gabarit recapturé sur copie de travail, sans toucher à l'original
`modeleChezlepro` (99999) cloné en `modeleChezlepro-travail` (99998), préparé, vérifié,

View file

@ -588,7 +588,7 @@ _attendre-flotte: ansible-runtime
.PHONY: reconstruire
raser: ansible-runtime _instance-requise ## DESTRUCTIF : detruit les VM derivees du plan — exige CONFIRMER=true ET INSTANCE=<nom>
@python3 scripts/raser.py $(if $(INSTANCE),--instance $(INSTANCE)) $(if $(filter true,$(CONFIRMER)),--confirmer)
@python3 scripts/raser.py $(if $(INSTANCE),--instance $(INSTANCE)) $(if $(HOTE),--hote $(HOTE)) $(if $(filter true,$(CONFIRMER)),--confirmer)
reconstruire: _instance-requise ## Reconstruit un ecosysteme depuis zero : VM puis deploiement complet
@set -e; \

View file

@ -71,11 +71,20 @@ def plan_derive(base: Path) -> list[tuple[str, int]]:
def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description="Rase les VM derivees du plan de l'instance active.")
ap.add_argument("--instance", help="nom de l'ecosysteme a raser (doit correspondre au monte)")
ap.add_argument("--hote", help="ne raser QUE cet hote (les quatre verrous s'appliquent)")
ap.add_argument("--confirmer", action="store_true")
a = ap.parse_args(argv)
base, nom_actif = instance_active()
couples = plan_derive(base)
if a.hote:
# Les quatre verrous restent en vigueur : on ne fait que RESTREINDRE la liste
# derivee du plan. Raser un seul hote sert a eprouver un gabarit ou a reprendre
# une machine, sans toucher aux treize autres.
couples = [c for c in couples if c[0] == a.hote]
if not couples:
print(f"L'hote « {a.hote} » n'est pas au plan : rien a raser.")
return 2
if not couples:
print("Le plan ne derive aucune VM : rien a raser.")
return 0
@ -116,7 +125,8 @@ def main(argv: list[str] | None = None) -> int:
if not a.confirmer:
print(f"\n{len(presentes)} VM seraient DETRUITES, disques compris.\n"
f"Relancer avec : make raser CONFIRMER=true INSTANCE={nom_actif}")
f"Relancer avec : make raser CONFIRMER=true INSTANCE={nom_actif}"
+ (f" HOTE={a.hote}" if a.hote else ""))
return 0
if (a.instance or "").lower() != nom_actif: