Przejdź do treści
Lekcja 98 z 117

Kurs › Moduł 10 › Lekcja 98

Kopie zapasowe na serio

⏱ ok. 13 min czytania· moduł 10: Usługi i serwery

Po tej lekcji: kursant zna zasadę 3-2-1, rozróżnia kopię pełną, przyrostową i różnicową, robi kopie przez rsync, tar, borg i restic, a przede wszystkim umie z nich odtworzyć dane i sprawdzić, że odtworzenie działa.

Wstęp

„Macie kopię, prawda?”. Każdy admin ma kopię. Dopóki ktoś nie poprosi, żeby z niej coś przywrócić. Wtedy okazuje się, że skrypt od pół roku kończył się błędem, którego nikt nie czytał. Albo że kopia leżała na tym samym dysku, który właśnie padł. Albo że jest, ale zaszyfrowana hasłem, które znał tylko kolega, który odszedł w marcu.

Powiem to raz i będę powtarzał do końca kursu: nikogo nie obchodzi, czy robisz kopie. Obchodzi ich, czy umiesz odtworzyć dane. Dziś zbudujemy porządny system kopii, a potem zrobimy rzecz, której nie robi prawie nikt: sprawdzimy, czy z tej kopii da się coś odzyskać.

Zasada 3-2-1

Wyobraź sobie, że masz jedyne zdjęcia z wesela. Trzymasz je na laptopie. Laptop kradną. Dobra, nauczka, robisz kopię na dysk zewnętrzny. Trzymasz go… w torbie z laptopem. Kradną torbę. Kolejna nauczka, kopiujesz na drugi dysk i trzymasz w szufladzie w domu. Mieszkanie zalewa sąsiad z góry. Wszystko w jednym miejscu, wszystko mokre.

Z takich nauczek powstała zasada 3-2-1:

  • 3 kopie danych, licząc oryginał,
  • na 2 różnych nośnikach albo w dwóch różnych systemach,
  • 1 kopia poza budynkiem.

W serwerowni wygląda to tak: dane na serwerze, kopia na serwerze kopii w tej samej firmie, druga kopia w innym mieście albo w chmurze.

Do tego dwie rzeczy, których zasada wprost nie mówi, a dziś są równie ważne. Po pierwsze: przynajmniej jedna kopia powinna być taka, której atakujący nie skasuje, nawet jak przejmie serwer. Program szyfrujący dane dla okupu najpierw szuka kopii. Jeśli Twój serwer może skasować kopie, to może je skasować też ten, kto go przejął. Po drugie: kopia jest warta tyle, ile ostatni udany test odtwarzania.

Pełna, przyrostowa, różnicowa

Trzy sposoby, jak często i ile kopiować. Wyobraź sobie zeszyt z notatkami ze studiów.

  • Pełna kopia — kserujesz cały zeszyt. Bezpieczne, ale drogie i długie.
  • Przyrostowa (incremental) — kserujesz tylko strony zapisane od ostatniego ksero, jakiegokolwiek. Szybkie i tanie. Ale żeby odtworzyć zeszyt, potrzebujesz pełnej i WSZYSTKICH przyrostów po kolei. Zgubisz jeden ze środka, łańcuch pęka.
  • Różnicowa (differential) — kserujesz wszystko, co się zmieniło od ostatniej PEŁNEJ. Każdego dnia trochę więcej stron. Do odtworzenia wystarczą dwie rzeczy: pełna i ostatnia różnicowa.

Pokażę wszystkie trzy na zwykłym tar. Klucz to opcja --listed-incremental, czyli plik-notes, w którym tar zapisuje, co już widział.

mkdir -p dane kopie
for i in 1 2 3; do echo "Faktura nr $i" > dane/faktura$i.txt; done
head -c 5M /dev/urandom > dane/zdjecie.jpg

# niedziela: pełna
tar -czf kopie/pelna.tar.gz --listed-incremental=kopie/stan.snar dane
cp kopie/stan.snar kopie/stan-pelna.snar

# poniedziałek: nowa faktura, przyrost
echo "Faktura nr 4" > dane/faktura4.txt
tar -czf kopie/przyrost1.tar.gz --listed-incremental=kopie/stan.snar dane

# wtorek: kolejna, przyrost
echo "Faktura nr 5" > dane/faktura5.txt
tar -czf kopie/przyrost2.tar.gz --listed-incremental=kopie/stan.snar dane

# wtorek, wersja różnicowa: zawsze od notesu z pełnej
cp kopie/stan-pelna.snar kopie/stan-roz.snar
tar -czf kopie/roznicowa.tar.gz --listed-incremental=kopie/stan-roz.snar dane

