# Des sauvegardes que vous n'avez jamais restaurées ne sont pas des sauvegardes. Un exercice de restauration, chronométré.

Chaque magasin de données AWS a des sauvegardes activées par défaut ou par une case à cocher. Aurora conserve des sauvegardes automatiques. DynamoDB a la récupération à un instant donné. S3 a le versionnage. La console affiche des coches vertes, le questionnaire de conformité reçoit un « oui », et personne n'a jamais rien restauré, donc personne ne sait combien de temps prend une restauration, si elle fonctionne, ou ce qui casse quand la copie restaurée a un autre nom.

Nous faisons un exercice de restauration chaque trimestre, dans un compte séparé, chronomètre en main. Voici l'exercice du trimestre dernier : ce que nous avons restauré, la durée de chaque étape, les quatre choses qui ont échoué, et ce que les chiffres disent de nos vrais objectifs de reprise, par opposition à ceux de la présentation.

## Ce qui est sauvegardé, et comment

L'état de la plateforme vit à trois endroits : un cluster Aurora Serverless v2, six tables DynamoDB et deux buckets S3. Les sauvegardes, toutes définies en CDK :

| Magasin | Mécanisme | Rétention | Où vit la copie |
|---|---|---|---|
| Aurora | Sauvegarde continue automatique, à un instant donné | 35 jours | Même compte, plus un snapshot quotidien copié vers le compte de sauvegarde via AWS Backup |
| DynamoDB | Récupération à un instant donné, continue | 35 jours | Même compte, plus AWS Backup quotidien vers un coffre inter-comptes |
| S3 | Versionnage + cycle de vie | Anciennes versions conservées 90 jours | Même bucket, plus réplication vers le compte de sauvegarde |
| Secrets, paramètres | AWS Backup ne les couvre pas | n/a | Reconstruits depuis [les définitions CDK et une checklist](/fr/blog/secrets-in-cdk-secrets-manager-ssm-or-nothing-in-the-template) |

La copie inter-comptes est la partie que les gens sautent. Une sauvegarde dans le même compte que la production protège d'une mauvaise migration ou d'une suppression maladroite. Elle ne protège pas de la compromission du compte lui-même, ni d'un `cdk destroy` avec le mauvais contexte qui [supprime des choses avec RETAIN désactivé](/fr/blog/cloudformation-deleted-our-app-runner-services). AWS Backup avec un coffre dans un compte séparé, avec un verrou de coffre, c'est ce qui fait que la copie survit au pire jour. Ça coûte environ 6 $ par mois à notre taille.

