Alla Ricerca del Backup Perfetto: Borg e Restic

/images/backup.thumbnail.png

Backup: tecniche principali

In passato ho già affrontato l'argomento backup ponendo alcune basi e dando alcune indicazioni sulle principali modalità e sul perché è fondamentale farlo, sul perché il RAID non può essere considerato una forma di backup e su alcuni software da me presi in esame e utilizzati in precedenza. Invito dunque a leggere l'articolo collegato, per una base di partenza. Questa volta, però, daro delle guide su come io realizzo i backup e su come io riesca a garantire un certo grado di sicurezza.

Backup: le basi di partenza

Proporrò l'argomento sotto forma di elenco puntato, onde coprire le principali problematiche da gestire:

  • Sistema Operativo e dati da salvare: ogni sistema operativo è un universo. E' ragionevole pensare che non esista una soluzione universale, anche se molti dei principali software open source sono multi-piattaforma. Il principale divario è tra i sistemi Unix-Like (GNU/Linux, BSD, MacOS, ecc.) e Windows. Lo stesso software, pur essendo disponibile per più piattaforme, potrebbe non essere il migliore per la propria o per le proprie necessità.
  • Tipo di dati da salvare: ogni tipo di backup richiede soluzioni diverse. Ci sono situazioni in cui si ha la necessità di copie pressoché sempre incrementali in quanto i file crescono ma non vengono modificati. In questo caso, un qualunque sistema appunto incrementale (es: Duplicity ) può essere sufficiente. Nel caso, invece, di backup sempre diversi (es: un database che cambia costantemente), un sistema come quello precedente può essere estremamente inefficiente, specialmente nel lungo periodo.
  • Necessità di crittografia o meno: i backup delle mie fatture, ad esempio, potrei anche evitare di crittarli...
  • Rapidità di esecuzione e di recupero: ci sono strumenti che sono estremamente efficienti nell'effettuare l'operazione di copia, ma che rendono estremamente lento il recupero. Una delle (poche) carenze da me riscontrate in BURP Backup, di cui ho parlato nell'articolo precedente e che utilizzo ancora con successo in certe situazioni, è che richiede il restore dei file e non consente, per lo meno direttamente, di navigare nel backup come fosse un file system locale. Stesso discorso vale, ad esempio, per il backup nativo di Proxmox: è comodissimo da impostare, completo e nativo ma il tempo di recupero può essere importante, specialmente se effettuato in postazioni remote e su connessioni lente. Recuperare un file richiederà, molto spesso, il recupero totale della macchina.
  • Snapshot: ultima in lista, ma questione di primaria importanza. Un backup di un file system "live" avrà un momento di inizio e un momento di fine. Nel frattempo, i dati cambieranno all'interno di esso e potrebbe generarsi incoerenza. In passato ho avuto problemi del genere: un database (mysql) di qualche GB è stato rovinato da un cliente e me ne è stato chiesto il recupero. Ho preso, con baldanza, l'ultimo backup e ho ripristinato i vari file (non un dump). Inutile dire che è stato impossibile farlo ripartire: il file molto grande era cambiato troppo tra l'inizio del backup dello stesso e la fine, per cui era incoerente. Per i maliziosi: avevo anche il dump, ovviamente, per cui ho recuperato quello. Ma la questione resta chiara: fare un backup di un file system live è periocoloso, a meno che non si stia copiando la cartella "Documenti" o "Immagini". Un database aperto, come anche semplicemente quello del browser, ha altissime probabilità di corrompersi e rendere inutile la copia di sicurezza fatta. La tecnica è fare uno snapshot dell'intero file system prima di iniziare la copia. Ci sono margini di rischio anche in questo (il backup avrà all'incirca lo stesso stato che avrebbe la macchina se venisse improvvisamente staccata la spina), ma largamente inferiori. Ad oggi, utilizzando snapshot, sono stato in grado di recuperare tutto.

Backup: Snapshot

Effettuare uno snapshot del file system è importante al fine di avere un backup sufficientemente coerente. Nel tempo ho provato varie soluzioni: Windows ha il suo VSS, su Linux ci sono varie opzioni diverse. Ne elencherò le principali:

  • Snapshot nativo del File System (es: BTRFS o ZFS): se il file system supporta nativamente la possibilità di fare uno snapshot, è opportuno sfruttare questo sistema. Sarà senza dubbio il meno dispendioso e il più corretto, a livello formale.
  • LVM snapshot: se si utilizza LVM, si può chiedere uno snapshot del volume logico, montarlo ed effettuare il backup dello stesso. Bisognerà specificare una dimensione massima di variazione (ovvero quanti dati al massimo il sistema può variare nel tempo di esistenza dello snapshot stesso) e questo spazio dovrà essere parte del VG ma non allocato ad alcun LV. Ci sarà dunque dello spazio sprecato e direttamente proporzionale al working set, ovvero ai dati che vengono maneggiati e scritti in uno specifico lasso di tempo. E' una soluzione che utilizzo ancora ma che mi ha causato alcuni problemi. In determinate circostanze, infatti, il file system si inchioda durante la distruzione dello snapshot, richiedendo un riavvio. Cosa poco carina, per fortuna è capitata di rado ma in situazioni diverse, dunque non imputabili a uno specifico disco/controller/hardware.
  • DattoBD: ho seguito lo sviluppo di questo strumento sin dai suoi albori e ho avuto, nelle primissime versioni, dei problemi che però sono spariti release dopo release. Ha repository per le principali distribuzioni e il funzionamento è ora stabile e, per ciò che ho potuto sperimentare fino ad oggi, sicuro. Per effettuare gli snapshot con Datto utilizzo gli script di UrBackup (altro ottimo sistema di backup che ho utilizzato molto ma che sto ormai usando solo per sistemi Windows), comodi e rapidi.

Backup: push o pull?

Una delle diatribe che da sempre divide gli esperti è proprio quella sulla tipologia di gestione e iniziazione del backup: dovrebbe essere il client a collegarsi al server (push), di fatto "iniziando" il backup, oppure il server a connettersi ai client e "chiedere" il backup (pull)?

A mio avviso, dipende. Nel complesso, tendo ad avere dei sistemi di backup centralizzati su server dedicati che, di conseguenza, tengo in massima sicurezza, facendo girare solo i servizi fondamentali. In alcuni casi, utilizzo Docker per inscatolare l'intero sistema di backup ed evitare il più possibile un coinvolgimento di altri servizi. Proprio per questa ragione tendo a preferire l'approccio "pull", in cui è il server che si collega al client e chiede l'operazione, oppure l'approccio misto del già citato BURP, in cui il client si collega al server che, però, decide se è il momento o meno di fare il backup e ne contingenta l'operazione. Il server, in questo caso, non è dunque uno "stupido" storage, ma un vero e proprio software che svolge la sua funzione.

Purtroppo, però, gli attuali sistemi da me preferiti non supportano questo genere di funzionalità. Ragion per cui ho messo in piedi delle soluzioni alternative che possano in qualche maniera ovviare la situazione.

Prenderò ora in esame i due strumenti che utilizzo maggiormente (insieme al già citato BURP), ovvero Borg Backup e Restic.

Borg Backup

Borg Backup è lo strumento che, da più di un anno, garantisce la sicurezza di quasi tutti i miei backup. Le peculiarità solo:

  • Compressione e deduplicazione dei dati all'interno dello stesso repository
  • Possibilità di effettuare il mount dei backup in una directory, permettendone navigazione, consultazione e ripristino in maniera semplice & comoda
  • Rapido nelle operazioni, genera una cache locale per tenere traccia dei file già copiati e la utilizza per i backup successivi

Addio dunque ai sistemi incrementali, ecc. Borg consente di avere tutti backup deduplicati e compressi come se fossero tutti completi, evitando lente ricostruzioni in fase di consultazione o ripristino.

Ho riscontrato alcuni difetti che, a mio avviso, non sono importantissimi ma è opportuno tenerne conto:

  • Il principale: si consiglia di utilizzare un repository per server in quanto l'operazione di backup è bloccante. Questo implica che la deduplicazione verrà effettuata solo tra "generazioni di backup" e dati dello stesso server. Nel mio caso, che ho centinaia di server "simili" (per OS, configurazioni, ecc.) ci sarà una grossa perdita di spazio. Questo può essere considerato sia un difetto che un pregio (la rottura di un repository sarà limitata a quel repository, non a tutti i backup). Dipende dalle situazioni.
  • Essendo in Python (adoro il Python), impiega qualche secondo a caricarsi ed eseguirsi. Niente di estremo, sia chiaro, ed è rapido e snello nel funzionamento, ma in alcuni casi può essere un po' fastidioso
  • Quando viene effettuato il mount di un repository, lui deve creare degli indici interni per mostrare le directory. Quando i backup cominciano a diventare molti e di molti dati, questa operazione può richiedere anche qualche minuto. Nessun problema se non si ha fretta, ma quando si ha il cliente al telefono e chiede disperatamente di rimettere in piedi un sito o recuperare un documento importantissimo e urgente, quei minuti sono preziosi
  • Anche lui è push, per cui è il client che si collega allo storage per piazzarci i dati. Se lo storage, come nel mio caso, è un server provvisto dell'eseguibile di borg, l'operazione sarà più rapida ed efficace pur girando comunque via ssh

Borg è rapido, efficace e la mia esperienza diretta mi suggerisce che sia sicuro. In molte occasioni sono stato in grado di recuperare server interi, oltre che dati sparsi, in brevissimo tempo e in maniera completa.

Restic

Seguo Restic già da un po', ho fatto esperimenti ma fino ad un po' di tempo fa ho continuato a preferire Borg. Sono due software molto diversi (Restic è scritto in Go) eppure molto simili, sia come approccio che come funzionalità.

Ultimamente, però, le cose stanno prendendo una buona piega per Restic: è diventato molto più rapido e lo sviluppo si sta direzionando verso una delle principali carenze di questo software (alla data odierna): la mancanza di compressione dei dati.

Ecco alcuni vantaggi di Restic:

  • Deduplicazione dei dati anche tra server diversi: Restic, a differenza di Borg, suggerisce l'utilizzo di un unico repository di dati per aumentare la possibilità di deduplicazione anche tra macchine diverse. E' un grosso vantaggio in situazioni, come la mia, in cui ci sono moltissimi server molto simili e ne snellisce anche il primo backup
  • Rapido: i miei test spannometrici dicono sia più rapido di borg, forse perché non comprime
  • Possibilità di effettuare il mount dei backup in una directory, permettendone navigazione, consultazione e ripristino in maniera semplice & comoda. La navigazione è rapida e divisa per snapshot, per host, per tag, rendendo molto semplice l'identificazione del backup richiesto. A differenza di Borg, le strutture vengono create man mano che si naviga all'interno delle directory, rallentando (di un pelo) l'accesso alle stesse ma garantendo prestazioni sempre accettabili.
  • Possibilità di separare le operazioni di "forget" e "prune": alla fine del backup, tendo a far dire al client "ok, butta via i backup più vecchi di..." e lui risponde immediatamente. L'operazione vera e propria di cancellazione verrà poi effettuata successivamente, lanciando il comando "prune". Nel mio caso, questo viene fatto dal server e per tutto il repository (garantendo differenti politiche di tenuta dati in base al server e al cliente), riducendo il tempo effettivo di backup allo stretto indispensabile
  • Community estremanente valida: lo sviluppatore principale, Alexander Neumann è estremamente gentile, cortese e disponibile, nel forum ufficiale, ad aiutare e ricevere feedback e proposte da chiunque.

Restic, come qualunque software, non è perfetto. Ecco alcuni lati negativi che ho riscontrato nell'utilizzo:

  • Mancanza di compressione: a oggi un problema, per me, ma lo sviluppatore ci sta lavorando ed è una feature che arriverà sufficientemente presto
  • Anche Restic è una soluzione push
  • Sembra una banalità, ma nell'uso può essere scomodo: quando si effettua il mount del repository (per navigare nei backup), non viene restituito il prompt ma un messaggio che dice di effettuare l'unmount alla fine dell'operazione. Alias c'è necessità di avere una seconda shell disponibile per entrare nel backup stesso. A volte la cosa può essere frustrante, specialmente se si ha un accesso d'emergenza. Quando me ne ricordo, apro un tmux prima del mount e una seconda shell sotto la prima, per non dimenticare di fare l'unmount alla fine

Esempio: Script utilizzato per effettuare il backup del mio portatile usando Borg e Restic

Passiamo ad un esempio pratico: lo script che utilizzo per effettuare il backup dei miei notebook. In primis, controllo che il computer non stia funzionando a batteria e che il server di backup sia presente (non siamo in una rete diversa o non sia giù per manutenzione). A quel punto, metto un lock ed effettuo uno snapshot utilizzando DattoBD, redirezionando tutto l'output su un file nella directory temporanea:

Borg:

#!/bin/bash
PATH="/usr/local/jdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/pkg/bin:/usr/pkg/sbin"
export PATH
server=192.168.2.254
export server
STATE=`upower -i /org/freedesktop/UPower/devices/battery_BAT0|grep state|grep discharging`
export STATE
if [[ $STATE == *'discharging'* ]]; then
       exit
fi
if nc -w 10 -z $server 22 2>/dev/null; then
echo "$server ✓";
else
echo "$server ✗";
       exit;
fi
if mkdir /tmp/backuphappening; then
  echo "Locking succeeded" >&2
else
  echo "Lock failed - exit" >&2
  exit 1
fi
exec > /tmp/borg_backup_log
exec 2>&1
date;
/usr/local/share/urbackup/dattobd_create_filesystem_snapshot 1 /
REPOSITORY=user@$server:repo
TAG=daily
ionice -c3 borg create -v --progress --compression zlib --stats                          \
    $REPOSITORY::$TAG'-{now:%Y-%m-%dT%H:%M:%S}'          \
    /mnt/urbackup_snaps/ /boot /boot/efi                                       \
    --exclude '*.cache*'                  \
    --exclude '*/home/*/.cache*'                  \
    --exclude '*/home/*/Scaricati*'                  \
    --exclude '*.datto*'                  \
    --exclude '*.overlay*'                  \
    --exclude '*.crdownload'                  \
    --exclude '*.rpm'                  \
    --exclude '*.deb'                  \
    --exclude '*swapfile*'                  \
    --exclude '*/home/*/Virtualbox VMs*'          \
    --exclude '*/home/*/VirtualBox VMs*'          \
    --exclude '*/home/*/.vagrant.d*'              \
    --exclude '*/root/.cache*'                    \
    --exclude '*/var/lib/docker*'                    \
    --exclude '*/tmp'
/usr/local/share/urbackup/dattobd_remove_filesystem_snapshot 1 /mnt/urbackup_snaps/1
borg prune -v $REPOSITORY --stats --prefix $TAG'-' \
    --keep-hourly=12 --keep-daily=60 --keep-weekly=12 --keep-monthly=24
rm -Rf /tmp/backuphappening
date;

Restic:

#!/bin/bash
PATH="/usr/local/jdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/pkg/bin:/usr/pkg/sbin"
export PATH
STATE=`upower -i /org/freedesktop/UPower/devices/battery_BAT0|grep state|grep discharging`
export STATE
if [[ $STATE == *'discharging'* ]]; then
       exit
fi
if nc -w 10 -z $server 22 2>/dev/null; then
echo "$server ✓";
else
echo "$server ✗";
exit;
fi
if mkdir /tmp/backuphappening; then
  echo "Locking succeeded" >&2
else
  echo "Lock failed - exit" >&2
  exit 1
fi
exec > /tmp/restic_backup_log
exec 2>&1
date;
/usr/local/share/urbackup/dattobd_create_filesystem_snapshot 1 /
ionice -c3 restic -r myrepo \
    backup                                     \
    --exclude='*/home/*/.cache*'                  \
    --exclude='*.cache*'                  \
    --exclude='*/home/*/Scaricati*'                  \
    --exclude='*.datto*'                  \
    --exclude='*.overlay*'                  \
    --exclude='*.crdownload'                  \
    --exclude='*.rpm'                  \
    --exclude='*.deb'                  \
    --exclude='*swapfile*'                  \
    --exclude='*/home/*/Virtualbox VMs*'          \
    --exclude='*/home/*/VirtualBox VMs*'          \
    --exclude='*/home/*/.vagrant.d*'              \
    --exclude='*/root/.cache*'                    \
    --exclude='*/var/lib/docker*'                    \
    --exclude='/sys'                    \
    --exclude='/proc'                    \
    --exclude='/dev'                    \
    --exclude='*/tmp'                    \
    --exclude='/run'    \
    /mnt/urbackup_snaps/1/
/usr/local/share/urbackup/dattobd_remove_filesystem_snapshot 1 /mnt/urbackup_snaps/1
restic forget -d 30 -w 8 -m 12 -y 1 --host myhost -r myrepo
rm -Rf /tmp/backuphappening
date;

Lo script di Restic è molto più grezzo, è solo un adattamento di quello di Borg per farlo andare e, come già scritto, non effettua il prune effettivo del repository ma marca solo come "da rimuovere" i backup più vecchi. Consiglio una lettura, almeno superficiale, dei manuali dei due strumenti, in modo da non avere i classici problemi da copia & incolla.

Conclusioni

In questo momento sto utilizzando sia Borg che Restic, il primo come sistema principale, il secondo come "emergente", nel senso che lo sto testando nel dettaglio e promette davvero bene. L'arrivo della compressione potrà, probabilmente, colmare il gap e permettermi di sostituire Borg, grazie alla maggiore efficienza di deduplicazione. In entrambi i casi, di notte effettuo un rsync dell'intero repository su uno storage remoto per avere backup replicati in più posti. E un Jenkins che si occupa di collegarsi alle singole macchine, effettuare il backup e avvisarmi in caso di problemi.

Commenti