Live backup of QEMU/KVM/libVirt virtual machines via Duplicity

The script below can be used to implement an efficient, secure and live backup of QEMU/KVM/libVirt virtual machines via Duplicity. It accepts the domain name and mode (snapshot/suspend) parameters and can handle offline domains and multiple disks as well. XML domain definition is also made as a part of the backup. The Duplicity part is configured for an sftp transfer in our example, but can be adjusted for other types of operations Duplicity supports.

  

#!/bin/bash
#

DOMAIN="$1"
MODE="$2"

if [ -z "$DOMAIN" -o -z $MODE ]; then
    echo "Usage: ./vm-backup <domain> mode:snapshot|suspend"
    exit 1
fi

logfile=$DOMAIN.virsh.log

# check domain state - we handle 'running' or 'shut off'
STATE=`virsh dominfo "$DOMAIN" |  awk '/State:/ {print $2$3}'`

if [ "$STATE" != 'running' -a "$STATE" != 'shutoff' ]; then
    echo "Domain $DOMAIN is not in a deterministic state ($STATE). Only 'running' or 'shut off' is acceptable."
    exit 1
fi

echo "Beginning backup for $DOMAIN (state $STATE)"

#
# Get the list of targets (disks) and the image paths.
#
TARGETS=`virsh domblklist "$DOMAIN" --details | awk '/^file\s+disk/ {print $3}'`
IMAGES=`virsh domblklist "$DOMAIN" --details | awk '/^file\s+disk/ {print $4}'`

if [ "$STATE" == 'running' ]; then

   if [ "$MODE" == 'snapshot' ]; then
       DISKSPEC=""
       for t in $TARGETS; do
           DISKSPEC="$DISKSPEC --diskspec $t,snapshot=external"
       done

       echo "Create a snapshot for $DOMAIN"

       virsh snapshot-create-as --domain "$DOMAIN" --name backup --no-metadata --atomic --disk-only $DISKSPEC > $logfile 2>&1
       if [ $? -ne 0 ]; then
           echo "Failed to create snapshot for $DOMAIN"
           exit 1
       fi
   fi

   if [ "$MODE" == 'suspend' ]; then
     virsh suspend "$DOMAIN"
   fi
   
fi

#
# Copy disk images
#
echo "Copy disk images/definition xml for $DOMAIN via Duplicity"
FILELIST=""
for t in $IMAGES; do
    FILELIST="$FILELIST --include $t"
done
# Dump vm definition xml and add to the files list
virsh dumpxml "$DOMAIN" > /tmp/$DOMAIN.xml
FILELIST="$FILELIST --include /tmp/$DOMAIN.xml"

# Duplicity
export PASSPHRASE=<backup encryption key>
export FTP_PASSWORD=<sftp user password>

sftpdest=pexpect+sftp://user@sftp.ser.ver/vm/$DOMAIN

duplicity remove-all-but-n-full 3 --force $sftpdest

# NB! Keep max-blocksize big (default 2048) for best performance
duplicity --full-if-older-than 2M --asynchronous-upload --allow-source-mismatch --verbosity INFO --max-blocksize=20480 --volsize 256 $FILELIST --exclude '**' / $sftpdest

#
#
# Merge changes back if running.
#
if [ "$STATE" == 'running' ]; then

   if [ "$MODE" == 'snapshot' ]; then
      echo "Merge changes back for $DOMAIN" >>$logfile

      BACKUPIMAGES=`virsh domblklist "$DOMAIN" --details | awk '/^file\s+disk/ {print $4}'`
      for t in $TARGETS; do
          virsh blockcommit "$DOMAIN" "$t" --verbose --active --pivot >>$logfile
          if [ $? -ne 0 ]; then
              echo "Could not merge changes for disk $t of $DOMAIN. VM may be in invalid state." >>$logfile
              exit 1
          fi
      done

      #
      # Cleanup left over backup images.
      #
      for t in $BACKUPIMAGES; do
          rm -f "$t"
      done
   fi

   if [ "$MODE" == 'suspend' ]; then
      virsh resume "$DOMAIN"
   fi
fi
echo "Finished backup" >>$logfile
echo ""

Related links:

Efficient live disk backup with active blockcommit

vm-backup.sh (by cabal95)