<div class="article-figure">
<svg viewBox="0 0 900 250" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Topologie des sauvegardes. Dans le compte de production : Aurora avec récupération à un instant donné sur 35 jours, DynamoDB avec PITR, S3 avec versionnage. AWS Backup copie des snapshots quotidiens vers un coffre dans un compte de sauvegarde séparé avec verrou ; S3 réplique vers le même compte. L'exercice trimestriel restaure depuis le compte de sauvegarde vers le compte de staging, jamais en production.">
<defs><marker id="arrB" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#4fffb0"/></marker></defs>
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<rect x="20" y="30" width="270" height="190" rx="14" fill="#151b2e" stroke="#ff6b8a" stroke-width="1.5"/><text x="155" y="54" text-anchor="middle" fill="#ff6b8a" font-weight="700">compte de production</text>
<rect x="40" y="70" width="230" height="36" rx="8" fill="#0d1120" stroke="#2a3150"/><text x="155" y="93" text-anchor="middle" fill="#f1f3ff" font-size="11">Aurora · PITR 35 jours</text>
<rect x="40" y="114" width="230" height="36" rx="8" fill="#0d1120" stroke="#2a3150"/><text x="155" y="137" text-anchor="middle" fill="#f1f3ff" font-size="11">DynamoDB × 6 · PITR 35 jours</text>
<rect x="40" y="158" width="230" height="36" rx="8" fill="#0d1120" stroke="#2a3150"/><text x="155" y="181" text-anchor="middle" fill="#f1f3ff" font-size="11">S3 × 2 · versionnage 90 jours</text>
<line x1="292" y1="125" x2="328" y2="125" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrB)"/><text x="310" y="112" text-anchor="middle" fill="#9aa3c7" font-size="10">quotidien</text>
<rect x="330" y="30" width="250" height="190" rx="14" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/><text x="455" y="54" text-anchor="middle" fill="#ffd166" font-weight="700">compte de sauvegarde</text>
<rect x="350" y="70" width="210" height="60" rx="8" fill="#0d1120" stroke="#2a3150"/><text x="455" y="92" text-anchor="middle" fill="#f1f3ff" font-size="11">coffre AWS Backup</text><text x="455" y="110" text-anchor="middle" fill="#9aa3c7" font-size="10">verrou · personne ne peut supprimer</text>
<rect x="350" y="140" width="210" height="54" rx="8" fill="#0d1120" stroke="#2a3150"/><text x="455" y="162" text-anchor="middle" fill="#f1f3ff" font-size="11">buckets répliques S3</text><text x="455" y="180" text-anchor="middle" fill="#9aa3c7" font-size="10">clé KMS séparée</text>
<line x1="582" y1="125" x2="618" y2="125" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrB)"/><text x="600" y="112" text-anchor="middle" fill="#9aa3c7" font-size="10">trimestriel</text>
<rect x="620" y="30" width="260" height="190" rx="14" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="750" y="54" text-anchor="middle" fill="#4fffb0" font-weight="700">compte de staging · l'exercice</text>
<text x="750" y="86" text-anchor="middle" fill="#f1f3ff" font-size="11">restaurer chaque magasin depuis le coffre</text>
<text x="750" y="106" text-anchor="middle" fill="#f1f3ff" font-size="11">pointer l'app de staging vers les copies</text>
<text x="750" y="126" text-anchor="middle" fill="#f1f3ff" font-size="11">lancer les smoke tests · chronométrer chaque étape</text>
<text x="750" y="156" text-anchor="middle" fill="#9aa3c7" font-size="11">jamais en production</text>
<text x="750" y="176" text-anchor="middle" fill="#9aa3c7" font-size="11">démonter à la fin · ~4 $ par exercice</text>
<text x="450" y="242" text-anchor="middle" fill="#9aa3c7">Les sauvegardes dans le même compte survivent à une mauvaise migration. Les sauvegardes inter-comptes survivent à une mauvaise journée.</text>
</g>
</svg>
</div>

## L'exercice, chronomètre en main

Le scénario : les données de production d'hier à 14 h 00 sont corrompues ; tout restaurer à 13 h 55 et remonter l'application sur les données restaurées. En staging, depuis le compte de sauvegarde, avec la suite de smoke tests comme définition de « remontée ». Un ingénieur, sans aide, en suivant le runbook.

| Étape | Temps | Notes |
|---|---|---|
| Aurora : restaurer le cluster à un instant donné depuis la copie du coffre | 22 min | Nouveau cluster, nouveau point de terminaison. Serverless v2, 0,5 ACU |
| Aurora : requête de vérification, comparer le nombre de lignes au tableau de bord de métriques | 3 min | Correspondance |
| DynamoDB : restaurer six tables depuis le coffre, en parallèle | 31 min | La plus grosse table, 18 Go, en a pris 28 |
| DynamoDB : réactiver PITR et TTL sur les tables restaurées | 4 min | La restauration ne les conserve pas. Ni l'auto-scaling, les tags ou le stream |
| S3 : rien à restaurer pour ce scénario ; vérification du versionnage seulement | 2 min | |
| Repointer l'app : écrire le nouveau point de terminaison du cluster et les noms de tables dans SSM | 5 min | Aurait dû prendre 1 minute ; voir l'échec 3 |
| Redémarrer les services App Runner et les Lambdas pour qu'ils lisent les nouveaux paramètres | 6 min | |
| Smoke tests verts | 4 min | |
| **Total** | **77 min** | |

Soixante-dix-sept minutes pour une restauration complète à un instant donné, par une seule personne suivant des instructions. C'est le vrai objectif de temps de reprise, et c'est le chiffre qui va dans le runbook, pas le « moins d'une heure » qui était sur la présentation avant le premier exercice.

## Les quatre choses qui ont échoué

Le premier exercice, il y a un an, a pris quatre heures et ne s'est pas terminé. Chaque trimestre depuis a trouvé quelque chose. Les quatre du trimestre dernier :

