# Backups, die Sie nie wiederhergestellt haben, sind keine Backups. Eine Restore-Übung, gestoppt.

Jeder AWS-Datenspeicher hat Backups standardmäßig oder per Häkchen aktiviert. Aurora hält automatische Backups. DynamoDB hat Point-in-Time-Recovery. S3 hat Versionierung. Die Konsole zeigt grüne Haken, der Compliance-Fragebogen bekommt ein „ja“, und niemand hat je tatsächlich etwas wiederhergestellt, also weiß niemand, wie lange ein Restore dauert, ob er funktioniert oder was kaputtgeht, wenn die wiederhergestellte Kopie einen anderen Namen hat.

Wir machen jedes Quartal eine Restore-Übung, in ein separates Konto, mit Stoppuhr. Das hier ist die Übung des letzten Quartals: was wir wiederhergestellt haben, wie lange jeder Schritt dauerte, die vier Dinge, die scheiterten, und was die Zahlen über unsere echten Wiederherstellungsziele sagen, im Gegensatz zu denen auf der Folie.

## Was gesichert wird, und wie

Der Zustand der Plattform lebt an drei Orten: ein Aurora-Serverless-v2-Cluster, sechs DynamoDB-Tabellen und zwei S3-Buckets. Die Backups, alle in CDK definiert:

| Speicher | Mechanismus | Aufbewahrung | Wo die Kopie liegt |
|---|---|---|---|
| Aurora | Automatisches kontinuierliches Backup, Point-in-Time | 35 Tage | Gleiches Konto, plus ein täglicher Snapshot per AWS Backup ins Backup-Konto kopiert |
| DynamoDB | Point-in-Time-Recovery, kontinuierlich | 35 Tage | Gleiches Konto, plus tägliches AWS Backup in einen kontoübergreifenden Vault |
| S3 | Versionierung + Lifecycle | Alte Versionen 90 Tage | Gleicher Bucket, plus Replikation ins Backup-Konto |
| Secrets, Parameter | AWS Backup deckt diese nicht ab | n/a | Aus den [CDK-Definitionen und einer Checkliste](/de/blog/secrets-in-cdk-secrets-manager-ssm-or-nothing-in-the-template) neu aufgebaut |

Die kontoübergreifende Kopie ist der Teil, den Leute überspringen. Ein Backup im selben Konto wie Produktion schützt vor einer schlechten Migration oder einem vertippten Löschen. Es schützt nicht davor, dass das Konto selbst kompromittiert wird, oder vor einem `cdk destroy` mit falschem Kontext, [das Dinge mit ausgeschaltetem RETAIN löscht](/de/blog/cloudformation-deleted-our-app-runner-services). AWS Backup mit einem Vault in einem separaten Konto, mit Vault Lock, ist das, was die Kopie den schlimmsten Tag überleben lässt. Es kostet bei unserer Größe etwa $6 im Monat.

<div class="article-figure">
<svg viewBox="0 0 900 250" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Backup-Topologie. Im Produktionskonto: Aurora mit 35 Tagen Point-in-Time-Recovery, DynamoDB mit PITR, S3 mit Versionierung. AWS Backup kopiert tägliche Snapshots in einen Vault in einem separaten Backup-Konto mit Vault Lock; S3 repliziert ins selbe Konto. Die vierteljährliche Restore-Übung stellt aus dem Backup-Konto ins Staging-Konto wieder her, nie in Produktion.">
<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">Produktionskonto</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 Tage</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 Tage</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 · Versionierung 90 Tage</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">täglich</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">Backup-Konto</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">AWS-Backup-Vault</text><text x="455" y="110" text-anchor="middle" fill="#9aa3c7" font-size="10">Vault Lock · niemand kann löschen</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">S3-Replikat-Buckets</text><text x="455" y="180" text-anchor="middle" fill="#9aa3c7" font-size="10">separater KMS-Schlüssel</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">vierteljährlich</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">Staging-Konto · die Übung</text>
<text x="750" y="86" text-anchor="middle" fill="#f1f3ff" font-size="11">jeden Speicher aus dem Vault wiederherstellen</text>
<text x="750" y="106" text-anchor="middle" fill="#f1f3ff" font-size="11">die Staging-App auf die Kopien zeigen lassen</text>
<text x="750" y="126" text-anchor="middle" fill="#f1f3ff" font-size="11">Smoke-Tests laufen lassen · jeden Schritt stoppen</text>
<text x="750" y="156" text-anchor="middle" fill="#9aa3c7" font-size="11">nie in Produktion</text>
<text x="750" y="176" text-anchor="middle" fill="#9aa3c7" font-size="11">danach abbauen · ~$4 pro Übung</text>
<text x="450" y="242" text-anchor="middle" fill="#9aa3c7">Backups im selben Konto überleben eine schlechte Migration. Kontoübergreifende Backups überleben einen schlechten Tag.</text>
</g>
</svg>
</div>