Co jest w środku:

ls -lh kopie/*.tar.gz
tar -tzf kopie/przyrost2.tar.gz
tar -tzf kopie/roznicowa.tar.gz
-rw-r--r-- 1 root root 5.1M Nov  4 02:38 kopie/pelna.tar.gz
-rw-r--r-- 1 root root  225 Nov  4 02:38 kopie/przyrost1.tar.gz
-rw-r--r-- 1 root root  208 Nov  4 02:38 kopie/przyrost2.tar.gz
-rw-r--r-- 1 root root  238 Nov  4 02:38 kopie/roznicowa.tar.gz

dane/
dane/faktura5.txt

dane/
dane/faktura4.txt
dane/faktura5.txt

Pełna: 5 MB. Przyrosty: po dwieście bajtów. Wtorkowy przyrost ma tylko fakturę 5, różnicowa ma 4 i 5. Dokładnie jak w zeszycie.

I teraz najważniejsze, odtworzenie. Przy przyrostach rozpakowujesz wszystko po kolei, od pełnej:

mkdir odtw
for f in pelna przyrost1 przyrost2; do
    tar -xzf kopie/$f.tar.gz --listed-incremental=/dev/null -C odtw
done
diff -r dane odtw/dane && echo IDENTYCZNE
IDENTYCZNE

--listed-incremental=/dev/null przy rozpakowaniu mówi tar-owi: „to są kawałki łańcucha, odtwarzaj także skasowania”. Jak ktoś między kopiami usunie plik, przyrost to zapamięta i przy odtwarzaniu plik też zniknie. diff -r porównuje dwa drzewa folderów i milczy, jeśli są identyczne. Milczenie diff-a to najpiękniejsza cisza w życiu admina.

rsync i migawki na twardych dowiązaniach

rsync znasz z modułu o sieci. Kopiuje tylko różnice i świetnie nadaje się do kopii na drugi serwer. Ale zwykły rsync -a --delete to lustro, nie kopia. Skasujesz plik w źródle, w nocy zniknie i w lustrze.

Jest sztuczka, która z rsync robi system migawek. Każdy dzień w osobnym folderze, ale niezmienione pliki nie zajmują miejsca drugi raz. To opcja --link-dest. Pamiętasz twarde dowiązania? Jeden plik, kilka nazw, jak jedno mieszkanie z dwoma tabliczkami na drzwiach.

rsync -a --delete dane/ migawki/2026-11-03/
echo "Faktura nr 6" > dane/faktura6.txt
rsync -a --delete --link-dest=../2026-11-03 dane/ migawki/2026-11-04/

du -sh migawki/*
ls -li migawki/*/zdjecie.jpg
5.1M	migawki/2026-11-03
8.0K	migawki/2026-11-04

1484127 -rw-r--r-- 2 root root 5242880 Nov  4 02:38 migawki/2026-11-03/zdjecie.jpg
1484127 -rw-r--r-- 2 root root 5242880 Nov  4 02:38 migawki/2026-11-04/zdjecie.jpg

Druga migawka zajmuje 8 KB, a jest w niej komplet. Ten sam numer i-węzła i licznik dowiązań 2: zdjęcie leży na dysku raz, ma dwie tabliczki. Ścieżka w --link-dest liczy się względem folderu docelowego, stąd ../. Zapamiętaj też ukośnik na końcu źródła: dane/ to „zawartość folderu”, dane to „folder razem z nazwą”.

Przed każdym --delete w nowym skrypcie: -n, czyli próba na sucho.

rm dane/faktura6.txt
rsync -an --delete -v dane/ migawki/2026-11-04/
sending incremental file list
deleting faktura6.txt

Wolisz przeczytać „deleting” na ekranie niż w mailu od księgowej.

borg: kopie z deduplikacją i szyfrowaniem

tar i rsync są wszędzie i to ich siła. Ale zarządzanie przyrostami, szyfrowanie i usuwanie starych kopii musisz sklejać sam. Do tego są narzędzia z deduplikacją. Pierwsze to borg.

Deduplikacja to szatnia w teatrze. Sto osób przyszło w identycznej czarnej kurtce, a szatniarz wiesza jedną i daje sto numerków. Borg tnie pliki na kawałki, liczy z każdego odcisk palca i każdy kawałek zapisuje tylko raz, choćby występował w stu kopiach.

[Ubuntu] sudo apt install borgbackup. [RHEL] Z repozytorium EPEL: sudo dnf install borgbackup.

