ajoute la supervision : règles d'alerte Prometheus, relais ntfy, Uptime Kuma

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-07 21:20:01 +02:00
parent d21c6ef544
commit aa31b6112f
12 changed files with 432 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
supervision_fichier_webhook: /root/.alertes-webhook
supervision_port_relais: 9099
supervision_port_uptime_kuma: 3001
supervision_dossier_textfile: /var/lib/node_exporter/textfile

View file

@ -0,0 +1,10 @@
---
- name: recharger systemd
ansible.builtin.systemd:
daemon_reload: true
- name: redémarrer le relais alertmanager-ntfy
ansible.builtin.systemd:
name: alertmanager-ntfy
state: restarted
enabled: true

View file

@ -0,0 +1,100 @@
---
- name: vérifier la présence du fichier webhook (jamais son contenu)
ansible.builtin.stat:
path: "{{ supervision_fichier_webhook }}"
register: supervision_webhook_stat
- name: échouer clairement si le webhook est absent
ansible.builtin.fail:
msg: >
{{ supervision_fichier_webhook }} est absent. Adam doit le déposer
lui-même (chmod 600) avant de continuer : c'est un secret, Claude ne le
demande jamais en clair.
when: not supervision_webhook_stat.stat.exists
- name: vérifier les permissions du fichier webhook
ansible.builtin.fail:
msg: "{{ supervision_fichier_webhook }} ne devrait pas être lisible par d'autres utilisateurs (mode actuel {{ supervision_webhook_stat.stat.mode }})."
when:
- supervision_webhook_stat.stat.exists
- supervision_webhook_stat.stat.mode != '0600'
- name: créer le dossier des métriques textfile (node_exporter)
ansible.builtin.file:
path: "{{ supervision_dossier_textfile }}"
state: directory
owner: root
group: root
mode: "0755"
- name: créer le dossier de configuration Prometheus personnalisée
ansible.builtin.file:
path: /etc/kolla/config/prometheus
state: directory
owner: root
group: root
mode: "0750"
- name: déployer les règles d'alerte Prometheus
ansible.builtin.copy:
src: "{{ playbook_dir }}/../kolla/config/prometheus/alertes.rules"
dest: /etc/kolla/config/prometheus/alertes.rules
owner: root
group: root
mode: "0640"
register: supervision_regles_alerte
- name: déployer la configuration Alertmanager
ansible.builtin.copy:
src: "{{ playbook_dir }}/../kolla/config/prometheus/prometheus-alertmanager.yml"
dest: /etc/kolla/config/prometheus/prometheus-alertmanager.yml
owner: root
group: root
mode: "0640"
register: supervision_config_alertmanager
- name: déployer le relais Alertmanager -> ntfy
ansible.builtin.template:
src: alertmanager-ntfy.py.j2
dest: /usr/local/sbin/alertmanager-ntfy.py
owner: root
group: root
mode: "0700"
notify: redémarrer le relais alertmanager-ntfy
- name: déployer le service systemd du relais
ansible.builtin.template:
src: alertmanager-ntfy.service.j2
dest: /etc/systemd/system/alertmanager-ntfy.service
mode: "0644"
notify:
- recharger systemd
- redémarrer le relais alertmanager-ntfy
- name: appliquer les changements du relais
ansible.builtin.meta: flush_handlers
- name: vérifier que le relais tourne
ansible.builtin.systemd:
name: alertmanager-ntfy
state: started
enabled: true
- name: déployer Uptime Kuma
community.docker.docker_container:
name: uptime-kuma
image: louislam/uptime-kuma:1
state: started
restart_policy: unless-stopped
ports:
- "127.0.0.1:{{ supervision_port_uptime_kuma }}:3001"
volumes:
- uptime-kuma-data:/app/data
- name: afficher s'il faut relancer Kolla pour les nouvelles règles
ansible.builtin.debug:
msg: >
Config Prometheus/Alertmanager modifiée : lancer
`kolla-ansible reconfigure -i /etc/kolla/all-in-one --tags prometheus`
sur le serveur (voir docs/runbooks/supervision.md).
when: supervision_regles_alerte.changed or supervision_config_alertmanager.changed

View file

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Relais local : reçoit le webhook JSON d'Alertmanager, le transforme en
notification ntfy. Géré par Ansible (rôle supervision) — ne pas modifier à la
main. N'écoute que sur 127.0.0.1 : jamais exposé en dehors du serveur.
L'URL ntfy (avec le topic) est un secret : lue depuis {{ supervision_fichier_webhook }}
à chaque notification, jamais mise en cache, jamais journalisée.
"""
import http.server
import json
import urllib.request
FICHIER_WEBHOOK = "{{ supervision_fichier_webhook }}"
ADRESSE = ("127.0.0.1", {{ supervision_port_relais }})
def lire_url_ntfy():
with open(FICHIER_WEBHOOK, encoding="utf-8") as f:
return f.read().strip()
class Gestionnaire(http.server.BaseHTTPRequestHandler):
def do_POST(self):
longueur = int(self.headers.get("Content-Length", 0))
corps = self.rfile.read(longueur)
try:
charge = json.loads(corps)
except ValueError:
self.send_response(400)
self.end_headers()
return
try:
url_ntfy = lire_url_ntfy()
except OSError:
self.send_response(500)
self.end_headers()
return
for alerte in charge.get("alerts", []):
statut = alerte.get("status", "inconnu")
nom = alerte.get("labels", {}).get("alertname", "?")
resume = (
alerte.get("annotations", {}).get("description")
or alerte.get("annotations", {}).get("summary")
or nom
)
titre = f"[{statut.upper()}] {nom}"
requete = urllib.request.Request(
url_ntfy,
data=resume.encode("utf-8"),
method="POST",
headers={
"Title": titre,
"Priority": "high" if statut == "firing" else "default",
},
)
try:
urllib.request.urlopen(requete, timeout=5)
except Exception:
pass # ne bloque jamais Alertmanager ; vérifier manuellement en cas de doute
self.send_response(200)
self.end_headers()
def log_message(self, format, *args): # silencieux : rien de secret à tracer non plus
pass
if __name__ == "__main__":
serveur = http.server.HTTPServer(ADRESSE, Gestionnaire)
serveur.serve_forever()

View file

@ -0,0 +1,18 @@
[Unit]
Description=Relais Alertmanager -> ntfy (Syllabus)
# Géré par Ansible (rôle supervision) — ne pas modifier à la main.
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/sbin/alertmanager-ntfy.py
Restart=on-failure
# Durcissement minimal : ce service lit un secret (fichier webhook), pas de
# raison qu'il écrive où que ce soit ou touche au reste du système.
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
ReadOnlyPaths=/root/.alertes-webhook
[Install]
WantedBy=multi-user.target