## Die Übung, mit Stoppuhr

Das Szenario: Die Produktionsdaten ab 14:00 gestern sind korrupt; alles auf 13:55 wiederherstellen und die Anwendung auf den wiederhergestellten Daten hochfahren. In Staging, aus dem Backup-Konto, mit der Smoke-Test-Suite als Definition von „läuft“. Ein Engineer, ohne Hilfe, nach dem Runbook.

| Schritt | Zeit | Anmerkungen |
|---|---|---|
| Aurora: Cluster aus der Vault-Kopie auf einen Zeitpunkt wiederherstellen | 22 Min | Neuer Cluster, neuer Endpunkt. Serverless v2, 0,5 ACU |
| Aurora: Smoke-Abfrage, Zeilenzahlen gegen das Metrik-Dashboard prüfen | 3 Min | Stimmten überein |
| DynamoDB: sechs Tabellen aus dem Vault wiederherstellen, parallel | 31 Min | Die größte Tabelle, 18 GB, brauchte 28 davon |
| DynamoDB: PITR und TTL auf den wiederhergestellten Tabellen wieder aktivieren | 4 Min | Der Restore übernimmt diese nicht. Auch nicht Auto-Scaling, Tags oder den Stream |
| S3: für dieses Szenario nichts wiederherzustellen; nur Versionierungsprüfung | 2 Min | |
| App umhängen: neuen Cluster-Endpunkt und Tabellennamen nach SSM schreiben | 5 Min | Hätte 1 Minute sein sollen; siehe Fehler 3 |
| App-Runner-Services und Lambdas neu starten, damit sie die neuen Parameter lesen | 6 Min | |
| Smoke-Tests grün | 4 Min | |
| **Gesamt** | **77 Min** | |

Siebenundsiebzig Minuten für einen vollständigen Restore auf einen Zeitpunkt, durch eine Person, die Anweisungen folgt. Das ist das echte Recovery Time Objective, und diese Zahl gehört ins Runbook, nicht das „unter einer Stunde“, das vor der ersten Übung auf der Folie stand.

## Die vier Dinge, die scheiterten

Die erste Übung vor einem Jahr dauerte vier Stunden und wurde nicht fertig. Jedes Quartal seitdem hat etwas gefunden. Die vier vom letzten Quartal:

**1. Die KMS-Schlüsselrichtlinie erlaubte dem Staging-Konto nicht, den Aurora-Snapshot zu entschlüsseln.** Die Vault-Kopie ist mit einem Schlüssel im Backup-Konto verschlüsselt. Ein Restore nach Staging braucht in der Schlüsselrichtlinie `kms:Decrypt` und `kms:CreateGrant` für die Restore-Rolle des Staging-Kontos. Sie gewährte sie der von Produktion. Der Restore scheiterte mit einem Berechtigungsfehler, der zwanzig Minuten brauchte, um richtig gelesen zu werden. In CDK behoben: Die Schlüsselrichtlinie listet jetzt jedes Konto, das wiederherstellen könnte.

**2. Der DynamoDB-Restore verwirft die Tabelleneinstellungen.** Eine wiederhergestellte Tabelle hat die Daten und das Key-Schema und sonst nichts: kein PITR, kein TTL-Attribut, kein Auto-Scaling, keine Tags, keinen Stream. Unsere App verlässt sich auf TTL, um Sessions ablaufen zu lassen, und auf den Stream, um den Suchindex zu speisen. Ohne einen Checklistenschritt zum Reaktivieren „funktionierte“ die wiederhergestellte App und hörte still auf, Sessions ablaufen zu lassen. Jetzt ist es ein Schritt im Runbook und, besser, ein kleines Skript, das die Einstellungen aus der CDK-Definition auf eine benannte Tabelle anwendet.

**3. Eine Lambda hatte den Tabellennamen hartkodiert.** Elf von zwölf Funktionen lesen ihre Tabellennamen aus SSM-Parametern, sodass das Umhängen ein Parameter-Schreiben war. Eine, die älteste, hatte den Produktionstabellennamen als Literal in ihrer Umgebung. Sie schrieb während der Übung munter weiter in die *alte* Tabelle, was bei einer echten Wiederherstellung bedeutet hätte, neue Daten in den korrupten Speicher zu schreiben. Gefunden, weil der Smoke-Test für diese Funktion scheiterte. Behoben, indem der Name wie bei den anderen nach SSM wanderte, und durch eine CI-Prüfung, die Umgebungsblöcke nach allem greppt, was einem Ressourcennamen-Muster entspricht.

**4. Die Restore-Rolle in Staging konnte kein `PassRole` an die Aurora-Servicerolle.** Eine einzeilige IAM-Auslassung, entdeckt bei Minute 30, behoben bei Minute 35, und eine Erinnerung daran, dass der Restore-Pfad seinen eigenen Satz Berechtigungen hat, den nichts außer der Übung ausübt.

