7 Commits

Author SHA1 Message Date
Bastian
5b7fd4986b Add preflight check: pool_exists 2023-03-23 16:14:04 +01:00
Bastian
41abd0429a Fix Regex to exclude cloud-init drive 2023-03-23 15:46:58 +01:00
Bastian
890567ad05 Remove vm from ha group before shutting down on migration 2023-03-23 14:10:55 +01:00
Bastian
f5441f4c0b Sanitize cloud-init drives from the config 2023-03-22 15:22:36 +01:00
Bastian
fb5b3a6d09 Merge pull request #1 from lephisto/feature-move
Add --migrate feature: near-live migrate between clusters
2023-03-22 14:42:05 +01:00
Bastian
5bf37e886c Add --migrate feature: near-live migrate between clusters 2023-03-22 14:40:01 +01:00
Bastian
010f04c412 make --vmids=num vorking with --prefixids, bump version 2022-12-06 14:20:28 +01:00
2 changed files with 90 additions and 27 deletions

View File

@@ -142,6 +142,10 @@ The use case is that you might want to keep a cold-standby copy of a certain VM
Another usecase could be that you want to migrate a VM from one cluster to another with the least downtime possible. Real live migration that you are used to inside one cluster is hard to achive cross-cluster, but you can easily make an initial migration while the VM is still running on the source cluster (fully transferring the block devices), shut it down on source, run the mirror process again (which is much faster now because it only needs to transfer the diff since the initial snapshot) and start it up on the target cluster. This way the migration basically takes one boot plus a few seconds for transferring the incremental snapshot. Another usecase could be that you want to migrate a VM from one cluster to another with the least downtime possible. Real live migration that you are used to inside one cluster is hard to achive cross-cluster, but you can easily make an initial migration while the VM is still running on the source cluster (fully transferring the block devices), shut it down on source, run the mirror process again (which is much faster now because it only needs to transfer the diff since the initial snapshot) and start it up on the target cluster. This way the migration basically takes one boot plus a few seconds for transferring the incremental snapshot.
## Near-live Migration
To minimize downtime and achive a near-live Migration from one Cluster to another it's recommended to do an initial Sync of a VM from the source to the destination cluster. After that, run the job again, and add the --migrate switch. This causes the source VM to be shut down prior snapshot + transfer, and be restarted on the destination cluster as soon as the incremental transfer is complete. Using --migrate will always try to start the VM on the destination cluster.
## Things to check ## Things to check
From Proxmox VE Hosts you want to backup you need to be able to ssh passwordless to all other Cluster hosts, that may hold VM's or Containers. This goes for the source and for the destination Cluster. From Proxmox VE Hosts you want to backup you need to be able to ssh passwordless to all other Cluster hosts, that may hold VM's or Containers. This goes for the source and for the destination Cluster.

113
crossover
View File

