Add monthly offsite restore tests and automatic cleanup
This commit is contained in:
1 parent
64eab1d3c2
commit
f0c761213d
18 files changed
+1386
-16
No files matched your search
+251
@@ -0,0 +1,251 @@
|
||||
# Monatlicher Offsite-Restore-Test
|
||||
|
||||
Der Restore-Test prüft den neuesten vollständigen Sicherungssatz direkt aus dem
|
||||
konfigurierten rclone-Crypt-Remote. Alle Komponenten werden heruntergeladen,
|
||||
entschlüsselt, validiert und in ein privates Testverzeichnis entpackt. SQL-Dumps
|
||||
werden zusätzlich in eine isolierte MariaDB importiert und mit `mariadb-check`
|
||||
geprüft. Produktive Anwendungen werden dabei nicht angehalten oder überschrieben.
|
||||
|
||||
## Speicherbedarf und Bereinigung
|
||||
|
||||
Wegen des knappen Speichers auf Gemini läuft immer nur **eine Komponente**:
|
||||
|
||||
1. Archiv herunterladen; Größe und SHA-256 gegen das Manifest prüfen.
|
||||
2. Archivstruktur und Kompression vollständig validieren.
|
||||
3. Platz für die entpackte Komponente abschätzen und diese mit GNU tar entpacken.
|
||||
4. Das komprimierte Archiv sofort löschen.
|
||||
5. Bei einem DB-Dump eine frische Test-DB starten, importieren und Tabellen prüfen.
|
||||
6. Den eigenen DB-Container samt eventuellen anonymen Volumes entfernen, danach
|
||||
alle temporären Dateien dieser Komponente löschen; erst dann die nächste laden.
|
||||
|
||||
Es liegt somit nie der gesamte Sicherungssatz gleichzeitig auf der Platte.
|
||||
Der Spitzenbedarf ist ungefähr das Maximum aus:
|
||||
|
||||
- komprimiertem Archiv plus entpackter **größter Komponente** und Dateisystem-Overhead;
|
||||
- SQL-Dump plus zugehöriger Testdatenbank einschließlich MariaDB-Systemtabellen,
|
||||
Indexen und temporären DB-Dateien.
|
||||
|
||||
Standard: höchstens **4 GiB geplantes Test-Arbeitsvolumen**, zusätzlich mindestens
|
||||
**2 GiB freie Reserve**. Die Komponente muss vollständig in dieses Budget passen;
|
||||
sonst scheitert der Test mit einem Bericht und bereinigt seine Dateien. Es wird
|
||||
nicht still auf eine Zufallsdatei oder eine Teilprüfung ausgewichen. Das Budget
|
||||
lässt sich nach Prüfung mit `df -h /var/backups/app-backup` anpassen.
|
||||
|
||||
Vor Download und Entpacken wird der Bedarf geprüft. Während externer Prozesse
|
||||
wird freier Speicher laufend kontrolliert, die tatsächliche Belegung regelmäßig
|
||||
nachgemessen und bei Grenzverletzung abgebrochen. Dies ist **keine harte
|
||||
Dateisystemquote**: schnelle DB-Schreibvorgänge und fremde Prozesse können zwischen
|
||||
Messungen weiteren Platz belegen. Daher die Reserve nicht knapp auf null setzen.
|
||||
Für den DB-Start werden zusätzlich mindestens 512 MiB eingeplant; das ist eine
|
||||
Mindestreserve, keine obere Grenze für die Größe der importierten Datenbank.
|
||||
|
||||
Normale Fehler, Speicher-/Zeitlimits und SIGTERM/SIGINT führen zur Bereinigung.
|
||||
Nach einem harten Abbruch führt systemd `ExecStopPost` aus. Bei einem Fehler wird
|
||||
außerdem der Bereinigungsdienst gestartet; er versucht eine fehlgeschlagene
|
||||
Bereinigung alle 15 Minuten erneut. Ein aktivierter Boot-Bereinigungsdienst und
|
||||
der nächste Teststart räumen unterbrochene Tests ebenfalls auf. Beide verwenden
|
||||
dieselbe Sperre wie Backup und Update.
|
||||
|
||||
Kann beispielsweise der Docker-Daemon nicht erreicht und der Testcontainer nicht
|
||||
sicher entfernt werden, lautet das Ergebnis **FAIL / Bereinigung FAILED**. Die
|
||||
Dateien werden dann nicht unter einer möglicherweise noch laufenden DB gelöscht.
|
||||
Die Bereinigung wird wiederholt; ein solcher Zustand darf nicht als Erfolg gelten.
|
||||
Bei einem ausgeschalteten Server erfolgt sie naturgemäß erst nach dem Neustart.
|
||||
|
||||
Nur der eindeutig zu diesem Test gehörende Container und das markierte
|
||||
`$WORKDIR/restore-test/scratch` werden bereinigt. Vorhandene Backups, reguläre
|
||||
manuelle Restore-Verzeichnisse und fremde Docker-Ressourcen werden nicht gelöscht.
|
||||
Es gibt kein allgemeines `docker prune`. Das einmal vorbereitete DB-Image bleibt
|
||||
als wiederverwendbare Abhängigkeit erhalten; sein einmaliger Platzbedarf kommt
|
||||
zum Arbeitsbudget hinzu. Images werden im Test weder heruntergeladen noch gebaut.
|
||||
|
||||
## Einmalige Vorbereitung auf Gemini
|
||||
|
||||
Am 12.09.2026 lesend auf Gemini festgestellt: MariaDB **10.11.18**, Docker-CLI
|
||||
vorhanden und rund **9,1 GiB frei auf `/var`**. Für diesen Stand passt die unten
|
||||
genannte Serie `mariadb:10.11`. Die freie Kapazität ist eine Momentaufnahme;
|
||||
vor Image-Download und Test nochmals prüfen.
|
||||
|
||||
Docker Engine und CLI müssen lokal vorhanden sein. Verwendet wird ausschließlich
|
||||
`unix:///var/run/docker.sock`. Zur produktiven MariaDB passende Major-/Minor-Version
|
||||
ermitteln:
|
||||
|
||||
```bash
|
||||
sudo mariadbd --no-defaults --version
|
||||
sudo docker --host unix:///var/run/docker.sock info
|
||||
```
|
||||
|
||||
Beispiel **nur wenn die Produktion MariaDB 10.11.x verwendet**:
|
||||
|
||||
```bash
|
||||
sudo docker --host unix:///var/run/docker.sock pull mariadb:10.11
|
||||
```
|
||||
|
||||
Bei einer anderen Produktionsversion den Tag entsprechend ersetzen. Platz für
|
||||
Image und Testdaten vorab prüfen. Ein vorhandenes kompatibles Image kann
|
||||
wiederverwendet werden; für reproduzierbare Tests kann statt des Tags ein Digest
|
||||
konfiguriert werden. Bei einem Major-/Minor-Upgrade der Produktions-DB auch das
|
||||
Testimage prüfen und aktualisieren.
|
||||
|
||||
Vor dem Start des neuen Update-Pakets `/etc/app-backup/app-backup.conf` bearbeiten:
|
||||
|
||||
```bash
|
||||
ENABLE_RESTORE_TEST="true"
|
||||
RESTORE_TEST_DB_IMAGE="mariadb:10.11" # Beispiel: an Produktionsversion anpassen!
|
||||
RESTORE_TEST_MAX_WORK_GB=4
|
||||
RESTORE_TEST_MIN_FREE_GB=2
|
||||
RESTORE_TEST_MAX_AGE_HOURS=72
|
||||
RESTORE_TEST_TIMEOUT=39600 # 11 Stunden, inklusive Remote-Arbeit
|
||||
MAIL_HOSTNAME="gemini.jr-it-services.de"
|
||||
```
|
||||
|
||||
Danach das neue `.run`-Update wie in [UPDATE_ANLEITUNG.md](UPDATE_ANLEITUNG.md)
|
||||
installieren. Der Starter prüft das lokale Image und die freie Reserve, bevor er
|
||||
installiert. Er lädt kein Image nach. Nach der normalen Update-Abnahme aktiviert
|
||||
er den Restore-Test-Timer und den Boot-Bereinigungsdienst, wenn
|
||||
`ENABLE_RESTORE_TEST=true` gesetzt ist. Der Timer kann bei einem nachzuholenden
|
||||
Termin sofort starten. Das Update-Ergebnis bestätigt weiterhin das Paket und sein
|
||||
Abnahmebackup; der neue Restore-Test hat ein eigenes Ergebnis und eine eigene Mail.
|
||||
|
||||
Bestehende Konfigurationen ohne die neue Option bleiben kompatibel: Der monatliche
|
||||
Test ist zunächst deaktiviert. Eine reine Installation mit `install.sh` installiert
|
||||
die Units und erhält bestehende Zeitpläne; für die erstmalige Aktivierung dann:
|
||||
|
||||
```bash
|
||||
sudo bash /usr/local/sbin/app-restore-test.sh --check
|
||||
sudo systemctl enable app-restore-test-cleanup.service
|
||||
sudo systemctl enable --now app-restore-test.timer
|
||||
```
|
||||
|
||||
`--check` prüft Konfiguration, lokale Werkzeuge, Image-Metadaten und freie Reserve.
|
||||
Es lädt kein Backup herunter und führt keinen DB-Import aus. Den ersten echten
|
||||
Test nach Installation anstoßen und sein Ergebnis kontrollieren:
|
||||
|
||||
```bash
|
||||
sudo systemctl start --no-block app-restore-test.service
|
||||
sudo journalctl -fu app-restore-test.service --no-pager
|
||||
sudo cat /var/backups/app-backup/restore-test/state.json
|
||||
sudo systemctl list-timers app-restore-test.timer --no-pager
|
||||
```
|
||||
|
||||
Bei angepasstem `WORKDIR` die Pfade entsprechend ersetzen. Einen bestimmten Satz
|
||||
kann man manuell prüfen; Altersgrenze und alle übrigen Prüfungen bleiben aktiv:
|
||||
|
||||
```bash
|
||||
sudo bash /usr/local/sbin/app-restore-test.sh \
|
||||
--remote-run appbackup_DATUM_UHRZEIT_ID
|
||||
```
|
||||
|
||||
## Zeitplan, Auswahl und Ergebnisse
|
||||
|
||||
Der Standardtermin ist der **1. jedes Monats um 06:00 Uhr Serverzeit**, mit bis zu
|
||||
15 Minuten zufälliger Verzögerung. `Persistent=true` holt verpasste Termine nach.
|
||||
Ein belegter Backup-/Restore-/Update-Lock liefert Exit-Code 75; systemd versucht
|
||||
es eine Stunde später erneut. Ein echter Prüffehler löst eine Fehlermail aus und
|
||||
wird nicht durch automatische Neuversuche als Erfolg verdeckt.
|
||||
|
||||
Unvollständige neuere Sätze ohne `COMPLETE` werden mit Protokolleintrag übersprungen.
|
||||
Für den neuesten abgeschlossenen Satz müssen Marker und Manifest zusammenpassen,
|
||||
die Lauf-ID muss dem Remote-Verzeichnis entsprechen, alle konfigurierten
|
||||
Komponenten müssen enthalten sein und die Remote-Dateiliste muss vollständig sein.
|
||||
Ein beschädigter abgeschlossener Satz wird nicht durch einen älteren ersetzt.
|
||||
Standardmäßig sind maximal 72 Stunden alte Backups zulässig; ein ungültiger
|
||||
Zeitstempel oder ein Zeitstempel mehr als fünf Minuten in der Zukunft scheitert.
|
||||
|
||||
Der Bericht verwendet die bisherigen Mail-Einstellungen (`ENABLE_MAIL_REPORT`,
|
||||
`MAIL_TO`, `MAIL_FROM`, `MAIL_HOSTNAME`, `MAIL_SENDMAIL_BIN`, `MAIL_SEND_TIMEOUT`).
|
||||
Der Betreff lautet beispielsweise:
|
||||
|
||||
```text
|
||||
[app-restore-test] SUCCESS gemini.jr-it-services.de appbackup_2026-…
|
||||
```
|
||||
|
||||
Enthalten sind Alter und ID des Sicherungssatzes, Laufzeit, Status je Komponente,
|
||||
Archivgröße, Anzahl der Archiveinträge, Anzahl der importierten Tabellen sowie
|
||||
**Bereinigung: OK/FAILED**. Es werden weder SQL-Inhalte noch Dateiinhalte in die
|
||||
Mail oder das Testprotokoll übernommen. Der Status je Komponente wird erst nach
|
||||
seiner Bereinigung als erfolgreich gespeichert.
|
||||
|
||||
Unter `$WORKDIR/restore-test/` bleiben nur kleine Betriebsnachweise erhalten:
|
||||
|
||||
| Datei | Bedeutung |
|
||||
| --- | --- |
|
||||
| `state.json` | Letzter/aktueller Lauf; bei Start sofort `RUNNING`, abschließend `SUCCESS` oder `FAIL`, Bereinigungs- und Mailstatus |
|
||||
| `last-success.json` | Letzter vollständig geprüfter **und bereinigter** Satz; bleibt bei einem späteren Fehler erhalten |
|
||||
| `report.eml` | Jeweils letzter Mailbericht, auch bei Zustellungsfehler |
|
||||
| `restore-test.log` und `.1` | Rotierendes Testprotokoll, jeweils ungefähr 512 KiB |
|
||||
| `.owner` | Kennzeichnung des Testverzeichnisses für sichere Bereinigung |
|
||||
|
||||
Temporäre Prozess-/Container-Marker verschwinden nach erfolgreicher Bereinigung.
|
||||
Die Nachweise sind root-privat. Für die unabhängige Überwachung sollte ein
|
||||
`RUNNING`, `cleanup != OK`, `mail == FAILED` oder ein mehr als etwa 40 Tage alter
|
||||
`last-success.json` auffallen. Ein erfolgreicher Restore bei fehlgeschlagener
|
||||
Mail bleibt inhaltlich `SUCCESS`, liefert aber Exit-Code 1 und `mail: FAILED`.
|
||||
Der SMTP-Versand ist wie bisher eine Übergabe an den lokalen MTA, kein Nachweis
|
||||
der tatsächlichen Zustellung.
|
||||
|
||||
Manuelle Wiederholung der Bereinigung, auch bei deaktivierter Testfunktion:
|
||||
|
||||
```bash
|
||||
sudo bash /usr/local/sbin/app-restore-test.sh --cleanup
|
||||
sudo journalctl -u app-restore-test-cleanup.service -n 50 --no-pager
|
||||
```
|
||||
|
||||
Zum Aussetzen des Zeitplans `app-restore-test.timer` deaktivieren. Den
|
||||
Boot-Bereinigungsdienst aktiviert lassen, bis eventuell unterbrochene Testdaten
|
||||
nachweislich entfernt wurden.
|
||||
|
||||
## Isolation und Aussagekraft
|
||||
|
||||
Jeder DB-Dump erhält eine neue Datenbankinstanz. Der Container hat ausschließlich
|
||||
ein Bind-Mount auf sein privates DB-Testverzeichnis, kein Netzwerk, keine
|
||||
veröffentlichten Ports, keine zusätzlichen Linux-Capabilities, ein schreibgeschütztes
|
||||
Root-Dateisystem und keinen persistenten Docker-Logtreiber. Er läuft mit maximal
|
||||
512 MiB RAM, 128 Prozessen und einer CPU. MariaDB liest keine Hostkonfiguration,
|
||||
verwendet nur ihren eigenen Unix-Socket und hat Event-Scheduler und Binlog aus.
|
||||
Der SQL-Client verwendet Batch-/Binary-Modus, damit Dumps keine lokalen
|
||||
Client-Kommandos ausführen. Docker-API-Ausgaben und SQL-Fehlertexte gelangen nicht
|
||||
ungefiltert in Logs. Hintergrunddatenbanken können keine produktiven DB-Sockets
|
||||
oder Konfigurationen erreichen.
|
||||
|
||||
Der Test belegt Download, Entschlüsselung, Integrität, tatsächliches Entpacken und
|
||||
DB-Import mit Tabellenprüfung. Er belegt weder eine vollständige Datenabdeckung,
|
||||
die schon im Backup fehlte, noch fachliche Konsistenz zwischen Anwendungen. Ein
|
||||
Start-/Funktionstest von Nextcloud, WordPress und Gitea auf einer getrennten VM
|
||||
bleibt eine zusätzliche Abnahme. Dateibesitz/ACLs werden beim Entpacken angefordert;
|
||||
die korrekte Anwendung aller Sicherheitskontexte auf einem neuen Produktivsystem
|
||||
bedarf dort einer eigenen Prüfung.
|
||||
|
||||
Technische Referenzen: [Docker run](https://docs.docker.com/reference/cli/docker/container/run),
|
||||
[Docker ohne Netzwerk](https://docs.docker.com/engine/network/drivers/none/),
|
||||
[MariaDB-Systemtabellen initialisieren](https://mariadb.com/docs/server/clients-and-utilities/deployment-tools/mariadb-install-db),
|
||||
[MariaDB-Serveroptionen](https://mariadb.com/docs/server/server-management/starting-and-stopping-mariadb/mariadbd-options).
|
||||
|
||||
## Entwicklung und lokale Verifikation
|
||||
|
||||
Der Standardlauf (`tests/run.sh` im bestehenden Testcontainer) prüfte am
|
||||
12.09.2026 81 Fälle: 80 erfolgreich, der separate Docker-Test planmäßig
|
||||
übersprungen. Zusätzlich bestanden alle 19 gezielten Restore-Tests mit lokalem
|
||||
Docker-Daemon und dem offiziellen `mariadb:10.11` (29,0 Sekunden). Vier gezielte
|
||||
Sperr-/Installer-Tests bestanden ebenfalls (23,7 Sekunden). Dazu gehören echte
|
||||
Crypt-Downloads, vollständiges Entpacken, DB-Import/Tabellenprüfung, ungültiges SQL,
|
||||
SIGTERM/SIGKILL, fehlender Platz, beschädigte Statusdateien und ein zeitweise nicht
|
||||
erreichbarer Docker-Daemon. Container-Isolation und vollständige Bereinigung
|
||||
werden nachgeprüft. Die bereits vorhandene Entwicklungstest-DB mit MariaDB 11.8.6
|
||||
wurde ebenfalls erfolgreich verwendet. Es werden keine echten Mails versendet.
|
||||
|
||||
Auf einem separaten Entwicklungs-/Testrechner mit den Abhängigkeiten aus
|
||||
`tests/Dockerfile`, Docker-CLI und lokal vorbereitetem Image lässt sich die
|
||||
gezielte Prüfung aus dem Quell-Checkout starten:
|
||||
|
||||
```bash
|
||||
sudo env RESTORE_TEST_REAL_DOCKER_IMAGE=mariadb:10.11 \
|
||||
python3 -m unittest discover -s tests -p test_restore_test.py -v
|
||||
```
|
||||
|
||||
Diese Tests verwenden ausschließlich selbst angelegte temporäre Konfigurationen,
|
||||
Daten und Container. Die vollständige Basissuite enthält zusätzlich simulierte
|
||||
systemd-/Installertests und gehört in den bestehenden isolierten Testcontainer.
|
||||
Die neuen Units wurden mit `systemd-analyze verify` geprüft. Ihre tatsächliche
|
||||
Ausführung unter Rocky/SELinux und der produktive OneDrive-/MTA-Pfad sind nach
|
||||
Installation auf Gemini noch abzunehmen.
|
||||
Reference in new issue
Block a user