Nichts davon wäre durch einen Blick auf die grünen Haken gefunden worden. Alle vier hätten aus einem echten Vorfall statt „schlechter Nachmittag“ eine „schlechte Woche“ gemacht.

<div class="article-figure">
<svg viewBox="0 0 900 220" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Zeitstrahl der 77-minütigen Restore-Übung. Aurora-Point-in-Time-Restore 22 Minuten, Smoke-Abfrage 3. Sechs DynamoDB-Tabellen parallel wiederhergestellt 31 Minuten, Einstellungen erneut angewandt 4. S3-Prüfung 2. Umhängen per SSM 5, Neustart der Services 6, Smoke-Tests 4. Vier Fehlermarker: KMS-Schlüsselrichtlinie, verworfene DynamoDB-Einstellungen, hartkodierter Tabellenname, fehlendes PassRole.">
<g font-family="Inter,system-ui,sans-serif" font-size="11">
<text x="20" y="22" fill="#f1f3ff" font-size="14" font-weight="700">Die Übung · 77 Minuten · ein Engineer · Staging-Konto</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 · parallel</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">Settings</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">umhängen</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">Neustart</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 · KMS-Schlüsselrichtlinie · 20 Min beim Lesen des Fehlers verloren</text>
<circle cx="90" cy="172" r="5" fill="#ff6b8a"/><text x="100" y="176" fill="#ff6b8a">2 · DynamoDB-Restore verwirft PITR, TTL, Stream, Auto-Scaling, Tags</text>
<circle cx="90" cy="194" r="5" fill="#ff6b8a"/><text x="100" y="198" fill="#ff6b8a">3 · eine Lambda mit hartkodiertem Tabellennamen schrieb weiter in die alte Tabelle</text>
<circle cx="500" cy="150" r="5" fill="#ff6b8a"/><text x="510" y="154" fill="#ff6b8a">4 · Restore-Rolle ohne iam:PassRole</text>
<text x="500" y="176" fill="#9aa3c7">die erste Übung vor einem Jahr: vier Stunden, nicht fertig</text>
<text x="500" y="198" fill="#9aa3c7">jedes Quartal seitdem fand mindestens eines davon</text>
</g>
</svg>
</div>

## Die Zahlen, die dabei herauskamen

Recovery Point Objective: fünf Minuten für Aurora und DynamoDB, weil Point-in-Time-Recovery kontinuierlich ist; S3 ist per Versionierung sofort. Dieser Teil stimmte schon vor der Übung.

Recovery Time Objective: 77 Minuten für einen vollständigen Restore, etwa 30 für eine einzelne DynamoDB-Tabelle, etwa 25 für Aurora allein. Diese Zahlen waren vor der Übung Fiktion und sind jetzt Messungen. Sie stehen im Runbook und in der Statusseiten-Vorlage, sodass die Nachricht während einer echten Wiederherstellung „Restore läuft, voraussichtlich fertig um 15:20“ lautet statt „bald“.

Kosten der Übung: etwa $4 an wiederhergestellten Ressourcen für den Nachmittag, plus der Nachmittag eines Engineers. Kosten, sie nicht zu machen: unbekannt bis zu dem Tag, an dem es zählt, und das ist der falsche Tag, um es zu lernen.

## Das Runbook, verdichtet

- Den Zielzeitpunkt festlegen. Aufschreiben, bevor irgendetwas angefasst wird.
- In das isolierte Konto wiederherstellen. Nie über Produktion; daneben wiederherstellen und umhängen.
- Aurora: Point-in-Time-Restore aus der Vault-Kopie; Zeilenzahlen gegen eine bekannte Metrik prüfen.
- DynamoDB: alle Tabellen parallel wiederherstellen; dann das Einstellungsskript für PITR, TTL, Streams, Auto-Scaling, Tags ausführen.
- S3: die Objektversionen zum Zielzeitpunkt identifizieren; bei Bedarf vorwärts kopieren.
- Umhängen: Die Anwendung liest jeden Ressourcennamen aus SSM. Die neuen Namen schreiben. Nichts ist hartkodiert, und CI erzwingt das.
- Alles neu starten, was Parameter cacht.
- Smoke-Tests. Nicht „es lädt“: die Suite, die Checkout, Login und den Suchindex ausübt.
- Jeden Schritt stoppen. Das RTO im Runbook aktualisieren, wenn es sich verschoben hat.
- Abbauen und die einseitige Notiz schreiben, was scheiterte.

Vierteljährlich. Im Kalender. Jedes Mal mit einem anderen Engineer, denn der Punkt ist, dass das Runbook für die Person funktioniert, die es nicht geschrieben hat.

Wenn Ihre Backups grüne Haken und keine Restore-Historie haben, [machen wir die erste Übung mit Ihnen](/contact). Planen Sie einen Nachmittag ein und rechnen Sie damit, etwas zu finden.
