Recently I wanted to create a Ubuntu 22.04LTS image for the aarch64 (ARM) infrastructure with 3GB of space (instead of the default 8GB). I had done a similar procedure years ago, on a x64 Ubuntu installation, but this time the operation presented complications. Specifically, the default boot mode for Graviton instance types is UEFI: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ami-boot.html so in addition to the Linux partition, there is also an EFI partition that has to be handled.
The following should help you in case you want to achieve something similar.
Prerequisites:
- A working "source" instance , which contains the "source" volume (the one you want to shrink).
- A new empty volume, which we will call the "target" volume.
- A throwaway "staging" instance, to which we will attach both the "source" and the "target" volumes.
(Even though the volume I was shrinking contained an ARM installation, I used an x64 image for the staging instance.)
The process doesn't actually shrink the volume. What happens instead is that the data we will be mirrored to the smaller drive.
The reason why a throawaway instance is preferred is that we don't want to accidentally risk affecting production machines and data.
Initial steps:
- Before dismounting the source volume, clean the instance of any unnecessary files. You want the target volume to be able to fit all the data in the source volume (I removed Snap entirely and obsolete kernels to get below 3GB).
- Detach the source volume from the source instance.
- Attach the source volume to the staging instance (as /dev/sdf).
- Attach the target volume to the staging instance (as /dev/sdg).
- Boot the staging instance, SSH into it, and get a root bash prompt with sudo su.
Once inside, df -h should show the attached volumes.
On my particular staging instance, they showed up as /dev/xvdf and /dev/xvdg respectively.
Other guides have them as /dev/nvme1n1 and /dev/nvme2n1. This has to do with whether the staging instance is a Xen (xvd*) or Nitro (nvme*) one. Adjust as needed, but I personally prefer the Xen nomenclature for this operation. You're not going to mistake "f" and "g".
Step 1
Inspect the partitioning of the source volume:
(Highlights mine, those are values we're are interested in).
fdisk -l /dev/xvdf
Disk /dev/xvdf: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 51ED0604-9F4E-4D14-B8E7-4FC378A5E100
Device Start End Sectors Size Type
/dev/xvdf1 206848 16777182 16570335 7.9G Linux filesystem
/dev/xvdf15 2048 204800 202753 99M EFI System
Partition table entries are not in disk order.
Notice that there are two partitions:
- Partition 15 is at the start of the volume, and holds the UEFI files.
- Partition 1 is at the end of the volume, and holds the Linux installation.
- Make note of the total # of sectors, as well as the "End" and "Sectors" value for the Linux filesystem partition.
Step 2
Inspect the target volume:
fdisk -l /dev/xvdg
Disk /dev/xvdg: 3 GiB, 3221225472 bytes, 6291456 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
This volume doesn't have any partitions yet, so just make note of the total # of sectors.
Step 3
Dump the partition table of the source volume:
sudo sfdisk -d /dev/xvdf > partitions.txt
label: gpt
label-id: 51ED0604-9F4E-4D14-B8E7-4FC378A5E100
device: /dev/xvdf
unit: sectors
first-lba: 34
last-lba: 16777182
sector-size: 512
/dev/xvdf1 : start= 206848, size= 16570335, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=69E1997E-A903-4643-A4FA-491F49342E1C
/dev/xvdf15 : start= 2048, size= 202753, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=CC54DFDE-E8B4-43AA-B808-0CBE0BDB7C0D
Notice that the value for "last-lba" and "size" are exactly the values from Step 1 for "End" and "Sectors".
Step 4
Calculate the new partition configuration.
We will leave partition 15 exactly as it is, what we're interested in is making sure partition 1 gets a valid partition configuration, so the partition is created perfectly the first time around.
If we look at Step 1, we notice that the "End" and "Sectors" values for /dev/xvdf1 are correlated to the total number of sectors of the disk.
Specifically:
"End" = disk sectors (16777216) - 34 = 16777182
"Sectors" = disk sectors (16777216) - 206881 = 16570335
To get the values for /dev/xvdg we simply substitute the total number of disk sectors:
"End" = 6291456 - 34 = 6291422
"Sectors" = 6291456 - 206881 = 6084575
On your volume the deltas may be different, adjust as needed.
Step 5
Edit the partition table dump file (partitions.txt) by filling in the values calculated at the previous step. The values that need to be changed are:
- "last-lba" = the calculated "End" value.
- "size" = the calculated "Sectors" value.
I also substituted "xvdf" for "xvdg" to avoid confusion when looking at the file, but I think only the numerical values are relevant. Other guides also suggest changing the uuid of the partitions, but here it's critical that we keep them.
The edited "partitions.txt":
label: gpt
label-id: 51ED0604-9F4E-4D14-B8E7-4FC378A5E100
device: /dev/xvdg
unit: sectors
first-lba: 34
last-lba: 6291422
sector-size: 512
/dev/xvdg1 : start= 206848, size= 6084575, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=69E1997E-A903-4643-A4FA-491F49342E1C
/dev/xvdg15 : start= 2048, size= 202753, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=CC54DFDE-E8B4-43AA-B808-0CBE0BDB7C0D
Step 6
Write the new partition table to the target disk:
sudo sfdisk /dev/xvdg < partitions.txt
Step 7
Make a raw low-level copy of the EFI partition, which will remain unchanged:
sudo dd if=/dev/xvdf15 of=/dev/xvdg15 bs=512
Step 8
Mirror the Linux partition data by mounting the partition and copying the files with rsync:
mkdir /tmp/xvdf1
mkdir /tmp/xvdg1
mount /dev/xvdf1 /tmp/xvdf1
mount /dev/xvdg1 /tmp/xvdg1
rsync -av /tmp/xvdf1/ /tmp/xvdg1/
umount /dev/xvdf1
umount /dev/xvdg1
Step 9Ensure source and target partitions have the same metadata.
First, query each of them using
blkid:
blkid /dev/xvdf1
/dev/xvdf1: LABEL="cloudimg-rootfs" UUID="466bbba3-507d-44a0-989e-e25286198eba" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="69e1997e-a903-4643-a4fa-491f49342e1c"
blkid /dev/xvdg1
/dev/xvdg1: UUID="69e1997e-a903-4643-a4fa-491f49342e1c" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="69e1997e-a903-4643-a4fa-491f49342e1c"
Notice that the source partition has a LABEL, and a different UUID.
Without mirroring these as well, the machine won't boot (the label is referenced by /etc/fstab, and the UUID is referenced by the UEFI configuration).
The following commands set the right values:
e2label /dev/xvdg1 cloudimg-rootfs
tune2fs /dev/xvdg1 -U 466bbba3-507d-44a0-989e-e25286198eba
One or both may ask you to run some integrity utility.
Do so, it should run without problems.
Step 10Wrapping up:
- Stop the machine
- On the EC2 "Volumes" page detach the volumes from the staging instance.
- Attach the target volume to the source machine as /dev/sda1
- Boot.