export BORG_PASSPHRASE='Dlugie-Haslo-Do-Kopii-2026'
borg init --encryption=repokey repo-borg
borg create --stats --compression zstd repo-borg::'{hostname}-{now:%Y-%m-%d_%H%M}' dane

Repozytorium to magazyn, archiwum to jedna kopia w magazynie. {hostname} i {now} borg sam zamienia na nazwę serwera i datę. repokey znaczy, że klucz szyfrujący leży w repozytorium, zamknięty Twoim hasłem.

Dopisuję jedną fakturę i robię drugą kopię. Statystyki z drugiego razu:

Archive name: vm-2026-11-04_0240
Number of files: 8
                       Original size      Compressed size    Deduplicated size
This archive:                5.24 MB              5.24 MB                609 B
All archives:               10.49 MB             10.49 MB              5.25 MB

Druga kopia ma komplet 5 MB danych, a dołożyła do magazynu 609 bajtów. Każde archiwum wygląda jak pełna kopia, a kosztuje jak przyrostowa. Najlepsze z obu światów. (Kompresja nic nie dała, bo moje „zdjęcie” to losowe bajty, a tych nie da się ścisnąć.)

borg list repo-borg
borg diff repo-borg::vm-2026-11-04_0239 vm-2026-11-04_0240
vm-2026-11-04_0239                   Wed, 2026-11-04 02:39:04 [45e67d1d...]
vm-2026-11-04_0240                   Wed, 2026-11-04 02:40:05 [d9a19fff...]

added          13 B dane/faktura7.txt

Odtwarzanie. borg extract rozpakowuje do BIEŻĄCEGO folderu, więc najpierw wchodzisz do pustego:

mkdir odtw-borg && cd odtw-borg
borg extract ../repo-borg::vm-2026-11-04_0240
cd .. && diff -r dane odtw-borg/dane && echo IDENTYCZNE

# jeden plik ze starszej kopii
borg extract ../repo-borg::vm-2026-11-04_0239 dane/faktura2.txt

Stare kopie usuwasz zasadą „ile dziennych, tygodniowych, miesięcznych”:

borg prune --list --keep-daily=7 --keep-weekly=4 --keep-monthly=6 repo-borg
borg compact repo-borg
borg check repo-borg

prune wybiera, co skasować. W borgu 1.2 miejsce na dysku zwalnia dopiero compact, jak opróżnienie kosza. check sprawdza, czy magazyn jest cały.

Kopia po sieci to ta sama komenda, tylko repozytorium ma adres: ssh://kopie@backup01/~/repo. Borg łączy się przez SSH, kluczem, jak w module o sieci.

I teraz rzecz, za którą podziękujesz mi kiedyś o trzeciej w nocy:

borg key export repo-borg borg-klucz.txt

Klucz i hasło trzymasz poza serwerem, na przykład w menedżerze haseł firmy. Borg sam to mówi przy init: „you will need both KEY AND PASSPHRASE to access this repo!”. Bez nich kopia jest najlepiej zabezpieczonym śmietnikiem na świecie.

restic: to samo, inaczej

Drugi popularny program to restic. Idea ta sama: deduplikacja, szyfrowanie zawsze włączone, migawki. Różnice: restic to jeden plik wykonywalny i umie pisać prosto do chmury, na przykład do S3, przez SFTP albo przez własny serwer REST. Borg do pracy po sieci potrzebuje borga po drugiej stronie.

[Ubuntu] sudo apt install restic. [RHEL] Według dokumentacji restica z EPEL: sudo dnf install epel-release, potem sudo dnf install restic. (Na samym RHEL-u EPEL włączasz instrukcją ze strony Fedory, na Rocky i Almie wystarczy epel-release.)

export RESTIC_REPOSITORY=/srv/repo-restic
export RESTIC_PASSWORD='Inne-Dlugie-Haslo-2026'
restic init
restic backup dane
echo "Faktura nr 8" > dane/faktura8.txt
restic backup dane
restic snapshots
using parent snapshot a3bf25f8
Files:           1 new,     0 changed,     8 unmodified
Added to the repository: 3.854 KiB (1.279 KiB stored)
snapshot d2e4c2ad saved

ID        Time                 Host        Tags        Paths
---------------------------------------------------------------
a3bf25f8  2026-11-04 02:40:34  vm                      .../dane
d2e4c2ad  2026-11-04 02:40:34  vm                      .../dane

Odtwarzanie do osobnego folderu i jeden plik:

restic restore latest --target odtw-restic
restic restore latest --target odtw-r1 --include /dane/faktura8.txt
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check

