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

9.4 KiB
Raw Blame History

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

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.

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 :

$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

# 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>"
# 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

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 :

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

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é.

# 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 (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 :
ssh deploy@<db_ip> 'sudo $EDITOR /opt/xpeditis/data-node/.env.data'
# BACKUP_HEARTBEAT_URL=https://hc-ping.com/<uuid>
  1. Déclenchez une sauvegarde pour vérifier :
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 :

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).


7. Ce qu'il faut regarder chaque semaine

Quinze minutes, le lundi :

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 ?
    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