xpeditis2.0/docs/mise-en-prod/11-observabilite.md
David b22f4e0b74 docs: procedure de mise en production pas a pas
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
2026-09-07 21:40:50 +02:00

274 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)**