Phase 5 : observabilité (réutilise le Prometheus/Grafana partagé)

- Pas de stack dédiée : une instance Prometheus/Grafana existe déjà sur
  ce serveur pour les 5 autres projets. En déployer une seconde aurait
  dupliqué de la RAM sur une machine à 3.7 Go partagée, sans bénéfice -
  même raisonnement que pour Caddy en Phase 2.
- Scrape job tripoli_edge (172.17.0.1:9000, même passerelle docker0 que
  Caddy) ajouté à la config Prometheus existante.
- Règles d'alerte : TripoliEdgeDown (up == 0), HostDiskAlmostFull
  (< 10% d'espace libre sur / - au niveau de la machine, pas seulement
  de ce projet, puisqu'il n'y a qu'un disque pour tout le monde).
- Sonde blackbox externe ajoutée pour https://tripoli.cloud/healthz.
- Dashboard Grafana versionné (uid tripoli-cloud-edge), importé via
  l'API (POST /api/dashboards/db) pour rester reproductible, pas
  construit à la main dans l'UI.
- Vérifié : tous les targets Prometheus up (dont tripoli_edge et la
  sonde tripoli.cloud), les deux règles d'alerte chargées et évaluées,
  les requêtes du dashboard retournent de vraies valeurs.
- Limite assumée : les alertes se déclenchent dans Prometheus mais ne
  sont pas encore routées par email (pas d'Alertmanager, pas de policy
  de notification Grafana dédiée) - documenté comme prochain pas, pas
  fait pour éviter de modifier la politique de notification globale de
  l'instance partagée sans plus de tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-09-16 23:09:32 +02:00
parent 991f4dd04f
commit db306433d5
5 changed files with 199 additions and 1 deletions

View file

@ -32,7 +32,7 @@ tenté, non simulé.
| `tripoli-edge` (Rust, Phase 2) | Fait et déployé en prod — `tripoli-edge/`, service systemd, derrière Caddy, vérifié via `https://tripoli.cloud/healthz` et `/metrics` |
| `tripoli-api` (Rust, Phase 3) | Fait et déployé en prod — `tripoli-api/`, auth par session (Argon2), upload/download, PostgreSQL dédié (Docker), vérifié via `https://tripoli.cloud/api/...` |
| `tripoli-netctl` (Rust, Phase 4) | Fait — `tripoli-netctl/`, labo pédagogique (netns + veth via `rtnetlink`), testé sur le serveur (création, ping traversant la veth, destruction propre), **volontairement pas branché sur le trafic de prod** |
| Observabilité dédiée (Phase 5) | Pas commencé (une stack Prometheus/Grafana existe déjà sur le serveur, partagée avec les autres projets) |
| Observabilité (Phase 5) | Fait — pas de stack dédiée (réutilise le Prometheus/Grafana partagé existant), scrape tripoli-edge + alertes (down, disque) + dashboard versionné (`monitoring/`), vérifié via l'API Prometheus/Grafana |
| Bird/BGP (Phase 6, bonus) | Pas commencé |
## Dépôt

52
monitoring/README.md Normal file
View file

@ -0,0 +1,52 @@
# Observabilité
Pas de Prometheus/Grafana dédiés à Tripoli Cloud : une instance de
chacun existe déjà sur ce serveur, partagée avec les 5 autres projets
qui y tournent. En déployer une seconde aurait dupliqué de la RAM sur
une machine à 3.7 Go déjà partagée, pour un bénéfice nul - même
raisonnement que pour Caddy en Phase 2.
## Ce qui a été ajouté à l'instance existante
- **Scrape job `tripoli_edge`** (`prometheus-scrape-snippet.yml`, appliqué
à `/opt/monitoring/prometheus/prometheus.yml` sur le serveur) :
`172.17.0.1:9000/metrics` - même passerelle docker0 que Caddy utilise
pour joindre tripoli-edge.
- **Règles d'alerte** (`tripoli-cloud-alerts.yml`, montée dans le
conteneur Prometheus) :
- `TripoliEdgeDown` : `up{job="tripoli_edge"} == 0` pendant 2 minutes.
- `HostDiskAlmostFull` : moins de 10% d'espace libre sur `/` pendant
10 minutes - une alerte au niveau de la machine, pas seulement de
Tripoli Cloud, puisqu'il n'y a qu'un seul disque pour tous les
projets hébergés.
- **Sonde blackbox externe** : `https://tripoli.cloud/healthz` ajoutée
à la liste existante du job `blackbox_http` (vérifie que le site
répond réellement depuis l'extérieur, pas seulement que Prometheus
arrive à le scraper en interne).
- **Dashboard Grafana** (`grafana-dashboard-tripoli-edge.json`,
`uid: tripoli-cloud-edge`) : état up/down, requêtes en cours, latence
moyenne, requêtes par classe de statut, et l'espace disque de l'hôte.
Importé via l'API Grafana (`POST /api/dashboards/db`), pas par copier-
coller dans l'UI - c'est ce qui permet de le garder versionné ici et
de le réimporter à l'identique si besoin.
## Ce qui n'a pas été fait
Les règles d'alerte **existent et se déclenchent** dans Prometheus (vérifiable
via `/api/v1/rules`), mais rien ne les **notifie** encore par email - pas
d'Alertmanager déployé, et pas de policy de notification Grafana
configurée pour ces alertes spécifiquement. Grafana a déjà un SMTP
fonctionnel (utilisé par d'autres projets sur cette instance) ; y
brancher les alertes de Tripoli Cloud est le prochain pas naturel, pas
fait dans cette passe pour éviter de modifier la politique de
notification globale de l'instance partagée sans plus de tests.
## Vérifié
```
$ curl .../api/v1/targets -> tripoli_edge: up, blackbox tripoli.cloud/healthz: up
$ curl .../api/v1/rules -> groupe "tripoli-cloud" : TripoliEdgeDown, HostDiskAlmostFull
$ curl .../api/v1/query?query=up{job="tripoli_edge"} -> 1
```
Dashboard accessible sur `https://grafana.belkacemi.me/d/tripoli-cloud-edge/`.

View file

@ -0,0 +1,102 @@
{
"title": "Tripoli Cloud - tripoli-edge",
"uid": "tripoli-cloud-edge",
"tags": ["tripoli-cloud"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"time": { "from": "now-6h", "to": "now" },
"refresh": "30s",
"panels": [
{
"id": 1,
"title": "tripoli-edge up",
"type": "stat",
"gridPos": { "h": 4, "w": 4, "x": 0, "y": 0 },
"datasource": { "type": "prometheus", "uid": "cfxoej9jr3ncwb" },
"targets": [
{ "expr": "up{job=\"tripoli_edge\"}", "refId": "A" }
],
"fieldConfig": {
"defaults": {
"mappings": [
{ "type": "value", "options": { "0": { "text": "DOWN", "color": "red" }, "1": { "text": "UP", "color": "green" } } }
],
"thresholds": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] }
}
}
},
{
"id": 2,
"title": "Requêtes en cours (in-flight)",
"type": "stat",
"gridPos": { "h": 4, "w": 4, "x": 4, "y": 0 },
"datasource": { "type": "prometheus", "uid": "cfxoej9jr3ncwb" },
"targets": [
{ "expr": "tripoli_edge_in_flight_requests", "refId": "A" }
]
},
{
"id": 3,
"title": "Latence moyenne (ms)",
"type": "stat",
"gridPos": { "h": 4, "w": 4, "x": 8, "y": 0 },
"datasource": { "type": "prometheus", "uid": "cfxoej9jr3ncwb" },
"targets": [
{ "expr": "tripoli_edge_request_duration_ms_avg", "refId": "A" }
]
},
{
"id": 4,
"title": "Espace disque libre sur / (hôte partagé)",
"type": "gauge",
"gridPos": { "h": 4, "w": 4, "x": 12, "y": 0 },
"datasource": { "type": "prometheus", "uid": "cfxoej9jr3ncwb" },
"targets": [
{
"expr": "100 * node_filesystem_avail_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"} / node_filesystem_size_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "red", "value": null },
{ "color": "orange", "value": 10 },
{ "color": "green", "value": 25 }
]
}
}
}
},
{
"id": 5,
"title": "Requêtes par classe de statut (par seconde)",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
"datasource": { "type": "prometheus", "uid": "cfxoej9jr3ncwb" },
"targets": [
{
"expr": "rate(tripoli_edge_responses_total[5m])",
"legendFormat": "{{class}}",
"refId": "A"
}
]
},
{
"id": 6,
"title": "Requêtes totales (par seconde)",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
"datasource": { "type": "prometheus", "uid": "cfxoej9jr3ncwb" },
"targets": [
{ "expr": "rate(tripoli_edge_requests_total[5m])", "legendFormat": "req/s", "refId": "A" }
]
}
]
}

