sonde fabric : le pare-feu, le SDN, la frontiere et les acces disent-ils le plan ?
Un minuteur horaire sur le runner du site joue les plans de lecture et consigne l ecart ; la sonde lit le constat, et signale un constat perime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a401481ae9
commit
79fcff3274
7 changed files with 173 additions and 0 deletions
|
|
@ -64,3 +64,16 @@ serveur_ops_site_cle_deposer: false
|
|||
serveur_ops_site_cle_source: ""
|
||||
serveur_ops_site_cle_destination: >-
|
||||
{{ serveur_ops_site_racine }}/.config/setops-vault-{{ serveur_ops_site_depot | lower }}
|
||||
|
||||
# LA CONFORMITE DE LA FABRIC (2026-09-28) — voir `templates/conformite-fabric.sh.j2`.
|
||||
# Des plans de LECTURE seulement : aucun n'ecrit.
|
||||
serveur_ops_site_conformite_plans:
|
||||
- proxmox-fw-plan
|
||||
- sdn-plan
|
||||
- frontiere-plan
|
||||
- vpn-admin-plan
|
||||
serveur_ops_site_conformite_etat: "/var/lib/setops/conformite-fabric.json"
|
||||
serveur_ops_site_conformite_horaire: "hourly"
|
||||
# Au-dela, le constat est PERIME et la sonde passe au rouge : un minuteur mort ne doit pas
|
||||
# laisser la derniere bonne nouvelle au vert.
|
||||
serveur_ops_site_conformite_age_max_h: 3
|
||||
|
|
|
|||
|
|
@ -34,3 +34,9 @@ sondes:
|
|||
service en disparaissant — on s''en apercoit quand un ecosysteme doit naitre. Et une
|
||||
voute dechiffree en transit ne fait echouer aucun deploiement : Ansible la lit tres
|
||||
bien, elle expose simplement tout.'
|
||||
- nom: fabric
|
||||
ttl: 5400
|
||||
raison: >-
|
||||
Le pare-feu Proxmox, le SDN, la frontiere et les acces WireGuard disent-ils encore ce
|
||||
que le plan dit ? Le 2026-09-28, aucune VM des locataires n'avait son pare-feu actif
|
||||
et rien ne le signalait ; les plans existaient, personne ne les jouait sans raison.
|
||||
|
|
|
|||
|
|
@ -151,3 +151,48 @@
|
|||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
||||
# LA CONFORMITE DE LA FABRIC (2026-09-28). Un minuteur joue les plans de lecture et
|
||||
# consigne l'ecart ; la sonde « fabric » le lit. Voir `templates/conformite-fabric.sh.j2`.
|
||||
- name: Assurer le repertoire d'etat
|
||||
ansible.builtin.file:
|
||||
path: /var/lib/setops
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
||||
- name: Deposer le constat de conformite de la fabric
|
||||
ansible.builtin.template:
|
||||
src: conformite-fabric.sh.j2
|
||||
dest: /usr/local/sbin/setops-conformite-fabric.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
||||
- name: Deposer l'unite et le minuteur du constat
|
||||
ansible.builtin.template:
|
||||
src: "{{ item }}.j2"
|
||||
dest: "/etc/systemd/system/{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- setops-conformite-fabric.service
|
||||
- setops-conformite-fabric.timer
|
||||
|
||||
- name: Activer le minuteur du constat
|
||||
ansible.builtin.systemd:
|
||||
name: setops-conformite-fabric.timer
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Deposer la sonde « fabric »
|
||||
ansible.builtin.template:
|
||||
src: sonde-fabric.sh.j2
|
||||
dest: /usr/local/lib/setops/sondes/fabric.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
|
|
|||
51
roles/serveur_ops_site/templates/conformite-fabric.sh.j2
Normal file
51
roles/serveur_ops_site/templates/conformite-fabric.sh.j2
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
# GENERE par Set-OPS (role serveur_ops_site). Ne pas editer a la main.
|
||||
#
|
||||
# LA FABRIC DIT-ELLE ENCORE CE QUE LE PLAN DIT ? (2026-09-28)
|
||||
#
|
||||
# Joue les plans de LECTURE (aucune ecriture) et consigne, pour chacun, ce qu'il resterait
|
||||
# a creer, retirer, mettre a jour ou corriger. La sonde « fabric » lit ce constat.
|
||||
#
|
||||
# POURQUOI : le 2026-09-28, aucune des 26 VM des locataires n'avait son pare-feu Proxmox
|
||||
# actif — les reconstructions clonent sans ses options —, et rien ne le signalait. Le meme
|
||||
# jour, un changement du moteur a fait vouloir a la frontiere une regle qui ne
|
||||
# correspondrait jamais ; seul un plan relu a la main l'a montre. Les plans existaient :
|
||||
# personne ne les jouait sans raison.
|
||||
#
|
||||
# Pourquoi un minuteur et pas la sonde elle-meme : une sonde dispose de quelques secondes,
|
||||
# et ces plans interrogent l'API du cluster et celle de la frontiere.
|
||||
set -uo pipefail
|
||||
|
||||
RACINE={{ serveur_ops_site_racine }}
|
||||
UTIL={{ serveur_ops_site_utilisateur }}
|
||||
ETAT={{ serveur_ops_site_conformite_etat }}
|
||||
# Le devis du pare-feu Proxmox resout les flux d'un locataire : n'importe lequel suffit,
|
||||
# il est borne par `underlay.tenants`.
|
||||
TENANT=$(ls -d "${RACINE}"/OPS-* 2>/dev/null | head -1)
|
||||
|
||||
TMP=$(mktemp); trap 'rm -f "${TMP}"' EXIT
|
||||
resultats=()
|
||||
for plan in {{ serveur_ops_site_conformite_plans | join(' ') }}; do
|
||||
runuser -u "${UTIL}" -- bash -lc "cd '${RACINE}/Set-OPS-public' && export PATH='${RACINE}/venv/bin':\$PATH && SETOPS_INSTANCE='${TENANT}' timeout 240 make ${plan}" > "${TMP}" 2>&1
|
||||
rc=$?
|
||||
# La sortie passe par un FICHIER : `python3 -` lit deja son code sur l'entree standard.
|
||||
resultats+=("$(python3 - "${plan}" "${rc}" "${TMP}" <<'EOF'
|
||||
import json, re, sys
|
||||
plan, rc, texte = sys.argv[1], int(sys.argv[2]), open(sys.argv[3], errors="replace").read()
|
||||
a_faire = sum(int(n) for n in re.findall(
|
||||
r"(?:a creer|a retirer|a mettre a jour|noeuds a corriger)\s*:\s*(\d+)", texte))
|
||||
detail = [l.strip() for l in texte.splitlines() if re.match(r"\s*[-+~]\s", l)][:6]
|
||||
print(json.dumps({"plan": plan, "rc": rc, "a_faire": a_faire, "detail": detail}))
|
||||
EOF
|
||||
)")
|
||||
done
|
||||
|
||||
python3 - "${ETAT}" "${resultats[@]}" <<'EOF'
|
||||
import json, sys, time, os
|
||||
etat, lignes = sys.argv[1], sys.argv[2:]
|
||||
doc = {"horodatage": int(time.time()), "plans": [json.loads(l) for l in lignes]}
|
||||
tmp = etat + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(doc, f)
|
||||
os.replace(tmp, etat)
|
||||
EOF
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# GENERE par Set-OPS (role serveur_ops_site). Ne pas editer a la main.
|
||||
[Unit]
|
||||
Description=La fabric dit-elle encore ce que le plan dit ? (plans de lecture, aucune ecriture)
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/setops-conformite-fabric.sh
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# GENERE par Set-OPS (role serveur_ops_site). Ne pas editer a la main.
|
||||
[Unit]
|
||||
Description=Constat de conformite de la fabric
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{ serveur_ops_site_conformite_horaire }}
|
||||
OnBootSec=5min
|
||||
RandomizedDelaySec=300
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
38
roles/serveur_ops_site/templates/sonde-fabric.sh.j2
Normal file
38
roles/serveur_ops_site/templates/sonde-fabric.sh.j2
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/bash
|
||||
# GENERE par Set-OPS (role serveur_ops_site). Ne pas editer a la main.
|
||||
#
|
||||
# SONDE « fabric » — le pare-feu Proxmox, le SDN, la frontiere et les acces WireGuard
|
||||
# disent-ils encore ce que le plan dit ?
|
||||
#
|
||||
# Contrat : docs/supervision-conception.md — une ligne, 0/1/2/3. Elle ne JOUE pas les
|
||||
# plans (trop lents pour une sonde) : elle lit le constat du minuteur
|
||||
# `setops-conformite-fabric`. Un constat trop VIEUX est une panne a part entiere : sans
|
||||
# lui, un minuteur mort laisserait la derniere bonne nouvelle au vert pour toujours.
|
||||
set -uo pipefail
|
||||
|
||||
python3 - {{ serveur_ops_site_conformite_etat }} {{ serveur_ops_site_conformite_age_max_h }} <<'EOF'
|
||||
import json, sys, time
|
||||
etat, age_max = sys.argv[1], int(sys.argv[2])
|
||||
try:
|
||||
doc = json.load(open(etat))
|
||||
except (OSError, ValueError):
|
||||
print(f"Aucun constat lisible ({etat}) : le minuteur setops-conformite-fabric a-t-il tourne ?")
|
||||
sys.exit(2)
|
||||
age_h = (time.time() - doc["horodatage"]) / 3600
|
||||
if age_h > age_max:
|
||||
print(f"Constat PERIME : {age_h:.0f} h (seuil {age_max} h). Le minuteur ne mesure plus.")
|
||||
sys.exit(2)
|
||||
plans = doc["plans"]
|
||||
illisibles = [p["plan"] for p in plans if p["rc"] != 0]
|
||||
ecarts = [p for p in plans if p["rc"] == 0 and p["a_faire"] > 0]
|
||||
perf = " ".join(f"{p['plan']}={p['a_faire'] if p['rc'] == 0 else -1}" for p in plans)
|
||||
if illisibles:
|
||||
print(f"Plan(s) illisible(s) : {', '.join(illisibles)} — la fabric n'a pas pu etre relue. | {perf}")
|
||||
sys.exit(3)
|
||||
if ecarts:
|
||||
quoi = "; ".join(f"{p['plan']} : {p['a_faire']} ({(p['detail'] or ['?'])[0][:60]})" for p in ecarts)
|
||||
print(f"ECART avec le plan — {quoi}. Relire le plan, puis appliquer ou corriger. | {perf}")
|
||||
sys.exit(1)
|
||||
print(f"Fabric conforme au plan ({len(plans)} plans, constat de {age_h * 60:.0f} min). | {perf}")
|
||||
sys.exit(0)
|
||||
EOF
|
||||
Loading…
Reference in a new issue