gabarit : refabrique, minimal, et puise aux ressources du SITE
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
REFABRIQUE (VMID 9006, modeleSetOPS-minimal). Quatre roles au lieu de dix-sept :
qemu_guest_agent, cloud_init, sudo_ansible, ssh_baseline — des conditions
d existence, pas des choix d efficacite. Tout le reste vient du socle, et P56 refuse
qu un role retire ne soit repris par personne.
FABRIQUE CHEZ LE SITE, ET C EST LA DECISION QUI COMPTE. « Les ressources du SITE
font autorite pour tous ses artefacts ; elles servent les tenants jusqu a ce qu ils
s emancipent. »
Il se fabriquait DEHORS : `-i "<ip>,"` ne porte aucun group_vars, donc ni mandataire
ni resolveur. L ancien gabarit allait chercher ses paquets chez Debian et resolvait
chez l ancien LAN — 192.168.10.10, lu sur la VM 99998. Le site avait son cache et
son resolveur, et son propre artefact les ignorait.
Desormais la fabrication DERIVE ses ressources du plan du site (underlay --adresses)
et tourne sur le reseau du genome. PROUVE : dix requetes de 10.0.33.31 servies par
site-cache-01, resolveur pose a 10.0.34.11.
L IDENTITE DU GABARIT VIENT DU SITE, PLUS DU TENANT. `proxmox_clone_vmid_modele`
vivait dans les group_vars de l ecosysteme : deux tenants pouvaient cloner deux
gabarits differents sans que rien ne le dise, et un tenant decidait d un objet dont
descend chaque VM de chaque ecosysteme. Meme mouvement que l INDEX (2026-08-25) : le
site ALLOUE, le tenant RECOIT. Cle `gabarit` du plan du site, lue par
underlay --gabarit.
PIEGE FERME EN CHEMIN : creer-vm passait VMID_MODELE="$SETOPS_VMID_MODELE" a
cloner-vm, or inventory_host.py n emet PAS cette variable. Elle valait donc le VIDE,
et ce vide ECRASAIT la valeur derivee — le gabarit du tenant reprenait la main sans
bruit.
ET UN PIEGE DEJA DOCUMENTE, PAYE UNE TROISIEME FOIS : le chemin du controleur
contient une espace, et `lookup('pipe', ...)` le decoupait. Guillemets.
EPROUVE DE BOUT EN BOUT : VM clonee du gabarit minimal — nom, adresse, machine-id
neuf, cle d hote regeneree, agent invite actif ; puis socle applique dessus,
changed=5, 0 echec, auditd compris. VM d essai retiree.
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
2f2323bd3e
commit
9aef3614ab
5 changed files with 174 additions and 3 deletions
48
CHANGELOG.md
48
CHANGELOG.md
|
|
@ -1,5 +1,53 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-09-01 — Le gabarit refabrique : minimal, et fabrique chez le SITE
|
||||||
|
|
||||||
|
**56 preuves.** Le gabarit dore est refait — VMID **9006**, `modeleSetOPS-minimal`, quatre
|
||||||
|
roles au lieu de dix-sept.
|
||||||
|
|
||||||
|
### Il se fabriquait dehors
|
||||||
|
|
||||||
|
*« Les ressources du SITE font autorite pour tous ses artefacts ; elles servent les tenants
|
||||||
|
jusqu'a ce qu'ils s'emancipent »* (decision de l'exploitant). Le gabarit en est un — c'est
|
||||||
|
de lui que descend **chaque VM de chaque tenant**.
|
||||||
|
|
||||||
|
Or `-i "<ip>,"` ne porte aucun `group_vars` : la fabrication n'avait ni mandataire ni
|
||||||
|
resolveur. L'ancien gabarit allait chercher ses paquets **chez Debian** et resolvait chez
|
||||||
|
**l'ancien LAN** (`192.168.10.10`, lu sur la VM 99998). Le site avait son cache et son
|
||||||
|
resolveur, et son propre artefact les ignorait.
|
||||||
|
|
||||||
|
La fabrication **derive** desormais ses ressources du plan du site et tourne sur le reseau
|
||||||
|
du genome. *Prouve : dix requetes de `10.0.33.31` servies par `site-cache-01`.*
|
||||||
|
|
||||||
|
### L'identite du gabarit vient du site, plus du tenant
|
||||||
|
|
||||||
|
`proxmox_clone_vmid_modele` vivait dans les `group_vars` de l'ecosysteme. **Deux tenants
|
||||||
|
pouvaient donc cloner deux gabarits differents sans que rien ne le dise**, et un tenant
|
||||||
|
decidait d'un objet qui n'est pas a lui.
|
||||||
|
|
||||||
|
*Meme mouvement que l'INDEX, tranche le 2026-08-25 : le site ALLOUE, le tenant RECOIT.*
|
||||||
|
|
||||||
|
### Deux pieges fermes en chemin
|
||||||
|
|
||||||
|
**Une valeur vide qui ecrasait la derivation.** `creer-vm` passait
|
||||||
|
`VMID_MODELE="$SETOPS_VMID_MODELE"` a `cloner-vm` — or `inventory_host.py` n'emet pas
|
||||||
|
cette variable. Elle valait le VIDE, et ce vide reprenait la main sur le site sans bruit.
|
||||||
|
|
||||||
|
**Le chemin du controleur contient une espace**, et `lookup('pipe', …)` le decoupait. Ce
|
||||||
|
depot l'a paye une troisieme fois — `ansible-galaxy` le 08-23, `git bundle` le 08-26, ici
|
||||||
|
le 09-01.
|
||||||
|
|
||||||
|
### Eprouve de bout en bout
|
||||||
|
|
||||||
|
```
|
||||||
|
VM clonee du gabarit minimal nom, adresse, machine-id neuf,
|
||||||
|
cle d'hote regeneree, agent invite actif
|
||||||
|
socle applique dessus changed=5, 0 echec, auditd compris
|
||||||
|
```
|
||||||
|
|
||||||
|
*VM d'essai retiree. L'ancien gabarit (99998) est garde et declare comme `precedent` : le
|
||||||
|
retirer est un geste, pas un effet, et il attend une reconstruction complete sur le neuf.*
|
||||||
|
|
||||||
## 2026-09-01 — Le gabarit etait un cache du socle, et il perimait sans le dire
|
## 2026-09-01 — Le gabarit etait un cache du socle, et il perimait sans le dire
|
||||||
|
|
||||||
**56 preuves.** Question de l'exploitant : le gabarit dore et cloud-init gagnent-ils leur
|
**56 preuves.** Question de l'exploitant : le gabarit dore et cloud-init gagnent-ils leur
|
||||||
|
|
|
||||||
21
Makefile
21
Makefile
|
|
@ -66,7 +66,24 @@ ADRESSE_IP ?=
|
||||||
GROUPES ?= $(GROUPE_DEBIAN)
|
GROUPES ?= $(GROUPE_DEBIAN)
|
||||||
GROUPE ?= $(GROUPE_DEBIAN)
|
GROUPE ?= $(GROUPE_DEBIAN)
|
||||||
UTILISATEUR_ANSIBLE ?= ansible
|
UTILISATEUR_ANSIBLE ?= ansible
|
||||||
VMID_MODELE ?=
|
# LE GABARIT VIENT DU SITE, PAS DU TENANT (2026-09-01).
|
||||||
|
#
|
||||||
|
# « Les ressources du SITE font autorite pour tous ses artefacts. » Le gabarit en est un :
|
||||||
|
# c'est de lui que descend chaque VM de chaque tenant. Son VMID vivait pourtant dans les
|
||||||
|
# group_vars de l'ecosysteme — deux tenants pouvaient donc cloner deux gabarits differents
|
||||||
|
# sans que rien ne le dise, et un tenant decidait d'un objet qui n'est pas a lui.
|
||||||
|
#
|
||||||
|
# Meme mouvement que l'INDEX, tranche le 2026-08-25 : le site ALLOUE, le tenant RECOIT.
|
||||||
|
#
|
||||||
|
# `?=` : une valeur donnee en ligne de commande gagne — necessaire pour eprouver un
|
||||||
|
# gabarit neuf avant de le declarer. Et si le site n'en declare aucun, la valeur reste
|
||||||
|
# vide et le plan du tenant reprend la main : degrader, jamais deviner.
|
||||||
|
VMID_MODELE ?= $(shell python3 scripts/underlay.py --gabarit 2>/dev/null | sed -n 's/^vmid=//p')
|
||||||
|
# NOTE SUR LE PIEGE QU'ON VIENT DE FERMER : `creer-vm` passait
|
||||||
|
# `VMID_MODELE="$$SETOPS_VMID_MODELE"` a `cloner-vm`, or `inventory_host.py` n'emet PAS
|
||||||
|
# cette variable — elle valait donc le VIDE, et ce vide ECRASAIT la valeur derivee. Le
|
||||||
|
# gabarit du tenant reprenait la main sans que rien ne le dise. `:-` rend la derivation au
|
||||||
|
# site quand l'inventaire ne dit rien.
|
||||||
VMID ?=
|
VMID ?=
|
||||||
NOEUD_PROXMOX ?=
|
NOEUD_PROXMOX ?=
|
||||||
STOCKAGE_PROXMOX ?=
|
STOCKAGE_PROXMOX ?=
|
||||||
|
|
@ -970,7 +987,7 @@ site-creer: ## Cree les machines du site depuis l'underlay — CONFIRMER=true
|
||||||
COEURS="$$SETOPS_COEURS" \
|
COEURS="$$SETOPS_COEURS" \
|
||||||
MEMOIRE="$$SETOPS_MEMOIRE" \
|
MEMOIRE="$$SETOPS_MEMOIRE" \
|
||||||
NOEUD_PROXMOX="$$SETOPS_NOEUD" \
|
NOEUD_PROXMOX="$$SETOPS_NOEUD" \
|
||||||
VMID_MODELE="$$SETOPS_VMID_MODELE" \
|
VMID_MODELE="$${SETOPS_VMID_MODELE:-$(VMID_MODELE)}" \
|
||||||
CLONE_COMPLET="$$SETOPS_CLONE_COMPLET" \
|
CLONE_COMPLET="$$SETOPS_CLONE_COMPLET" \
|
||||||
FORMAT_DISQUE="$(FORMAT_DISQUE)"; \
|
FORMAT_DISQUE="$(FORMAT_DISQUE)"; \
|
||||||
printf '=== %s : %s s ===\n' "$$m" "$$(( $$(date +%s) - t0 ))"; \
|
printf '=== %s : %s s ===\n' "$$m" "$$(( $$(date +%s) - t0 ))"; \
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
| 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 |
|
| 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. |
|
| 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 | 53 scripts expliques et atteignables, 107 cibles make documentees, 63 roles avec README. |
|
||||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (130 cle(s) declaree(s) par l'instance). |
|
| 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). |
|
| 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)). |
|
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (29 genere(s) exempte(s)). |
|
||||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,75 @@
|
||||||
gather_facts: true
|
gather_facts: true
|
||||||
|
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
|
# ─── LE GABARIT EST UN ARTEFACT DU SITE, ET IL SE FABRIQUE CHEZ LUI ──────────
|
||||||
|
#
|
||||||
|
# « Les ressources du SITE font autorité pour tous les artefacts du site » (décision
|
||||||
|
# de l'exploitant, 2026-09-01). Le gabarit en est un — c'est de lui que descend
|
||||||
|
# chaque VM de chaque tenant.
|
||||||
|
#
|
||||||
|
# Il se fabriquait pourtant DEHORS : `-i "<ip>,"` ne porte aucun `group_vars`, donc
|
||||||
|
# ni mandataire ni résolveur. La machine allait chercher ses paquets chez Debian et
|
||||||
|
# résolvait chez l'ancien LAN (`192.168.10.10`, lu sur le gabarit du 2026-08-09).
|
||||||
|
# Le site avait son cache et son résolveur, et son propre artefact les ignorait.
|
||||||
|
#
|
||||||
|
# DÉRIVÉ, JAMAIS ÉCRIT : les adresses viennent du plan du site, par le groupe que la
|
||||||
|
# machine porte. Le jour où le cache déménage, la fabrication suit — c'est déjà
|
||||||
|
# arrivé une fois, au découpage du site en zones.
|
||||||
|
- name: Où le SITE sert-il ses paquets et ses noms ?
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
# LE CHEMIN DU CONTROLEUR PEUT CONTENIR UNE ESPACE — « Espace Chezlepro/… »
|
||||||
|
# chez le mainteneur — et `pipe` passe sa chaine au shell. Sans guillemets, il
|
||||||
|
# decoupe le chemin et rend 2. Meme piege que `ansible-galaxy` le 2026-08-23 et
|
||||||
|
# que `git bundle` le 2026-08-26 : ce depot l'a paye trois fois.
|
||||||
|
modele_cache_site: >-
|
||||||
|
{{ (lookup('pipe', 'python3 "' ~ playbook_dir ~ '/../../scripts/underlay.py" --adresses serveur_artefacts')
|
||||||
|
| trim | split('\n') | first | default('', true)) }}
|
||||||
|
modele_resolveur_site: >-
|
||||||
|
{{ (lookup('pipe', 'python3 "' ~ playbook_dir ~ '/../../scripts/underlay.py" --adresses serveur_resolveur')
|
||||||
|
| trim | split('\n') | first | default('', true)) }}
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
# PAS de `run_once` : la fabrication ne vise qu'une machine de toute facon, et
|
||||||
|
# `run_once` se comporte autrement sous la strategie `free` — une garde qui depend
|
||||||
|
# de la strategie n'en est pas une.
|
||||||
|
|
||||||
|
- name: Exiger que le site offre ce qu'il faut pour fabriquer son gabarit
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- modele_cache_site | length > 0
|
||||||
|
- modele_resolveur_site | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Le plan du SITE ne déclare pas de `serveur_artefacts` et/ou de
|
||||||
|
`serveur_resolveur`. Le gabarit est un artefact du site : le fabriquer sans eux
|
||||||
|
le ferait dépendre d'Internet et de l'ancien LAN, alors que tout ce dont il a
|
||||||
|
besoin doit venir du site.
|
||||||
|
|
||||||
|
# UNE ADRESSE, PAS UN NOM : au moment où ces deux fichiers servent, la machine ne
|
||||||
|
# résout encore rien — c'est justement ce qu'on est en train de lui donner.
|
||||||
|
- name: Poser le résolveur du SITE
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/resolv.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
# Géré par Set-OPS pendant la FABRICATION du gabarit — le résolveur du SITE.
|
||||||
|
# `template_cleanup` le remet à zéro : un gabarit ne transporte pas d'adresse.
|
||||||
|
nameserver {{ modele_resolveur_site }}
|
||||||
|
|
||||||
|
- name: Poser le cache d'artefacts du SITE
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/apt/apt.conf.d/00-setops-amorcage-artefacts
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
// Géré par Set-OPS pendant la FABRICATION du gabarit — le cache du SITE.
|
||||||
|
// Les VM clonées le remplacent par celui de leur écosystème (client_artefacts).
|
||||||
|
Acquire::http::Proxy "http://{{ modele_cache_site }}:3142";
|
||||||
|
// `DIRECT` obligatoire : apt fait HÉRITER la valeur HTTPS de la valeur HTTP.
|
||||||
|
Acquire::https::Proxy "DIRECT";
|
||||||
|
|
||||||
- name: Vérifier que la cible est Debian
|
- name: Vérifier que la cible est Debian
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,22 @@ def services_site() -> dict[str, list[str]]:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def gabarit() -> dict:
|
||||||
|
"""Le gabarit dore, tel que le SITE le declare — VMID, nom, noeud.
|
||||||
|
|
||||||
|
LE GABARIT EST UN ARTEFACT DU SITE (2026-09-01). C'est de lui que descend chaque VM de
|
||||||
|
chaque tenant ; son identite ne peut donc pas etre decidee par un tenant. Elle l'etait :
|
||||||
|
`proxmox_clone_vmid_modele` vivait dans les group_vars de l'ecosysteme, si bien que
|
||||||
|
deux tenants pouvaient cloner deux gabarits differents sans que rien ne le dise.
|
||||||
|
|
||||||
|
Meme mouvement que l'INDEX, tranche le 2026-08-25 : le site ALLOUE, le tenant RECOIT.
|
||||||
|
|
||||||
|
Rend {} quand la carte de l'hebergeur n'est pas montee : l'ecosysteme retombe alors sur
|
||||||
|
ce que son propre plan declare, comme avant. Degrader, jamais deviner.
|
||||||
|
"""
|
||||||
|
return dict((lire_plan_site("10-intrants.yml") or {}).get("gabarit") or {})
|
||||||
|
|
||||||
|
|
||||||
def adresses_site_portant(groupe: str) -> list[str]:
|
def adresses_site_portant(groupe: str) -> list[str]:
|
||||||
"""Les adresses des machines du SITE qui portent ce groupe.
|
"""Les adresses des machines du SITE qui portent ce groupe.
|
||||||
|
|
||||||
|
|
@ -813,7 +829,28 @@ def afficher() -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
ap = argparse.ArgumentParser(description=__doc__)
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
ap.add_argument("--verifier", action="store_true", help="valide l'underlay (P23), code de sortie")
|
ap.add_argument("--verifier", action="store_true", help="valide l'underlay (P23), code de sortie")
|
||||||
|
# LE SITE, INTERROGEABLE DEPUIS UN PLAYBOOK. La fabrication du gabarit doit puiser aux
|
||||||
|
# ressources du site (« elles font autorite pour tous ses artefacts »), et elle tourne
|
||||||
|
# sur un inventaire d'un seul hote — sans `group_vars`, donc sans rien deriver
|
||||||
|
# elle-meme. Elle demande ici, plutot que d'ecrire une adresse qui vieillirait.
|
||||||
|
ap.add_argument("--gabarit", action="store_true",
|
||||||
|
help="le gabarit dore declare par le SITE (vmid, nom, noeud)")
|
||||||
|
ap.add_argument("--adresses", metavar="GROUPE",
|
||||||
|
help="adresses des machines du SITE portant ce groupe, une par ligne")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
if args.gabarit:
|
||||||
|
g = gabarit()
|
||||||
|
if not g:
|
||||||
|
return
|
||||||
|
# `cle=valeur` par ligne : consommable par `eval` en shell comme par Ansible.
|
||||||
|
for k in ("vmid", "nom", "noeud"):
|
||||||
|
if g.get(k) is not None:
|
||||||
|
print(f"{k}={g[k]}")
|
||||||
|
return
|
||||||
|
if args.adresses:
|
||||||
|
for a in adresses_site_portant(args.adresses):
|
||||||
|
print(a)
|
||||||
|
return
|
||||||
if args.verifier:
|
if args.verifier:
|
||||||
sys.exit(verifier())
|
sys.exit(verifier())
|
||||||
afficher()
|
afficher()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue