Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

14.6 — Fiabilité

🎯 Objectif : se préparer aux pires scénarios. Une sauvegarde non testée n’existe pas. Un runbook que personne ne lit ne sert à rien. Un post-mortem qui blâme une personne empêche d’apprendre. Cette section couvre les pratiques que les équipes ops mûres mettent en place avant d’en avoir besoin.

À l'issue de cet axe, tu sauras :

  • Mettre en place des sauvegardes testées et chiffrées (3-2-1)
  • Rédiger un runbook actionnable qu'un on-call lira à 3h du matin
  • Mener un post-mortem blameless qui aboutit à des actions concrètes
  • Définir RPO et RTO et choisir une stratégie de reprise adaptée
  • Introduire du chaos engineering et organiser un game day

Confirmé 14 min prérequis : axes 4 et 8 lus

Quand un incident arrive, ce qui te sauve est rarement la stack. C’est :

Sauvegardes
/\
/ \
/ \
Runbooks ───── Post-mortems
  • Sauvegardes te protègent de la perte de données.
  • Runbooks te font réagir vite.
  • Post-mortems transforment chaque incident en apprentissage durable.

Sans ces trois, même AWS ne te sauve pas.


3 copies des données
2 supports différents (disque + S3)
1 hors site (chez un autre fournisseur, autre région)
TypeOutil 2026
Postgres dump quotidienpg_dump + S3 / R2 / Backblaze B2
Postgres point-in-time recovery (PITR)WAL archive (pgBackRest, wal-g)
Snapshots cloudRDS automated snapshots, EBS snapshots, GCP snapshots
Volumes Linuxrestic, borgbackup
Buckets / S3Versioning + lifecycle + Object Lock

Une sauvegarde n’existe que si tu sais la restaurer.

Tester une restauration trimestriellement dans un env staging :

Fenêtre de terminal
# 1. Récupérer le dernier dump
aws s3 cp s3://myorg-backups/db-2026-04-30.sql.gz .
# 2. Restaurer dans une instance jetable
gunzip -c db-2026-04-30.sql.gz | psql -h staging-db -U postgres restore_test
# 3. Vérifier
psql -c "SELECT count(*) FROM users;"
psql -c "SELECT max(created_at) FROM events;"
# 4. Mesurer le temps total
# → c'est ton RTO réel pour ce scénario

Si tu n’as jamais restauré, considère que tu n’as pas de sauvegarde.

Fenêtre de terminal
# Chiffrer avec restic + clé dédiée (KMS, Vault)
restic init --repo s3:s3.amazonaws.com/myorg-backups
restic backup /var/lib/postgresql/backups
RèglePourquoi
Chiffrement at-restRGPD + risque fournisseur compromis
Clé séparée du providerSi AWS est compromis, ta clé KMS chez Vault tient
Accès en append-onlyObject Lock S3 / immutability → un attaquant ne peut pas effacer
Rétention multi-niveau7 j quotidiens + 4 sem + 12 mois
Test régulier de restaurationTrimestriel minimum

SigleDéfinitionQuestion qu’il pose
RPO (Recovery Point Objective)Combien de données tolère-t-on de perdre ?« Si on perd les 12 dernières minutes, c’est OK ? »
RTO (Recovery Time Objective)Combien de temps d’indisponibilité tolère-t-on ?« Si on est down 4h, c’est OK ? »
StratégieRPO typiqueRTO typiqueCoût
Backup quotidien S324 h4-12 h
Backup horaire + WAL5-15 min1-2 h€€
Replica synchrone même région< 1 min5-30 min€€€
Multi-région actif/passif< 1 min< 5 min€€€€
Multi-région actif/actif~ 0~ 0€€€€€

Ne te survends pas. Le coût croît exponentiellement. Pour un MVP, RPO 24h / RTO 4h coûte rien et suffit largement.


Un runbook est un script écrit pour quelqu’un de fatigué et stressé. Son public n’est pas l’expert qui l’a écrit.

