GCP: Moving a VM instance to a different region using snapshots

The ‘gcloud compute instances move‘ command is convenient for moving VM instances from one region to another, but only works within a narrow scope of OS image types and disks. For example, only older non-UEFI OS images can be moved with this command.

Trying to move even the simplest Ubuntu bionic/focal or Debian bullseye/buster VM from one region to another results in the following error message.

$ gcloud compute instances move instance-1 --zone=us-west4-b --destination-zone=us-east1-b

ERROR: (gcloud.compute.instances.move) HTTPError 400: Invalid resource usage: 'Instance is a UEFI-enabled instance and does not support MoveInstance.'.

In this case, you need to perform a manual move of the VM by creating snapshots of the disks, and from that creating new disks in the target zone. Then create a new VM instance in the target zone with the new disks attached.

This is described in the official documentation, and I will present an example below moving a VM instance that has an additional data disk from us-east4-b to us-west4-b.

Setup variables

Define variables required during these instructions.

project_id=$(gcloud config get project)

# show OS images available
gcloud compute images list --project=$project_id --filter="family~'ubuntu-*'"
os_flags="--image-project=ubuntu-os-cloud --image-family=ubuntu-2004-lts"

instance_name=vmtest1
machine_type=e2-micro
zone=us-east4-b
destination_zone=us-west4-b
subnet=default

Create Data Disk for VM

In addition to the boot disk, we want to attach a data disk to the VM.

# get list of disk types available
gcloud compute disk-types list --filter="zone=$zone"

# create 100G data disk that will be ext4 format
gcloud compute disks create datadisk1 --type=pd-balanced --zone=$zone --size=100GB

# flag that will be used during VM creation
data_disk_flag="--disk=device-name=datadisk1,boot=no,mode=rw,name=datadisk1,scope=zonal"

SSH keypair for auth

Create public/private pair for VM authentication.

login_user=ubuntu
ssh-keygen -t ed25519 -f gcp-ssh -C $login_user -N "" -q
pub_side=$(cat gcp-ssh.pub)

Create VM instance in source zone

First, download my gcp-add-data-disk.sh script that is invoked at VM instance startup and formats/mounts the additional data disk.

wget https://raw.githubusercontent.com/fabianlee/blogcode/master/gcloud/gcp-add-data-disk.sh

Create a VM instance with boot and data disk, using an ssh keypair for authentication, and startup script for additional data disk.

gcloud compute instances create $instance_name $os_flags --project=$project_id --zone=$zone --machine-type=$machine_type --network-interface=network-tier=PREMIUM,subnet=$subnet $data_disk_flag --metadata="os-login=FALSE,ssh-keys=$login_user:$pub_side" --metadata-from-file=startup-script=gcp-add-data-disk.sh

Validate VM

Login to the VM instance and validate data disk mount and zone.

external_ip=$(gcloud compute instances describe $instance_name --zone=$zone --format='value(networkInterfaces[0].accessConfigs[0].natIP)')

# clear any previous fingerprints
ssh-keygen -f ~/.ssh/known_hosts -R $external_ip

# check that ssh is available
nc -vz $external_ip 22

# login to VM instance
ssh $login_user@${external_ip} -i gcp-ssh

# from inside VM
$ df -h /datadisk1
Filesystem Size Used Avail Use% Mounted on
/dev/sdb 98G 61M 98G 1% /datadisk1

# log from gcp-add-data-disk.sh shows VM region
$ cat /datadisk1/hello.txt 
2022-05-01 14:21:45
projects/949168343060/zones/us-east4-b

# exit vm shell
exit

Create snapshots of disks

In order to create disks in the destination zone, we have to create snapshots of the disks.

# get name of boot and data disk
boot_disk=$(gcloud compute instances describe $instance_name --zone=$zone --format='value(disks[0].source)' | sed 's#.*/##')
data_disk=$(gcloud compute instances describe $instance_name --zone=$zone --format='value(disks[1].source)' | sed 's#.*/##')

# disks are set to no-delete, so they persist even if VM is deleted
for disk in $boot_disk $data_disk; do gcloud compute instances set-disk-auto-delete $instance_name --zone $zone --disk $disk --no-auto-delete; done

# save old metadata
gcloud compute instances describe $instance_name --zone $zone > /tmp/$instance_name.metadata.old

# take snapshots of disks
gcloud compute snapshots list
for disk in $boot_disk $data_disk; do gcloud compute disks snapshot $disk --snapshot-names snapshot-$disk --zone $zone; done

To show that only the snapshot content is used to create the destination disks, we are going to place another file on the source disk. This will not (and should not) show up on the disks created in the target zone.

# create file, but it is not in snapshot, so will not be moved
ssh $login_user@${external_ip} -i gcp-ssh 'touch /datadisk1/not-part-of-snapshot.txt; ls -l /datadisk1'

Delete original VM and disks

With the snapshots created, we can now completely remove the original source disks and VM instance.

# delete current vm, does not delete disks
gcloud compute instances delete $instance_name --zone=$zone --quiet

# delete disks in source zone
for disk in $boot_disk $data_disk; do gcloud compute disks delete $disk --zone=$zone --quiet; done

Create disks in destination zone

# create new disks in target zone from snapshots
for disk in $boot_disk $data_disk; do gcloud compute disks create $disk --source-snapshot snapshot-$disk --zone=$destination_zone; done

# show disks in each zone, should be none in source and both in destination
gcloud compute disks list --filter="zone~$zone"
gcloud compute disks list --filter="zone~$destination_zone"

Create new VM instance in destination zone

# original was e2-tiny
target_machine_type=e2-small

# boot disk flag
boot_disk_flag="--disk name=$boot_disk,boot=yes,mode=rw"
# data_disk_flag does not need to be changed

# create new VM instance, attach disks
gcloud compute instances create $instance_name $os_flags --project=$project_id --zone=$destination_zone --machine-type=$target_machine_type --network-interface=network-tier=PREMIUM,subnet=$subnet $boot_disk_flag $data_disk_flag --metadata="os-login=FALSE,ssh-keys=$login_user:$pub_side" --metadata-from-file=startup-script=gcp-add-data-disk.sh

Validate new VM instance

Now let’s validate that this new VM instance has the expected data disk content.

external_ip=$(gcloud compute instances describe $instance_name --zone=$destination_zone --format='value(networkInterfaces[0].accessConfigs[0].natIP)')

# remove any older fingerprint
ssh-keygen -f ~/.ssh/known_hosts -R $external_ip

# login
ssh $login_user@${external_ip} -i gcp-ssh


# from inside new VM
$ df -h /datadisk1
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb         98G   61M   98G   1% /datadisk1

# notice that zone has now changed in last line
$ cat /datadisk1/hello.txt
2022-05-01 11:25:44
projects/949168343060/zones/us-east4-b
2022-05-01 13:40:03
projects/949168343060/zones/us-west4-b

# no 'not-part-of-snapshot.txt' file because it was not part of snapshot
$ ls /datadisk1
hello.txt  lost+found

# exit shell
$ exit

You can compare the original metadata against the newer.

# save new metadata
gcloud compute instances describe $instance_name --zone $destination_zone > /tmp/$instance_name.metadata.new

# compare
diff /tmp/$instance_name.metadata.old /tmp/$instance_name.metadata.new

Destroy new VM and disks

# mark disks with auto-delete, so they get deleted with VM
for disk in $boot_disk $data_disk; do gcloud compute instances set-disk-auto-delete $instance_name --zone $destination_zone --disk $disk; done

# delete snapshots
for disk in $boot_disk $data_disk; do gcloud compute snapshots delete snapshot-$disk --quiet; done

# delete VM instance along with disks
gcloud compute instances delete $instance_name --zone=$destination_zone --quiet

# check for any orphaned disks or snapshots
gcloud compute disks list
gcloud compute snapshots list

 

REFERENCES

google ref, moving VM to a different region

stackoverflow, moving VM to a different region using snapshots

google ref, gcloud compute instances move

google ref, gcloud compute instances create

uly.me, using ‘gcloud compute instances move’ to move regions

google ref, adding startup script

artark.ca, creating gcp persistent disk, format, mount

devopscube.com, mount extra data disk on GCP

hackingthe.cloud, pulling up metadata on GCP VM instance

stackoverflow, example showing snapshot and then disk creation in different zone

 

NOTES

check for UEFI enabled OS image

gcloud compute instances describe $instance_name --zone=$zone | grep -i uefi
  - type: UEFI_COMPATIBLE

check metadata of VM instance

ssh $login_user@${external_ip} -i gcp-ssh 'curl -s http://metadata.google.internal/computeMetadata/v1/instance/zone -H "Metadata-Flavor: Google"; echo ""'
ssh $login_user@${external_ip} -i gcp-ssh 'curl -s http://metadata.google.internal/computeMetadata/v1/instance/machine-type -H "Metadata-Flavor: Google"; echo ""'