**1. La politique de la clé KMS n'autorisait pas le compte de staging à déchiffrer le snapshot Aurora.** La copie du coffre est chiffrée avec une clé du compte de sauvegarde. Restaurer en staging exige que la politique de cette clé accorde `kms:Decrypt` et `kms:CreateGrant` au rôle de restauration du compte de staging. Elle les accordait à celui de la production. La restauration a échoué avec une erreur de permissions qui a pris vingt minutes à lire correctement. Corrigé en CDK : la politique de la clé liste maintenant chaque compte susceptible de restaurer.

**2. La restauration DynamoDB perd les réglages de la table.** Une table restaurée a les données et le schéma de clés et rien d'autre : pas de PITR, pas d'attribut TTL, pas d'auto-scaling, pas de tags, pas de stream. Notre app s'appuie sur le TTL pour expirer les sessions et sur le stream pour alimenter l'index de recherche. Sans étape de checklist pour les réactiver, l'app restaurée « fonctionnait » et avait silencieusement cessé d'expirer les sessions. C'est maintenant une étape du runbook et, mieux, un petit script qui applique les réglages depuis la définition CDK à une table nommée.

**3. Une Lambda avait le nom de table codé en dur.** Onze fonctions sur douze lisent leurs noms de tables depuis des paramètres SSM, donc repointer était une écriture de paramètre. Une, la plus ancienne, avait le nom de la table de production en littéral dans son environnement. Elle a continué joyeusement à écrire dans l'*ancienne* table pendant l'exercice, ce qui, dans une vraie reprise, aurait signifié écrire de nouvelles données dans le magasin corrompu. Trouvée parce que le smoke test de cette fonction a échoué. Corrigée en déplaçant le nom vers SSM comme les autres, et par une vérification en CI qui greppe les blocs d'environnement à la recherche de tout ce qui ressemble à un nom de ressource.

**4. Le rôle de restauration en staging ne pouvait pas faire `PassRole` vers le rôle de service Aurora.** Une omission IAM d'une ligne, découverte à la minute 30, corrigée à la minute 35, et un rappel que le chemin de restauration a son propre jeu de permissions que rien n'exerce à part l'exercice.

Aucune de ces choses n'aurait été trouvée en regardant les coches vertes. Toutes les quatre auraient transformé un vrai incident de « mauvais après-midi » en « mauvaise semaine ».

