Table of Contents

About

With newer versions of libvirt, it is possible to create complete live backups of virtual machines. The system involves the following steps:

  1. create a snapshot of the domain as an overlay such that all write operations will be diverted to the overlay
  2. copy over domain storage while the overlay is active
  3. merge the snapshot back into the domain and restore any changes that have been made in the meanwhile
  4. remove the snapshot file

Requirements

The script presented in the code section uses parallel xz in order to speed-up the compression of files while copying the storage to the backup directory.

Setup

As it currently is written, the script just needs to be run. Alternatively, you may want to copy the script to the corresponding cron directories in order to have periodic backups. Note that the script should not be run more often than once per day - mainly because backups do take a long time (the script does have protections in place to not run concurrently and corrupt files).

Directory Structure

The script will create directories under the configured backup directory for all domain names and then inside those directories the backups will be created following the year-month-day format.

Code

virsh-backup
#!/bin/sh
###########################################################################
##  Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3      ##
##  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  ##
##  rights of fair usage, the disclaimer and warranty conditions.        ##
###########################################################################
# Live backup script for libvirt/qemu virtual machines using blockcommit. #
# The script is designed to query all the active libvirt domains, create  #
# a snap overlay, copy over the image or the block device to a file and   #
# finally merge the overlay with the image - all these operations are     #
# performed without needing to suspend or shut down the virtual machine.  #
#                                                                         #
# Requirements:                                                           #
#   * pxz (parallel xz)                                                   #
#                                                                         #
# Setup: Configure the script in the configuration section and then run   #
# the script - the script is designed to be run from cron.                #
###########################################################################
 
###########################################################################
#                             CONFIGURATION                               #
###########################################################################
 
# This absolute path to a directory where the machines will be stored.
BACKUP_DIRECTORY=/mnt/archie/Backups/VMS
 
# A temporary directory to hold the temporary snapshots.
SNAP_DIRECTORY=/var/lib/libvirt/snapshots
 
# A space-separated list of domains to exclude from backup.
EXCLUDE_DOMAINS="corrade.internal"
 
###########################################################################
#                                INTERNALS                                #
###########################################################################
 
# Get active domains.
DOMAINS=`virsh -c qemu:///system list | grep -o '[0-9]* [a-z]*.*running' | awk '{print $2}'`
# Generate a timestamp.
TIMESTAMP=`date +%Y%m%d`
 
# Acquire lock.
if mkdir /var/lock/virsh-backup; then
    for DOMAIN in $DOMAINS; do
        # Check if this domain has been excluded.
        for EXCLUDE in $EXCLUDE_DOMAINS; do
            if [ "$DOMAIN" = "$EXCLUDE" ]; then
                DOMAIN=""
            fi
        done
        if [ -z "$DOMAIN" ]; then
            continue;
        fi
        # Get block devices.
        VIRT_BLOCK_DEVICE=`virsh domblklist "$DOMAIN" | grep '/' | head -1 | awk '{print $1}'`
        HOST_BLOCK_DEVICE=`virsh domblklist "$DOMAIN" | grep '/' | head -1 | awk '{print $2}'`
        if [ -z "$VIRT_BLOCK_DEVICE" ]; then
            echo "Block device not found for domain "$DOMAIN
            continue;
        fi
        if [ ! -d "$SNAP_DIRECTORY" ]; then
            mkdir -p "$SNAP_DIRECTORY"
        fi
        # Create snapshot
        virsh snapshot-create-as \
           --domain "$DOMAIN" "$DOMAIN-snap-$TIMESTAMP" \
           --diskspec "$VIRT_BLOCK_DEVICE",file="$SNAP_DIRECTORY/$DOMAIN-snap-$TIMESTAMP" \
           --disk-only \
           --atomic \
           --no-metadata \
           --quiesce >/dev/null 2>&1
       if [ "$?" -ne "0" ]; then
           echo "Failed snapshoting domain "$DOMAIN
           continue;
       fi
       if [ ! -f "$SNAP_DIRECTORY/$DOMAIN-snap-$TIMESTAMP" ]; then
           echo "Snapshot file for domain "$DOMAIN" does not exit"
           continue;
       fi
       if [ ! -d "$BACKUP_DIRECTORY/$DOMAIN" ]; then
           mkdir -p "$BACKUP_DIRECTORY/$DOMAIN"
       fi
       dd if="$HOST_BLOCK_DEVICE" \
           conv=noerror status=none \
           bs=1M | pxz -0 > "$BACKUP_DIRECTORY/$DOMAIN/$TIMESTAMP.img.xz"
       virsh blockcommit "$DOMAIN" "$VIRT_BLOCK_DEVICE" \
           --active \
           --pivot >/dev/null 2>&1
       if [ "$?" -ne "0" ]; then
           echo "Could not commit back snapshot for domain "$DOMAIN
           continue;
       fi
       rm "$SNAP_DIRECTORY/$DOMAIN-snap-$TIMESTAMP"
    done
 
    # Release lock.
    rm -rf /var/lock/virsh-backup
fi