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 as a parameter 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"

if [ -z "$DOMAIN" ]; then
    echo "Usage: ./vm-backup <domain>"
    exit 1
fi

# 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}'`

#
# Create the snapshot if running.
#
if [ "$STATE" == 'running' ]; then

    echo "Create a snapshot for $DOMAIN"

    DISKSPEC=""
    for t in $TARGETS; do
        DISKSPEC="$DISKSPEC --diskspec $t,snapshot=external"
    done

    virsh snapshot-create-as --domain "$DOMAIN" --name backup --no-metadata --atomic --disk-only $DISKSPEC >/dev/null
    if [ $? -ne 0 ]; then
        echo "Failed to create snapshot for $DOMAIN"
        exit 1
    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 parameters
export PASSPHRASE=<duplicity-password>
export FTP_PASSWORD=<SFTP-password>
sftpdest=pexpect+sftp://<sftp-host-address>/$DOMAIN

# Housekeeping - keep only two full backups
duplicity remove-all-but-n-full 2 --force $sftpdest
# Full backup each month, incremental otherwise, volume size 512MB
duplicity --full-if-older-than 1M --allow-source-mismatch --verbosity INFO --volsize 512 $FILELIST --exclude '**' / $sftpdest

unset FTP_PASSWORD
unset PASSPHRASE

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

    echo "Merge changes back for $DOMAIN"

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

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

echo "Finished backup"
echo ""

The helper script below can be used to take backup of all virtual machines.

#!/bin/bash
#
# Get the list of all domains, active and inactive, iterate over the list with the main backup-script such that all domains get backed up.

test="$(virsh list --all | awk {'print $2'} | tail -n +3)"

while read -r line; do
    bash <script-dir>/vm-backup "$line" > "<log-dir>/$line.log" 2>&1
done <<< "$test"

Related links:

Efficient live disk backup with active blockcommit

vm-backup.sh (by cabal95)

Newsletter: