Parto con una premessa: sebbene una copia (o tar) "semplice" con la macchina virtuale in esecuzione funzioni, non è una strada che voglio seguire perché poi, nel momento del bisogno, i file che servono sono sistematicamente inconsistenti o sono stati copiati mentre il sistema li teneva aperti. Quindi un backup inutile.

A mio avviso il backup delle macchine virtuali, con VMware Server s'intende, deve avvenire con le stesse spente o sospese, senza però avere downtime lunghi tutta la notte.

La soluzione su cui ho lavorato consiste nel sospendere una ad una le macchine virtuali, farne una copia in un filesystem temporaneo, ripristinare lo stato ed eseguire il backup a partire dalla copia. Alla fine di tutto mi piace ricevere un'e-mail con l'esito dell'operazione.

La macchina di test è, come sempre, il server dell'ufficio a cui ho aggiunto:
  • controller SCSI Adaptec
  • DDS-4
  • HD 250 GB SATA

sendmail.expect

Per quanto riguarda l'invio dell'e-mail da script ho preferito evitare di installare software quali mailx che richiedono alla base un sendmail o exim che sia. Telnet è più che sufficiente, comandato da expect:
apt-get install telnet expect

cd ~
touch sendmail.expect
chmod +x sendmail.expect
All'interno di sendmail.expect lo script per l'inivo dell'e-mail:
#!/usr/bin/expect

spawn telnet [lindex $argv 0] [lindex $argv 1]

expect "220"
send "HELO localhost.localdomain\n"
expect "250"
send "MAIL FROM: backup@localhost.localdomain\n"
expect "250"
send "RCPT TO: [lindex $argv 2]\n"
expect "250"
send "DATA\n"
expect "354"
send "From: \"Backup\" <backup@localhost.localdomain>\n"
send "To: \"Backup Operators\" <[lindex $argv 2]>\n"
send "Subject: Rapporto di Backup\n"

set fhandle [open [lindex $argv 3] r]
while {[gets $fhandle inline] >= 0} {
        send "> $inline\n"
}
close $fhandle

send ".\n"
expect "250"
send "QUIT\n"
La sintassi:
./sendmail.expect [SMTPHOST] [SMTPPORT] [RECIPIENT] [FILE]
Legge il contenuto di [FILE] e lo invia a [RECIPIENT] utilizzando host e porta del server SMTP specificato.

~/.vmbackup

Soprattutto nel mio server ho delle macchine virtuali di test, o copie, di cui non voglio eseguirne il backup. Voglio poter decidere liberamente e senza alcun vincolo che cosa salvare. Per questo ho deciso di appoggiarmi al file .vmbackup nel quale andare a inserire i nomi delle directory da includere nel processo di backup:
cd ~
touch .vmbackup
echo I3S01 > .vmbackup
echo I3S02 >> .vmbackup

vmbackup.sh

Riporto a pezzi (e con qualche commento) lo script principale, che ho comunque zippato se volete scaricarlo interamente: VMbackup.
#!/bin/sh

# Configurazione

virtualMachines=/var/lib/vmware/Virtual\ Machines/
tapeDevice=/dev/st0
tapeBlockSize=4 # * 512
tapeLabelName=$(date +%d%m%Y)
logFile=/var/log/vmbackup.log
tempFileSystem=/mnt/backuphd/

smtpHost=172.20.3.101
smtpPort=25
mailTo=Backup\ Operators
Nel dettaglio:
  • $virtualMachines: directory di VMware dove risiedono tutte le macchine virtuali;
  • $tapeDevice: unità di backup;
  • $tapeBlockSize: dimensione dei blocchi dell'unità di backup, 4 vuole dire blocco di 2048 bytes. Per sapere la dimensione del blocco dell'unità di backup:
    md -f /dev/st0 status
  • $tapeLabelName: nome da assegnare al nastro;
  • $logFile: nome del file di log (temporaneo, a fine processo viene eliminato se tutto va a buon fine);
  • $tempFileSystem: mountpoint del filesystem su cui verranno copiate le macchine virtuali prima di eseguire il backup;
  • $smtpHost: hostname del server SMTP;
  • $smtpPort: porta del server SMTP;
  • $mailTo: recipient dell'e-mail;
# Verifica il nastro

mt -f $tapeDevice status || exit 0

# Verifica il file di configurazione

cd ~
test -e ".vmbackup" || exit 0
Lo script esce qualora l'unità di backup non fosse pronta (o non ci fosse un nastro al suo interno) o il file di configurazione non dovesse esistere.
# Monta il filesystem temporaneo

umount $tempFileSystem > /dev/null
mount $tempFileSystem || exit 0
Il filesystem temporaneo viene montato a inizio script e smontato al termine. A monte dev'essere aggiunta la riga relativa al filesystem nel fstab.
# Log

touch $logFile
echo $(date +%b\ %d\ %H:%M:%S) Avvio... > $logFile
echo >> $logFile
Registrazione sul file di log delle attività. Ovviamente integrabile a proprio piacimento.
# Legge VM

vms=

while read vm
do

        if [ ${#vm} -gt 0 ]
        then

                # .vmx

                cd "$virtualMachines$vm/"
                vmxFile=$(ls *.vmx)

                # Sospende la macchina virtuale

                vmware-cmd "$virtualMachines$vm/$vmxFile" suspend
                sleep 1

                # Copia la VM nel filesystem temporaneo

                mkdir "$tempFileSystem$vm/" > /dev/null
                cd "$virtualMachines$vm/"
                cp -v * "$tempFileSystem$vm/"
                rm --force "$tempFileSystem$vm/*.log" > /dev/null

                # Ripristina la VM

                vmware-cmd "$virtualMachines$vm/$vmxFile" start

                # Aggiunge la VM alla lista delle VM

                vms="$vms$vm/ "

                # Aggiunge VM al Log

                echo $(date +%b\ %d\ %H:%M:%S) VM: $vm $vmxFile >> $logFile

        fi

done < ~/.vmbackup
Questo è il cuore dello script. Brevemente, legge le directory definite nel file di configurazione e, per ciascuna, sospende la macchina virtuale, esegue la copia di tutti i file nel filesystem temporaneo (rimuove i log nella directory temporanea, personalmente non mi serve salvarli), ripristina la macchina virtuale, accoda alla variabile $vms il nome della directory e scrive nel log il riferimento della virtual machine che verrà salvata.
# Nessuna VM da salvare

if [ ${#vms} -le 0 ]
then
        exit 0
fi
Se il while precedente non avesse dato risultati in termine di macchine da salvare, esce.
# Riavvolge e cancella il contenuto del nastro

mt -f $tapeDevice rewind
mt -f $tapeDevice erase
Ripulisce il nastro.
# Esegue Backup

echo >> $logFile
echo $(date +%b\ %d\ %H:%M:%S) Backup in corso: $vms >> $logFile
cd "$tempFileSystem"
tar cpzvfbV $tapeDevice $tapeBlockSize "$HOSTNAME VMBackup $tapeLabelName" $vms
echo $(date +%b\ %d\ %H:%M:%S) Backup terminato. >> $logFile
Esegue il backup vero e proprio, la cui origine è la lista delle macchine virtuali lette in precedenza, e la destinazione è $tapeDevice.
# Elimina VM dal filesystem temporaneo

while read vm
do
	if [ ${#vm} -gt 0 ]
        then
			cd "$tempFileSystem"
			rm --force -R "$vm/" > /dev/null
	fi
done < ~/.vmbackup
Rimuove file e directory scritte nel filesystem temporaneo.
# Smonta il filesystem temporaneo

cd ~
sleep 1
umount $tempFileSystem > /dev/null || echo $(date +%b\ %d\ %H:%M:%S) ATTENZIONE FileSystem temporaneo non smontato. >> $logFile
Smonta il filesystem.
# Contenuto backup (per verifica)

echo >> $logFile
echo $(date +%b\ %d\ %H:%M:%S) Riepilogo Backup >> $logFile
echo >> $logFile
tar tzfb $tapeDevice $tapeBlockSize >> $logFile
Nel file di log è bene avere anche una contro-verifica di cosa risulti effettivamente scritto nel nastro.
# Riavvolge ed espelle il nastro (offline fa ambo le cose)

sleep 5
mt -f $tapeDevice offline
Riavvolge ed espelle il nastro. Cinque secondi di attesa sono di precauzione, dato che mt non digerisce bene operazioni in sequenza come in questo caso, evitiamo di cadere nell'errore "Device is busy".
# Log

echo >> $logFile
echo $(date +%b\ %d\ %H:%M:%S) Terminato. >> $logFile

./sendmail.expect $smtpHost $smtpPort $mailTo $logFile

rm $logFile
Completamento del log e invio dell'e-mail.

Dopo qualche test non resta che aggiungere lo script al cron:
ln -s /root/vmbackup.sh /etc/cron.daily/vmbackup

Recupero

Già presente nello script, tar tzfb permette di avere una lista del contenuto del nastro, preceduta dal nome del nastro stesso ($tapeLabelName).
Ottenuto il contenuto del backup, il ripristino è davvero semplice:
tar zxvfb /dev/st0 4 I3S01/I3S01.vmx
La dimensione del blocco è necessaria in qualsiasi operazione di lettura/scrittura dal/sul nastro, al fine di evitare errori bloccanti o recuperare file in realtà inutilizzabili/illeggibili.

Considerazioni

I vantaggi sono palpabili: downtime dei server estremamente ridotto, direttamente proporzionale alla dimensione del disco virtuale (vmdk) e inversamente proporzionale al transfer rate verso il filesystem d'appoggio. E in più la certezza di un backup affidabile, il cui contenuto sia sicuramente valido e ripristinabile in qualsiasi momento senza andare incontro a problemi di varia natura derivanti da copie inventate sul momento da sistemisti della domenica.