D-86 mis a l epreuve : Chezlepro rasee et refaite depuis zero
Quatorze machines detruites, quatorze refaites. 1 h 08, 0 injoignable. D-86 TIENT, et la mesure le dit : 30 resultats de controle recus a 12:20:41 alors que la reconstruction ne s est terminee qu a 12:35:29. Trente services rapportaient leur etat pendant que Keycloak, Forgejo et Nextcloud montaient encore. Sa limite, dite franchement : D-86 affirme que le DNS peut rester en couche 6 grace au plancher /etc/hosts. Or _amorcer-socle monte infra-dns-01 EN ENTIER d abord. Le DNS etait debout, le plancher n a rien eu a porter, et la partie la plus audacieuse de la decision reste non testee. LE PLACEMENT DES VM N ETAIT DECLARE NULLE PART. Les quatorze vivaient sur asgard depuis toujours, mais SETOPS_NOEUD valait le vide : ce placement n existait que dans l etat d execution de Proxmox. Sans cible, le clone reste sur le noeud du gabarit - vishnu, qui a 14 Go libres et heberge eregion et site-forge-01. Le defaut avait survecu a la reconstruction du 2026-09-02 parce que les VM existaient deja et que le clone les sautait. Il faut un from-zero VRAI pour voir ce genre de chose. LE CLONAGE ETAIT 45 FOIS TROP LENT, et pas a cause du reseau : source et destination etaient sur le meme stockage. C est le LVM epais qui interdit a Proxmox tout clone autre que complet. Gabarit deplace sur CephNVMe, mesure 480 s -> 8 s. On garde les clones complets : le clone lie descend a 1 s mais enchaine chaque VM a l image de base pour toujours. Un champ stockage: entre au gabarit, branche en quatre points - declare, expose, consomme par le Makefile, garde par gabarit_etat. Declarer sans consommer, c est decorer. ICINGA NE POUVAIT PAS FAIRE SES PROPRES CONTROLES : icinga2 n apporte aucun greffon, et la supervision etant passive, le manque etait masque. Quatorze ping4 muets d une seule cause. monitoring-plugins-basic entre au role. Le site l avait deja - pose A LA MAIN, jamais declare : troisieme occurrence du jour d un etat qui ne survivrait pas a une reconstruction. Trois defauts laisses ouverts : le cache apt attendu pendant l amorcage, la regression D-85 sur /etc/cloud - qui a fait decrocher deux hotes de la tache deposant icinga-ca.crt, rendant leurs sondes muettes en silence - et auditd sans regles dans le gabarit. Mesure finale : Icinga 93 OK sur 98, site 51/51, Prometheus 15/15, prouver 64 OK, lint 0 defaut. Les quatre non-OK restants sont des sauvegarde dont le minuteur nocturne n a pas encore visite une flotte nee il y a deux heures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
84b5ecc71d
commit
4ef3a1d553
6 changed files with 184 additions and 9 deletions
111
CHANGELOG.md
111
CHANGELOG.md
|
|
@ -1,5 +1,116 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-10 (7) — D-86 mis a l'epreuve : Chezlepro rasee et refaite depuis zero
|
||||
|
||||
Quatorze machines detruites, quatorze refaites. **1 h 08**, 0 injoignable. La reconstruction
|
||||
a confirme D-86 — et revele quatre defauts qu'aucune preuve statique n'aurait pu voir,
|
||||
parce qu'ils n'existent QUE pendant une naissance.
|
||||
|
||||
### D-86 tient, et voici la mesure
|
||||
|
||||
L'ordre declare est l'ordre reel : socle → `step_ca` → `client_pki` → **observabilite**
|
||||
(postgresql, prometheus, loki, grafana, icinga) → **agents** (metrique, journal, sante) →
|
||||
services → apps.
|
||||
|
||||
Mais l'ordre ne prouve pas que quelqu'un REGARDAIT. Ce qui le prouve :
|
||||
|
||||
30 resultats de controle recus a 12:20:41
|
||||
fin de la reconstruction 12:35:29
|
||||
|
||||
Trente services rapportaient leur etat pendant que Keycloak, Forgejo et Nextcloud montaient
|
||||
encore. Prometheus scrutait 14 cibles sur 15. **Ce qui se deploie ensuite l'est bien sous
|
||||
l'oeil de la supervision.**
|
||||
|
||||
### La limite, dite franchement : le plancher n'a pas ete eprouve
|
||||
|
||||
D-86 affirme que le DNS peut rester en couche 6 *grace au plancher `/etc/hosts`*. Ce n'est
|
||||
pas ce qui s'est passe : `_amorcer-socle` monte `infra-dns-01` EN ENTIER d'abord —
|
||||
`serveur_powerdns` puis `serveur_resolveur`, bien avant l'observabilite. Le DNS etait
|
||||
debout ; le plancher n'a rien eu a porter. **La partie la plus audacieuse de la decision
|
||||
reste non testee**, et l'eprouver demanderait de retirer l'amorcage DNS — une autre
|
||||
decision.
|
||||
|
||||
### Le placement des VM n'etait declare NULLE PART
|
||||
|
||||
Les quatorze machines vivaient sur `asgard` depuis toujours. `SETOPS_NOEUD` valait le
|
||||
VIDE : ce placement n'existait que dans l'etat d'execution de Proxmox. Sans cible, le clone
|
||||
reste sur le noeud du gabarit — `vishnu`, qui a **14 Go libres** et heberge `eregion` et
|
||||
`site-forge-01`. Quarante Go de VM neuves y auraient atterri.
|
||||
|
||||
Le defaut avait survecu a la reconstruction du 2026-09-02 : les VM existaient deja, et le
|
||||
clone les sautait. **Un etat qui n'est declare nulle part ne se reproduit pas** — il faut
|
||||
un `from-zero` VRAI pour le voir. `noeud: asgard` est desormais au plan.
|
||||
|
||||
### Le clonage etait 45 fois trop lent, et la cause n'etait pas le reseau
|
||||
|
||||
Mesure sur le vrai gabarit :
|
||||
|
||||
disque sur `TrueNAS` (lvm sur iSCSI) 352 a 480 s par clone
|
||||
disque sur `CephNVMe` (rbd) 8 a 9 s par clone
|
||||
|
||||
Source ET destination etaient sur le meme stockage : rien ne traversait d'hyperviseur a
|
||||
hyperviseur. La cause est que **le LVM epais interdit a Proxmox tout clone autre que
|
||||
complet** — 16 Go copies par machine, a travers iSCSI.
|
||||
|
||||
Le clone LIE descend a 1 s, mais enchaine chaque VM a l'image de base pour toujours. Pour
|
||||
7 s gagnees sur 480, l'echange ne vaut pas la dependance : **on garde les clones complets.**
|
||||
|
||||
Gabarit deplace par `qm move_disk` sans `--delete` — les anciens disques restent attaches
|
||||
en `unused`, le retour arriere tient en une commande.
|
||||
|
||||
### Un champ `stockage:` au gabarit, branche en quatre points
|
||||
|
||||
Declarer sans consommer, c'est decorer. Le champ vit donc partout ou il compte :
|
||||
|
||||
| ou | quoi |
|
||||
|---|---|
|
||||
| `plan/10-intrants.yml` | la declaration, avec la mesure qui la justifie |
|
||||
| `underlay.py --gabarit` | l'expose |
|
||||
| `Makefile` | `STOCKAGE_PROXMOX` en derive — le clone ne l'HERITE plus en silence |
|
||||
| `gabarit_etat.py` | compare le declare au reel, eprouve dans les deux sens |
|
||||
|
||||
Et le remede devient contextuel : le conseil *« NE PAS CONVERTIR… REFABRIQUER le gabarit »*
|
||||
s'imprimait pour TOUT ecart, y compris un deplacement de disque qui se repare en une
|
||||
commande. *Un remede plus lourd que le mal se fait ignorer, puis le vrai avec lui.*
|
||||
|
||||
### Icinga ne pouvait pas faire ses propres controles
|
||||
|
||||
ping4 UNKNOWN execvpe(/usr/lib/nagios/plugins/check_ping) failed: No such file
|
||||
|
||||
`icinga2` n'apporte AUCUN greffon. Toute la supervision de Set-OPS etant PASSIVE, le manque
|
||||
etait masque : les services qui rapportent d'eux-memes etaient verts, et personne ne
|
||||
regardait les autres. Quatorze `ping4` muets d'une seule cause.
|
||||
|
||||
`monitoring-plugins-basic` entre au role — `-basic` et non le metapaquet : `-standard`
|
||||
ajoute des greffons LDAP/SMTP/PostgreSQL que Set-OPS n'appelle jamais.
|
||||
|
||||
**Le site l'avait deja — pose A LA MAIN plus tot dans la journee, jamais declare.** Meme
|
||||
defaut que le placement, troisieme occurrence du jour : un etat qui ne vit que sur la
|
||||
machine ne survit pas a une reconstruction.
|
||||
|
||||
### Trois defauts trouves, laisses ouverts
|
||||
|
||||
1. **`infra-pki-01` et `infra-dns-01` attendent `forge-01:3142`** pendant l'amorçage — le
|
||||
cache apt naitra bien plus tard. Un ordre qui se mord la queue.
|
||||
2. **Regression D-85** : `Destination directory /etc/cloud/templates does not exist`. Un
|
||||
role ecrit encore la ou cloud-init a ete supprime. **C'est ce defaut qui a fait
|
||||
decrocher deux hotes de la tache deposant `icinga-ca.crt`** — leur porteur de sante
|
||||
tournait, et chaque rapport echouait sur `curl: (77)`, en silence.
|
||||
3. **`auditd` dans le gabarit, sans regles** : `audit-rules.service` echoue au premier
|
||||
demarrage de CHAQUE VM neuve, jusqu'au passage du socle. Le CRITICAL ressemble alors a
|
||||
un probleme de la machine alors qu'il vient du modele.
|
||||
|
||||
### Etat mesure, apres correction
|
||||
|
||||
Icinga Chezlepro 93 OK | 1 WARNING | 1 CRITICAL | 3 UNKNOWN sur 98
|
||||
Icinga du site 51 / 51 OK
|
||||
Prometheus 15 / 15 cibles
|
||||
prouver 64 OK, 0 echec
|
||||
ansible-lint 0 defaut, profil production
|
||||
|
||||
Les quatre non-OK restants sont tous des `sauvegarde` : le minuteur nocturne n'a pas encore
|
||||
visite une flotte nee il y a deux heures.
|
||||
|
||||
## 2026-09-10 (6) — La frontiere passe, et trois sondes disaient faux
|
||||
|
||||
Application de ce que l'entree precedente avait prepare, puis correction de ce que
|
||||
|
|
|
|||
12
Makefile
12
Makefile
|
|
@ -86,7 +86,17 @@ VMID_MODELE ?= $(shell python3 scripts/underlay.py --gabarit 2>/dev/null | sed -
|
|||
# site quand l'inventaire ne dit rien.
|
||||
VMID ?=
|
||||
NOEUD_PROXMOX ?=
|
||||
STOCKAGE_PROXMOX ?=
|
||||
# LE STOCKAGE DU CLONE SUIT CELUI DU GABARIT, ET IL EST DECLARE (2026-09-10).
|
||||
#
|
||||
# Vide, `qm clone` heritait SILENCIEUSEMENT du stockage de la source. Ca marchait, et
|
||||
# c'est bien la le probleme : quand le gabarit a change de stockage, la destination a
|
||||
# suivi sans qu'aucun fichier ne le dise. Le site le declare desormais
|
||||
# (`gabarit.stockage`), et cette ligne le rend a la commande de clonage.
|
||||
#
|
||||
# `SETOPS_STOCKAGE` — le `stockage:` d'une machine au plan du tenant — garde la priorite
|
||||
# (voir `creer-vm`) : un ecosysteme peut poser une machine ailleurs, ce qui n'est pas la
|
||||
# meme question que « d'ou nait la flotte ».
|
||||
STOCKAGE_PROXMOX ?= $(shell python3 scripts/underlay.py --gabarit 2>/dev/null | sed -n 's/^stockage=//p')
|
||||
FORMAT_DISQUE ?=
|
||||
TAILLE_DISQUE ?=
|
||||
DISQUE_PROXMOX ?=
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 36 groupes (inventaire dechiffre et parse). |
|
||||
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
|
||||
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
|
||||
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, 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 | 58 scripts expliques et atteignables, 115 cibles make documentees, 67 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 39 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 39 exigence(s) de role, toutes satisfaites (132 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 | 44 document(s) declarent leur lecteur (36 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). |
|
||||
|
|
|
|||
|
|
@ -4,6 +4,29 @@ serveur_icinga_paquets:
|
|||
- icingadb
|
||||
- icingadb-redis
|
||||
- postgresql-client # pour importer le schema vers la BD distante
|
||||
# LES GREFFONS DE CONTROLE, SANS LESQUELS LE TEMOIN NE VOIT RIEN DE LUI-MEME.
|
||||
#
|
||||
# `icinga2` n'apporte AUCUN greffon : le paquet ne pose que `check_nscp_api`. Toute la
|
||||
# supervision de Set-OPS est PASSIVE — chaque noeud pousse son propre resultat — et
|
||||
# cette architecture a masque le manque : les 53 services qui rapportent d'eux-memes
|
||||
# etaient verts, et personne ne regardait les autres.
|
||||
#
|
||||
# CE QUE CA A COUTE (reconstruction du 2026-09-10) : les quatorze services `ping4`
|
||||
# rendaient UNKNOWN avec
|
||||
#
|
||||
# execvpe(/usr/lib/nagios/plugins/check_ping) failed: No such file or directory
|
||||
#
|
||||
# Or `hostalive` est un ping. Un hote qu'Icinga ne peut pas joindre n'est pas DOWN,
|
||||
# il est UNKNOWN — et Icinga ne supprime pas les notifications d'un hote UNKNOWN comme
|
||||
# il le fait d'un hote DOWN. Le defaut est donc moins grave qu'une extinction, mais il
|
||||
# rend la colonne « l'hote repond-il ? » definitivement muette.
|
||||
#
|
||||
# `-basic` ET NON LE METAPAQUET : il porte `check_ping`, `check_disk`, `check_http`,
|
||||
# `check_load` — ce qu'un temoin verifie lui-meme. `-standard` ajoute des greffons
|
||||
# applicatifs (LDAP, SMTP, PostgreSQL) que Set-OPS ne consomme pas : chaque noeud
|
||||
# mesure SES services et les rapporte. Installer ce qu'on n'appelle jamais, c'est
|
||||
# elargir la surface pour rien.
|
||||
- monitoring-plugins-basic
|
||||
|
||||
serveur_icinga_service_icinga2: "icinga2"
|
||||
serveur_icinga_service_icingadb: "icingadb"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ 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`.
|
||||
# Ce que l'on compare EN `cle: valeur`. Le `stockage` se traite a part : il est
|
||||
# enfoui dans la ligne du disque, pas rendu comme une cle a lui.
|
||||
CHAMPS = ("machine", "bios")
|
||||
|
||||
|
||||
|
|
@ -102,6 +103,26 @@ def main() -> int:
|
|||
if reel != attendu:
|
||||
ecarts.append(f"{champ} : declare « {attendu} », reel « {reel or 'absent'} »")
|
||||
|
||||
# LE STOCKAGE NE SE LIT PAS EN `cle: valeur` — il est ENFOUI dans la ligne du disque :
|
||||
#
|
||||
# scsi0: CephNVMe:base-9006-disk-0,discard=on,iothread=1,size=16G,ssd=1
|
||||
# ^^^^^^^^
|
||||
#
|
||||
# On le compare quand meme, parce qu'il decide du temps de reproduction de tout
|
||||
# ecosysteme : mesure du 2026-09-10, 8 s par clone sur `CephNVMe` contre 352 a 480 s
|
||||
# sur `TrueNAS`. Un gabarit qui glisse en arriere ne casse rien — il rend simplement
|
||||
# chaque reconstruction quarante-cinq fois plus lente, sans qu'aucune erreur ne le dise.
|
||||
attendu_stockage = str(g.get("stockage", "")).strip()
|
||||
if attendu_stockage:
|
||||
disque = str(cfg.get("scsi0") or cfg.get("virtio0") or "")
|
||||
reel_stockage = disque.split(":", 1)[0].strip() if ":" in disque else ""
|
||||
etat = "=" if reel_stockage == attendu_stockage else "≠"
|
||||
print(f" {'stockage':<10} declare {attendu_stockage:<12} "
|
||||
f"reel {reel_stockage or '(absent)':<12} {etat}")
|
||||
if reel_stockage != attendu_stockage:
|
||||
ecarts.append(f"stockage : declare « {attendu_stockage} », reel "
|
||||
f"« {reel_stockage or 'absent'} » — chaque clone en paiera le prix")
|
||||
|
||||
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")
|
||||
|
|
@ -110,8 +131,18 @@ def main() -> int:
|
|||
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.")
|
||||
# LE REMEDE DEPEND DE L'ECART, et ce n'est pas un detail de presentation : le
|
||||
# conseil « refabriquer le gabarit » etait imprime pour TOUT ecart, y compris un
|
||||
# simple deplacement de disque qui se repare en une commande sans rien refaire.
|
||||
# Un remede plus lourd que le mal se fait ignorer, puis le vrai avec lui.
|
||||
if any(e.startswith(("machine", "bios")) for e in ecarts):
|
||||
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.")
|
||||
if any(e.startswith("stockage") for e in ecarts):
|
||||
print(f"\n Le stockage, lui, se corrige SANS refabriquer :")
|
||||
print(f" qm move_disk {vmid} scsi0 {attendu_stockage}")
|
||||
print(" Sans `--delete`, l'ancien disque reste attache en `unused` — le")
|
||||
print(" retour arriere tient en une commande.")
|
||||
return 1
|
||||
|
||||
print("\nConforme — le gabarit porte ce que le SITE declare de lui.")
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ def services_site() -> dict[str, list[str]]:
|
|||
|
||||
|
||||
def gabarit() -> dict:
|
||||
"""Le gabarit dore, tel que le SITE le declare — VMID, nom, noeud.
|
||||
"""Le gabarit dore, tel que le SITE le declare — VMID, nom, noeud, stockage.
|
||||
|
||||
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 :
|
||||
|
|
@ -834,7 +834,7 @@ def main() -> None:
|
|||
# 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)")
|
||||
help="le gabarit dore declare par le SITE (vmid, nom, noeud, stockage)")
|
||||
ap.add_argument("--adresses", metavar="GROUPE",
|
||||
help="adresses des machines du SITE portant ce groupe, une par ligne")
|
||||
args = ap.parse_args()
|
||||
|
|
@ -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", "machine", "bios"):
|
||||
for k in ("vmid", "nom", "noeud", "machine", "bios", "stockage"):
|
||||
if g.get(k) is not None:
|
||||
print(f"{k}={g[k]}")
|
||||
return
|
||||
|
|
|
|||
Loading…
Reference in a new issue