<div class="article-figure">
<svg viewBox="0 0 900 220" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Chronologie de l'exercice de restauration de 77 minutes. Restauration Aurora à un instant donné 22 minutes, requête de vérification 3. Six tables DynamoDB restaurées en parallèle 31 minutes, réglages réappliqués 4. Vérification S3 2. Repointage via SSM 5, redémarrage des services 6, smoke tests 4. Quatre marqueurs d'échec : politique de clé KMS, réglages DynamoDB perdus, nom de table codé en dur, PassRole manquant.">
<g font-family="Inter,system-ui,sans-serif" font-size="11">
<text x="20" y="22" fill="#f1f3ff" font-size="14" font-weight="700">L'exercice · 77 minutes · un ingénieur · compte de staging</text>
<line x1="60" y1="110" x2="880" y2="110" stroke="#2a3150" stroke-width="2"/>
<rect x="60" y="60" width="234" height="22" rx="4" fill="#7b8cff" opacity="0.8"/><text x="177" y="75" text-anchor="middle" fill="#0d1120" font-weight="700">Aurora PITR · 22 min</text>
<rect x="60" y="86" width="372" height="22" rx="4" fill="#4fffb0" opacity="0.8"/><text x="246" y="101" text-anchor="middle" fill="#0d1120" font-weight="700">DynamoDB × 6 · 31 min · en parallèle</text>
<rect x="294" y="60" width="32" height="22" rx="4" fill="#7b8cff" opacity="0.5"/>
<rect x="432" y="86" width="43" height="22" rx="4" fill="#4fffb0" opacity="0.5"/><text x="453" y="101" text-anchor="middle" fill="#0d1120" font-size="9">réglages</text>
<rect x="475" y="86" width="21" height="22" rx="4" fill="#9aa3c7" opacity="0.5"/>
<rect x="496" y="86" width="53" height="22" rx="4" fill="#ffd166" opacity="0.8"/><text x="522" y="101" text-anchor="middle" fill="#0d1120" font-size="9">repointage</text>
<rect x="549" y="86" width="64" height="22" rx="4" fill="#ffd166" opacity="0.6"/><text x="581" y="101" text-anchor="middle" fill="#0d1120" font-size="9">redémarrage</text>
<rect x="613" y="86" width="43" height="22" rx="4" fill="#4fffb0"/><text x="634" y="101" text-anchor="middle" fill="#0d1120" font-size="9">smoke</text>
<text x="60" y="130" fill="#9aa3c7" font-size="10">0</text><text x="656" y="130" fill="#9aa3c7" font-size="10">77 min</text>
<circle cx="90" cy="150" r="5" fill="#ff6b8a"/><text x="100" y="154" fill="#ff6b8a">1 · politique de clé KMS · 20 min perdues à lire l'erreur</text>
<circle cx="90" cy="172" r="5" fill="#ff6b8a"/><text x="100" y="176" fill="#ff6b8a">2 · la restauration DynamoDB perd PITR, TTL, stream, auto-scaling, tags</text>
<circle cx="90" cy="194" r="5" fill="#ff6b8a"/><text x="100" y="198" fill="#ff6b8a">3 · une Lambda au nom de table codé en dur a continué d'écrire dans l'ancienne table</text>
<circle cx="500" cy="150" r="5" fill="#ff6b8a"/><text x="510" y="154" fill="#ff6b8a">4 · rôle de restauration sans iam:PassRole</text>
<text x="500" y="176" fill="#9aa3c7">le premier exercice, il y a un an : quatre heures, non terminé</text>
<text x="500" y="198" fill="#9aa3c7">chaque trimestre depuis a trouvé au moins l'un de ceux-ci</text>
</g>
</svg>
</div>

## Les chiffres qui en sont sortis

Objectif de point de reprise : cinq minutes pour Aurora et DynamoDB, parce que la récupération à un instant donné est continue ; S3 est immédiat via le versionnage. Cette partie était vraie avant l'exercice.

Objectif de temps de reprise : 77 minutes pour une restauration complète, environ 30 pour une seule table DynamoDB, environ 25 pour Aurora seul. Ces chiffres étaient de la fiction avant l'exercice et sont des mesures maintenant. Ils sont dans le runbook et dans le modèle de page de statut, donc le message pendant une vraie reprise dit « restauration en cours, fin estimée à 15 h 20 » au lieu de « bientôt ».

Coût de l'exercice : environ 4 $ de ressources restaurées pour l'après-midi, plus l'après-midi d'un ingénieur. Coût de ne pas le faire : inconnu jusqu'au jour où ça compte, qui est le mauvais jour pour l'apprendre.

## Le runbook, condensé

- Déclarer l'instant cible. L'écrire avant de toucher à quoi que ce soit.
- Restaurer dans le compte isolé. Jamais par-dessus la production ; restaurer à côté et repointer.
- Aurora : restauration à un instant donné depuis la copie du coffre ; vérifier le nombre de lignes contre une métrique connue.
- DynamoDB : restaurer toutes les tables en parallèle ; puis lancer le script de réglages pour PITR, TTL, streams, auto-scaling, tags.
- S3 : identifier les versions d'objets à l'instant cible ; copier vers l'avant si nécessaire.
- Repointer : l'application lit chaque nom de ressource depuis SSM. Écrire les nouveaux noms. Rien n'est codé en dur, et la CI l'impose.
- Redémarrer tout ce qui met des paramètres en cache.
- Smoke tests. Pas « ça se charge » : la suite qui exerce le checkout, la connexion et l'index de recherche.
- Chronométrer chaque étape. Mettre à jour le RTO du runbook s'il a bougé.
- Démonter, et écrire la note d'une page sur ce qui a échoué.

Trimestriel. Au calendrier. Avec un ingénieur différent à chaque fois, parce que le but est que le runbook fonctionne pour la personne qui ne l'a pas écrit.

Si vos sauvegardes ont des coches vertes et aucun historique de restauration, [nous ferons le premier exercice avec vous](/contact). Prévoyez un après-midi et attendez-vous à trouver quelque chose.