latest to najnowsza migawka. forget wybiera, co usunąć, a --prune od razu zwalnia miejsce. Zanim puścisz to na prawdziwym repozytorium, dodaj --dry-run:

keep 1 snapshots:
d2e4c2ad  2026-11-04 02:40:34  vm   last snapshot  .../dane
remove 1 snapshots:
a3bf25f8  2026-11-04 02:40:34  vm                  .../dane

Repozytorium w chmurze albo na innym serwerze to tylko inny adres, na przykład sftp:kopie@backup01:/srv/restic. Dokumentacja restica mówi bez owijania: zgubisz hasło, dane przepadają. Restic powtarza to przy każdym init.

Co wybrać? Borg, jeśli masz własny serwer kopii z SSH. Restic, jeśli kopie mają lecieć do chmury. Oba lepsze niż „mam gdzieś skrypt z tarem, chyba działa”.

Test odtwarzania

Kopia, z której nikt nie próbował niczego odtworzyć, to kot Schrödingera. Dopóki nie otworzysz pudełka, jest jednocześnie dobra i do niczego. Test odtwarzania to otwarcie pudełka w spokojny wtorek, a nie w piątek o 16:52.

Minimum, raz w miesiącu i po każdej zmianie skryptu:

  1. Odtwórz do osobnego folderu albo na osobną maszynę wirtualną. Nigdy na żywe dane.
  2. Porównaj: diff -r dla folderów, sha256sum dla ważnych plików.
  3. Dla baz danych: wczytaj zrzut do pustej bazy i zrób SELECT. Plik .sql o rozmiarze zero też jest plikiem.
  4. Zmierz czas. Jak odtworzenie 2 TB z chmury trwa dwa dni, szef powinien to wiedzieć teraz, a nie w trakcie awarii.
  5. Zapisz wynik: data, co odtwarzałeś, ile trwało, czy się udało.
restic restore latest --target /tmp/test-odtw
diff -r dane /tmp/test-odtw/dane && echo IDENTYCZNE
restic check --read-data-subset=5%

--read-data-subset=5% każe resticowi przeczytać i sprawdzić naprawdę 5% danych w repozytorium, a nie tylko spis treści. Co miesiąc inne 5%.

Jak to działa pod spodem

tar --listed-incremental zapisuje w pliku .snar listę folderów z numerami urządzeń, i-węzłów i czasami zmian. Przy następnym przebiegu porównuje stan z notesem i pakuje tylko to, co nowe. Do archiwum dopisuje też listę zawartości każdego folderu, dzięki temu przy odtwarzaniu wie, co trzeba skasować.

rsync --link-dest dla każdego pliku sprawdza, czy w folderze porównawczym leży identyczny, czyli o tym samym rozmiarze, czasie i prawach. Jeśli tak, zamiast kopiować robi twarde dowiązanie, czyli dopisuje nową nazwę do istniejącego i-węzła.

Borg i restic tną pliki na kawałki o zmiennej długości. Granice kawałków wyznacza funkcja licząca skrót z „okienka” przesuwanego po danych, więc dopisanie bajtu na początku pliku nie przesuwa wszystkich kawałków. Każdy kawałek dostaje odcisk palca, czyli kryptograficzny skrót, i jeśli taki odcisk jest już w repozytorium, kawałek nie jest zapisywany drugi raz. Archiwum to w gruncie rzeczy lista odcisków palców. Dlatego kasowanie kopii nie jest proste: kawałek można usunąć dopiero wtedy, gdy nie wskazuje na niego żadne archiwum. Tym zajmuje się compact w borgu i prune w resticu.

Ściąga flag

KomendaFlagaCo robi
tar--listed-incremental=PLIKkopia przyrostowa z notesem stanu
tar--listed-incremental=/dev/nullprzy rozpakowaniu: odtwarzaj łańcuch razem ze skasowaniami
tar-tzflista zawartości bez rozpakowania
rsync-a --deletelustro z prawami i czasami, usuwa nadmiarowe
rsync--link-dest=DIRniezmienione pliki jako twarde dowiązania
rsync-npróba na sucho
borginit --encryption=repokeynowe szyfrowane repozytorium
borgcreate --statsnowe archiwum ze statystyką
borglist / diff / extractlista, różnice, odtworzenie
borgprune --keep-daily=N + compactusuwanie starych kopii i zwolnienie miejsca
borgkey exportkopia klucza poza serwer
resticinit / backup / snapshotsrepozytorium, kopia, lista
resticrestore latest --target DIRodtworzenie do folderu
restic--include ŚCIEŻKAodtworzenie wybranego pliku
resticforget --keep-daily N --pruneusuwanie starych migawek
resticcheck --read-data-subset=5%sprawdzenie części danych

