--- # LE NOEUD RAPPORTE SES UNITES EN ECHEC. Voir le gabarit du script pour le pourquoi. # # CE ROLE N'A PAS A SE DEMANDER S'IL Y A UNE SUPERVISION — IL EN A UNE (2026-09-09). # # Il portait une branche « aucun serveur_icinga : rien a poser », et un `when` sur tout le # bloc. Ni l'une ni l'autre ne pouvait s'executer : `client_sante` est une INTEGRATION # UNIVERSELLE, et `instancier` ne pose une integration universelle que si son serveur # existe dans l'ecosysteme. Eprouve sur le modele public — sans Icinga au plan, le groupe # `client_sante` est ABSENT de l'inventaire genere ; avec, les quatre noeuds y entrent. # 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. 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: 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" # LES GREFFONS STANDARD, POSES PAR LE PORTEUR ET PAS PAR CHAQUE ROLE (2026-09-14). # # `docs/supervision-conception.md` dit qu'un greffon Nagios EST une sonde valide, « sans # la moindre colle », et compte sur les 54 que fournit `monitoring-plugins`. Mesure du # 2026-09-14 sur la flotte : **aucun n'etait installe**. Le document decrivait une # possibilite qui n'existait pas — et chaque role qui avait besoin d'un controle HTTP # n'avait d'autre choix que d'ecrire du shell, exactement ce que la doctrine refuse. # # ICI, ET PAS DANS CHAQUE ROLE. C'est l'affaire du PORTEUR de pouvoir executer des # sondes ; les poser role par role en installerait autant de copies de la decision, et # laisserait sans greffon les machines dont aucun role n'en reclame — alors qu'elles en # ont tout autant besoin le jour ou on leur en ajoute un. # # 1,2 Mo par machine, pour `check_http`, `check_tcp`, `check_disk`, `check_procs`, # `check_ntp_time`, `check_file_age`… et vingt ans de cas limites deja rencontres par # d'autres. - name: Installer les outils du rapport passif et les greffons standard ansible.builtin.apt: name: "{{ client_sante_paquets }}" state: present # --- UN ROLE QU'ON RETIRE DOIT POUVOIR DEFAIRE CE QU'IL A FAIT (2026-09-10) ----------- # # Chaque role depose SES sondes dans `/usr/local/lib/setops/sondes/`. Quand un ecosysteme # cesse de declarer un service — `serveur_artefacts` retire du plan le 2026-09-10, parce # que le SITE fournit les paquets — l'hote quitte le groupe, le role ne tourne plus, et # PERSONNE ne retire ses sondes. Le porteur continue alors de les executer et de pousser # un resultat pour un service qu'Icinga ne definit plus : # # ECHEC du rapport Icinga pour « cache-apt » : {"error":404,"status":"No objects found."} # # Ce n'est pas qu'un bruit : une sonde orpheline consomme le delai du porteur a chaque # passage, et son echec ressemble a celui d'une vraie sonde en panne. # # CE QUI EST ATTENDU SE DERIVE, comme chez `serveur_icinga` : les `meta/supervision.yml` # des roles, croises avec les GROUPES de cet hote. On ne retire donc jamais la sonde d'un # role qui s'applique — seulement celles dont plus aucun groupe ne repond. - name: Relever les sondes que les roles declarent ansible.builtin.find: paths: "{{ role_path }}/.." patterns: supervision.yml recurse: true depth: 3 delegate_to: localhost become: false run_once: true register: client_sante_metas - name: Lire chaque declaration de supervision ansible.builtin.slurp: src: "{{ item.path }}" delegate_to: localhost become: false run_once: true loop: "{{ client_sante_metas.files }}" loop_control: label: "{{ item.path | dirname | dirname | basename }}" register: client_sante_declarations - name: Assembler le registre des sondes (role -> sondes) ansible.builtin.set_fact: client_sante_registre: >- {{ dict(client_sante_declarations.results | map(attribute='item.path') | map('dirname') | map('dirname') | map('basename') | zip(client_sante_declarations.results | map(attribute='content') | map('b64decode') | map('from_yaml') | map(attribute='sondes'))) }} run_once: true - name: Deriver les sondes attendues sur CET hote ansible.builtin.set_fact: client_sante_attendues: >- {{ group_names | map('extract', client_sante_registre) | select('defined') | select('sequence') | flatten | map(attribute='nom') | list }} - name: Relever les sondes reellement posees ansible.builtin.find: paths: "{{ client_sante_repertoire_sondes | default('/usr/local/lib/setops/sondes') }}" patterns: "*.sh" register: client_sante_posees - name: Retirer les sondes qu'aucun groupe de cet hote ne declare plus ansible.builtin.file: path: "{{ item.path }}" state: absent loop: "{{ client_sante_posees.files }}" loop_control: label: "{{ item.path | basename }}" when: (item.path | basename | regex_replace('\.sh$', '')) not in client_sante_attendues - 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