Moving VMs between Azure Zones
Sven Illert -
I am currently involved in a project that aims to migrate an on premises installation with about 150 Servers and 2 Exadata Database Machines into the Microsoft Azure cloud. Such a large project should include a reasonable amount of planning and as you probably know, that doesn’t always work perfectly from the start. In that phase considerations about high availability were made and since the project is located in a region that provides multiple availability zonss as a first line of defense, it was decided to spread systems across them. The details for the implementation were tracked in an Excel sheet which contains a lot of information about the target VM state including computing shape, ip addresses, target availability zone etc. That is then converted to several JSON files which provide the input for OpenTofu code.
But as always, errors happen and so some of the target availability zones for the automatically created virtual machines were plain wrong (e.g. 1 instead of 3). Trying to figure out how to move virtual machines and all their attached disks resulted in a quick disappointment. It’s just not possible by using standard methods using the portal or command line utility. Changing the zone in Tofu files didn’t help either, since the resources would be recreated then. Also moving the resource via site recovery wouldn’t work as desired, since this would create a clone and not move it.
With a little bit of research you can find some instructions to do a movement manually via backup and restore. Also Microsoft advertises a python script in a blog post which also creates an automated copy for several VMs from a CSV file. But there are some problems with the implementation. First, I see no reason to use pyhton just to invoke the az
command, why not use the API when you have a real programming language at hand? Of course, that’s just personal preference. Second problem is that I don’t want to fill in a CSV and need to specify more options than really needed (e.g. VM name and target zone). Last but not least, the script creates a copy with a new name which is not compatible with IaC and would just be ugly if there’s a new name.
So I started creating an own shell script that tries to automate as much as possble in my project and came up with the solution shown below. It can be invoked with ./move-zone.sh <zone> <vm-name>
. I wouldn’t say you could use it out of the box effortlessly, since not every aspect that might be different from the defaults is considered and specifying things like resource group is not part of it. But it might be a start for your own version.
In general the script works in the following way:
- stop and deallocate the vm
- create a backup of the disks
- delete vm and disks
- restore os disk in target zone
- restore VM
- restore additional disks and attach them
- start VM
#!/bin/sh
tzone=$1
vm=$2
if [ "x${tzone}" = "x" ]; then
echo "ERROR: Target zone not specified"
exit 1
fi
if [ "x${vm}" = "x" ]; then
echo "ERROR: VM to move not specified"
exit 1
fi
vmdescraw=$(az vm show --name ${vm})
ret=$?
if [ $ret != 0 ]; then
echo "ERROR: Failed to get vm description"
exit 1
fi
echo $vmdescraw | jq -R '.' | jq -s '.' | jq -r 'join("")' > ./vm-move.json
vmstatus=$(az vm show -d --name ${vm} --query "powerState")
echo $vmstatus | grep -q "deallocated"
if [ $? != 0 ]; then
echo "Stopping vm ${vm}…"
az vm stop --name ${vm}
echo "Deallocating vm ${vm}…"
az vm deallocate --name ${vm}
fi
collection=rpc-az-move-${vm}
az restore-point collection show --collection-name $collection
if [ $? != 0 ]; then
echo "Creating restore point collection ${collection}…"
az restore-point collection create --collection-name $collection --source-id "$(jq -r '.id' ./vm-move.json)" > rpc.json
fi
restorepoint=rp-az-move-${vm}
az restore-point show --collection-name $collection --name $restorepoint
if [ $? != 0 ]; then
echo "Creating restore point ${restorepoint}…"
az restore-point create --collection-name $collection --name $restorepoint > rp.json
fi
echo "Deleting vm ${vm}"
az vm delete --name ${vm} --yes
osdisk=$(jq -r '.storageProfile.osDisk.name' ./vm-move.json)
echo "Deleting disk ${osdisk}"
az disk delete --name ${osdisk}
datadisks=$(jq -r '.storageProfile.dataDisks[].name' ./vm-move.json)
for disk in $(echo ${datadisks}); do
echo "Deleting disk ${disk}"
az disk delete --name ${disk}
done
rposdiskid=$(jq -r '.sourceMetadata.storageProfile.osDisk.diskRestorePoint.id' rp.json)
rposdiskname=$(jq -r '.sourceMetadata.storageProfile.osDisk.name' rp.json)
rposdisksize=$(jq -r '.sourceMetadata.storageProfile.osDisk.diskSizeGb' rp.json)
rposdiskos=$(jq -r '.sourceMetadata.storageProfile.osDisk.osType' rp.json)
rposdiskdes=$(jq -r '.sourceMetadata.storageProfile.osDisk.managedDisk.diskEncryptionSet.id' rp.json)
rposdiskstorage=$(jq -r '.sourceMetadata.storageProfile.osDisk.managedDisk.storageAccountType' rp.json)
echo "Creating os disk ${rposdiskname}…"
az disk create --name $rposdiskname --zone $tzone --source $rposdiskid --size-gb $rposdisksize --os-type $rposdiskos --disk-encryption-set $rposdiskdes --network-access-policy DenyAll --sku $rposdiskstorage
rpvmname=$vm
rpvmshape=$(jq -r '.sourceMetadata.hardwareProfile.vmSize' rp.json)
rpvmcomputername=$(jq -r '.sourceMetadata.hardwareProfile.vmSize' rp.json)
rpvmnic=$(jq -r '.sourceMetadata.hardwareProfile.vmSize' rp.json)
echo "Creating vm ${rpvmname}…"
az vm create --name $rpvmname --zone $tzone --attach-os-disk $rposdiskname --size $rpvmshape --os-type $rposdiskos --computer-name $rpvmcomputername --nics nic-${vm}
datadisks=$(jq -c '.sourceMetadata.storageProfile.dataDisks[]' ./rp.json)
for disk in $(echo $datadisks); do
rpdiskid=$(echo $disk | jq -r '.diskRestorePoint.id')
rpdiskname=$(echo $disk | jq -r '.name')
rpdisksize=$(echo $disk | jq -r '.diskSizeGb')
rpdisklun=$(echo $disk | jq -r '.lun')
rpdiskdes=$(echo $disk | jq -r '.managedDisk.diskEncryptionSet.id')
rpdiskstorage=$(echo $disk | jq -r '.managedDisk.storageAccountType')
echo "Creating app disk ${rpdiskname}…"
az disk create --name $rpdiskname --zone $tzone --source $rpdiskid --size-gb $rpdisksize --sku $rpdiskstorage
echo "Attaching app disk ${rpdiskname}…"
az vm disk attach --vm-name $vm --name $rpdiskname --lun $rpdisklun --caching ReadWrite
done
az vm start --name $vm