⚠ Typowy błąd

Nocny backup borga z crona, codziennie o 2:00. Pewnego dnia danych jest więcej, kopia trwa ponad dobę i o 2:00 następnego dnia startuje druga. W skrzynce:

Failed to create/acquire the lock /srv/repo-borg/lock.exclusive (timeout).

Kod wyjścia 2. Druga kopia się nie zrobiła, bo pierwsza trzyma kłódkę na repozytorium. Kłódka chroni magazyn przed dwoma piszącymi naraz i to dobrze. Źle jest, jak nikt nie czyta maili z crona i kopie nie robią się od tygodnia.

Naprawa: najpierw ps aux | grep borg. Jeśli borg nadal pracuje, czekaj. Jeśli nie pracuje, bo proces został zabity albo serwer się zrestartował w trakcie, to kłódka została po zmarłym. Wtedy i tylko wtedy: borg break-lock repo-borg. Na przyszłość: flock w skrypcie, jak w module o skryptach, sprawdzanie kodu wyjścia i alarm przez logger z poprzedniej lekcji, gdy kopia się nie udała. Kopia, która cicho nie działa, jest gorsza niż jej brak, bo daje fałszywe poczucie bezpieczeństwa.

Drugi klasyk z restica, po zmianie serwera albo hasła w zmiennej:

Fatal: wrong password or no key found

Złe hasło. Jeśli to prawdziwe zgubione hasło, a nie literówka, to nie ma tylnych drzwi. Dlatego hasło do kopii trzymasz w dwóch miejscach, a test odtwarzania robisz z hasła wyjętego z sejfu, nie z historii Basha.

✎ Zadanie

  1. Zrób repozytorium borga albo restica i kopię swojego folderu /etc.
  2. Zmień jeden plik w /etc, na przykład dopisz komentarz do /etc/hosts, i zrób drugą kopię. Pokaż różnice między kopiami (borg diff albo restic diff).
  3. Odtwórz /etc/hosts z pierwszej kopii do folderu /tmp/test i porównaj z obecnym przez diff.
  4. Ustaw usuwanie starych kopii: 7 dziennych, 4 tygodniowe, i uruchom to na sucho.
Pokaż rozwiązanie

Kopia /etc borgiem. Ścieżkę podaję względną, z /, żeby w archiwum leżało ładne etc/hosts, a nie cała ścieżka:

export BORG_PASSPHRASE='Haslo-Do-Kopii-Etc-2026'
sudo -E borg init -e repokey /root/repo
cd /
sudo -E borg create --stats /root/repo::etc-1 etc
echo '# serwer kopii: backup01' | sudo tee -a /etc/hosts
sudo -E borg create --stats /root/repo::etc-2 etc
sudo -E borg diff /root/repo::etc-1 etc-2
Number of files: 1860
This archive:                8.95 MB              2.82 MB              2.72 MB
...
This archive:                8.95 MB              2.82 MB                825 B

   +106 B     -81 B [mtime: ...] etc/hosts

Drugi raz cały /etc, prawie 9 MB, a w magazynie przybyło 825 bajtów. sudo -E przekazuje zmienną z hasłem do sudo. Odtworzenie starej wersji do /tmp/test i porównanie:

mkdir -p /tmp/test && cd /tmp/test
sudo -E borg extract /root/repo::etc-1 etc/hosts
diff /tmp/test/etc/hosts /etc/hosts
4a5
> # serwer kopii: backup01

Różnica to dokładnie moja linijka. Na koniec sprzątanie na sucho:

sudo -E borg prune --dry-run --list --keep-daily=7 --keep-weekly=4 /root/repo

Na razie obie kopie zostają, bo mamy ich mniej, niż pozwala reguła. Wersja z resticiem wygląda tak samo: restic backup /etc, restic diff ID1 ID2, restic restore ID1 --target /tmp/test --include /etc/hosts, restic forget --keep-daily 7 --keep-weekly 4 --dry-run.

Co dalej

Kopie masz. A co, jeśli serwer w ogóle nie chce wstać? W następnej lekcji tryb ratunkowy i awaryjny, edycja wpisu w GRUB-ie, reset zapomnianego hasła roota na Red Hacie z rd.break, fsck przy starcie, kernel panic i jedna literówka w /etc/fstab, która potrafi położyć cały serwer.