# Runbook — API checkout en erreur
## Symptômes
- Pages d'erreur 5xx sur /checkout
- Alerte « checkout-error-rate > 5 % » déclenchée
## Diagnostic rapide (≤ 5 min)
1. Vérifier le status Stripe : https://status.stripe.com
2. Vérifier la trace dans Tempo (lien direct ↗)
3. Regarder les logs : `kubectl logs -l app=checkout --since=10m`
## Causes connues et résolutions
| Cause | Comment confirmer | Mitigation |
|-------|-------------------|------------|
| Stripe down | status.stripe.com | Activer le banner "paiement temporairement indispo" |
| DB Postgres saturée | `SELECT count(*) FROM pg_stat_activity` | Tuer les requêtes longues, scale-up RDS |
| OOM sur les pods | `kubectl get pods -l app=checkout` | Restart pods, augmenter memory limits |
## Escalation
- Pas de cause connue trouvée en 15 min → ping #checkout-oncall
- Impact > 30 min → ping CTO + déclarer incident pubic
## Liens utiles
- Dashboard Grafana : https://grafana.example/d/checkout
- Repo : https://github.com/myorg/checkout
- Owner : @alice (PT), @bob (US)
RèglePourquoi
Court (1-2 pages max)Personne ne lit 10 pages à 3h du matin
Commandes copy-pastablesÉviter les fautes de frappe sous stress
Liens directs dashboardsPas « va dans Grafana → cherche le dashboard… »
Mis à jour à chaque incidentSinon il pourrit
Testé en game dayVérifier qu’il marche encore

« Aucune personne, intelligente et bien intentionnée, ne souhaite causer un incident. Si elle l’a fait, c’est que le système l’a permis. »

Le post-mortem ne dit pas : « Marie a oublié de vérifier la migration. » Il dit : « La revue ne demande pas de checklist migration ; ajouter un template. »

L’enjeu : les gens partagent honnêtement ce qui s’est passé. Si on blâme, on cache la prochaine fois — et on cache la cause profonde.

# Post-mortem — Incident 2026-04-30 — Indisponibilité checkout
**Date** : 2026-04-30, 14:23 → 15:47 UTC (84 min)
**Sévérité** : SEV2 (impact partiel, ~12 % du trafic)
**Auteurs** : @alice, @bob
## Résumé
Le service checkout a renvoyé 5xx pendant 84 min suite au déploiement
de v1.34.0 qui contenait une migration DB longue mal détectée.
## Impact
- 1 247 commandes en erreur (~ 18 200 € de CA perdu / décalé)
- 3 280 utilisateurs affectés
- 12 tickets support ouverts
## Timeline
- 14:23 Déploiement v1.34.0 sur prod
- 14:24 Migration DB démarre, lock sur la table `orders`
- 14:25 Premières 5xx, alerte « checkout-error-rate » déclenchée
- 14:27 On-call (alice) prend l'astreinte
- 14:42 Diagnostic : migration bloquante identifiée
- 14:48 Migration interrompue, rollback de la migration partielle
- 14:53 Déploiement v1.33.7 (rollback applicatif)
- 15:47 Erreurs revenues à zéro après backfill manuel
## Cause racine (5 whys)
1. Pourquoi 5xx ? La table `orders` était lockée.
2. Pourquoi lockée ? Migration `ALTER TABLE` sans `LOCK MODE`.
3. Pourquoi cette migration ? Ajout d'une colonne NOT NULL sur 8M lignes.
4. Pourquoi ce design ? La review n'a pas alerté sur la taille de la table.
5. Pourquoi pas la review ? Aucun garde-fou, pas de test en staging à
volumétrie réelle.
## Ce qui a bien marché
- Détection en 2 min via alerte symptôme
- Rollback applicatif possible (la nouvelle colonne avait un défaut)
- Communication claire dans #incident
## Ce qui n'a pas marché
- Aucune validation préalable de la migration en staging
- Le runbook ne mentionnait pas le scénario "migration bloquante"
- Le rollback de migration a pris 15 min (manuel)
## Actions correctives
| # | Action | Owner | Échéance |
|---|--------|-------|----------|
| 1 | Migration linter (gh-action) qui bloque ALTER sur > 1M lignes sans review explicite | @bob | 2026-05-15 |
| 2 | Stagging avec un dump prod anonymisé pour les migrations | @alice | 2026-05-30 |
| 3 | Runbook « migration bloquante » + script rollback | @alice | 2026-05-07 |
| 4 | Post-mortem présenté en all-hands | @cto | 2026-05-10 |
RèglePourquoi
BlamelessApprendre > culpabiliser
Rapide : sous 7 joursLa mémoire des détails s’efface vite
Public dans l’orgApprentissage transversal
Actions concrètes avec owner et deadlineSinon c’est de la littérature
Suivi des actions correctivesSinon le prochain incident est le même
Pas une chasse aux sorcièresSinon plus personne ne signale

Au-delà du backup, le DR couvre les cas où tout part :

ScénarioPlan typique
Région cloud downBascule vers région secondaire (DNS / Route53 healthcheck)
Compte cloud compromis / ferméBackup off-site, multi-fournisseur
Ransomware sur les serveursBackup immutable (Object Lock), recovery from scratch
Erreur humaine catastrophique (DROP DATABASE)PITR + audit logs
Disparition d’un fournisseur SaaSPlan de migration documenté

Une fois par trimestre, l’équipe simule un incident :

