Kurs NestJS · Moduł 9: Deployment i infrastruktura

Backup i Recovery - ratowanie tributów po katastrofie

4 min czytania
W tej lekcji4

Strażniku tributów! Pewnej nocy migracja z literówką kasuje kolumnę, a rano okazuje się, że ostatnia działająca kopia bazy ma trzy tygodnie, bo skrypt backupu od miesiąca kończył się błędem i nikt tego nie zauważył. Nawet najlepsze forty mogą lec w gruzach podczas oblężenia, dlatego każdy mądry legionista ma plan ratowania swoich tributów.

Dwie liczby, od których wszystko się zaczyna

Plan odtwarzania zaczyna się od dwóch celów. RTO (Recovery Time Objective) to maksymalny czas przywrócenia usługi po awarii. RPO (Recovery Point Objective) to dopuszczalna utrata danych, mierzona w czasie: backup raz na dobę oznacza, że stracisz do 24 godzin pracy. Chcesz mniej - potrzebujesz częstszych kopii albo ciągłego archiwizowania dziennika zmian.

Kopie trzymaj według zasady 3-2-1: trzy kopie, na dwóch różnych nośnikach, w tym jedna poza główną lokalizacją.

Database Backup Strategy

Skrypt dla PostgreSQL robi zrzut, sprawdza go, wysyła do S3 i sprząta stare kopie:

1#!/bin/bash
2# backup-database.sh
3set -euo pipefail
4
5# Bez adresu bazy skrypt kończy się błędem, zamiast zrzucić domyślną bazę
6: "${DATABASE_URL:?Ustaw DATABASE_URL}"
7BACKUP_DIR="${BACKUP_DIR:-/backups}"
8DATE=$(date +%Y%m%d_%H%M%S)
9BACKUP_NAME="legionariusze_legion_backup_${DATE}.dump"
10
11# Create backup (format custom jest już skompresowany)
12echo "Creating database backup..."
13pg_dump --format=custom --file="$BACKUP_DIR/$BACKUP_NAME" "$DATABASE_URL"
14
15# Verify backup - pg_restore musi odczytać spis zawartości
16pg_restore --list "$BACKUP_DIR/$BACKUP_NAME" > /dev/null
17
18# Upload to S3
19aws s3 cp "$BACKUP_DIR/$BACKUP_NAME" s3://legionariusze-legion-backups/
20
21# Cleanup old backups (keep last 7 days)
22find "$BACKUP_DIR" -name "*.dump" -mtime +7 -delete
23
24echo "Backup completed: ${BACKUP_NAME}"

set -euo pipefail przerywa skrypt przy pierwszym błędzie, nieustawionej zmiennej albo awarii w potoku. Linijka z :? to poprawka: w pierwszej wersji puste DATABASE_URL sprawiało, że pg_dump po cichu łączył się z domyślną bazą lokalną. Format custom jest już skompresowany, więc osobny gzip zniknął, a pg_restore --list sprawdza, czy plik da się odczytać.

W MongoDB, na której stoi wiele aplikacji NestJS, ta sama strategia wygląda tak:

1#!/bin/bash
2# backup-mongo.sh - ta sama strategia dla MongoDB
3set -euo pipefail
4
5: "${MONGODB_URI:?Ustaw MONGODB_URI}"
6BACKUP_DIR="${BACKUP_DIR:-/backups/mongo}"
7TIMESTAMP=$(date +%Y%m%d_%H%M%S)
8ARCHIVE="$BACKUP_DIR/imperium_$TIMESTAMP.archive.gz"
9
10mkdir -p "$BACKUP_DIR"
11
12# Zrzut bazy do jednego skompresowanego archiwum
13mongodump --uri="$MONGODB_URI" --archive="$ARCHIVE" --gzip
14
15# Weryfikacja: próbne odtworzenie bez zapisu danych
16mongorestore --uri="$MONGODB_URI" --archive="$ARCHIVE" --gzip --dryRun
17
18# Suma kontrolna do sprawdzenia kopii po przesłaniu
19sha256sum "$ARCHIVE" > "$ARCHIVE.sha256"
20
21# Rotacja: usuń kopie starsze niż 7 dni
22find "$BACKUP_DIR" -name "imperium_*" -mtime +7 -delete
23
24echo "Backup completed: $ARCHIVE"

mongodump zapisuje bazę do jednego skompresowanego archiwum, mongorestore --dryRun próbuje je odczytać bez zapisywania danych, a suma kontrolna pozwala sprawdzić kopię po przesłaniu. Próba na sucho to jednak nie test: kopia, której nigdy nie odtworzyłeś na osobnej bazie, jest tylko nadzieją.

Disaster Recovery Plan

Plan odtwarzania warto zapisać jako kod, żeby w stresie nikt nie pominął kroku:

1// Recovery service
2@Injectable()
3export class RecoveryService {
4  async restoreFromBackup(backupFile: string) {
5    // 1. Download backup from S3
6    const backup = await this.downloadBackup(backupFile);
7
8    // 2. Create new database
9    await this.createRecoveryDatabase();
10
11    // 3. Restore data
12    await this.restoreData(backup);
13
14    // 4. Verify integrity
15    await this.verifyDataIntegrity();
16
17    // 5. Switch traffic
18    await this.switchTraffic();
19  }
20}

