gabarit : q35 n est pas un reglage, c est la raison de la procedure manuelle
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
CONSTAT DE L EXPLOITANT, PAYE EN ANOMALIES : convertir une machine deja installee d `i440fx` a `q35` produit une serie de pannes dont chacune ressemble a autre chose qu a sa cause. Ce n est pas une correction, c est une transplantation. LE MECANISME, ECRIT POUR QU ON NE LE REDECOUVRE PAS : `i440fx` est un chipset PCI, `q35` est PCIe. La topologie des bus change, donc les NOMS D INTERFACES PREDICTIBLES changent avec le chemin PCI (enp0s3 -> enp1s0) et la machine perd le reseau ; les chemins de disques bougent ; l ordre d enumeration suit. C EST AUSSI POURQUOI SET-OPS N UTILISE PAS L IMAGE CLOUD OFFICIELLE DE DEBIAN : `genericcloud` est livree configuree pour `i440fx`. Une machine nait `q35`, ou elle ne le sera jamais proprement — et c est ce que l installation depuis l ISO garantit. Ces deux lignes de la procedure n etaient qu une ligne de tableau. Elles portent maintenant leur pourquoi, et le SITE les declare comme DONNEES (cle `gabarit`), plus seulement comme prose. `make gabarit-etat` compare le gabarit reel a ce que le site declare de lui. ON VERIFIE LA SOURCE, PAS CHAQUE COPIE. Ma premiere version gardait le CLONAGE : la propriete s herite, donc verifier chaque clone coute a chaque creation sans rien dire de plus que verifier le gabarit une fois. Retiree. TROIS FOIS J AI DEVINE LA FORME DE LA REPONSE AU LIEU DE LA REGARDER — regex_search a groupe qui rend None, proxmox_vm_info sans `config: current` qui ne rend que l etat. La garde a declare « ? » sur une VM parfaitement conforme : une garde qui crie toujours est pire qu aucune, on apprend a l ignorer. A la demande et non dans `make prouver` : ce controle exige le cluster, que le harnais ne suppose pas joignable. Meme nature que genome-etat et underlay-plan. Controle negatif verifie : declarer i440fx fait echouer, rc=1. make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
9aef3614ab
commit
4abf875e5f
6 changed files with 212 additions and 3 deletions
41
CHANGELOG.md
41
CHANGELOG.md
|
|
@ -1,5 +1,46 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-01 — `q35` n'est pas un reglage : c'est la raison de la procedure manuelle
|
||||
|
||||
**56 preuves.** L'exploitant a explique pourquoi Set-OPS n'utilise pas l'image cloud
|
||||
officielle de Debian — et c'est un constat **paye en anomalies**, qui n'etait ecrit nulle
|
||||
part.
|
||||
|
||||
`genericcloud` est livree configuree pour **`i440fx`**, le defaut de Proxmox. La convertir
|
||||
en `q35` apres coup ne change pas un parametre : ça **remplace le materiel virtuel sous un
|
||||
systeme qui croit connaitre le sien**.
|
||||
|
||||
```
|
||||
i440fx = PCI q35 = PCIe
|
||||
la topologie des bus change, donc :
|
||||
les noms d'interfaces predictibles suivent le chemin PCI et changent
|
||||
les chemins de disques bougent
|
||||
l'ordre d'enumeration des peripheriques n'est plus le meme
|
||||
```
|
||||
|
||||
*La conversion n'est pas une correction — c'est une transplantation.* D'ou la regle : **une
|
||||
machine nait `q35`, ou elle ne le sera jamais proprement**, et c'est l'installation depuis
|
||||
l'ISO qui le garantit.
|
||||
|
||||
Ces deux lignes n'existaient que comme une ligne de tableau dans la procedure. Elles
|
||||
portent desormais leur **pourquoi**, et le SITE les declare comme **donnees** — plus
|
||||
seulement comme prose.
|
||||
|
||||
### `make gabarit-etat`
|
||||
|
||||
Il compare le gabarit reel a ce que le site declare de lui. *Controle negatif verifie :
|
||||
declarer `i440fx` le fait echouer.*
|
||||
|
||||
**On verifie la SOURCE, pas chaque copie.** Ma premiere version gardait le clonage — mais
|
||||
la propriete s'herite : verifier chaque clone coute a chaque creation sans rien dire de
|
||||
plus que verifier le gabarit une fois.
|
||||
|
||||
*Et trois fois j'ai devine la forme de la reponse au lieu de la regarder — `regex_search` a
|
||||
groupe qui rend `None`, `proxmox_vm_info` sans `config: current` qui ne rend que l'etat. La
|
||||
garde a declare « ? » sur une VM parfaitement conforme. **Une garde qui crie toujours est
|
||||
pire qu'aucune** : on apprend a l'ignorer, et le jour ou elle a raison plus personne ne la
|
||||
lit.*
|
||||
|
||||
## 2026-09-01 — Le gabarit refabrique : minimal, et fabrique chez le SITE
|
||||
|
||||
**56 preuves.** Le gabarit dore est refait — VMID **9006**, `modeleSetOPS-minimal`, quatre
|
||||
|
|
|
|||
13
Makefile
13
Makefile
|
|
@ -943,6 +943,19 @@ site-appliquer: ## Applique un role aux machines du site — GROUPE=<serveur_ops
|
|||
#
|
||||
# CE QUI EST POUSSE SE DERIVE de `serveur_ops_depots` (au plan du site) : on pousse ceux
|
||||
# dont une copie locale existe a cote du moteur, et rien d'autre.
|
||||
# LE GABARIT PORTE-T-IL CE QUE LE SITE DECLARE DE LUI ?
|
||||
#
|
||||
# Son type de machine ne se corrige pas apres coup : convertir `i440fx` -> `q35` remplace
|
||||
# le materiel sous un systeme qui croit connaitre le sien, et chaque panne qui s'ensuit
|
||||
# ressemble a autre chose qu'a sa cause. On verifie donc la SOURCE — la propriete s'herite,
|
||||
# tous les clones d'un gabarit conforme le sont.
|
||||
#
|
||||
# A LA DEMANDE, pas dans `make prouver` : ce controle exige le cluster, que le harnais ne
|
||||
# suppose pas joignable. Meme nature que `genome-etat` et `underlay-plan`.
|
||||
.PHONY: gabarit-etat
|
||||
gabarit-etat: ## Le gabarit porte-t-il ce que le SITE declare ? — ne corrige rien, regarde
|
||||
python3 scripts/gabarit_etat.py
|
||||
|
||||
.PHONY: genome-etat
|
||||
genome-etat: ## Ce que la forge du site porte, face au poste — ne corrige rien, regarde
|
||||
@set -e; \
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 31 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 20, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 53 scripts expliques et atteignables, 107 cibles make documentees, 63 roles avec README. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 54 scripts expliques et atteignables, 108 cibles make documentees, 63 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (128 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (29 genere(s) exempte(s)). |
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 38 role(s) serveur/client tous nommes, 39 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
|
||||
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 49 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 50 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
|
||||
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 66 regle(s) du site. |
|
||||
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
|
||||
|
|
|
|||
|
|
@ -58,6 +58,39 @@ BIOS : OVMF / UEFI
|
|||
Machine : q35
|
||||
```
|
||||
|
||||
### `q35` n'est pas un réglage — c'est la raison de cette procédure
|
||||
|
||||
**Ces deux lignes sont pourquoi l'installation est manuelle.** Elles expliquent aussi
|
||||
pourquoi Set-OPS n'utilise pas l'image cloud officielle de Debian.
|
||||
|
||||
`genericcloud` est livrée configurée pour **`i440fx`**, le défaut de Proxmox. La convertir
|
||||
en `q35` après coup ne change pas un paramètre : ça **remplace le matériel virtuel sous un
|
||||
système qui croit connaître le sien**. `i440fx` est un chipset PCI, `q35` est PCIe — la
|
||||
topologie des bus change, donc :
|
||||
|
||||
- les **noms d'interfaces prédictibles** changent, puisqu'ils dérivent du chemin PCI
|
||||
(`enp0s3` devient `enp1s0`) — la machine perd le réseau, et sa configuration réseau
|
||||
désigne une interface qui n'existe plus ;
|
||||
- les **chemins de disques** bougent, ce qui peut valoir un initramfs qui ne trouve plus
|
||||
sa racine ;
|
||||
- l'ordre d'énumération des périphériques n'est plus le même, et ce qui en dépend suit.
|
||||
|
||||
**Constat de l'exploitant, paye en anomalies** : une conversion `i440fx` → `q35` sur une
|
||||
machine déjà installée produit une série de pannes dont chacune ressemble à autre chose
|
||||
qu'à sa cause. *La conversion n'est pas une correction — c'est une transplantation.*
|
||||
|
||||
D'où la règle : **une machine naît `q35`, ou elle ne le sera jamais proprement.** C'est ce
|
||||
que cette installation depuis l'ISO garantit, et ce qu'une image préconfigurée pour
|
||||
`i440fx` interdit.
|
||||
|
||||
*Le gabarit hérite ces valeurs à chaque clonage — le vérifier avant de le convertir en
|
||||
modèle est le dernier moment où la correction est gratuite :*
|
||||
|
||||
```sh
|
||||
qm config <vmid> | grep -E '^machine|^bios'
|
||||
# attendu : machine: q35 / bios: ovmf
|
||||
```
|
||||
|
||||
### Disque EFI Proxmox
|
||||
|
||||
Avec OVMF/UEFI, Proxmox crée un petit disque EFI, par exemple :
|
||||
|
|
|
|||
122
scripts/gabarit_etat.py
Executable file
122
scripts/gabarit_etat.py
Executable file
|
|
@ -0,0 +1,122 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Le gabarit dore, face a ce que le SITE declare de lui. Ne corrige rien, regarde.
|
||||
|
||||
POURQUOI CE CONTROLE EXISTE (2026-09-01). Le gabarit porte des choix qui NE SE CORRIGENT
|
||||
PAS APRES COUP — au premier rang, son type de machine.
|
||||
|
||||
`q35` (PCIe) et non `i440fx` (PCI, le defaut de Proxmox). Convertir une machine deja
|
||||
installee d'un chipset a l'autre remplace son materiel sous un systeme qui croit connaitre
|
||||
le sien : les noms d'interfaces predictibles suivent le chemin PCI et changent, les chemins
|
||||
de disques bougent, et chaque panne qui s'ensuit ressemble a autre chose qu'a sa cause.
|
||||
C'est un constat paye en anomalies par l'exploitant, et c'est aussi pourquoi Set-OPS
|
||||
n'utilise pas l'image cloud officielle de Debian : elle est livree pour `i440fx`.
|
||||
|
||||
UNE MACHINE NAIT `q35`, OU ELLE NE LE SERA JAMAIS PROPREMENT.
|
||||
|
||||
CE QU'ON VERIFIE, ET POURQUOI ICI PLUTOT QU'AU CLONAGE. La propriete s'HERITE : si le
|
||||
gabarit est conforme, tous ses clones le sont. Verifier chaque copie coute a chaque
|
||||
creation et ne dit rien de plus que verifier la source une fois. On regarde donc la SOURCE.
|
||||
|
||||
Ce n'est pas une preuve du harnais : elle exigerait le cluster, que `make prouver` ne
|
||||
suppose pas joignable. C'est un controle a la demande, comme `make genome-etat` et
|
||||
`make underlay-plan`.
|
||||
|
||||
Usage :
|
||||
python3 scripts/gabarit_etat.py # code de sortie 0 si conforme
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
import underlay as underlay_mod # noqa: E402
|
||||
|
||||
# Ce que l'on compare. `qm config` les rend en `cle: valeur`.
|
||||
CHAMPS = ("machine", "bios")
|
||||
|
||||
|
||||
def _config_distante(noeud_nom: str, vmid: int) -> dict:
|
||||
"""`qm config <vmid>` sur l'hyperviseur qui porte le gabarit.
|
||||
|
||||
ON PASSE PAR LE SHELL DU NOEUD, pas par l'API : le poste de l'exploitant l'atteint
|
||||
deja (c'est ainsi qu'on lit les configurations de VM depuis toujours), et cela evite
|
||||
d'exiger un jeton la ou une lecture suffit.
|
||||
"""
|
||||
hote = _adresse_du_noeud(noeud_nom)
|
||||
if not hote:
|
||||
raise SystemExit(f"Noeud « {noeud_nom} » introuvable dans l'underlay de l'hebergeur.")
|
||||
r = subprocess.run(
|
||||
["ssh", "-o", "ConnectTimeout=8", "-o", "BatchMode=yes",
|
||||
f"ansible@{hote}", f"sudo -n qm config {int(vmid)}"],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
if r.returncode != 0:
|
||||
raise SystemExit(f"Lecture impossible sur {noeud_nom} ({hote}) : "
|
||||
f"{(r.stderr or '').strip()[:160]}")
|
||||
out = {}
|
||||
for ligne in r.stdout.splitlines():
|
||||
if ":" in ligne:
|
||||
k, _, v = ligne.partition(":")
|
||||
out[k.strip()] = v.strip()
|
||||
return out
|
||||
|
||||
|
||||
def _adresse_du_noeud(nom: str) -> str:
|
||||
"""L'adresse de gestion d'un hyperviseur, DERIVEE de l'underlay.
|
||||
|
||||
Jamais ecrite ici : les trois noeuds ne sont pas consecutifs (`.41`, `.43`, `.47`), et
|
||||
sonder une adresse plausible est une erreur qu'aucun outil ne rattrape.
|
||||
"""
|
||||
u = underlay_mod.charger()
|
||||
for h in underlay_mod.hotes(u):
|
||||
if h.get("nom") == nom and h.get("role") == "hyperviseur" \
|
||||
and str(h.get("ip", "")).startswith("192.168.11."):
|
||||
return str(h["ip"])
|
||||
return ""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
g = underlay_mod.gabarit()
|
||||
if not g:
|
||||
print("Le SITE ne declare aucun gabarit (cle `gabarit` de son plan). Rien a comparer.")
|
||||
return 0
|
||||
|
||||
vmid, noeud = g.get("vmid"), g.get("noeud")
|
||||
if not vmid or not noeud:
|
||||
print("Le gabarit declare est incomplet : il lui faut `vmid` et `noeud`.")
|
||||
return 1
|
||||
|
||||
cfg = _config_distante(str(noeud), int(vmid))
|
||||
print(f"Gabarit {vmid} « {cfg.get('name', '?')} » sur {noeud}")
|
||||
|
||||
ecarts = []
|
||||
for champ in CHAMPS:
|
||||
attendu = str(g.get(champ, "")).strip()
|
||||
if not attendu:
|
||||
continue
|
||||
reel = cfg.get(champ, "")
|
||||
etat = "=" if reel == attendu else "≠"
|
||||
print(f" {champ:<10} declare {attendu:<12} reel {reel or '(absent)':<12} {etat}")
|
||||
if reel != attendu:
|
||||
ecarts.append(f"{champ} : declare « {attendu} », reel « {reel or 'absent'} »")
|
||||
|
||||
if not str(cfg.get("template", "")).strip() == "1":
|
||||
ecarts.append("ce VMID n'est PAS un gabarit (`template: 1` absent) — le cloner "
|
||||
"marcherait, mais il derivera au premier demarrage")
|
||||
|
||||
if ecarts:
|
||||
print("\nECART — le gabarit ne porte pas ce que le SITE declare :")
|
||||
for e in ecarts:
|
||||
print(f" - {e}")
|
||||
print("\n NE PAS CONVERTIR la machine : changer son chipset apres coup casse les")
|
||||
print(" noms d'interfaces et les chemins de disques. REFABRIQUER le gabarit.")
|
||||
return 1
|
||||
|
||||
print("\nConforme — le gabarit porte ce que le SITE declare de lui.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -843,7 +843,7 @@ def main() -> None:
|
|||
if not g:
|
||||
return
|
||||
# `cle=valeur` par ligne : consommable par `eval` en shell comme par Ansible.
|
||||
for k in ("vmid", "nom", "noeud"):
|
||||
for k in ("vmid", "nom", "noeud", "machine", "bios"):
|
||||
if g.get(k) is not None:
|
||||
print(f"{k}={g[k]}")
|
||||
return
|
||||
|
|
|
|||
Loading…
Reference in a new issue