Editing
2 Node Cluster: Dual Primary DRBD + CLVM + KVM + Live Migrations
(section)
Jump to navigation
Jump to search
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
==== CLVM - KVM virt Snapshot ==== * you will have to edit some variables for this to work properly * It will also set DRBD and others in unmanaged mode, so pacemaker will not potential fence on failures * This is a heavily modified version of ''http://repo.firewall-services.com/misc/virt/virt-backup.pl'' (other options like cleanup do not work) ;Usage: ./virt-backup-drbd_clvm.pl vm=<virt_name> [--compress] ;virt-backup-drbd_clvm.pl <pre> #!/usr/bin/perl -w ## lots of hacks due to bugs.. in lvm/clustered vg use XML::Simple; use Sys::Virt; use Getopt::Long; use Data::Dumper; # Set umask umask(022); # Some constant my $drbd_dir = '/etc/drbd.d/'; our %opts = (); our @vms = (); our @excludes = (); our @disks = (); our $drbd_dev; my $migrate_to = 'blindpig'; ## host to migrate machines to if they are running locally my $migrate_from = 'bigeye'; ## ht # Sets some defaults values my $host =`hostname`; chomp($host); my $migration = 0; #placeholder # What to run. The default action is to dump $opts{action} = 'dump'; $opts{backupdir} = '/NFS/_local_/_backups/KVM/'; $opts{snapsize} = '1G'; # Debug $opts{debug} = 1; $opts{snapshot} = 1; $opts{compress} = 'none'; $opts{lvcreate} = '/sbin/lvcreate -c 512'; $opts{lvremove} = '/sbin/lvremove'; $opts{blocksize} = '262144'; $opts{nice} = 'nice -n 19'; $opts{ionice} = 'ionice -c 2 -n 7'; $opts{livebackup} = 1; $opts{wasrunning} = 1; # get command line arguments GetOptions( "debug" => \$opts{debug}, "keep-lock" => \$opts{keeplock}, "state" => \$opts{state}, "snapsize=s" => \$opts{snapsize}, "backupdir=s" => \$opts{backupdir}, "vm=s" => \@vms, "action=s" => \$opts{action}, "cleanup" => \$opts{cleanup}, "dump" => \$opts{dump}, "unlock" => \$opts{unlock}, "connect=s" => \$opts{connect}, "snapshot!" => \$opts{snapshot}, "compress:s" => \$opts{compress}, "exclude=s" => \@excludes, "blocksize=s" => \$opts{blocksize}, "help" => \$opts{help} ); # Set compression settings if ($opts{compress} eq 'lzop'){ $opts{compext} = ".lzo"; $opts{compcmd} = "lzop -c"; } elsif ($opts{compress} eq 'bzip2'){ $opts{compext} = ".bz2"; $opts{compcmd} = "bzip2 -c"; } elsif ($opts{compress} eq 'pbzip2'){ $opts{compext} = ".bz2"; $opts{compcmd} = "pbzip2 -c"; } elsif ($opts{compress} eq 'xz'){ $opts{compext} = ".xz"; $opts{compcmd} = "xz -c"; } elsif ($opts{compress} eq 'lzip'){ $opts{compext} = ".lz"; $opts{compcmd} = "lzip -c"; } elsif ($opts{compress} eq 'plzip'){ $opts{compext} = ".lz"; $opts{compcmd} = "plzip -c"; } # Default is gzip elsif (($opts{compress} eq 'gzip') || ($opts{compress} eq '')) { $opts{compext} = ".gz"; $opts{compcmd} = "gzip -c"; # $opts{compcmd} = "pigz -c -p 2"; } else{ $opts{compext} = ""; $opts{compcmd} = "cat"; } # Allow comma separated multi-argument @vms = split(/,/,join(',',@vms)); @excludes = split(/,/,join(',',@excludes)); # Backward compatible with --dump --cleanup --unlock $opts{action} = 'dump' if ($opts{dump}); $opts{action} = 'cleanup' if ($opts{cleanup}); $opts{action} = 'unlock' if ($opts{unlock}); # Libvirt URI to connect to $opts{connect} = "qemu:///system"; # Stop here if we have no vm # Or the help flag is present if ((!@vms) || ($opts{help})){ usage(); exit 1; } if (! -d $opts{backupdir} ){ print "$opts{backupdir} is not a valid directory\n"; exit 1; } print "\n" if ($opts{debug}); # Connect to libvirt print "\n\nConnecting to libvirt daemon using $opts{connect} as URI\n" if ($opts{debug}); our $libvirt = Sys::Virt->new( uri => $opts{connect} ) || die "Error connecting to libvirt on URI: $opts{connect}"; foreach our $vm (@vms){ print "Checking $vm status\n\n" if ($opts{debug}); our $backupdir = $opts{backupdir}.'/'.$vm; my $vdom = $vm . '-ha'; $vdom =~ s/-ha-ha/-ha/; our $dom = $libvirt->get_domain_by_name($vdom) || die "Error opening $vm object"; if ($opts{action} eq 'cleanup'){ print "Running cleanup routine for $vm\n\n" if ($opts{debug}); # run_cleanup(); } elsif ($opts{action} eq 'dump'){ print "Running dump routine for $vm\n\n" if ($opts{debug}); run_dump(); } # else { # usage(); # exit 1; # } } ############################################################################ ############## FUNCTIONS #################### ############################################################################ sub prepare_backup{ my ($source,$res); my $target = $vm; my $match=0; my $xml = new XML::Simple (); my $data = $xml->XMLin( $dom->get_xml_description(), forcearray => ['disk'] ); my @drbd_res = &runcmd("drbdadm dump $vm"); foreach my $line (@drbd_res) { $res = $line; if ($line =~ /device\s+.*(drbd\d+)\s+minor/) { $drbd_dev = $1; last; } } # Create a list of disks used by the VM foreach $disk (@{$data->{devices}->{disk}}){ if ($disk->{type} eq 'block'){ $source = $disk->{source}->{dev}; } elsif ($disk->{type} eq 'file'){ $source = $disk->{source}->{file}; } else{ print "\nSkiping $source for vm $vm as it's type is $disk->{type}: " . " and only block is supported\n" if ($opts{debug}); next; } ## we only support the first block device for now. if ($target && $source) { last; } } ## locate the backing device for this res #my @drbd_res = &runcmd("drbdadm dump $vm"); #foreach my $line (@drbd_res) { # $res = $line; # if ($match == 1 && $line =~ /disk\s+(.*);/) { # $source = $1; # $match = 0; # } # # if ($line =~ /on\s$host\s+{/i) { $match = 1; } # } # if (!$source) { # print "Did not find DRBD backing deviced for VM\n"; # exit; # } else { # ## set target backup file based on device # $target = $source; # $target =~ s/\//_-_/g; ## rename / to _-_ # $target =~ s/^_-_//g; ## remove leading _-_ # } ## check if running on node2 - migrate here if so my $local_test = join("",&runcmd("virsh list")); if ($local_test !~ /$vm.*running/i) { my $status = 'not running'; print "$vm running remotely - migration to $migrate_to\n"; my $pvd = &GetPVD($vm); &runcmd("crm resource migrate $pvd $migrate_to"); $migration = 1; sleep 1; my $remote_test = join("",&runcmd("ssh $migrate_from -C virsh list | grep -i $vm",1)); while($remote_test =~ /(.*$vm.*)/) { print " $migrate_to:\t" . $status . "\n"; print "(r)$migrate_from:\t$remote_test\n"; sleep 5; $local_test = join("",&runcmd("virsh list",1)); if ($local_test =~ /(.*$vm.*)/i) { $status = $1; } $remote_test = join("",&runcmd("ssh $migrate_from -C virsh list | grep -i $vm",1)); } $remote_test = join("",&runcmd("ssh $migrate_from -C virsh list | grep -i $vm",1)); print "We must of migrated ok... \n"; print "(r)$migrate_to:\t$remote_test\n"; } &runcmd("crm resource unmanage clone_lvm-" . $vm); &runcmd("crm resource unmanage ms_drbd-" . $vm); sleep 1; &runcmd("ssh $migrate_from -C vgchange -aln drbd_" . $vm); # sleep 2; &runcmd("ssh $migrate_from -C drbdadm secondary " . $vm); &runcmd("ssh $migrate_from -C touch /tmp/backup.$drbd_dev"); &runcmd("ssh $migrate_from -C touch /tmp/backup.p_drbd-$vm"); &runcmd("touch /tmp/backup.$drbd_dev"); &runcmd("touch /tmp/backup.p_drbd-$vm"); my $sec_check = join("",&runcmd("drbdadm role $vm")); if( $sec_check !~ /Primary\/Secondary/) { print "Fail: DRBD res [$vm] is not the ONLY primary! result: $sec_check\n"; exit; } else { print "OK: DRBD res [$vm] is the ONLY Primary. result: $sec_check\n"; } if (!-d $backupdir) { mkdir $backupdir || die $!; } if (!-d $backupdir.'.meta') { mkdir $backupdir . '.meta' || die $!; } lock_vm(); &runcmd("vgchange -c n drbd_" . $vm); sleep 1; &runcmd("vgchange -aey drbd_" . $vm); #save_drbd_res($res); save_xml($res); my $time = "_".time(); # Try to snapshot the source if snapshot is enabled if ( ($opts{snapshot}) && (create_snapshot($source,$time)) ){ print "$source seems to be a valid logical volume (LVM), a snapshot has been taken as " . $source . $time ."\n" if ($opts{debug}); $source = $source.$time; push (@disks, {source => $source, target => $target . '_' . $time, type => 'snapshot'}); } # Summarize the list of disk to be dumped if ($opts{debug}){ if ($opts{action} eq 'dump'){ print "\n\nThe following disks will be dumped:\n\n"; foreach $disk (@disks){ print "Source: $disk->{source}\tDest: $backupdir/$vm" . '_' . $disk->{target} . ".img$opts{compext}\n"; } } } if ($opts{livebackup}){ print "\nWe can run a live backup\n" if ($opts{debug}); } } sub run_dump{ # Pause VM, dump state, take snapshots etc.. prepare_backup(); # Now, it's time to actually dump the disks foreach $disk (@disks){ my $source = $disk->{source}; my $dest = "$backupdir/$vm" . '_' . $disk->{target} . ".img$opts{compext}"; print "\nStarting dump of $source to $dest\n\n" if ($opts{debug}); my $ddcmd = "$opts{ionice} dd if=$source bs=$opts{blocksize} | $opts{nice} $opts{compcmd} > $dest 2>/dev/null"; unless( system("$ddcmd") == 0 ){ die "Couldn't dump the block device/file $source to $dest\n"; } # Remove the snapshot if the current dumped disk is a snapshot destroy_snapshot($source) if ($disk->{type} eq 'snapshot'); } $meta = unlink <$backupdir.meta/*>; rmdir "$backupdir.meta"; print "$meta metadata files removed\n\n" if $opts{debug}; &runcmd("ssh $migrate_from -C drbdadm primary " . $vm); &runcmd("ssh $migrate_from -C rm /tmp/backup.$drbd_dev"); &runcmd("ssh $migrate_from -C rm /tmp/backup.p_drbd-$vm"); &runcmd("rm /tmp/backup.$drbd_dev"); &runcmd("rm /tmp/backup.p_drbd-$vm"); sleep 1; &runcmd("vgchange -c y drbd_" . $vm); sleep 1; &runcmd("vgchange -ay drbd_" . $vm); sleep 1; &runcmd("crm resource manage p_drbd-" . $vm); &runcmd("crm resource manage ms_drbd-" . $vm); &runcmd("crm resource manage clone_lvm-" . $vm); sleep 1; &runcmd("crm_resource -r ms_drbd-$vm -C"); sleep 1; &runcmd("crm_resource -r clone_lvm-$vm -C"); sleep 3; my $prim_check = join("",&runcmd("drbdadm role $vm")); print "DRBD resource: $prim_check\n"; ## if this was migrations, move it back if ($migration) { if ($prim_check =~ /primary\/primary/i) { ## migrate back my $local_test = join("",&runcmd("virsh list")); if ($local_test =~ /$vm.*running/i) { print "$vm running locally - migration to $migrate_from from $migrate_to\n"; my $pvd = &GetPVD($vm); &runcmd("crm resource migrate $pvd $migrate_from"); sleep 1; my$remote_test = join("",&runcmd("ssh $migrate_to -C virsh list | grep -i $vm")); my $status = 'unknown'; while($local_test =~ /(.*$vm.*running)/i) { if ($local_test =~ /(.*$vm.*)/i) { $status = $1; } print " $migrate_to:\t" . $status . "\n"; print "(r)$migrate_from:\t$remote_test\n"; sleep 5; $local_test = join("",&runcmd("virsh list",1)); $remote_test = join("",&runcmd("ssh $migrate_from -C virsh list | grep -i $vm",1)); } print "Migration is Done!\n"; print "(r)$migrate_from:\t$local_test\n"; } } } ## done # And remove the lock file, unless the --keep-lock flag is present unlock_vm() unless ($opts{keeplock}); } sub usage{ print "usage:\n$0 --action=[dump|cleanup|chunkmount|unlock] --vm=vm1[,vm2,vm3] [--debug] [--exclude=hda,hdb] [--compress] ". "[--state] [--no-snapshot] [--snapsize=<size>] [--backupdir=/path/to/dir] [--connect=<URI>] ". "[--keep-lock] [--bs=<block size>]\n" . "\n\n" . "\t--action: What action the script will run. Valid actions are\n\n" . "\t\t- dump: Run the dump routine (dump disk image to temp dir, pausing the VM if needed). It's the default action\n" . "\t\t- unlock: just remove the lock file, but don't cleanup the backup dir\n\n" . "\t--vm=name: The VM you want to work on (as known by libvirt). You can backup several VMs in one shot " . "if you separate them with comma, or with multiple --vm argument. You have to use the name of the domain, ". "ID and UUID are not supported at the moment\n\n" . "\n\nOther options:\n\n" . "\t--snapsize=<snapsize>: The amount of space to use for snapshots. Use the same format as -L option of lvcreate. " . "eg: --snapsize=15G. Default is 5G\n\n" . "\t--compress[=[gzip|bzip2|pbzip2|lzop|xz|lzip|plzip]]: On the fly compress the disks images during the dump. If you " . "don't specify a compression algo, gzip will be used.\n\n" . "\t--backupdir=/path/to/backup: Use an alternate backup dir. The directory must exists and be writable. " . "The default is /var/lib/libvirt/backup\n\n" . "\t--keep-lock: Let the lock file present. This prevent another " . "dump to run while an third party backup software (BackupPC for example) saves the dumped files.\n\n"; } # Dump the domain description as XML sub save_drbd_res{ my $res = shift; print "\nSaving XML description for $vm to $backupdir/$vm.res\n" if ($opts{debug}); open(XML, ">$backupdir/$vm" . ".res") || die $!; print XML $res; close XML; } # Create an LVM snapshot # Pass the original logical volume and the suffix # to be added to the snapshot name as arguments sub create_snapshot{ my ($blk,$suffix) = @_; my $ret = 0; print "Running: $opts{lvcreate} -p r -s -n " . $blk . $suffix . " -L $opts{snapsize} $blk > /dev/null 2>&1\n" if $opts{debug}; if ( system("$opts{lvcreate} -s -n " . $blk . $suffix . " -L $opts{snapsize} $blk > /dev/null 2>&1") == 0 ) { $ret = 1; open SNAPLIST, ">>$backupdir.meta/snapshots" or die "Error, couldn't open snapshot list file\n"; print SNAPLIST $blk.$suffix ."\n"; close SNAPLIST; } return $ret; } # Remove an LVM snapshot sub destroy_snapshot{ my $ret = 0; my ($snap) = @_; print `lvs drbd_$vm`; print "Removing snapshot $snap\n" if $opts{debug}; if (system ("$opts{lvremove} -f $snap > /dev/null 2>&1") == 0 ){ $ret = 1; } return $ret; } # Lock a VM backup dir # Just creates an empty lock file sub lock_vm{ print "Locking $vm\n" if $opts{debug}; open ( LOCK, ">$backupdir.meta/$vm.lock" ) || die $!; print LOCK ""; close LOCK; } # Unlock the VM backup dir # Just removes the lock file sub unlock_vm{ print "Removing lock file for $vm\n\n" if $opts{debug}; unlink <$backupdir.meta/$vm.lock>; } sub runcmd() { my $cmd = shift; my $quiet = shift; my $ignore; ## ignore exit code 1 with greps -- not found is OK.. if ($cmd =~ /grep/) { $ignore = 1; } if (!$quiet) { print "exec: $cmd ... ";} my @output = `$cmd`; if ($?) { my $e = sprintf("%d", $? >> 8); if ($ignore && $ignore == $e) { print "grep - ignore exit code $e\n"; } else { printf "\n******** command $cmd exited with value %d\n", $? >> 8; print @output; exit $? >> 8; } } if (!$quiet) { print "success\n"; } return @output; } ## get primative VirtualDomain sub GetPVD() { my $vm = shift; my $out = join("",&runcmd("crm resource show | grep $vm | grep VirtualDomain")); if ($out =~ /([\d\w\-\_]+)/) { return $1; } else { print "Could not locate Primative VirtualDomain for $vm\n"; } } # Dump the domain description as XML sub save_xml{ print "\nSaving XML description for $vm to $backupdir/$vm.xml\n" if ($opts{debug}); open(XML, ">$backupdir/$vm" . ".xml") || die $!; print XML $dom->get_xml_description(); close XML; } </pre>
Summary:
Please note that all contributions to RARForge may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
RARForge:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Navigation menu
Personal tools
Not logged in
Talk
Contributions
Log in
Namespaces
Page
Discussion
English
Views
Read
Edit
View history
More
Search
Navigation
Home
All Pages
All Files
View Categories
Recent changes
Random page
Edit this menu
Tools
What links here
Related changes
Special pages
Page information