Metody serwisu to szkielet do uzupełnienia pod Twoją infrastrukturę. Kolejność ma znaczenie: nową bazę tworzysz obok uszkodzonej, a nie na niej, żeby zachować ślady do analizy, a integralność sprawdzasz liczbą rekordów, sumą kontrolną i testem dymnym aplikacji, zanim przełączysz ruch. Cały proces disaster recovery przebiega tak:

  1. wykrycie i ocena incydentu,
  2. aktywacja planu disaster recovery,
  3. przywrócenie serwisów z backupów,
  4. walidacja integralności i wznowienie operacji.

Infrastruktura jako kod

Dane to połowa odtwarzania - drugą jest infrastruktura. Infrastructure as Code (IaC), np. Terraform albo Pulumi, opisuje serwery, sieci i bazy w plikach trzymanych w repozytorium. Daje wersjonowanie, powtarzalność i automatyzację: po katastrofie odtwarzasz środowisko jednym poleceniem, także w innym regionie, zamiast klikać w konsoli z pamięci.

Tak samo pewny musi być obraz aplikacji. Bezpieczny pipeline budowania obrazów Docker ma etapy:

  1. zoptymalizowany Dockerfile z multi-stage build,
  2. skanowanie bezpieczeństwa i podatności, np. Trivy,
  3. tagowanie obrazu i wypchnięcie do rejestru,
  4. podpis obrazu i atestacja, np. przez cosign.

Podpis powstaje po wypchnięciu, bo dotyczy konkretnego skrótu obrazu w rejestrze; przy odtwarzaniu wdrażasz tylko obrazy z ważnym podpisem.

Polecam Ci raz w miesiącu ćwiczyć pełne odtworzenie na świeżym środowisku i mierzyć, ile trwa - to jedyny uczciwy sprawdzian RTO. W projekcie kończącym moduł połączysz backup z całym wdrożeniem.

Pamiętaj: fort odbudujesz z planów, ale tributów nie odtworzysz bez kopii, którą ktoś naprawdę sprawdził.

Kod do tej lekcji: scripts/backup.sh
1#!/bin/bash
2# Backup i Recovery - Ratowanie Tributyw po Katastrofie
3
4# === KONFIGURACJA ===
5BACKUP_DIR="/backups/roman-empire"
6DB_NAME="roman_empire"
7DB_HOST="localhost"
8DB_PORT="27017"
9RETENTION_DAYS=7
10TIMESTAMP=$(date +%Y%m%d_%H%M%S)
11BACKUP_PATH="$BACKUP_DIR/$TIMESTAMP"
12
13echo "=== Roman Empire Database Backup ==="
14echo "Timestamp: $TIMESTAMP"
15echo "Database: $DB_NAME"
16
17# === 1. MONGODUMP ===
18mkdir -p "$BACKUP_PATH"
19
20echo "Starting mongodump..."
21mongodump \
22  --host "$DB_HOST" \
23  --port "$DB_PORT" \
24  --db "$DB_NAME" \
25  --out "$BACKUP_PATH"
26
27# Sprawdz czy backup sie powiodl
28if [ $? -eq 0 ]; then
29  echo "Backup successful!"
30else
31  echo "ERROR: Backup FAILED!"
32  exit 1
33fi
34
35# === 2. KOMPRESJA ===
36echo "Compressing backup..."
37tar -czf "$BACKUP_PATH.tar.gz" -C "$BACKUP_DIR" "$TIMESTAMP"
38rm -rf "$BACKUP_PATH"
39echo "Compressed: $BACKUP_PATH.tar.gz"
40
41# === 3. ROTACJA (usun stare backupy) ===
42echo "Removing backups older than $RETENTION_DAYS days..."
43find "$BACKUP_DIR" -name "*.tar.gz" \
44  -mtime +$RETENTION_DAYS -delete
45
46# === 4. RESTORE (odtwarzanie) ===
47# mongorestore \
48#   --host "$DB_HOST" \
49#   --port "$DB_PORT" \
50#   --db "$DB_NAME" \
51#   --drop \
52#   "$BACKUP_PATH/$DB_NAME"
53
54# === 5. CRON - automatyczne backupy ===
55# Dodaj do crontab:
56# 0 2 * * * /scripts/backup.sh >> /var/log/backup.log 2>&1
57# (codziennie o 2:00 w nocy)
58
59# === 6. STRATEGIA BACKUPOW ===
60# - Daily: pelny backup co 24h
61# - Incremental: zmiany co 6h
62# - Offsite: kopia na zewnetrznym serwerze
63# - Test restore: co miesiac testuj odtwarzanie!
64
65echo "=== Backup Complete ==="
66echo "File: $BACKUP_PATH.tar.gz"
67echo "Size: $(du -h "$BACKUP_PATH.tar.gz" | cut -f1)"
68

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Infrastructure as Code (IaC) pozwala na:

  2. 2. RTO (Recovery Time Objective) i RPO (Recovery Point Objective) określają:

Zadania praktyczne w grze

  • Edytor kodu

    Napisz skrypt backup z mongodump, rotacją i weryfikacją integralności

  • Układanie w pionie

    Uporządkuj kroki procesu disaster recovery:

  • Układanie w pionie

    Ułóż etapy bezpiecznego pipeline budowania obrazów Docker:

Przydatne artykuły