Unix Shell: Time Machine dla systemu GNU/Linux
Opublikował siefca
Komputery Apple’a działające pod kontrolą systemu Mac OS X (Leopard) mają wygodny system tworzenia kopii zapasowych nazywający się Time Machine. To co również podoba się użytkownikom to graficzny interfejs pozwalający “latać” nad systemem plików i obserwować ułożone w trójwymiarowej osi czasu pliki i foldery. Niektóre aplikacje (jak wbudowany program pocztowy czy narzędzie do odtwarzania muzyki) są przygotowane do współpracy z Time Machine i “podróżując w czasie” przy otwartym na pierwszym planie oknie którejś z tych aplikacji możesz zobaczyć zmieniającą się w czasie zawartość (np. skrzynki pocztowej czy list odtwarzania utworów).
Działa to w ten sposób, że co jakiś czas odpowiedni program kopiuje wszystkie dane na zewnętrzny dysk, na którym dla każdego backupu zakładany jest osobny katalog. Można pomyśleć, że w ten sposób wolne miejsce na docelowym drive’ie powinno szybko się wyczerpać, jednak jest na to pewien sposób. Tworzone kopie zachowują wszystkie zalety tzw. kopii różnicowych, to znaczy, że kopiowane do nowego katalogu są jedynie te zbiory, które zmieniły się od czasu ostatniego backupu. Jednak tego typu kopie zwykle mają jedną wadę: małą szybkość przywracania starszych plików. Wynika to z faktu, że należy odwołać się do ostatnio wykonanej pełnej kopii i nanieść na nią wszystkie różnice (kopie różnicowe) zapisane później. Jak więc sobie z tym radzą przebiegłe Uniksy?
Dowiązania twarde
Trick polega na tym, aby podczas tworzenia kopii będącej różnicą w stosunku do ostatnio wykonanego backupu użyć mechanizmu systemu plików zwanego dowiązaniami twardymi (ang. hardlinks) dla wszystkich plików, które się nie zmieniły. Dowiązania twarde są alternatywnymi nazwami wskazującymi na ten sam zasób zwany i-węzłem (ang. i-node), czyli w skrócie na pewien plik. I-node to po prostu struktura na dysku (a czasem w pamięci) przechowująca informacje o zbiorze, czyli takie dane jak na przykład jego rozmiar, początek i koniec, słowo trybu dostępu, właściciela i grupę. Wewnątrz i-węzła znajduje się też licznik dowiązań, który stanowi informację o tym, ile nazw nadano danemu plikowi. Gdy tworzysz nowe dowiązanie, wartość licznika się zwiększa, a gdy je usuwasz – zmniejsza. Plik jest fizycznie usuwany z dysku (wraz z i-węzłem) dopiero wtedy, gdy licznik dowiązań osiągnie wartość 0, czyli wtedy, gdy skasuje się ostatnią wskazującą na niego nazwę.
Spójrz, tak wygląda tradycyjny backup różnicowy:
Na powyższym szkicu widać, że pliki, które się zmieniły w stosunku do ostatnio wykonanej pełnej kopii są umieszczane w backupie. Aby przywrócić dane trzeba skopiować ostatnią pełną kopię, a następnie wprowadzić do niej zmiany zawarte we wszystkich późniejszych kopiach różnicowych. Dla większych kopii, i tam, gdzie mamy wiele backupów różnicowych, może to trochę potrwać.
A tak wygląda to, gdy użyjemy twardych dowiązań:
Zauważ, że w zasadzie nie istnieje tu abstrakcyjna warstwa zwana ostatnią pełną kopią. Drugi zbiór, którego zawartość pozostawała taka sama ma po prostu dwa dowiązania umieszczone wewnątrz katalogów z kolejnymi kopiami zapasowymi. Nawet, jeśli skasujesz pierwszą kopię, to plik nie zostanie usunięty, jeśli w kolejnych backupach występuje. Ostatnio wykonana kopia bezpieczeństwa jest gotowa do użytku bez konieczności przeprowadzania operacji przywracania, ponieważ przygotowywanie do operacji odzyskiwania danych odbywa się w trakcie wykonywania każdego backupu i polega na tworzeniu dowiązań do plików, które nie uległy zmianie.
Rsync
Do przeprowadzania automatycznych, zdalnych backupów moich systemów używałem odkąd pamiętam narzędzia rsync. To sprytne bydle potrafi używać protokołu SSH, a co ważniejsze, przesyła tylko zmienione fragmenty zbiorów, które się różnią. Dodam, że nie jest to nic trudnego. Jeśli ciekawi Cię, jak to działa, to wyobraź sobie, że dla każdej pary plików (źródłowego i docelowego) obliczasz sumę kontrolną – np. używając funkcji MD5 czy SHA – a następnie porównujesz te sumy. Jeśli są różne to znaczy, że zawartości plików są różne i któryś z nich powinien przesłonić swojego filesystemowego ziomka. Ale nie kopiujesz jeszcze, tylko dzielisz każdy z nich w połowie i obliczasz skrót dla połówki. W przypadku wykrycia różnicy czynność powtarzasz dla różniących się fragmentów. Dla ułatwienia robisz sobie podręczną tablicę i odnotowujesz w niej, które obszary plików są różne, a które takie same. Gdyby to odzwierciedlić graficznie, to dobrym porównaniem byłaby mapa sektorów kopiowanego dysku. W ten sposób masz mapkę pokazującą, które fragmenty obu zbiorów się różnią, a które są takie same. Ot i cała tajemnica. Potem wystarczy już tylko wysłać zmienione fragmenty, a po drugiej stronie skleić w całość modyfikowany zbiór. Tak w uproszczeniu działa rsync (tak, przypomina to trochę działanie narzędzi diff i patch, ale tylko trochę).
Narzędzie rsync ma jeszcze jedną zaletę, która zaklęta jest w opcji --link-dest. Dzięki niej to seksowne polecenie jest w stanie tworzyć dowiązania twarde dla plików, które się nie zmieniły. W ten sposób pozwala Ci ono emulować zachowanie apple’owej Time Machine.
Jeśli chcesz używać zamieszczonego poniżej skryptu do wykonywania kopii zapasowych to zainstaluj pakiet rsync.
Skrypt
Ok, dosyć teoretyzowania. Napiszemy sobie skrypt, który używając narzędzia rsync zrobi różnicowe kopie zapasowe używające twardych dowiązań. Żeby łatwiej było się orientować w przedstawianych poniżej modułach zaprezentuję najpierw ich strukturę katalogową i przeznaczenie:
/usr/local/sbin/sysbackups # główny skrypt /$BROOT # katalog z kopiami zapasowymi per system /$BROOT/conf # katalog z konfiguracjami per system (pliki *.conf) /$BROOT/conf/functions # plik z funkcjami wykonującymi backupy
Poniższy kod zapisz jako /usr/local/sbin/sysbackups:
#!/bin/sh # systems backup v1.0 by pw at gnu dot org # # backups' root BROOT=/opt/backups source ${BROOT}/conf/functions errore=0 for i in ${BROOT}/conf/*.conf do unset USER unset RHOST unset VHOST unset SRC unset DEST unset KEYF unset EXCLUDES source ${i} name=$(basename "${i}" | cut -d. -f1) [ -z "${USER}" -o -z "${KEYF}" -o -z "${DEST}" ] && continue [ -z "${RHOST}" ] && continue res="" backitup if [ $? -ne 0 ]; then errore=$? echo "backup ${name} (${VHOST}) failed: ${errore}" 1>&2 echo "message: $res" 1>&2 echo 1>&2 echo "issued command:" 1>&2 echo "${cmd}" 1>&2 fi done exit ${errore}
Ten skrypt jest krótki i ma taki być. Cała logika (równie krótka) znajdzie się w pliku functions, do którego prowadzi ścieżka umieszczona w zmiennej ${BROOT} (dokładnie jest to ${BROOT}/conf/functions). Zmienną ${BROOT} ustaw tak, aby zawierała ścieżkę do Twojej partycji przeznaczonej na backupy. Może to być też katalog na głównej partycji, jednak uważaj wtedy na zajętość miejsca.
Powód, dla którego podstawowa logika skryptu i konfiguracja systemu backupów jest tylko wywoływana z zalążka umieszczonego powyżej jest prozaiczny. Możesz nie życzyć sobie, aby ktokolwiek widział, jakie systemy zabezpieczasz. Umieszczając konfigurację skryptu i główną funkcję na przykład na szyfrowanym systemie plików zwiększasz swoją prywatność i chronisz dane w przypadku utraty dysku lub całego komputera. Uważaj z założeniem „przecież lepiej przenieść cały skrypt na szyfrowany dysk” – może to działać u Ciebie teraz, ale nie musi na innym systemie lub w innym czasie, ze względu na atrybut noexec użyty podczas montowania dodatkowego systemu plików.
Tak więc, zależnie od upodobań, zmień zawartość zmiennej ${BROOT} i utwórz odpowiedni katalog, jeśli jeszcze nie istnieje. Kolejny krok to stworzenie podkatalogu conf a w nim pliku o nazwie functions wewnątrz katalogu określonego wcześniej w ${BROOT}. Zawartość pliku conf/functions powinna być taka jak poniżej:
#!/bin/sh # systems backup v1.0 by pw at gnu dot org function backitup() { local date=`date "+%Y-%m-%dT%H:%M:%S"` res="" eline="" set -f for i in ${EXCLUDES} do eline="${eline} --exclude ${i}" done sshstr="ssh -i ${KEYF}" res=$(rsync -azqP --link-dest=${DEST}/current ${eline} \ -e "${sshstr}" ${USER}@${RHOST}:${SRC}/ ${DEST}/${date} \ && cd ${DEST} && ln -fns ${date} ./current 2>&1) return $? }
Konfiguracja
Ostatni etap to konfiguracja. Jeśli chcesz objąć jakąś maszynę systemem backupów potrzebujesz:
- pary kluczy SSH pozwalających logować się bez hasła
- plik konfiguracyjny
Klucze SSH
Jeśli chcesz, aby twój system nie zgłaszał się do Ciebie po hasło wymagane do przeprowadzenia kopii, możesz wygenerować niezabezpieczony hasłem klucz SSH pozwalający skryptowi dobierać się do zdalnych zasobów. Używa się do tego polecenia:
ssh-keygen -trsa -b4096
Parametr -trsa mówi, że chcemy stworzyć parę kluczy RSA, a -b4096 określa długość klucza. Program zapyta o frazę kodującą (ang. passphrase) zabezpieczającą prywatną część klucza. Jeśli chcesz pełnej automatyki wciśnij «ENTER». Kolejną rzeczą jaką trzeba będzie podać jest nazwa pliku, do której ma być zapisany klucz. Jeśli wiesz co robisz zaakceptuj domyślnie proponowaną nazwę. Możesz też wybrać inną. Osobiście używam osobnego klucza dla każdego z systemów, ale nic nie stoi na przeszkodzie, aby posługiwać się jednym do wszystkich.
Uwaga: niezabezpieczony hasłem klucz prywatny można Ci ukraść. Upewnij się, że prawa dostępu do podkatalogu ~/.ssh są odpowiednio ustawione, a najlepiej przerzuć ten katalog na zaszyfrowaną partycję (jeśli ją posiadasz) i użyj symbolicznego dowiązania, aby go wskazać. Zabezpiecz swój system! Jeśli intruz uzyska dostęp do Twojego katalogu .ssh i znajdujących się w nim kluczy, to uzyska dostęp do wszystkich Twoich systemów objętych mechanizmem wykonującym kopie zapasowe. Najlepiej używać osobnej maszyny do robienia backupów lub wybrać półinteraktywny model, w którym klucze zabezpieczone są frazą kodującą, ale przechowywana jest ona przez agenta SSH. W praktyce oznacza to konieczność jednorazowego podawania hasła po każdym restarcie systemu.
Gdy masz już parę kluczy, na przykład w plikach id_rsa (część prywatna) i id_rsa.pub (część publiczna), to przenosisz jeden z nich na system, który chcesz objąć systemem backupów. Aby przenieść klucz posłuż się bezpiecznym tunelem (np. scp lub sftp). Musisz wybrać, czy będziesz robić backupy używając na docelowym systemie konta superużytkownika (root) czy też nie. Wadą jest uzależnienie Twojego zdalnego systemu od Twojej lokalnej maszyny, zaletą to, że nie masz kłopotów z dostępem. Na docelowym systemie dopisz przeniesiony klucz do pliku ~/.ssh/authorized_keys. Sprawdź, czy jest możliwe logowanie bez podawania hasła. Jeśli klucz umieszczony jest w pliku o innej niż standardowa nazwie, to użyj przełącznika -i polecenia ssh. Aby uzyskać więcej informacji poczytaj podręcznik ekranowy do polecenia ssh.
Oczywiście, można skonstruować podobny mechanizm pozwalający zdalnym systemom przeprowadzać backupy w taki sposób, że to systemy żądają dostępu do wydzielonej części maszyny składującej kopie zapasowe, ale o tym postaram się napisać w przyszłości. Nie ryzykujesz wtedy, że skuteczny atak wymierzony w jeden system pozwoli napastnikowi na dostęp do wszystkich hostów objętych backupami. Dodatkowym zabezpieczeniem może być zmiana powłoki na rssh, jednak o tym również napiszę kiedy indziej.
Pliki konfiguracyjne
Pliki konfiguracyjne czytane podczas pracy skryptu powinny znajdować się w katalogu o nazwie pasującej do wzorca ${BROOT}/conf/*.conf. Oto przykładowy plik o przykładowej, pełnej nazwie ścieżkowej /opt/backups/conf/mojhost.conf:
USER="root" RHOST="host.moja-domena.org" VHOST="moj_vhost.moja-domena.org" VNAME="moj_vhost" SRC="/mnt/vservers/vdirbase/${VNAME}" DEST="/opt/backups/systems/${VHOST}" KEYF="${HOME}/.ssh/id_rsa_${RHOST}" EXCLUDES="/proc /sys /var/tmp /tmp /var/lib/vz/* /mnt/* lost?found*"
Tak naprawdę zmiennymi używanymi przez skrypt są:
SRC |
katalog źródłowy na zdalnym systemie |
DEST |
katalog docelowy na systemie lokalnym |
KEYF |
plik z kluczem prywatnym |
EXCLUDES |
pliki i katalogi, które trzeba pominąć |
RHOST |
nazwa zdalnego systemu |
USER |
nazwa zdalnego użytkownika |
Przykładowe ustawienia wyglądają trochę strasznie, dlatego, że używam środowisk wirtualnych i pomogłem sobie trochę wprowadzając dodatkowe zmienne. Jeśli interesuje Cię prostszy przykład, oto on:
RHOST="host.moja-domena.org" SRC="" DEST="/opt/backups/systems/${RHOST}" KEYF="${HOME}/.ssh/id_rsa" EXCLUDES="/proc /sys /var/tmp /tmp /var/lib/vz/* /mnt/* lost?found*"
Pusta zmienna SRC oznacza, że obejmujemy kopiami bezpieczeństwa cały docelowy system plików. Zauważ, że aby to wszystko zadziałało, musisz jeszcze utworzyć katalog o nazwie (w tym przypadku) /opt/backups/systems/host.moja-domena.org
Uruchamianie
Pierwszy raz warto uruchomić skrypt ręcznie. Pojawi się ostrzeżenie o nieistniejącym podkatalogu current, które należy zignorować. Ten podkatalog to naprawdę symboliczne dowiązanie wskazujące na ostatnią udaną kopię. Używany jest on, aby rsync wiedział, z czym porównywać kopiowane zbiory.
Licencja
Copyright © 2008 by Paweł Wilk.
Powyższy program jest rozpowszechniany w nadziei, że będzie przydatny, ale bez żadnych gwarancji – wyrażonych wprost lub domyślnie – w tym bez gwarancji użyteczności lub przydatności do konkretnych celów. Używasz go na własne ryzyko i na własną odpowiedzialność.
Używając zamieszczonego powyżej programu, dystrybuując go, modyfikując lub zapisując go na nośniku służącym do przechowywania informacji, oświadczasz, że posługujesz się językiem angielskim i akceptujesz spisane w tym języku umowne regulacje określające warunki korzystania z programu zwane Licencją. Nazwa tej Licencji to GNU Lesser General Public License w wersji 3, a jej zasady zostały wyłożone w dokumencie dostępnym pod adresem http://www.fsf.org/licensing/licenses/lgpl-3.0.html. Nieoficjalne tłumaczenie Licencji możesz znaleźć na stronie Konrada Stobieckiego . Jeśli nie możesz połączyć się z witryną, na której przedstawiona jest treść Licencji, to aby ją uzyskać napisz pod adres:
Free Software Foundation
51 Franklin Street, Fifth Floor
Boston, MA 02110-1301
USA
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see http://www.gnu.org/licenses/

