client_sante : retrait d une garde qui ne pouvait pas se declencher
Le role portait une branche « aucun serveur_icinga : rien a poser » et un when sur tout son bloc. Ni l une ni l autre ne pouvait s executer. Eprouve sur le modele public, dans une copie jetable : instancier ne pose une integration UNIVERSELLE que si son serveur existe dans l ecosysteme. Sans Icinga au plan, le groupe client_sante est ABSENT de l inventaire genere — comme client_metrique et client_journal le sont deja. Le role n est donc jamais appele sans destinataire. Une garde qui ne peut pas se declencher n est pas une garde : elle rassure sans rien tenir, et elle coute le jour ou l on cherche pourquoi rien n a alerte. Ce qui la remplace se declenche vraiment, et les deux cotes sont eprouves : hote vide -> FAILED, secret vide -> FAILED, et l echec dit lequel des deux manque. Le bloc, prive de sa condition, est aplati : douze taches a plat. Rejoue sur les deux flottes : zero changement sur zero hote. make prouver : CONFORME, 63 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
018d55ec6c
commit
69b84f4e2c
4 changed files with 139 additions and 102 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -1,5 +1,31 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-09-09 (5) — Retrait d'une garde qui ne pouvait pas se declencher
|
||||||
|
|
||||||
|
`client_sante` portait une branche « aucun `serveur_icinga` : rien a poser », et un `when`
|
||||||
|
sur tout son bloc. **Ni l'une ni l'autre ne pouvait s'executer.**
|
||||||
|
|
||||||
|
Eprouve sur le modele public, dans une copie jetable : `instancier` ne pose une
|
||||||
|
integration UNIVERSELLE que si son serveur existe dans l'ecosysteme. Sans Icinga au plan,
|
||||||
|
le groupe `client_sante` est **absent** de l'inventaire genere — exactement comme
|
||||||
|
`client_metrique` et `client_journal` le sont deja. Le role n'est donc jamais appele sans
|
||||||
|
destinataire, et sa branche defensive etait du decor.
|
||||||
|
|
||||||
|
*Une garde qui ne peut pas se declencher n'est pas une garde : elle rassure sans rien
|
||||||
|
tenir.* Et elle coute deux fois — a la lecture, puis le jour ou l'on cherche pourquoi rien
|
||||||
|
n'a alerte.
|
||||||
|
|
||||||
|
Ce qui la remplace se declenche vraiment, et les deux cotes sont eprouves :
|
||||||
|
|
||||||
|
-e client_sante_icinga_hote='' -> FAILED (assertion sur l'hote)
|
||||||
|
-e client_sante_icinga_motdepasse='' -> FAILED (assertion sur le secret)
|
||||||
|
|
||||||
|
L'echec dit LEQUEL des deux manque. Le bloc, prive de sa condition, est aplati : douze
|
||||||
|
taches a plat au lieu de onze indentees sous un `when` toujours vrai.
|
||||||
|
|
||||||
|
Rejoue sur les deux flottes : **zero changement sur zero hote**. `make prouver` :
|
||||||
|
CONFORME, 63 OK, 0 echec, 0 saute.
|
||||||
|
|
||||||
## 2026-09-09 (4) — `systemctl --failed` entre dans la supervision
|
## 2026-09-09 (4) — `systemctl --failed` entre dans la supervision
|
||||||
|
|
||||||
**63 preuves. `make prouver` : CONFORME, 63 OK, 0 echec, 0 saute.**
|
**63 preuves. `make prouver` : CONFORME, 63 OK, 0 echec, 0 saute.**
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
| 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. |
|
| 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. |
|
| 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). |
|
| 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). |
|
||||||
| 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 | 43 document(s) declarent leur lecteur (35 genere(s) exempte(s)). |
|
| 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). |
|
| 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). |
|
||||||
|
|
|
||||||
|
|
@ -37,5 +37,14 @@ où l'on cherche pourquoi rien n'a alerté. Le défaut est la liste **vide** —
|
||||||
|
|
||||||
## Ce dont il dépend
|
## 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
|
Un hôte `serveur_icinga` dans l'écosystème, le secret `vault_icinga_api_depot`, et le flux
|
||||||
`vault_icinga_api_depot`, et le flux sortant TCP 5665 vers lui.
|
TCP 5665 vers lui — **déclaré des deux côtés**. Le côté serveur
|
||||||
|
(`roles/serveur_icinga/meta/flux.yml`) nomme le pair `serveur_debian`, pas `client_sante` :
|
||||||
|
la frontière ne résout que les rôles que les machines *portent au plan*, et une intégration
|
||||||
|
universelle n'y figure pas puisqu'elle est dérivée. L'oubli s'est payé le 2026-09-09 —
|
||||||
|
déploiement à 7/7, et cinq rapports sur sept en `TimeoutError`.
|
||||||
|
|
||||||
|
Le rôle n'a **pas** de branche « et s'il n'y a pas de supervision ». Il n'en a pas besoin :
|
||||||
|
`instancier` ne pose une intégration universelle que si son serveur existe, donc le groupe
|
||||||
|
`client_sante` est absent d'un écosystème sans Icinga. À la place, un `assert` qui *peut*
|
||||||
|
échouer, et qui dit lequel des deux manque.
|
||||||
|
|
|
||||||
|
|
@ -1,113 +1,115 @@
|
||||||
---
|
---
|
||||||
# LE NOEUD RAPPORTE SES UNITES EN ECHEC. Voir le gabarit du script pour le pourquoi.
|
# 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
|
# CE ROLE N'A PAS A SE DEMANDER S'IL Y A UNE SUPERVISION — IL EN A UNE (2026-09-09).
|
||||||
# n'irait nulle part, et un timer qui echoue chaque quart d'heure apprend a ignorer
|
#
|
||||||
# le rouge. Meme regle que `client_backup`.
|
# Il portait une branche « aucun serveur_icinga : rien a poser », et un `when` sur tout le
|
||||||
- name: Aucune supervision dans cet écosystème — rien à poser
|
# bloc. Ni l'une ni l'autre ne pouvait s'executer : `client_sante` est une INTEGRATION
|
||||||
ansible.builtin.debug:
|
# UNIVERSELLE, et `instancier` ne pose une integration universelle que si son serveur
|
||||||
msg: >-
|
# existe dans l'ecosysteme. Eprouve sur le modele public — sans Icinga au plan, le groupe
|
||||||
Aucun hôte `serveur_icinga` : le rapport de santé n'est pas posé. Ce n'est pas une
|
# `client_sante` est ABSENT de l'inventaire genere ; avec, les quatre noeuds y entrent.
|
||||||
erreur — c'est un écosystème sans supervision, et le dire vaut mieux que d'installer
|
# Le role n'est donc jamais appele sans destinataire.
|
||||||
un minuteur qui échouerait dans le vide.
|
#
|
||||||
when: not client_sante_icinga_hote
|
# Une garde qui ne peut pas se declencher n'est pas une garde : elle rassure sans rien
|
||||||
|
# tenir. Ce qui la remplace se declenche vraiment — si ce role s'execute, un destinataire
|
||||||
|
# ET son secret sont exiges, et l'echec dit LEQUEL des deux manque.
|
||||||
|
- name: Exiger un destinataire et de quoi lui parler
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- client_sante_icinga_hote | length > 0
|
||||||
|
- client_sante_icinga_motdepasse | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Rapport de santé impossible :
|
||||||
|
{{ "aucun hôte `serveur_icinga` dans l'inventaire"
|
||||||
|
if not client_sante_icinga_hote
|
||||||
|
else "`vault_icinga_api_depot` absent de la voûte" }}.
|
||||||
|
Le nœud verrait ses unités en échec sans pouvoir le dire.
|
||||||
|
|
||||||
- name: Poser le rapport de santé
|
- name: Assurer le répertoire des secrets Set-OPS
|
||||||
when: client_sante_icinga_hote | length > 0
|
ansible.builtin.file:
|
||||||
block:
|
path: /etc/setops
|
||||||
- name: Exiger de quoi rapporter à Icinga
|
state: directory
|
||||||
ansible.builtin.assert:
|
owner: root
|
||||||
that:
|
group: root
|
||||||
- client_sante_icinga_motdepasse | length > 0
|
mode: "0755"
|
||||||
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
|
- name: Déposer le mot de passe d'API Icinga
|
||||||
ansible.builtin.file:
|
ansible.builtin.copy:
|
||||||
path: /etc/setops
|
content: "{{ client_sante_icinga_motdepasse }}\n"
|
||||||
state: directory
|
dest: /etc/setops/icinga-api.pass
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0755"
|
mode: "0600"
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: Déposer le mot de passe d'API Icinga
|
# L'AC d'ICINGA, PAS CELLE DE step-ca : Icinga refuse de servir un certificat qu'il
|
||||||
ansible.builtin.copy:
|
# n'a pas emis (il renouvelle tout ce qui expire sous 30 jours, nos certificats vivent
|
||||||
content: "{{ client_sante_icinga_motdepasse }}\n"
|
# 24 h). Domaine de confiance ferme, pair authentifie malgre tout.
|
||||||
dest: /etc/setops/icinga-api.pass
|
- name: L'AC d'Icinga est-elle déjà disponible ?
|
||||||
owner: root
|
ansible.builtin.stat:
|
||||||
group: root
|
path: "{{ client_sante_icinga_ca_source }}"
|
||||||
mode: "0600"
|
delegate_to: "{{ client_sante_icinga_hote }}"
|
||||||
no_log: true
|
register: client_sante_ca_presente
|
||||||
|
|
||||||
# L'AC d'ICINGA, PAS CELLE DE step-ca : Icinga refuse de servir un certificat qu'il
|
- name: Récupérer l'AC d'Icinga depuis l'hôte de supervision
|
||||||
# n'a pas emis (il renouvelle tout ce qui expire sous 30 jours, nos certificats vivent
|
when: client_sante_ca_presente.stat.exists
|
||||||
# 24 h). Domaine de confiance ferme, pair authentifie malgre tout.
|
ansible.builtin.slurp:
|
||||||
- name: L'AC d'Icinga est-elle déjà disponible ?
|
src: "{{ client_sante_icinga_ca_source }}"
|
||||||
ansible.builtin.stat:
|
delegate_to: "{{ client_sante_icinga_hote }}"
|
||||||
path: "{{ client_sante_icinga_ca_source }}"
|
register: client_sante_ca_icinga
|
||||||
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
|
- name: Déposer l'AC d'Icinga pour la vérification du pair
|
||||||
when: client_sante_ca_presente.stat.exists
|
when: client_sante_ca_presente.stat.exists
|
||||||
ansible.builtin.slurp:
|
ansible.builtin.copy:
|
||||||
src: "{{ client_sante_icinga_ca_source }}"
|
content: "{{ client_sante_ca_icinga.content | b64decode }}"
|
||||||
delegate_to: "{{ client_sante_icinga_hote }}"
|
dest: "{{ client_sante_ca_verification }}"
|
||||||
register: client_sante_ca_icinga
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
- name: Déposer l'AC d'Icinga pour la vérification du pair
|
- name: Installer curl pour le rapport passif
|
||||||
when: client_sante_ca_presente.stat.exists
|
ansible.builtin.apt:
|
||||||
ansible.builtin.copy:
|
name: curl
|
||||||
content: "{{ client_sante_ca_icinga.content | b64decode }}"
|
state: present
|
||||||
dest: "{{ client_sante_ca_verification }}"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Installer curl pour le rapport passif
|
- name: Déployer le script de rapport de santé
|
||||||
ansible.builtin.apt:
|
ansible.builtin.template:
|
||||||
name: curl
|
src: setops-sante.sh.j2
|
||||||
state: present
|
dest: /usr/local/sbin/setops-sante.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
||||||
- name: Déployer le script de rapport de santé
|
- name: Déployer l'unité systemd du rapport
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: setops-sante.sh.j2
|
src: setops-sante.service.j2
|
||||||
dest: /usr/local/sbin/setops-sante.sh
|
dest: /etc/systemd/system/setops-sante.service
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0750"
|
mode: "0644"
|
||||||
|
notify: Recharger systemd
|
||||||
|
|
||||||
- name: Déployer l'unité systemd du rapport
|
- name: Déployer le minuteur du rapport
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: setops-sante.service.j2
|
src: setops-sante.timer.j2
|
||||||
dest: /etc/systemd/system/setops-sante.service
|
dest: /etc/systemd/system/setops-sante.timer
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
notify: Recharger systemd
|
notify: Recharger systemd
|
||||||
|
|
||||||
- name: Déployer le minuteur du rapport
|
- name: Activer le rapport de santé
|
||||||
ansible.builtin.template:
|
ansible.builtin.systemd:
|
||||||
src: setops-sante.timer.j2
|
name: setops-sante.timer
|
||||||
dest: /etc/systemd/system/setops-sante.timer
|
enabled: true
|
||||||
owner: root
|
state: started
|
||||||
group: root
|
daemon_reload: true
|
||||||
mode: "0644"
|
|
||||||
notify: Recharger systemd
|
|
||||||
|
|
||||||
- name: Activer le rapport de santé
|
# PREMIER RAPPORT IMMEDIAT, et ce n'est pas une commodite : sans lui, le service
|
||||||
ansible.builtin.systemd:
|
# reste « en attente » jusqu'au premier declenchement, et un deploiement qui vient
|
||||||
name: setops-sante.timer
|
# d'installer la supervision laisserait un tableau vide qu'on prendrait pour un
|
||||||
enabled: true
|
# tableau sain.
|
||||||
state: started
|
- name: Rapporter tout de suite (ne pas laisser un tableau vide passer pour sain)
|
||||||
daemon_reload: true
|
ansible.builtin.command: /usr/local/sbin/setops-sante.sh
|
||||||
|
changed_when: false
|
||||||
# PREMIER RAPPORT IMMEDIAT, et ce n'est pas une commodite : sans lui, le service
|
failed_when: false
|
||||||
# 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
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue