Simple Automated Image Backup Script for Active Linux KVM Virtual Machines

If you’re running a Linux KVM Virtual Machine Host you may find yourself wanting to backup full images without taking the VMs offline or specify the VMs (so you can add and delete VMs at your own discretion).

This would have saved my arse when I accidentally flattened a database that I’d forgotten was on a server I rebuilt after only recently spinning it up.

This script will jump on virsh, flick through each output line for active VMs and fetch its integer ID.

Then find the path and name of the image file, freeze the file system, image it, then thaw it and move on.

Often image files and VM names are the same plus qcow2 but in my case I appended the IP on the VM name for quick reference which made them differ. So this will get the real names, and refer by ID.

If you do not have the qemu-guest-agent installed it will be unable to freeze the file system and will throw an error then move on. So you can use this as a means to control which VMs are and aren’t backed up when setting up the server itself.

I saved mine to /root then called it with a cronjob running as root. You don’t have to run it as root, but you’ll need to set up permissions accordingly.

Finally I back up using my own means, you could use Lsync or UrBackup to automate either of these.

Script

#!/bin/bash

###########################################################################################

#Linux KVM Active VM Image Backup Script
#Author: Daniel Chong
#Date 28 September 2021

#This finds all active VMs, loops through them freezes their file system, takes an image then thaws them.

#It logs an error if it can't freeze the file system (usually due to the QEMU-Guest-Agent not being installed,
#on Debian builds it can be installed using apt/apt-get.

#This can actually be used to your advantage, if you want a VM to be backed up, install the QEMU-Guest-Agent, if you don't, don't install it.

#Files are backed up to the backupPath variable
#Current Date is prefixed onto the back up name, in the event of a conflict it will overwrite the old file

###########################################################################################


#Backups are saved here, full path ending on a forward slash
backupPath=""; 

#currentDate will be appended to the backup name
currentDate=$(date +"%m_%d_%Y");

#fetch the VM list, for each, make the variable $line;
sudo virsh list | while read line; 
do

        #take the first column from line and output it into a variable $vmID ($() makes the variable);
        vmID=$(awk '{print $1}' <<< $line)

        #echo for testing
        #echo "VM ID: $vmID";

        #check if the variable is in the number regex;
        if [[ $vmID =~ ^[0-9]+$ ]] ; then

                #backup the VM
                #get the qcow2 file name

                #grab the whole block record and loop through each line
                sudo virsh domblklist $vmID | while read blockLine;
                do

                        #fetch first column
                        elementOne=$(awk '{print $1}' <<< $blockLine);

                        #echo for testing
                        #echo "Column One: $elementOne";

                        #if column1 of the line = 'vda'
                        if [ "$elementOne" = 'vda' ]; then
                                #then save column2 as $fileName
                                filePath=$(awk '{print $2}' <<< $blockLine);
                                fileName=$(basename "$filePath");
                                #echo for testing
                                #echo "VDA Path: $filePath";
                                #echo "VDA FileName: $fileName";

                                backupName=$currentDate$fileName;

                                #this is the snapshot name, it will be overridden each time
                                tempName='snapshot.qcow2';
                                #echo $backupName;
                                # Freeze guest filesystem so that it can't corrupt the image, this will render the VM mostly useless
                                sudo virsh domfsfreeze $vmID

                                outcome=$?;

                                #echo for testing
                                #echo "$outcome";

                                if [ "$outcome" = '0' ]; then
                                        #echo 'Frozen';
                                        # Create snapshot, this is basically instantaneous
                                        sudo qemu-img create -f qcow2 -b $filePath "$backupPath$tempName";

                                        # Create image from snapshot, this will take a while
                                        sudo qemu-img convert -O raw "$backupPath$tempName" "$backupPath$backupName.img" --force-share

                                        # Thaw guest filesystems to restore function
                                        sudo virsh domfsthaw $vmID

                                        #echo for testing
                                        #echo 'Thawed';
                                else
                                        #echo for testing
                                        echo "Error Freezing on $vmID";
                                fi;
                        fi || exit;

                done;

                #echo for testing
                #echo "VDA NAME: $fileName";
        fi

done

Crontab

My Crontab as root then looks like the below, run the script every Sunday morning, with it running a line that will delete any files 31 days old or older.

Cronjobs
5 1 * * 7 /bin/bash -c "/root/backupVM.sh"
0 0 * * 7 find /[PATHTOBACKUPS]/backups/* -mtime +30 -type f -delete

2 thoughts on “Simple Automated Image Backup Script for Active Linux KVM Virtual Machines

    1. Nice, thanks! That probably would have been easier. I ended up moving to ProxMox on my new VM Host and just using its automation. At some point I’ll probably write a new script to push the snapshots on ProxMox to AWS S3 Deep Archive.

Leave a Reply

Your email address will not be published. Required fields are marked *