@@ -16,7 +16,7 @@ declare opt_influx_summary_metrics='crossover_jobs'
# Cross Pool Migration and incremental replication Tool for Proxmox VMs using Ceph. # Cross Pool Migration and incremental replication Tool for Proxmox VMs using Ceph.
# Author: Bastian Mäuser <bma@netz.org> # Author: Bastian Mäuser <bma@netz.org>
declare -r VERSION=0.5 declare -r VERSION=0.7
declare -r NAME=$(basename "$0") declare -r NAME=$(basename "$0")
declare -r PROGNAME=${NAME%.*} declare -r PROGNAME=${NAME%.*}
@@ -66,6 +66,7 @@ declare -i opt_keepslock=0
declare -i opt_keepdlock=0 declare -i opt_keepdlock=0
declare -i opt_overwrite=0 declare -i opt_overwrite=0
declare -i opt_online=0 declare -i opt_online=0
declare -i opt_migrate=0
declare opt_keep_local='0s' declare opt_keep_local='0s'
declare opt_keep_remote='0s' declare opt_keep_remote='0s'
@@ -116,8 +117,9 @@ Options:
--influxtoken Influx API token with write permission --influxtoken Influx API token with write permission
--influxbucket Influx Bucket to write to (e.g. --influxbucket=telegraf/autogen) --influxbucket Influx Bucket to write to (e.g. --influxbucket=telegraf/autogen)
--jobname Descriptive name for the job, used in Statistics --jobname Descriptive name for the job, used in Statistics
Switches: Switches:
--online Allow online Copy --online Allow online Copy
--migrate Stop VM on Source Cluster before final Transfer and restart on destination Cluster
--nolock Don't lock source VM on Transfer (mainly for test purposes) --nolock Don't lock source VM on Transfer (mainly for test purposes)
--keep-slock Keep source VM locked on Transfer --keep-slock Keep source VM locked on Transfer
--keep-dlock Keep VM locked after transfer on Destination --keep-dlock Keep VM locked after transfer on Destination
@@ -137,7 +139,7 @@ function parse_opts(){
local args local args
args=$(getopt \ args=$(getopt \
--options '' \ --options '' \
--longoptions=vmid:,prefixid:,excludevmids:,destination:,pool:,keeplocal:,keepremote:,rewrite:,influxurl:,influxorg:,influxtoken:,influxbucket:,jobname:,online,nolock,keep-slock,keep-dlock,overwrite,dry-run,debug \ --longoptions=vmid:,prefixid:,excludevmids:,destination:,pool:,keeplocal:,keepremote:,rewrite:,influxurl:,influxorg:,influxtoken:,influxbucket:,jobname:,online,migrate,nolock,keep-slock,keep-dlock,overwrite,dry-run,debug \
--name "$PROGNAME" \ --name "$PROGNAME" \
-- "$@") \ -- "$@") \
|| end_process 128 || end_process 128
@@ -159,8 +161,8 @@ function parse_opts(){
--influxtoken) opt_influx_token=$2; shift 2;; --influxtoken) opt_influx_token=$2; shift 2;;
--influxbucket) opt_influx_bucket=$2; shift 2;; --influxbucket) opt_influx_bucket=$2; shift 2;;
--jobname) opt_influx_jobname=$2; shift 2;; --jobname) opt_influx_jobname=$2; shift 2;;
--online) opt_online=1; shift ;;
--online) opt_online=1; shift 2;; --migrate) opt_migrate=1; shift ;;
--dry-run) opt_dry_run=1; shift;; --dry-run) opt_dry_run=1; shift;;
--debug) opt_debug=1; shift;; --debug) opt_debug=1; shift;;
--nolock) opt_lock=0; shift;; --nolock) opt_lock=0; shift;;
@@ -202,10 +204,16 @@ function parse_opts(){
fi fi
fi fi
if [ $opt_keepdlock -eq 1 ] && [ $opt_migrate -eq 1 ]; then
log error "--keepdlock/--migrate: Invalid parameter Combination: you can't keep the destination locked in near-live migration mode"
end_process 255
fi
if [ "$opt_vm_ids" = "all" ]; then if [ "$opt_vm_ids" = "all" ]; then
local all='' local all=''
local data='' local data=''
local cnt='' local cnt=''
local ids=''
all=$(get_vm_ids "$QEMU_CONF_CLUSTER/*$EXT_CONF" "$LXC_CONF_CLUSTER/*$EXT_CONF") all=$(get_vm_ids "$QEMU_CONF_CLUSTER/*$EXT_CONF" "$LXC_CONF_CLUSTER/*$EXT_CONF")
all=$(echo "$all" | tr ',' "\n") all=$(echo "$all" | tr ',' "\n")
@@ -218,8 +226,17 @@ function parse_opts(){
done done
vm_ids=$(echo "$vm_ids" | tr ',' "\n") vm_ids=$(echo "$vm_ids" | tr ',' "\n")
else else
vm_ids=$(echo "$opt_vm_ids" | tr ',' "\n") if [ ! -z $opt_prefix_id ]; then
ids=$(echo "$opt_vm_ids" | tr ',' "\n")
for id in $ids; do
vm_ids=$(echo "$vm_ids$id:$opt_prefix_id$id,")
done
vm_ids=$(echo "$vm_ids" | tr ',' "\n")
else
vm_ids=$(echo "$opt_vm_ids" | tr ',' "\n")
fi
fi fi
log debug "vm_ids: $vm_ids"
} }
human_readable() { human_readable() {
@@ -416,6 +433,11 @@ function mirror() {
map_vmids_to_host map_vmids_to_host
map_vmids_to_dsthost "$opt_destination" map_vmids_to_dsthost "$opt_destination"
if [ $(check_pool_exist "$opt_pool") -eq 0 ]; then
log error "Preflight check: Destination RBD-Pool $opt_pool does not exist."
end_process 255
fi
for vm_id in $svmids; do for vm_id in $svmids; do
(( vmcount++ )) (( vmcount++ ))
local file_config; file_config=$(get_config_file) local file_config; file_config=$(get_config_file)
@@ -435,7 +457,7 @@ function mirror() {
if [ $host_on_destination ]; then if [ $host_on_destination ]; then
dststatus=$(ssh root@${dstpvnode[$dvmid]} qm status $dvmid|cut -d' ' -f 2) dststatus=$(ssh root@${dstpvnode[$dvmid]} qm status $dvmid|cut -d' ' -f 2)
if [ $dststatus == "running" ]; then if [ $dststatus == "running" ]; then
log error "Destination VM is running. bailing out" log error "VM is running on Destination. bailing out"
end_process 255 end_process 255
fi fi
fi fi
@@ -456,21 +478,33 @@ function mirror() {
map_vmids_to_dsthost "$opt_destination" map_vmids_to_dsthost "$opt_destination"
fi fi
#--move so we need to shutdown and remove from ha group?
if [ $opt_migrate -eq 1 ]; then
log info "VM $vm_id - Migration requested, shutting down VM on ${pvnode[$vm_id]}"
if [ "$(get_ha_status "$vm_id")" == "started" ]; then
log info "VM $vm_id - remove from HA"
do_run "ha-manager remove $vm_id"
fi
do_run "ssh root@${pvnode[$vm_id]} qm shutdown $vm_id >/dev/null"
fi
#Lock on source + destination #Lock on source + destination
if [ $opt_lock -eq 1 ]; then if [ $opt_lock -eq 1 ]; then
do_run "ssh root@"${pvnode[$vm_id]}" qm set "$vm_id" --lock backup" >/dev/null do_run "ssh root@"${pvnode[$vm_id]}" qm set "$vm_id" --lock backup" >/dev/null
log info "VM $vm_id - locked $vm_id [rc:$?]" log info "VM $vm_id - locked $vm_id [rc:$?] on source"
do_run "ssh root@"${dstpvnode[$dvmid]}" qm set "$dvmid" --lock backup" >/dev/null do_run "ssh root@"${dstpvnode[$dvmid]}" qm set "$dvmid" --lock backup" >/dev/null
log info "VM $dvmid - locked $dvmid [rc:$?]" log info "VM $dvmid - locked $dvmid [rc:$?] on destination"
fi fi
#Freeze fs only if no migration running
vm_freeze "$vm_id" "${pvnode[$vm_id]}" >/dev/null if [ $opt_migrate -eq 0 ]; then
freezerc=$? vm_freeze "$vm_id" "${pvnode[$vm_id]}" >/dev/null
if [ $freezerc -gt 0 ]; then freezerc=$?
log error "VM $vm_id - QEMU-Guest could not fsfreeze on guest." if [ $freezerc -gt 0 ]; then
(( perf_freeze_failed++ )) log error "VM $vm_id - QEMU-Guest could not fsfreeze on guest."
else (( perf_freeze_failed++ ))
(( perf_freeze_ok++ )) else
(( perf_freeze_ok++ ))
fi
fi fi
for disk in $(get_disks_from_config "$file_config"); do for disk in $(get_disks_from_config "$file_config"); do
src_image_spec=$(get_image_spec "$disk") src_image_spec=$(get_image_spec "$disk")
@@ -483,16 +517,17 @@ function mirror() {
(( perf_ss_ok++ )) (( perf_ss_ok++ ))
fi fi
done done
vm_unfreeze "$vm_id" "${pvnode[$vm_id]}" >/dev/null if [ $opt_migrate -eq 0 ]; then
unfreezerc=$? vm_unfreeze "$vm_id" "${pvnode[$vm_id]}" >/dev/null
if [ $unfreezerc -gt 0 ]; then unfreezerc=$?
log error "VM $vm_id - QEMU-Guest could not fsunfreeze on guest." if [ $unfreezerc -gt 0 ]; then
log error "VM $vm_id - QEMU-Guest could not fsunfreeze on guest."
fi
if [ ! $opt_keepslock -eq 1 ]; then
do_run "ssh root@${pvnode[$vm_id]} qm unlock $vm_id" >/dev/null
log info "VM $vm_id - unlocked source VM $vm_id [rc:$?]"
fi
fi fi
if [ ! $opt_keepslock -eq 1 ]; then
do_run "ssh root@${pvnode[$vm_id]} qm unlock $vm_id" >/dev/null
log info "VM $vm_id - unlocked source VM $vm_id [rc:$?]"
fi
for disk in $(get_disks_from_config "$file_config"); do for disk in $(get_disks_from_config "$file_config"); do
(( diskcount++ )) (( diskcount++ ))
log debug "VMID: $vm_id Disk: $disk DESTVMID: $dvmid" log debug "VMID: $vm_id Disk: $disk DESTVMID: $dvmid"
@@ -591,6 +626,12 @@ function mirror() {
ssh root@${dstpvnode[$dvmid]} qm unlock $dvmid ssh root@${dstpvnode[$dvmid]} qm unlock $dvmid
log info "VM $dvmid - Unlocking destination VM $dvmid" log info "VM $dvmid - Unlocking destination VM $dvmid"
fi fi
#--migrate so start on destination?
if [ $opt_migrate -eq 1 ]; then
log info "VM $dvmid - Starting VM on ${pvnode[$vm_id]}"
do_run "ssh root@"${dstpvnode[$dvmid]}" qm start "$dvmid >/dev/null
fi
done done
endjob=$(date +%s) endjob=$(date +%s)
log info "Finnished mirror $(date "+%F %T")" log info "Finnished mirror $(date "+%F %T")"
@@ -710,7 +751,7 @@ function rewriteconfig(){
else else
sedcmd='sed -e /^$/,$d' sedcmd='sed -e /^$/,$d'
fi fi
cat "$oldconfig" | sed -r -e "s/^(virtio|ide|scsi|sata|mp)([0-9]+):\s([a-zA-Z0-9]+):(.*)-([0-9]+)-disk-([0-9]+).*,(.*)$/\1\2: $newpool:\4-$newvmid-disk-\6-\3,\7/g" | $sedcmd | sed -e '/^$/,$d' | grep -v "^parent:\s.*$" | ssh "$dst" "cat - >$newconfig" cat "$oldconfig" | sed -r -e "s/^(virtio|ide|scsi|sata|mp)([0-9]+):\s([a-zA-Z0-9]+):(.*)-([0-9]+)-disk-([0-9]+).*,(.*)$/\1\2: $newpool:\4-$newvmid-disk-\6-\3,\7/g" | $sedcmd | sed -e '/^$/,$d' | sed -e '/ide[0-9]:.*-cloudinit,media=cdrom.*/d' | grep -v "^parent:\s.*$" | ssh "$dst" "cat - >$newconfig"
} }
function checkvmid(){ function checkvmid(){
@@ -774,6 +815,24 @@ function get_image_spec(){
echo "$image_spec" echo "$image_spec"
} }
function get_ha_status() {
local havmid="$1"
ha_status=$(ha-manager status| grep vm:"$havmid" | cut -d " " -f 4| sed 's/.$//')
echo "$ha_status"
}
function check_pool_exist() {
local poolname="$1"
local -i exists=255
pool_status=$(ssh $opt_destination pvesm status|grep rbd|cut -d " " -f 1|grep $poolname)
if [ "$pool_status" == "$poolname" ]; then
exists=1
else
exists=0
fi
echo $exists
}
function main(){ function main(){
[ $# = 0 ] && usage; [ $# = 0 ] && usage;