9h00 — Annonce : "La région eu-west-3 est inaccessible. Bascule, allez-y."
9h05 — L'équipe ouvre le runbook DR.
9h15 — Premiers actions (DNS, scale-up region secondaire).
10h30 — Service rétabli, RTO mesuré.
11h00 — Debrief : ce qui a marché, ce qui a manqué.

Le game day révèle ce que les docs ne disent plus. C’est inconfortable mais c’est le seul moyen de connaître ton vrai RTO.


« Si vous voulez être prêt pour les pannes, créez-en délibérément. » — Netflix Chaos Monkey

Au lieu d’attendre une panne pour découvrir tes faiblesses, tu en provoques dans un cadre contrôlé :

OutilQuoi
Chaos MeshK8s, OSS, mature
LitmusChaosK8s, OSS
GremlinSaaS, propre, cher
AWS Fault Injection ServiceAWS-natif
PumbaDocker chaos

Expériences typiques :

  • Network partition : couper la connexion DB ↔ app pendant 30 s.
  • Latence injectée : ajouter 500 ms à toutes les requêtes vers Redis.
  • Pod kill : tuer 1 pod sur 3 toutes les minutes.
  • Disk full : remplir / dans un node.
  • Clock skew : décaler l’horloge de 5 min.

Le chaos engineering commence par un game day en staging avec une équipe alignée. Jamais en prod sur un coup de tête. Une fois mature, tu peux l’introduire en prod sur un petit % de trafic.


Au-delà de l’infra, le code lui-même peut être résilient :

PatternQuand
Retry avec backoff exponentielToute requête réseau qui peut flap
Timeout explicite (3-10 s max)Tout appel externe (sinon Node attend des heures)
Circuit breakerService tiers répétitivement KO → couper court
BulkheadIsoler les pools de connexions par sous-système
IdempotencePour pouvoir retry sans dupliquer
FallbackCache stale, valeur par défaut, dégradation gracieuse
Graceful shutdownDrainer les requêtes en cours avant exit
// Retry avec backoff exponentiel + jitter
async function retry<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {
let last: unknown;
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch (err) {
last = err;
const delay = Math.min(1000 * 2 ** i, 30_000) + Math.random() * 1000;
await new Promise((r) => setTimeout(r, delay));
}
}
throw last;
}
// Circuit breaker simple (opossum est la lib de référence)
import CircuitBreaker from 'opossum';
const breaker = new CircuitBreaker(callStripe, {
timeout: 5000,
errorThresholdPercentage: 50,
resetTimeout: 30_000,
});
breaker.fallback(() => ({ status: 'fallback' }));

MétriqueBonMédiocre
MTTR (Mean Time To Recover)< 30 min> 4 h
MTBF (Mean Time Between Failures)> 30 j< 7 j
Change failure rate< 15 %> 30 %
Deployment frequencyquotidienmensuel

Source : DORA reports (Google) — corrélations entre ces 4 métriques et la performance d’une équipe.


À cocher avant de pointer un domaine vers ton service :

  • Backups configurés ET testés (restauration ≥ 1 fois)
  • RPO et RTO définis et accordés avec le métier
  • Healthcheck implémenté côté app
  • Logs structurés vers une stack centralisée
  • Métriques RED + USE exposées
  • Tracing distribué (OTel) actif
  • Sentry (ou équivalent) installé avec source maps
  • Au moins 3 alertes critiques avec runbook
  • On-call rotation définie
  • Page de status (statuspage.io, Better Stack, ou simple page statique)
  • Plan de rollback déploiement (1 commande)
  • Migrations DB réversibles
  • Post-mortem template prêt
  • Game day prévu dans les 60 j

Ne pas remplir un de ces items ne te bloque pas — mais sois conscient du risque.


Tu as un cron qui dump Postgres quotidiennement vers S3 depuis 2 ans. Personne n'a jamais restauré. Tu pars en weekend, tu pars confiant ?
Pendant un post-mortem, un membre de l'équipe dit « Marie a déployé sans regarder, c'est elle la responsable ». Que faire ?
Tu définis RPO = 1 h et RTO = 30 min pour un SaaS B2B avec 200 clients PME. Est-ce raisonnable ?
Quand commencer le chaos engineering ?

  • Site Reliability Engineering — Google (gratuit), chapitres post-mortem & DR
  • Chaos Engineering — Casey Rosenthal & Nora Jones (O’Reilly)
  • Drift into Failure — Sidney Dekker (sur la culture sans blâme)
  • DORA report annuel — métriques d’équipe à benchmarker
  • Restic / borgbackup — outils de sauvegarde modernes
  • Better Stack Status / statuspage.io — pages de status

Retour : Index axe 14 — applique tout ça via le projet de l’axe.