View file

@ -0,0 +1,19 @@
# Bloc ajouté à /opt/monitoring/prometheus/prometheus.yml sur le serveur
# (fichier partagé avec les 5 autres projets surveillés par cette même
# instance - donc pas commité dans son intégralité ici, même logique
# que le Caddyfile de tripoli-edge).
#
# rule_files pointe vers tripoli-cloud-alerts.yml (ce dossier), monté
# dans le conteneur Prometheus via un ajout au docker-compose.yml
# existant.
rule_files:
- /etc/prometheus/tripoli-cloud-alerts.yml
scrape_configs:
- job_name: tripoli_edge
static_configs:
- targets: ['172.17.0.1:9000'] # passerelle docker0, même pattern que Caddy -> tripoli-edge
# Ajouté à la liste existante du job blackbox_http :
# - https://tripoli.cloud/healthz

View file

@ -0,0 +1,25 @@
groups:
- name: tripoli-cloud
rules:
- alert: TripoliEdgeDown
expr: up{job="tripoli_edge"} == 0
for: 2m
labels:
severity: critical
project: tripoli-cloud
annotations:
summary: "tripoli-edge est down"
description: "Prometheus n'arrive plus à scraper tripoli-edge (job tripoli_edge) depuis 2 minutes."
# Alerte au niveau de la machine, pas seulement de Tripoli Cloud :
# un seul disque, partagé avec 6 autres projets - un disque plein
# affecterait tout le monde, pas que ce projet.
- alert: HostDiskAlmostFull
expr: node_filesystem_avail_bytes{mountpoint="/",fstype!="tmpfs"} / node_filesystem_size_bytes{mountpoint="/",fstype!="tmpfs"} < 0.10
for: 10m
labels:
severity: warning
project: tripoli-cloud
annotations:
summary: "Disque du serveur presque plein"
description: "Moins de 10% d'espace libre sur / depuis 10 minutes."