La supervision du site voyait ses sept VM et rien d autre. Au depart, depuis site-mon-01, les trois hyperviseurs, les neuf pattes de la frontiere et sa PROPRE passerelle par defaut rendaient tous 100 pourcent de perte au ping. 16 hotes UP sur 16 dont 9 pattes de frontiere en controle ACTIF 11 cibles Prometheus dont 3 hyperviseurs, job fabric separe LE PARTAGE. Les hyperviseurs portent node_exporter - D-48 l autorise - et exposent 1005 unites systemd avec leur etat, soit ce que la sonde sante mesurait, plus la charge et le disque. Ils n entrent PAS dans le socle : leur appliquer serveur_durci reecrirait le pare-feu, le SSH et les sysctl de la machine qui tient tout le reste. La frontiere, elle, n accueille aucun agent : controle actif, une entree par PATTE, parce qu une interface eteinte coupe une zone pendant que les autres vont bien. ON TIRE, ON NE POUSSE PAS. J avais propose du passif et il avait ete valide ; la mesure a dit non. Un hyperviseur envoie vers un routeur qui ne connait pas les reseaux du site, et sa route par defaut est GELEE (D-57). Le porteur de sante y expirait en 20 s. LE VRAI DEFAUT ETAIT UNE LISTE QUI N A PAS SUIVI. Le mecanisme de routage existait deja sur vmbr0, avec un commentaire du 2026-08-26 tenant exactement le raisonnement qu on venait de refaire. Sa liste s arretait a 10.0.34.0/24 quand le site en declare six : les zones sauvegarde et supervision sont nees, les routes n ont pas suivi. Le symptome ne ressemblait pas a une route manquante - il ressemblait a un pare-feu, puis a un probleme de reseau chez l exploitant. make routes-fabric-etat compare desormais TROIS choses : zones declarees, routes declarees dans /etc/network/interfaces, routes vivantes dans le noyau. Le cas le plus traitre est vivante mais non declaree : tout fonctionne, la supervision est verte, et la panne attend la prochaine maintenance. Eprouvee dans les deux sens. Deux defauts trouves en construisant. bifrost-2 est un nom RESERVE, pas un boitier - l underlay le disait en prose, illisible par le moteur ; il porte desormais etat: reserve. Et un service passif n existe que pour un hote qui peut POUSSER : l appartenance a un groupe sert deux choses qui ne coincident pas toujours, a qui l on deploie et de qui l on attend un rapport. Les commutateurs restent dehors, choix de l exploitant, coherent avec D-48. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
141 lines
6.2 KiB
Python
141 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Les hyperviseurs routent-ils toutes les zones que le SITE declare ? Ne corrige rien.
|
|
|
|
POURQUOI CE CONTROLE EXISTE (2026-09-10).
|
|
|
|
Un hyperviseur atteint les zones du site par des routes SPECIFIQUES posees sur `vmbr0`,
|
|
et non par sa route par defaut — celle-ci est GELEE (D-57) et vise le routeur du site.
|
|
Le commentaire qui les accompagne, ecrit le 2026-08-26, dit pourquoi :
|
|
|
|
Aller et retour empruntent la MEME patte — pf cree son etat sur une seule
|
|
interface, et une reponse revenant par une autre serait jetee.
|
|
|
|
CE QUE CA A COUTE. Le site a gagne deux zones — `site-sauvegarde` (35) et
|
|
`site-supervision` (36) — et la liste des routes ne les a pas suivies. Elle s'arretait a
|
|
34. Consequence mesuree : la supervision du site ne pouvait PAS collecter les
|
|
hyperviseurs, parce que la reponse partait vers un routeur qui ne connait pas 10.0.36.0/24.
|
|
Le symptome ne ressemblait pas a une route manquante — il ressemblait a un pare-feu, puis
|
|
a un probleme de reseau chez l'exploitant.
|
|
|
|
Une liste qui doit suivre une autre liste finit toujours par prendre du retard. La seule
|
|
question est de savoir si quelqu'un s'en apercevra avant la panne.
|
|
|
|
CE QU'ON COMPARE, ET DANS LES DEUX SENS :
|
|
|
|
1. les zones DECLAREES par `underlay.reseaux` (la verite du site) ;
|
|
2. les routes DECLAREES dans `/etc/network/interfaces` (ce qui survit au redemarrage) ;
|
|
3. les routes VIVANTES dans la table du noyau (ce qui vaut maintenant).
|
|
|
|
Une route vivante mais non declaree disparait au prochain redemarrage — c'est le cas le
|
|
plus traitre, parce que tout fonctionne jusqu'a la maintenance suivante.
|
|
|
|
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 gabarit-etat`.
|
|
|
|
Usage :
|
|
python3 scripts/routes_fabric_etat.py # code de sortie 0 si conforme
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
import underlay as underlay_mod # noqa: E402
|
|
|
|
# La patte de la frontiere sur le plan d'administration : le prochain saut de ces routes.
|
|
# DERIVEE, jamais ecrite : c'est l'hote `frontiere` du reseau `grappe-controle`.
|
|
RESEAU_PROCHAIN_SAUT = "grappe-controle"
|
|
|
|
|
|
def _lire(hote: str, commande: str) -> str:
|
|
r = subprocess.run(
|
|
["ssh", "-o", "ConnectTimeout=8", "-o", "BatchMode=yes",
|
|
"-o", "StrictHostKeyChecking=no", f"ansible@{hote}", commande],
|
|
capture_output=True, text=True, timeout=60)
|
|
return r.stdout if r.returncode == 0 else ""
|
|
|
|
|
|
def main() -> int:
|
|
u = underlay_mod.charger()
|
|
if not u:
|
|
print("Aucun underlay monte : rien a comparer.")
|
|
return 0
|
|
|
|
hotes = underlay_mod.hotes(u)
|
|
|
|
# 1. Les zones du site, telles que la carte les declare.
|
|
zones = sorted({str(r["sous_reseau"]) for r in underlay_mod.reseaux(u)
|
|
if str(r.get("nom", "")).startswith("site-") and r.get("sous_reseau")})
|
|
if not zones:
|
|
print("Le site ne declare aucune zone : rien a router.")
|
|
return 0
|
|
|
|
saut = next((str(h["ip"]) for h in hotes
|
|
if h.get("role") == "frontiere"
|
|
and str(h.get("reseau")) == RESEAU_PROCHAIN_SAUT and h.get("ip")), "")
|
|
if not saut:
|
|
print(f"Aucune patte `frontiere` sur `{RESEAU_PROCHAIN_SAUT}` : "
|
|
f"le prochain saut ne se derive pas.")
|
|
return 1
|
|
|
|
# Les hyperviseurs, par leur adresse d'administration — la seule que le poste route.
|
|
noeuds = {}
|
|
for h in hotes:
|
|
if h.get("role") != "hyperviseur" or not h.get("ip"):
|
|
continue
|
|
if str(h.get("reseau")) != RESEAU_PROCHAIN_SAUT:
|
|
continue
|
|
noeuds.setdefault(str(h["nom"]), str(h["ip"]))
|
|
if not noeuds:
|
|
print("Aucun hyperviseur sur le plan d'administration : rien a interroger.")
|
|
return 0
|
|
|
|
print(f"Zones declarees par le site : {len(zones)} — prochain saut {saut}")
|
|
ecarts: list[str] = []
|
|
for nom, ip in sorted(noeuds.items()):
|
|
conf = _lire(ip, "cat /etc/network/interfaces")
|
|
table = _lire(ip, "ip route show")
|
|
if not conf and not table:
|
|
ecarts.append(f"{nom} ({ip}) : injoignable — le controle ne peut rien dire")
|
|
print(f" {nom:<9} INJOIGNABLE")
|
|
continue
|
|
declarees = set(re.findall(r"ip route replace (\d+\.\d+\.\d+\.\d+/\d+) via " + re.escape(saut), conf))
|
|
vivantes = set(re.findall(r"^(\d+\.\d+\.\d+\.\d+/\d+) via " + re.escape(saut), table, re.M))
|
|
manque_decl = [z for z in zones if z not in declarees]
|
|
manque_vive = [z for z in zones if z not in vivantes]
|
|
# Le cas le plus traitre : vivante mais non declaree — elle part au redemarrage.
|
|
ephemeres = sorted(vivantes - declarees)
|
|
etat = "=" if not (manque_decl or manque_vive or ephemeres) else "≠"
|
|
print(f" {nom:<9} declarees {len(declarees)}/{len(zones)} "
|
|
f"vivantes {len(vivantes)}/{len(zones)} {etat}")
|
|
for z in manque_decl:
|
|
ecarts.append(f"{nom} : {z} n'est pas declaree dans `/etc/network/interfaces` "
|
|
f"— elle ne survivra pas au redemarrage")
|
|
for z in manque_vive:
|
|
ecarts.append(f"{nom} : {z} absente de la table du noyau — les reponses vers "
|
|
f"cette zone partent par la route par defaut et se perdent")
|
|
for z in ephemeres:
|
|
ecarts.append(f"{nom} : {z} est VIVANTE mais non declaree — elle disparaitra "
|
|
f"au prochain redemarrage, et tout marchera jusque-la")
|
|
|
|
if ecarts:
|
|
print("\nECART — la fabric ne route pas ce que le site declare :")
|
|
for e in ecarts:
|
|
print(f" - {e}")
|
|
print(f"\n Remede, sur le noeud concerne :")
|
|
print(f" ip route replace <zone> via {saut} dev vmbr0 # tout de suite")
|
|
print(f" puis la meme ligne en `post-up` dans /etc/network/interfaces # pour apres")
|
|
print(" L'ordre importe peu ; faire les DEUX importe.")
|
|
return 1
|
|
|
|
print("\nConforme — chaque hyperviseur route toutes les zones du site, "
|
|
"declarees ET vivantes.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|