diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ac094d..e3fbfa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,88 @@ # CHANGELOG — Set-OPS +## 2026-09-09 (4) — `systemctl --failed` entre dans la supervision + +**63 preuves. `make prouver` : CONFORME, 63 OK, 0 echec, 0 saute.** +Nouveau role `client_sante`, integration **universelle** (67 roles). + +### Ce qu'il ferme + +Le defaut trouve quelques heures plus tot : `openipmi.service` echouait a CHAQUE +demarrage sur les quatorze machines depuis le 2026-09-02, et `systemctl --failed` rendait +ZERO partout — non parce qu'elles allaient bien, mais parce qu'AUCUNE n'avait redemarre +depuis. Il a fallu qu'un humain redemarre une machine pour que le defaut existe aux yeux +de quelqu'un. + +*Un controle qui ne peut echouer qu'au demarrage ne mesure rien tant que rien ne demarre.* + +### PASSIF, et a duree de vie + +Un controle ACTIF ne voit pas la machine MUETTE : si elle ne repond plus, la sonde echoue +et on met ca sur le compte du reseau. Ici c'est le NOEUD qui parle, et le `ttl` de son +envoi fait la fraicheur — sans nouvelle, Icinga perime le service tout seul. **Le silence +alerte autant que l'echec**, et le silence est precisement ce qui n'a alerte personne. + +Le minuteur declenche **au demarrage** (`OnBootSec=2min`) autant que toutes les 15 min : +les echecs de cette famille NAISSENT au boot, et attendre le premier quart d'heure +laisserait une fenetre pendant laquelle la machine est en panne et le tableau au vert. + +**CRITIQUE des la premiere unite**, jamais un seuil. Une unite en echec est soit un vrai +probleme, soit du bruit a retirer : dans les deux cas il faut agir. Un seuil ferait vivre +le bruit indefiniment — exactement ce qu'on vient de corriger. + +Les tolerances se nomment **une par une** (`client_sante_unites_tolerees`, vide par +defaut). Jamais un motif large : un filtre qui cache une unite en cache d'autres, et on ne +s'en apercoit que le jour ou l'on cherche pourquoi rien n'a alerte. + +### Le controle negatif + +Une garantie qu'on n'a jamais vue echouer n'est pas une garantie. Unite factice posee sur +`obs-01`, rapport rejoue, etat relu dans IcingaDB : + + obs-01 sante CRITICAL 1 unite(s) systemd en echec : setops-controle-negatif.service + (13 autres) sante OK Aucune unite systemd en echec. + +Le verdict NOMME l'unite. Apres nettoyage : **14/14 OK**. + +### Un conflit evite de justesse + +`setops-sauvegardes.conf` definissait les `object Host`. Un second fichier de controle +aurait redefini les memes, et **Icinga refuse un objet en double** : la configuration +entiere aurait ete rejetee, donc AUCUNE supervision — en voulant en ajouter. Les hotes +vivent desormais dans `setops-hotes.conf`, definis UNE fois ; les fichiers de controle +n'attachent que des services. Verifie : 14 hotes, 14 services, zero doublon, +`icinga2 daemon -C` valide. + +### Trois obstacles, et ce qu'ils apprennent + +**`${#tableau[@]}` contient `{#`**, que Jinja lit comme un debut de commentaire — le rendu +echouait sur « Missing end of comment tag ». Le shell a besoin de `${#...}` ; c'est donc a +Jinja de s'ecarter (`#jinja2: comment_start_string:...` en tete du gabarit). + +**Ma premiere sonde a traduit un refus en « 0 service ».** Le compte `setops-depot` ne peut +que POSER un resultat, pas lire — moindre privilege voulu. L'API rendait +`{"error":403,"status":"Missing permission: objects/query/service"}`, et mon script, qui +cherchait une clef `results`, a affiche « 0 service(s) sante ». *Encore un « echec » qui +ecrasait « permission ».* L'etat se lit dans IcingaDB, pas par ce compte. + +**Et un echec `apt` transitoire** sur `mon-01` : `503 unexpected range` puis « Message has +been manipulated » sur la signature de `debian-security`. Alarmant a la lecture, local a +une machine (13 autres propres), et disparu au second essai — un hoquet du cache +apt-cacher-ng, pas un incident de signature. Mesure avant conclusion : les deux +mandataires servaient le meme contenu, au meme condensat. + +### Le meme compte d'API, elargi de deux noms a trois + +Un second compte serait plus pur — un secret par usage. Il exigerait une clef de voute de +plus dans CHAQUE ecosysteme, donc un geste manuel a chaque nouvel ecosysteme, pour une +portee identique : ce compte ne peut deja que poser un resultat passif, sur des services +NOMMES. Le filtre passe de `sauvegarde: *` / `sauvegarde` a ces deux-la plus `sante`. Aucun +pouvoir nouveau. + +### Non fait + +Le SITE n'a pas recu `client_sante`. Sept machines, meme role, meme raison. + ## 2026-09-09 (3) — Le redemarrage a tenu, et il a montre autre chose ### Ce que le redemarrage prouve diff --git a/docs/audit/preuve-2026-09-09.md b/docs/audit/preuve-2026-09-09.md index 23775d0..05d7e06 100644 --- a/docs/audit/preuve-2026-09-09.md +++ b/docs/audit/preuve-2026-09-09.md @@ -20,8 +20,8 @@ | P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | | | P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. | | P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. | -| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 40 groupes classes, aucun cycle, aucune arete en arriere. | -| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 99 flux, schéma + matrice OK. | +| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. | +| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 100 flux, schéma + matrice OK. | | P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). | | P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site | | P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. | @@ -38,30 +38,30 @@ | P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. | | P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. | | P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 80 regle(s). | -| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). | +| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). | | P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. | | P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. | | 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, 66 roles avec README. | +| 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 : 37 exigence(s) de role, toutes satisfaites (131 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 (37 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 | 43 document(s) declarent leur lecteur (35 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). | | P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). | | P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. | -| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. | +| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 40 role(s) serveur/client tous nommes, 41 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 : 54 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 : 7 machine(s) du plan retrouvees, 104 regle(s) du site. | -| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. | +| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. | | P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). | | P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos | | P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). | | P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. | -| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (117 lignes). | +| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (118 lignes). | | P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 169 regles `pass`), tous non consignes et tous motives. | | P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 | | P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. | @@ -69,7 +69,7 @@ | P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. | | P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. | | P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. | -| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (63 preuves, 66 roles, 40 groupes). | +| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (63 preuves, 67 roles, 41 groupes). | | P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. | | P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). | | P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `2d9dc86` (publie le 2026-09-09). | diff --git a/docs/carte-set-ops.md b/docs/carte-set-ops.md index 3f38165..57bde35 100644 --- a/docs/carte-set-ops.md +++ b/docs/carte-set-ops.md @@ -23,8 +23,8 @@ README de rôles). Cette page comble ces deux trous. | Ce qu'on compte | Combien | Comment on le mesure | |---|---|---| -| rôles | 66 | `roles/*/` | -| README de rôles | 66 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette | +| rôles | 67 | `roles/*/` | +| README de rôles | 67 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette | | documents | 39 | `docs/*.md` | | pièces d'audit | 40 | `docs/audit/*` | | unités de wiki | 27 | `wiki/*.md` | diff --git a/docs/catalogue-services.md b/docs/catalogue-services.md index b65de96..8cfb454 100644 --- a/docs/catalogue-services.md +++ b/docs/catalogue-services.md @@ -65,7 +65,7 @@ Ce que la reconstruction couvre, par capacité : | Forge du genome du site | `serveur_forge_site` | designe LA forge dont les ecosystemes de ce site se reproduisent (D-81), et l'ouvre a eux. Marqueur : `serveur_forgejo` installe. | | Resolveur du site | `serveur_resolveur_site` | designe LE resolveur que les ecosystemes de ce site interrogent tant qu'ils n'ont pas le leur. Marqueur : `serveur_resolveur` installe. | | Depot de sauvegarde du site | `serveur_backup_site` | designe LE depot ou les ecosystemes de ce site posent leur etat tant qu'ils n'ont pas le leur. Marqueur : `serveur_backup` installe. | -| Agents de flotte | `client_metrique`, `client_journal`, `client_smtp` | collecte et relais sur toute la flotte | +| Agents de flotte | `client_metrique`, `client_journal`, `client_smtp`, `client_sante` | collecte, relais et rapport de santé sur toute la flotte | *Rôles retirés (2026-07-04, supersédés ou hors conception)* : `serveur_sendmail` (→ Postfix), `client_dns` (→ plancher `/etc/hosts` + `client_resolveur`), `client_ldap` @@ -174,6 +174,7 @@ boîtes (Dovecot). La coupure suit l'exposition, pas le logiciel. | Journaux vers Loki | `client_journal` | **tout hôte** (universelle) | | Résolution locale (Unbound) | `client_resolveur` | **tout hôte** (universelle) | | Source d'artefacts (cache apt) | `client_artefacts` | **tout hôte** (universelle) | +| Santé du nœud (unités en échec) | `client_sante` | **tout hôte** (universelle) | | Relais SMTP | `client_smtp` | déclaré par hôte, dans le plan | | Sauvegarde restic | `client_backup` | déclaré par hôte — **obligatoire pour tout détenteur d'état** (P36) | @@ -199,7 +200,7 @@ dans le plan. ## Ordre de déploiement — le raisonnement > **L'ordre exécutable n'est pas ici.** Il vit dans `docs/couches-deploiement.yml` et -> `docs/dependances-groupes.yml`, que P08 prouve cohérents (40 groupes classés, aucun +> `docs/dependances-groupes.yml`, que P08 prouve cohérents (41 groupes classés, aucun > cycle, aucune arête en arrière) et que `make reconstruire` suit. Ce qui suit en est le > **raisonnement**, utile pour comprendre pourquoi cet ordre-là — et pour placer un > service nouveau. Les phases sont franchies : les « intégrations à prévoir » ci-dessous diff --git a/docs/couches-deploiement.yml b/docs/couches-deploiement.yml index 34d7712..082fb32 100644 --- a/docs/couches-deploiement.yml +++ b/docs/couches-deploiement.yml @@ -99,3 +99,7 @@ couches: - client_backup - client_resolveur - client_artefacts + # Le rapport de sante vient en DERNIER de la couche : il constate ce que tout le + # reste a laisse derriere lui. Le poser plus tot ferait rapporter une machine + # a moitie deployee, et le premier verdict serait faux. + - client_sante diff --git a/docs/dependances-groupes.yml b/docs/dependances-groupes.yml index 7258e6e..abda1f2 100644 --- a/docs/dependances-groupes.yml +++ b/docs/dependances-groupes.yml @@ -12,6 +12,12 @@ groupes: raison: "Les exporters clients doivent etre collectes par Prometheus." surveillance: "Verifier targets Prometheus, scrape duration et erreurs de collecte." + client_sante: + requiert_groupes_actifs: + - serveur_icinga + raison: "Le rapport de sante depose un resultat passif sur l'API d'Icinga." + surveillance: "Verifier que chaque noeud rapporte : un service `sante` EXPIRE vaut un echec." + client_journal: requiert_groupes_actifs: - serveur_loki diff --git a/docs/registre-flux.md b/docs/registre-flux.md index d608322..d1a0174 100644 --- a/docs/registre-flux.md +++ b/docs/registre-flux.md @@ -13,6 +13,7 @@ | `client_pki` | egress | 8443 | tcp | serveur_step_ca | tls-requis | Émission/renouvellement des certificats par ACME et récupération de la racine auprès de l'AC interne. | | `client_resolveur` | egress | 53 | udp | serveur_resolveur | clair | Résoudre auprès du résolveur de l'écosystème, et de personne d'autre. | | `client_resolveur` | egress | 53 | tcp | serveur_resolveur | clair | Réponses longues et bascule TCP, obligatoires en DNS. | +| `client_sante` | egress | 5665 | tcp | serveur_icinga | tls | Depot d'un resultat passif sur l'API Icinga (process-check-result) : les unites systemd en echec du noeud. Le pair est authentifie par l'AC d'Icinga. | | `client_smtp` | egress | 25 | tcp | serveur_postfix | starttls | Relais des notifications locales vers le MTA central (Postfix), STARTTLS. | | `serveur_artefacts` | ingress | 3142 | tcp | flotte | clair | Toute la flotte prend ses paquets ici. En clair, et c'est correct : l'intégrité d'un dépôt apt vient de ses signatures, qu'apt vérifie de toute façon — un intermédiaire ne peut pas altérer un paquet sans se faire prendre. | | `serveur_artefacts` | egress | 80 | tcp | externe | clair | Remplir le cache depuis les dépôts Debian amont (deb.debian.org, security). | @@ -112,6 +113,6 @@ - **n-a** : 3 flux - **ssh** : 8 flux - **starttls** : 6 flux -- **tls** : 9 flux +- **tls** : 10 flux - **tls-cible** : 2 flux - **tls-requis** : 34 flux diff --git a/playbooks/groupes/client_sante.yml b/playbooks/groupes/client_sante.yml new file mode 100644 index 0000000..457c55e --- /dev/null +++ b/playbooks/groupes/client_sante.yml @@ -0,0 +1,39 @@ +--- +# client_sante — L'ORDRE FAIT PARTIE DE L'INTEGRATION. +# +# Cette integration depend de `serveur_icinga`, et sa metadonnee le declare +# (`roles/client_sante/meta/integration.yml`). L'hote qui PORTE la supervision est donc +# configure AVANT ceux qui lui rapportent — sinon les noeuds poussent un resultat passif +# vers une API qui n'existe pas encore, et le premier verdict de chaque machine est un +# echec qui accuse le reseau. +# +# Ce fichier est le miroir de la declaration ; la preuve P44 refuse tout ecart. +# +# `any_errors_fatal` sur le premier play : si la supervision n'a pas pu etre configuree, +# y raccrocher des rapporteurs ne produit que du bruit. + +- name: Intégration client_sante — le serveur d'abord + hosts: client_sante:&serveur_icinga + become: true + any_errors_fatal: true + module_defaults: &defauts_sante + ansible.builtin.apt: + lock_timeout: 300 + gather_facts: true + pre_tasks: &pre_sante + - name: Vérifier que la cible est Debian + ansible.builtin.assert: + that: + - ansible_facts.distribution == "Debian" + fail_msg: "Ce playbook est prévu pour Debian." + roles: + - client_sante + +- name: Intégration client_sante — puis les hôtes qui s'y adressent + hosts: client_sante:!serveur_icinga + become: true + module_defaults: *defauts_sante + gather_facts: true + pre_tasks: *pre_sante + roles: + - client_sante diff --git a/roles/client_sante/README.md b/roles/client_sante/README.md new file mode 100644 index 0000000..97610d4 --- /dev/null +++ b/roles/client_sante/README.md @@ -0,0 +1,41 @@ +# client_sante + +Le nœud rapporte à Icinga ses **unités systemd en échec**, par résultat passif. + +## Pourquoi + +Trouvé le 2026-09-09 : `openipmi.service` échouait à **chaque démarrage** sur les quatorze +machines de Chezlepro depuis le 2026-09-02. `systemctl --failed` rendait pourtant **zéro** +partout — non parce qu'elles allaient bien, mais parce qu'**aucune n'avait redémarré** +depuis. Six jours et vingt heures pour la première. Il a fallu qu'un humain redémarre une +machine pour que le défaut existe aux yeux de quelqu'un. + +*Un contrôle qui ne peut échouer qu'au démarrage ne mesure rien tant que rien ne démarre.* + +Et le dégât n'est pas cosmétique : une unité en échec permanent **use le seul signal qui +devrait alerter**. Le jour où une vraie unité tombe, le compte passe de 1 à 2 et personne +ne fait la différence. + +## Passif, et à durée de vie + +Un contrôle **actif** ne voit pas la machine **muette** : si elle ne répond plus, la sonde +échoue et on met ça sur le compte du réseau. Ici c'est le nœud qui parle, et le `ttl` de +son envoi fait la fraîcheur — sans nouvelle, Icinga périme le service tout seul. **Le +silence alerte autant que l'échec**, et le silence est précisément ce qui n'a alerté +personne. + +Le minuteur déclenche **au démarrage** (`OnBootSec=2min`) autant que périodiquement : les +échecs de cette famille naissent au boot, et attendre le premier quart d'heure laisserait +une fenêtre pendant laquelle la machine est en panne et le tableau au vert. + +## Tolérances + +`client_sante_unites_tolerees` nomme les unités acceptées **une par une**, jamais un motif +large : un filtre qui cache une unité en cache d'autres, et on ne s'en aperçoit que le jour +où l'on cherche pourquoi rien n'a alerté. Le défaut est la liste **vide** — une unité en +échec est soit un vrai problème, soit du bruit à retirer ; dans les deux cas il faut agir. + +## Ce dont il dépend + +Un hôte `serveur_icinga` dans l'écosystème (sinon rien n'est posé, et c'est dit), le secret +`vault_icinga_api_depot`, et le flux sortant TCP 5665 vers lui. diff --git a/roles/client_sante/defaults/main.yml b/roles/client_sante/defaults/main.yml new file mode 100644 index 0000000..bfdb474 --- /dev/null +++ b/roles/client_sante/defaults/main.yml @@ -0,0 +1,33 @@ +--- +# A QUI L'ON RAPPORTE — derive du groupe, jamais ecrit en dur. Vide = pas de supervision +# dans cet ecosysteme, et le role ne pose alors ni script ni minuteur : un timer qui +# echoue chaque heure apprend a ignorer le rouge. +client_sante_icinga_hote: "{{ (groups['serveur_icinga'] | default([]) | first) | default('') }}" + +# LE MEME COMPTE D'API QUE LE RAPPORT DE SAUVEGARDE, et c'est un choix. +# +# Un second compte serait plus pur — un secret par usage. Il exigerait une CLEF DE VOUTE +# DE PLUS dans chaque ecosysteme, donc un geste manuel a chaque nouvel ecosysteme, pour +# une portee identique : ce compte ne peut deja QUE poser un resultat passif, et +# seulement sur des services nommes. On elargit le filtre de deux noms a trois, on ne +# donne aucun pouvoir nouveau. +client_sante_icinga_utilisateur: "setops-depot" +client_sante_icinga_motdepasse: "{{ vault_icinga_api_depot | default('') }}" +client_sante_ca_verification: "/etc/setops/icinga-ca.crt" +client_sante_icinga_ca_source: "/var/lib/icinga2/ca/ca.crt" + +# `ttl` du resultat passif. AU-DELA, ICINGA PERIME LE SERVICE DE LUI-MEME — c'est ce qui +# fait que le SILENCE alerte, et pas seulement l'echec. Un noeud eteint, un timer casse, +# un reseau coupe : trois facons de ne plus rien dire, et les trois doivent se voir. +# +# Trois fois la periode : deux passages peuvent manquer sans crier au loup. +client_sante_periode: "15min" +client_sante_ttl_icinga: 2700 + +# UNITES TOLEREES — nommees, jamais un motif large. +# +# Vide par defaut, et c'est le bon defaut : une unite en echec permanent use le seul +# signal qui devrait alerter. Le jour ou une VRAIE unite tombe, le compte passe de 1 a 2 +# et personne ne fait la difference. Si une unite doit etre toleree, elle est ECRITE ICI, +# avec sa raison — pas absorbee par un filtre qui en cacherait d'autres. +client_sante_unites_tolerees: [] diff --git a/roles/client_sante/handlers/main.yml b/roles/client_sante/handlers/main.yml new file mode 100644 index 0000000..34a9aca --- /dev/null +++ b/roles/client_sante/handlers/main.yml @@ -0,0 +1,4 @@ +--- +- name: Recharger systemd + ansible.builtin.systemd: + daemon_reload: true diff --git a/roles/client_sante/meta/flux.yml b/roles/client_sante/meta/flux.yml new file mode 100644 index 0000000..3da7ca7 --- /dev/null +++ b/roles/client_sante/meta/flux.yml @@ -0,0 +1,12 @@ +--- +# Flux reseau du rapport de sante (resultat passif -> API Icinga). Voir +# docs/flux-conception.md. +flux: + - sens: egress + port: 5665 + protocole: tcp + pair: serveur_icinga + chiffrement: tls + raison: >- + Depot d'un resultat passif sur l'API Icinga (process-check-result) : les unites + systemd en echec du noeud. Le pair est authentifie par l'AC d'Icinga. diff --git a/roles/client_sante/meta/integration.yml b/roles/client_sante/meta/integration.yml new file mode 100644 index 0000000..5f5fd76 --- /dev/null +++ b/roles/client_sante/meta/integration.yml @@ -0,0 +1,15 @@ +--- +# Politique d'integration. Voir roles/client_metrique/meta/integration.yml pour le +# raisonnement, et docs/decisions-architecture.md (D-33). +integration: + # DE QUI CETTE INTEGRATION DEPEND. Le noeud pousse un resultat PASSIF vers Icinga : + # sans destinataire, le rapport n'irait nulle part, et un timer qui echoue chaque + # quart d'heure apprend a ignorer le rouge. + serveur: serveur_icinga + universelle: true + raison: >- + Une unite systemd en echec ne se signale a personne. Le noeud est le seul a pouvoir + le dire, et le SILENCE doit alerter autant que l'echec — d'ou un resultat passif a + duree de vie, plutot qu'un controle actif qui ne verrait pas la machine muette. + # Pas d'exemption : l'hote de supervision a lui aussi des unites, et rien ne dit + # qu'elles vont bien. diff --git a/roles/client_sante/tasks/main.yml b/roles/client_sante/tasks/main.yml new file mode 100644 index 0000000..6b9ea62 --- /dev/null +++ b/roles/client_sante/tasks/main.yml @@ -0,0 +1,113 @@ +--- +# LE NOEUD RAPPORTE SES UNITES EN ECHEC. Voir le gabarit du script pour le pourquoi. +# +# Conditionne a l'existence d'un `serveur_icinga` : sans destinataire, le rapport +# n'irait nulle part, et un timer qui echoue chaque quart d'heure apprend a ignorer +# le rouge. Meme regle que `client_backup`. +- name: Aucune supervision dans cet écosystème — rien à poser + ansible.builtin.debug: + msg: >- + Aucun hôte `serveur_icinga` : le rapport de santé n'est pas posé. Ce n'est pas une + erreur — c'est un écosystème sans supervision, et le dire vaut mieux que d'installer + un minuteur qui échouerait dans le vide. + when: not client_sante_icinga_hote + +- name: Poser le rapport de santé + when: client_sante_icinga_hote | length > 0 + block: + - name: Exiger de quoi rapporter à Icinga + ansible.builtin.assert: + that: + - client_sante_icinga_motdepasse | length > 0 + fail_msg: >- + `vault_icinga_api_depot` requis : un hôte `serveur_icinga` existe, mais aucun + secret d'API. Le nœud verrait ses unités en échec sans pouvoir le dire. + + - name: Assurer le répertoire des secrets Set-OPS + ansible.builtin.file: + path: /etc/setops + state: directory + owner: root + group: root + mode: "0755" + + - name: Déposer le mot de passe d'API Icinga + ansible.builtin.copy: + content: "{{ client_sante_icinga_motdepasse }}\n" + dest: /etc/setops/icinga-api.pass + owner: root + group: root + mode: "0600" + no_log: true + + # L'AC d'ICINGA, PAS CELLE DE step-ca : Icinga refuse de servir un certificat qu'il + # n'a pas emis (il renouvelle tout ce qui expire sous 30 jours, nos certificats vivent + # 24 h). Domaine de confiance ferme, pair authentifie malgre tout. + - name: L'AC d'Icinga est-elle déjà disponible ? + ansible.builtin.stat: + path: "{{ client_sante_icinga_ca_source }}" + delegate_to: "{{ client_sante_icinga_hote }}" + register: client_sante_ca_presente + + - name: Récupérer l'AC d'Icinga depuis l'hôte de supervision + when: client_sante_ca_presente.stat.exists + ansible.builtin.slurp: + src: "{{ client_sante_icinga_ca_source }}" + delegate_to: "{{ client_sante_icinga_hote }}" + register: client_sante_ca_icinga + + - name: Déposer l'AC d'Icinga pour la vérification du pair + when: client_sante_ca_presente.stat.exists + ansible.builtin.copy: + content: "{{ client_sante_ca_icinga.content | b64decode }}" + dest: "{{ client_sante_ca_verification }}" + owner: root + group: root + mode: "0644" + + - name: Installer curl pour le rapport passif + ansible.builtin.apt: + name: curl + state: present + + - name: Déployer le script de rapport de santé + ansible.builtin.template: + src: setops-sante.sh.j2 + dest: /usr/local/sbin/setops-sante.sh + owner: root + group: root + mode: "0750" + + - name: Déployer l'unité systemd du rapport + ansible.builtin.template: + src: setops-sante.service.j2 + dest: /etc/systemd/system/setops-sante.service + owner: root + group: root + mode: "0644" + notify: Recharger systemd + + - name: Déployer le minuteur du rapport + ansible.builtin.template: + src: setops-sante.timer.j2 + dest: /etc/systemd/system/setops-sante.timer + owner: root + group: root + mode: "0644" + notify: Recharger systemd + + - name: Activer le rapport de santé + ansible.builtin.systemd: + name: setops-sante.timer + enabled: true + state: started + daemon_reload: true + + # PREMIER RAPPORT IMMEDIAT, et ce n'est pas une commodite : sans lui, le service + # reste « en attente » jusqu'au premier declenchement, et un deploiement qui vient + # d'installer la supervision laisserait un tableau vide qu'on prendrait pour un + # tableau sain. + - name: Rapporter tout de suite (ne pas laisser un tableau vide passer pour sain) + ansible.builtin.command: /usr/local/sbin/setops-sante.sh + changed_when: false + failed_when: false diff --git a/roles/client_sante/templates/setops-sante.service.j2 b/roles/client_sante/templates/setops-sante.service.j2 new file mode 100644 index 0000000..04a561a --- /dev/null +++ b/roles/client_sante/templates/setops-sante.service.j2 @@ -0,0 +1,9 @@ +# GENERE par Set-OPS (role client_sante). +[Unit] +Description=Rapporter les unites systemd en echec a Icinga +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/setops-sante.sh diff --git a/roles/client_sante/templates/setops-sante.sh.j2 b/roles/client_sante/templates/setops-sante.sh.j2 new file mode 100644 index 0000000..5b10a2b --- /dev/null +++ b/roles/client_sante/templates/setops-sante.sh.j2 @@ -0,0 +1,76 @@ +#jinja2: comment_start_string:'{=#', comment_end_string:'#=}' +#!/bin/bash +# GENERE par Set-OPS (role client_sante). Ne pas editer a la main. +# +# (Le gabarit redefinit le delimiteur de commentaire Jinja : `${#tableau[@]}` contient +# la sequence `{#`, que Jinja lirait comme un debut de commentaire — et le rendu +# echouait sur « Missing end of comment tag ». Le shell a besoin de `${#...}` ; c'est +# donc a Jinja de s'ecarter.) +# +# CE NOEUD RAPPORTE SES UNITES EN ECHEC — parce que personne d'autre ne peut le voir. +# +# CE QUI A REVELE LE BESOIN (2026-09-09). `openipmi.service` echouait a CHAQUE demarrage +# sur les quatorze machines depuis le 2026-09-02. `systemctl --failed` rendait pourtant +# ZERO partout — non parce qu'elles allaient bien, mais parce qu'aucune n'avait REDEMARRE +# depuis. Six jours et vingt heures pour la premiere. Il a fallu qu'un humain redemarre +# une machine pour que le defaut existe aux yeux de quelqu'un. +# +# Un controle qui ne peut echouer qu'au demarrage ne mesure rien tant que rien ne demarre. +# +# POURQUOI UN RAPPORT PASSIF, ET PAS UN CONTROLE ACTIF. Un controle actif ne voit pas la +# machine MUETTE : si elle ne repond plus, la sonde echoue et on croit a un probleme de +# reseau. Ici c'est le noeud qui parle, avec un `ttl` : sans nouvelle, Icinga perime le +# service tout seul. Le silence alerte autant que l'echec — et le silence est precisement +# ce qui n'a alerte personne. +set -uo pipefail + +API="https://{{ client_sante_icinga_hote }}.{{ domaine_interne }}:5665" +TTL={{ client_sante_ttl_icinga }} +MOI="{{ inventory_hostname }}" +MOTDEPASSE="$(cat /etc/setops/icinga-api.pass)" + +rapporter() { # $1=code $2=texte + local charge reponse + charge=$(python3 -c 'import json,sys; print(json.dumps({ + "type": "Service", "service": sys.argv[1], "exit_status": int(sys.argv[2]), + "plugin_output": sys.argv[3], "ttl": int(sys.argv[4])}))' \ + "${MOI}!sante" "$1" "$2" "${TTL}") + reponse=$(curl -sS --cacert "{{ client_sante_ca_verification }}" --max-time 20 \ + -u "{{ client_sante_icinga_utilisateur }}:${MOTDEPASSE}" \ + -H 'Accept: application/json' -H 'Content-Type: application/json' \ + -X POST "${API}/v1/actions/process-check-result" -d "${charge}" 2>&1) + if ! printf '%s' "${reponse}" | grep -q '"code": *200'; then + echo "ECHEC du rapport Icinga : ${reponse}" >&2 + exit 1 + fi +} + +# `--plain` et `--no-legend` : la sortie doit etre analysable, pas jolie. Sans eux, +# systemd insere une puce « ● » en tete de ligne, qui devient le premier champ. +mapfile -t echecs < <(systemctl list-units --state=failed --plain --no-legend 2>/dev/null \ + | awk '{print $1}') + +{% if client_sante_unites_tolerees %} +# UNITES TOLEREES, NOMMEES UNE PAR UNE. Jamais un motif large : un filtre qui cache une +# unite en cache d'autres, et on ne s'en apercoit que le jour ou l'on cherche pourquoi +# rien n'a alerte. +tolerees=({% for u in client_sante_unites_tolerees %}"{{ u }}" {% endfor %}) +restantes=() +for u in "${echecs[@]}"; do + garder=1 + for t in "${tolerees[@]}"; do [[ "$u" == "$t" ]] && garder=0 && break; done + [[ $garder -eq 1 ]] && restantes+=("$u") +done +echecs=("${restantes[@]}") +{% endif %} + +n=${#echecs[@]} +if [[ $n -eq 0 ]]; then + rapporter 0 "Aucune unite systemd en echec." + exit 0 +fi + +# CRITIQUE des la premiere, et non un seuil. Une unite en echec est soit un vrai +# probleme, soit du bruit qu'il faut retirer : dans les deux cas il faut agir. Un seuil +# ferait vivre le bruit indefiniment, et c'est exactement ce qu'on vient de corriger. +rapporter 2 "$n unite(s) systemd en echec : ${echecs[*]}" diff --git a/roles/client_sante/templates/setops-sante.timer.j2 b/roles/client_sante/templates/setops-sante.timer.j2 new file mode 100644 index 0000000..b53d7f4 --- /dev/null +++ b/roles/client_sante/templates/setops-sante.timer.j2 @@ -0,0 +1,16 @@ +# GENERE par Set-OPS (role client_sante). +[Unit] +Description=Rapport de sante du noeud (unites en echec) + +[Timer] +# AU DEMARRAGE AUSSI, et c'est le point : les echecs de ce type naissent au boot. +# Attendre le premier quart d'heure laisserait une fenetre pendant laquelle la machine +# est en panne et le tableau au vert. +OnBootSec=2min +OnUnitActiveSec={{ client_sante_periode }} +# Sans dispersion, toute la flotte frappe l'API a la meme seconde. +RandomizedDelaySec=60 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/roles/serveur_icinga/defaults/main.yml b/roles/serveur_icinga/defaults/main.yml index fdf33f0..3c9e959 100644 --- a/roles/serveur_icinga/defaults/main.yml +++ b/roles/serveur_icinga/defaults/main.yml @@ -71,6 +71,28 @@ serveur_icinga_sauvegarde_attendue: >- | select('defined') | flatten | unique | list) | intersect(groups['client_backup'] | default([])) }} +# LES NOEUDS DONT ON RAPPORTE LA SANTE : tous ceux du plan. +# +# `serveur_debian` est le socle — tout hote deploye le porte. Il n'y a donc pas de liste a +# tenir : un noeud qui entre dans l'ecosysteme entre dans la supervision, et un noeud +# rase en sort. Une liste ecrite a la main aurait ce defaut precis : elle ne bouge que +# quand quelqu'un y pense. +serveur_icinga_sante_attendue: "{{ groups['serveur_debian'] | default([]) }}" + +# LES HOTES A DEFINIR, UNE SEULE FOIS — l'union de ce que les controles attachent. +# +# Icinga REFUSE un `object Host` en double : deux fichiers de controle qui definissent le +# meme hote font rejeter TOUTE la configuration, donc AUCUNE supervision. Les hotes vivent +# donc dans `setops-hotes.conf`, et les fichiers de controle n'attachent que des services. +serveur_icinga_hotes_supervises: >- + {{ (serveur_icinga_sante_attendue + + serveur_icinga_sauvegarde_attendue + + ([serveur_icinga_hote_sauvegarde] if serveur_icinga_hote_sauvegarde else [])) + | unique | list }} + +serveur_icinga_hotes_conf: "/etc/icinga2/conf.d/setops-hotes.conf" +serveur_icinga_sante_conf: "/etc/icinga2/conf.d/setops-sante.conf" + # A QUI LA SUPERVISION PARLE — vide = personne, et on le DIT. # # Icinga livre son exemple avec `root@localhost`. Une supervision qui voit tout et diff --git a/roles/serveur_icinga/tasks/main.yml b/roles/serveur_icinga/tasks/main.yml index e9c597e..501e599 100644 --- a/roles/serveur_icinga/tasks/main.yml +++ b/roles/serveur_icinga/tasks/main.yml @@ -182,6 +182,18 @@ no_log: true notify: Redemarrer icinga2 +# LES HOTES D'ABORD : les fichiers de service s'y attachent, et Icinga refuse un service +# dont l'hote n'existe pas. L'ordre dans `conf.d` n'est pas garanti par le nom, mais +# Icinga charge tout le repertoire avant de resoudre — l'ordre de deploiement suffit. +- name: Deployer les hotes supervises (definis une seule fois) + ansible.builtin.template: + src: setops-hotes.conf.j2 + dest: "{{ serveur_icinga_hotes_conf }}" + owner: root + group: nagios + mode: "0640" + notify: Redemarrer icinga2 + - name: Deployer les objets de supervision des sauvegardes ansible.builtin.template: src: setops-sauvegardes.conf.j2 @@ -191,6 +203,15 @@ mode: "0640" notify: Redemarrer icinga2 +- name: Deployer les objets de supervision de la sante des noeuds + ansible.builtin.template: + src: setops-sante.conf.j2 + dest: "{{ serveur_icinga_sante_conf }}" + owner: root + group: nagios + mode: "0640" + notify: Redemarrer icinga2 + - name: Valider la configuration Icinga 2 avant de la rendre vivante ansible.builtin.command: cmd: icinga2 daemon -C diff --git a/roles/serveur_icinga/templates/setops-api-users.conf.j2 b/roles/serveur_icinga/templates/setops-api-users.conf.j2 index 9388c52..2a26ca3 100644 --- a/roles/serveur_icinga/templates/setops-api-users.conf.j2 +++ b/roles/serveur_icinga/templates/setops-api-users.conf.j2 @@ -1,7 +1,14 @@ /* * Gere par Set-OPS (role serveur_icinga). Ne pas editer a la main. * - * Compte d'API dedie au DEPOT de sauvegarde, pour deposer ses resultats passifs. + * Compte d'API par lequel un NOEUD depose ses resultats passifs. + * + * IL S'APPELLE ENCORE `setops-depot` (2026-09-09) : il servait d'abord au seul rapport de + * sauvegarde. Il porte desormais aussi le rapport de SANTE — un second compte serait plus + * pur, un secret par usage, mais exigerait une clef de voute de plus dans chaque + * ecosysteme, donc un geste manuel a chaque nouvel ecosysteme, pour une portee identique. + * Le renommer churnerait les voutes sans rien gagner. Le filtre ci-dessous reste etroit : + * trois noms de service, et aucun autre pouvoir. * Portee minimale : uniquement `actions/process-check-result`, et uniquement sur les * services de sauvegarde. Ce compte ne peut ni lire la configuration, ni agir ailleurs. */ @@ -29,7 +36,7 @@ object ApiUser "{{ serveur_icinga_api_utilisateur }}" { La portee reste etroite : ce compte ne peut poser un resultat que sur un service de sauvegarde, jamais ailleurs. #} - filter = {{ '{{' }} match("sauvegarde: *", service.name) || service.name == "sauvegarde" {{ '}}' }} + filter = {{ '{{' }} match("sauvegarde: *", service.name) || service.name == "sauvegarde" || service.name == "sante" {{ '}}' }} } ] } diff --git a/roles/serveur_icinga/templates/setops-hotes.conf.j2 b/roles/serveur_icinga/templates/setops-hotes.conf.j2 new file mode 100644 index 0000000..87203c1 --- /dev/null +++ b/roles/serveur_icinga/templates/setops-hotes.conf.j2 @@ -0,0 +1,21 @@ +/* + * Gere par Set-OPS (role serveur_icinga). Ne pas editer a la main. + * + * LES HOTES, DEFINIS UNE SEULE FOIS. + * + * Ils vivaient dans `setops-sauvegardes.conf` — le seul fichier qui en avait besoin. Des + * qu'un second controle est arrive (la sante des noeuds, 2026-09-09), le meme `object + * Host` se serait retrouve dans deux fichiers, et Icinga REFUSE un objet en double : la + * configuration entiere aurait ete rejetee, donc AUCUNE supervision. + * + * Les fichiers de controle n'attachent plus que des SERVICES. Un controle de plus + * n'ajoute plus un hote de plus. + */ + +{% for noeud in serveur_icinga_hotes_supervises | sort %} +object Host "{{ noeud }}" { + check_command = "hostalive" + address = "{{ noeud }}.{{ domaine_interne }}" + vars.role = "{{ 'depot de sauvegarde' if noeud == serveur_icinga_hote_sauvegarde else 'noeud de l ecosysteme' }}" +} +{% endfor %} diff --git a/roles/serveur_icinga/templates/setops-sante.conf.j2 b/roles/serveur_icinga/templates/setops-sante.conf.j2 new file mode 100644 index 0000000..46bd866 --- /dev/null +++ b/roles/serveur_icinga/templates/setops-sante.conf.j2 @@ -0,0 +1,31 @@ +/* + * Gere par Set-OPS (role serveur_icinga). Ne pas editer a la main. + * + * SANTE DES NOEUDS : les unites systemd en echec. + * + * NE POSAIT PROBLEME QU'AU DEMARRAGE, ET C'EST POURQUOI PERSONNE NE L'A VU. + * `openipmi.service` echouait a chaque demarrage sur les quatorze machines depuis le + * 2026-09-02. `systemctl --failed` rendait ZERO partout — non parce qu'elles allaient + * bien, mais parce qu'aucune n'avait REDEMARRE depuis. Il a fallu qu'un humain redemarre + * une machine, le 2026-09-09, pour que le defaut existe aux yeux de quelqu'un. + * + * PASSIF, ET AVEC UNE DUREE DE VIE. Un controle ACTIF ne voit pas la machine muette : si + * elle ne repond plus, la sonde echoue et on met ca sur le compte du reseau. Ici c'est le + * noeud qui parle, et le `ttl` de son envoi fait la fraicheur — sans nouvelle, Icinga + * perime le service tout seul. Le silence alerte autant que l'echec, et le silence est + * precisement ce qui n'a alerte personne. + * + * Les HOTES sont definis dans `setops-hotes.conf` — ici, rien que des services. + */ + +{% for noeud in serveur_icinga_sante_attendue | sort %} +object Service "sante" { + host_name = "{{ noeud }}" + check_command = "setops-passif" + enable_active_checks = false + enable_passive_checks = true + volatile = false + max_check_attempts = 1 + vars.setops_source = "{{ noeud }}" +} +{% endfor %} diff --git a/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 b/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 index 15c6015..8985451 100644 --- a/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 +++ b/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 @@ -6,6 +6,10 @@ * mois (2026-07-03 -> 2026-08-11). Il porte sur la VERITE DE TERRAIN, cote depot : * l'instantane du noeud existe-t-il, est-il RECENT, et n'est-il pas VIDE. * + * Les HOTES sont definis ailleurs (`setops-hotes.conf`) depuis le 2026-09-09 : deux + * fichiers qui definissent le meme `object Host` font rejeter TOUTE la configuration. + * Ce fichier n'attache que des services. + * * Les resultats sont POUSSES par `backup-01`, qui est le seul a pouvoir lire ses depots. * Le `ttl` porte dans chaque envoi fait la fraicheur : sans nouvelle, Icinga bascule tout * seul en « expire ». C'est le SILENCE qui doit alerter, pas seulement l'echec — le @@ -22,12 +26,6 @@ object CheckCommand "setops-passif" { L'ECOSYSTEME A SON PROPRE DEPOT : c'est LUI qui rapporte, parce qu'il est le seul a voir ce qui est reellement arrive. Les services s'attachent donc a son hote. #} -object Host "{{ serveur_icinga_hote_sauvegarde }}" { - check_command = "hostalive" - address = "{{ serveur_icinga_hote_sauvegarde }}.{{ domaine_interne }}" - vars.role = "depot de sauvegarde" -} - {% for noeud in serveur_icinga_sauvegarde_attendue | sort %} object Service "sauvegarde: {{ noeud }}" { host_name = "{{ serveur_icinga_hote_sauvegarde }}" @@ -54,12 +52,6 @@ object Service "sauvegarde: {{ noeud }}" { donnerait « idm-01 / sauvegarde: idm-01 ». #} {% for noeud in serveur_icinga_sauvegarde_attendue | sort %} -object Host "{{ noeud }}" { - check_command = "hostalive" - address = "{{ noeud }}.{{ domaine_interne }}" - vars.role = "detenteur d etat" -} - object Service "sauvegarde" { host_name = "{{ noeud }}" check_command = "setops-passif"