Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
274 lines
9.4 KiB
Markdown
274 lines
9.4 KiB
Markdown
# 11 — Observabilité
|
||
|
||
**Durée : environ 2 h.**
|
||
|
||
Principe : **ce qui n'est pas surveillé n'existe pas.** Une panne détectée par
|
||
un client est une panne détectée trop tard.
|
||
|
||
---
|
||
|
||
## 1. Les composants, et pourquoi chacun est là
|
||
|
||
| Composant | Rôle | Pourquoi lui |
|
||
|---|---|---|
|
||
| **Loki** | Journaux | Indexe des étiquettes, pas le texte intégral : ~10× moins gourmand qu'Elasticsearch, largement suffisant ici. |
|
||
| **Promtail** | Collecte | Lit `/var/log/pods` (containerd, pas Docker : k3s n'utilise pas Docker). Découpe le JSON pino en champs. |
|
||
| **Prometheus** | Métriques | 15 jours de rétention. Sans métriques, on ne voit pas une dégradation venir. |
|
||
| **node-exporter** | Métriques système | Présent pour une alerte avant tout : **disque plein**, la panne la plus fréquente d'un serveur laissé seul. |
|
||
| **Alertmanager** | Routage | Envoie sur Discord. Règles d'inhibition pour éviter le déluge. |
|
||
| **Grafana** | Consultation | Une interface pour les deux sources. |
|
||
| **healthchecks.io** ou **BetterStack** | Externe | **Indispensable** : voir §5. |
|
||
|
||
Coût : 0 €. Empreinte : environ 1,5 Go de RAM sur les 16 du serveur.
|
||
|
||
---
|
||
|
||
## 2. Déployer
|
||
|
||
```bash
|
||
export KUBECONFIG=~/.kube/xpeditis-prod.yaml
|
||
cd infra/prod
|
||
make deploy-monitoring
|
||
```
|
||
|
||
Le script vérifie d'abord que les secrets `grafana-admin` et
|
||
`alertmanager-secrets` existent, puis déploie les six composants et attend leur
|
||
démarrage.
|
||
|
||
```bash
|
||
kubectl -n monitoring get pods,pvc
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Grafana
|
||
|
||
`https://grafana.xpeditis.com` — identifiants dans le `Secret`
|
||
`monitoring/grafana-admin`.
|
||
|
||
### Si vous obtenez un 403
|
||
|
||
C'est le middleware Traefik `admin-ip-allowlist`. Renseignez votre IP :
|
||
|
||
```bash
|
||
$EDITOR k8s/base/08-traefik-middlewares.yaml # section admin-ip-allowlist
|
||
kubectl apply -f k8s/base/08-traefik-middlewares.yaml
|
||
```
|
||
|
||
> Grafana donne accès à l'intégralité de vos journaux applicatifs, jetons
|
||
> tronqués et identifiants clients compris. Trois couches le protègent : le
|
||
> firewall Hetzner (IP Cloudflare uniquement), le filtrage par IP Traefik, et
|
||
> l'authentification Grafana. Si votre IP est dynamique, remplacez la deuxième
|
||
> par **Cloudflare Access** (Zero Trust, gratuit) plutôt que d'élargir la liste.
|
||
|
||
### Vérifier les sources de données
|
||
|
||
**Connections → Data sources** : `Loki` et `Prometheus` doivent afficher
|
||
« Data source is working ».
|
||
|
||
### Premières requêtes utiles
|
||
|
||
```logql
|
||
# Toutes les erreurs du backend sur 1 h
|
||
{namespace="xpeditis-prod", service="xpeditis-backend"} | json | level="error"
|
||
|
||
# Échecs d'authentification
|
||
{namespace="xpeditis-prod"} |= "Unauthorized" or "Invalid credentials"
|
||
|
||
# Requêtes PostgreSQL lentes (> 500 ms)
|
||
{namespace="xpeditis-prod"} |= "duration:" | json | duration > 500
|
||
|
||
# Suivre une requête de bout en bout par son identifiant
|
||
{namespace="xpeditis-prod"} | json | reqId="<uuid>"
|
||
```
|
||
|
||
```promql
|
||
# Taux d'erreur 5xx
|
||
sum(rate(traefik_service_requests_total{code=~"5.."}[5m]))
|
||
/ sum(rate(traefik_service_requests_total[5m]))
|
||
|
||
# Latence P95 par service
|
||
histogram_quantile(0.95, sum(rate(traefik_service_request_duration_seconds_bucket[5m])) by (le, service))
|
||
|
||
# Connexions PostgreSQL utilisées
|
||
sum(pg_stat_database_numbackends) / on() pg_settings_max_connections
|
||
|
||
# Espace disque restant
|
||
node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Les alertes
|
||
|
||
Définies dans `k8s/monitoring/03-prometheus.yaml`.
|
||
|
||
| Alerte | Seuil | Gravité |
|
||
|---|---|---|
|
||
| `BackendIndisponible` | Aucune instance saine, 2 min | critique |
|
||
| `FrontendIndisponible` | Aucune instance saine, 2 min | critique |
|
||
| `TauxErreur5xxEleve` | > 5 % pendant 5 min | critique |
|
||
| `LatenceElevee` | P95 > 2 s pendant 10 min | avertissement |
|
||
| `DisqueBientotPlein` | < 15 % libre, 10 min | critique |
|
||
| `MemoireNoeudSaturee` | > 90 %, 10 min | avertissement |
|
||
| `ConteneurRedemarreEnBoucle` | > 3 démarrages en 15 min | critique |
|
||
| `PostgresInjoignable` | `pg_up == 0`, 2 min | critique |
|
||
| `ConnexionsPostgresSaturees` | > 80 %, 5 min | avertissement |
|
||
| `CertificatBientotExpire` | < 15 jours | critique |
|
||
|
||
Les critiques sont répétées toutes les heures tant qu'elles ne sont pas
|
||
résolues ; les avertissements toutes les 12 h.
|
||
|
||
### Tester le routage — avant d'en avoir besoin
|
||
|
||
```bash
|
||
kubectl -n monitoring port-forward svc/alertmanager 9093:9093 &
|
||
|
||
curl -XPOST http://localhost:9093/api/v2/alerts \
|
||
-H 'Content-Type: application/json' \
|
||
-d '[{"labels":{"alertname":"TestDeRoutage","severity":"critique"},
|
||
"annotations":{"summary":"Test de la chaine d alerte"}}]'
|
||
```
|
||
|
||
Le message doit arriver sur Discord en moins d'une minute. **S'il n'arrive pas,
|
||
toutes les alertes ci-dessus sont décoratives.** Vérifiez alors :
|
||
|
||
```bash
|
||
kubectl -n monitoring logs deploy/alertmanager --tail=50
|
||
kubectl -n monitoring get secret alertmanager-secrets -o jsonpath='{.data.discord-webhook}' | base64 -d
|
||
```
|
||
|
||
### Vérifier que Prometheus collecte bien
|
||
|
||
```bash
|
||
kubectl -n monitoring port-forward svc/prometheus 9090:9090 &
|
||
open http://localhost:9090/targets
|
||
```
|
||
|
||
Toutes les cibles doivent être `UP`. La plus fragile est `postgres`
|
||
(`10.10.1.20:9187`) : elle passe par le réseau privé.
|
||
|
||
```bash
|
||
# Si postgres est DOWN
|
||
ssh deploy@<db_ip> 'cd /opt/xpeditis/data-node && sudo docker compose ps postgres-exporter'
|
||
kubectl -n monitoring exec deploy/prometheus -- wget -qO- http://10.10.1.20:9187/metrics | head -3
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Supervision externe — la seule qui compte quand tout brûle
|
||
|
||
Prometheus, Alertmanager et Grafana tournent **sur le serveur qu'ils
|
||
surveillent**. Si app-01 s'éteint, ils s'éteignent avec lui : personne ne vous
|
||
prévient. C'est le point aveugle structurel de toute supervision interne.
|
||
|
||
Il faut donc un observateur **extérieur**.
|
||
|
||
### 5.1 Disponibilité
|
||
|
||
Sur [healthchecks.io](https://healthchecks.io) (gratuit) ou BetterStack :
|
||
|
||
| Contrôle | URL | Fréquence | Alerte après |
|
||
|---|---|---|---|
|
||
| API | `https://api.xpeditis.com/api/v1/health` | 1 min | 2 échecs |
|
||
| Application | `https://app.xpeditis.com/` | 1 min | 2 échecs |
|
||
| Vitrine | `https://xpeditis.com/` | 5 min | 2 échecs |
|
||
|
||
Notification par e-mail **et** SMS (ou notification téléphonique) : un e-mail
|
||
n'est pas lu à 3 h du matin.
|
||
|
||
### 5.2 Battement de cœur des sauvegardes
|
||
|
||
C'est le **silence** qui doit alerter. Une alerte « la sauvegarde n'a pas
|
||
tourné » évaluée par un composant hébergé sur la même machine ne se déclenche
|
||
pas quand la machine est éteinte — précisément quand elle serait utile.
|
||
|
||
1. Créez un *check* de type heartbeat, période 1 jour, marge 6 h.
|
||
2. Copiez son URL de ping dans `.env.data` de db-01 :
|
||
|
||
```bash
|
||
ssh deploy@<db_ip> 'sudo $EDITOR /opt/xpeditis/data-node/.env.data'
|
||
# BACKUP_HEARTBEAT_URL=https://hc-ping.com/<uuid>
|
||
```
|
||
|
||
3. Déclenchez une sauvegarde pour vérifier :
|
||
|
||
```bash
|
||
ssh deploy@<db_ip> 'sudo systemctl start xpeditis-backup.service'
|
||
```
|
||
|
||
Le *check* doit passer au vert sur healthchecks.io.
|
||
|
||
### 5.3 Expiration du certificat
|
||
|
||
Ajoutez un contrôle TLS externe sur `app.xpeditis.com` (alerte à 14 jours).
|
||
Il double l'alerte Prometheus, mais fonctionne même quand le cluster est mort.
|
||
|
||
---
|
||
|
||
## 6. Sentry
|
||
|
||
Sentry apporte ce que les journaux ne donnent pas : la pile d'appels, les
|
||
variables locales et le regroupement des erreurs identiques.
|
||
|
||
Le backend embarque `@sentry/node`. Ajoutez `SENTRY_DSN` au `Secret` puis
|
||
redémarrez :
|
||
|
||
```bash
|
||
sops k8s/base/03-secrets.sops.yaml # SENTRY_DSN: "https://...@sentry.io/..."
|
||
make secrets-apply
|
||
kubectl -n xpeditis-prod rollout restart deploy/xpeditis-backend
|
||
```
|
||
|
||
> Vérifiez que le DSN est bien lu au démarrage. Le code appelle
|
||
> `configService.get('SENTRY_DSN')` : si l'initialisation n'est pas câblée dans
|
||
> `main.ts`, la variable est ignorée en silence. Provoquez une erreur de test et
|
||
> vérifiez qu'elle apparaît dans Sentry — sinon la variable ne sert à rien.
|
||
|
||
Configurez la **purge des données personnelles** côté Sentry (Settings →
|
||
Security & Privacy → Data Scrubbing) : sans elle, des e-mails et jetons clients
|
||
partent chez un sous-traitant américain — ce qui a des conséquences RGPD
|
||
([16](./16-rgpd-conformite.md)).
|
||
|
||
---
|
||
|
||
## 7. Ce qu'il faut regarder chaque semaine
|
||
|
||
Quinze minutes, le lundi :
|
||
|
||
```bash
|
||
make -C infra/prod status
|
||
```
|
||
|
||
1. **Erreurs** — Grafana, `{namespace="xpeditis-prod"} | json | level="error"`
|
||
sur 7 jours. Une erreur récurrente est un défaut, pas du bruit.
|
||
2. **Latence** — le P95 dérive-t-il ? Une dégradation lente précède la panne.
|
||
3. **Disque** — `node_filesystem_avail_bytes`. Projetez : dans combien de
|
||
semaines l'alerte se déclenchera-t-elle ?
|
||
4. **Sauvegardes** — le *check* heartbeat est-il vert ? La vérification
|
||
hebdomadaire de restauration est-elle passée ?
|
||
```bash
|
||
ssh deploy@<db_ip> 'sudo journalctl -u xpeditis-backup-verify -n 30 --no-pager'
|
||
```
|
||
5. **Firewall CI** — `hcloud firewall describe xpeditis-prod-fw-cicd` : vide ?
|
||
6. **Certificats** — `kubectl get certificate -A` : `Ready: True` partout ?
|
||
|
||
---
|
||
|
||
## 8. Contrôle
|
||
|
||
```
|
||
[ ] 6 composants de supervision Running
|
||
[ ] Grafana accessible, sources Loki et Prometheus fonctionnelles
|
||
[ ] Toutes les cibles Prometheus UP, y compris postgres
|
||
[ ] Alerte de test reçue sur Discord
|
||
[ ] Contrôles de disponibilité externes configurés (API, app, vitrine)
|
||
[ ] Battement de cœur des sauvegardes configuré et vert
|
||
[ ] Contrôle externe d'expiration du certificat
|
||
[ ] Sentry connecté et erreur de test visible
|
||
[ ] Purge des données personnelles activée dans Sentry
|
||
[ ] Routine hebdomadaire notée dans l'agenda
|
||
```
|
||
|
||
→ **Suite : [12 — Sauvegardes et restauration](./12-sauvegardes-restauration.md)**
|