# 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="" ``` ```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@ '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@ 'sudo $EDITOR /opt/xpeditis/data-node/.env.data' # BACKUP_HEARTBEAT_URL=https://hc-ping.com/ ``` 3. Déclenchez une sauvegarde pour vérifier : ```bash ssh deploy@ '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@ '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)**