Use Docker and libguestfs-tools to Shrink Virtual Machine Disks (VMDKs)

Tony Tannous
9 min readMay 31, 2020

The following article describes the process for using libguestfs-tools to shrink an existing pre-allocated secondary VMDK, used by a VMWare Ubuntu guest OS.

The process will be performed from the host machine using a customised docker image. The image’s Dockerfile includes libguestfs-tools as part of the build.

The Virtual Machine needs to be offline/shutdown before proceeding. It goes without saying, make backups of any VMDKs before attempting to manipulate them.

Existing VMDK Details

For the purposes of the demonstration, an existing 5GB pre-allocated VDMK will be used for the shrink operation. The disk is used as a secondary drive by an Ubuntu VMWare guest OS and contains a single partition.

The relevant details for this disk are as follows:

  • VMDK Descriptor file: Ubuntu-5GB.vmdk
  • VMDK Data File Name: Ubuntu-5GB-flat.vmdk
  • Size: 5GB

Target VMDK Requirements

The new, target disk should be 3GB in size. Filesystem, partition and data from the existing 5GB source disk, also need to be sized down in order to be able to fit onto this new disk.

Details for the new disk are:

  • VMDK Descriptor file: Ubuntu-3GB.vmdk
  • VMDK Disk File Name: Ubuntu-3GB-flat.vmdk
  • Target Size: 3GB

Understanding the Existing Disk Descriptor File (Ubuntu-5GB.vmdk)

When VMDKs are created, a Disk Descriptior File is generated. This file contains details which describe the accompanying raw/data disk extent details. Information such as geometry, adapter type and other identifiers are also contained within the file. The file provides VM Workstation/Player the necessary VMDK metadata, which is used by the corresponding VMWare guest machine configuration.

In order to ensure we set out on the right foot, we need to create a new Disk Descriptor File for the smaller disk (3GB), which conforms to the VMWare Virtual Disk Specification. Before getting to that, we’ll need to gain some understanding of our existing source disk’s Descriptor File ( Ubuntu-5GB.vmdk). Based on this, we can then use this file as a base template for our new 3GB target drive.

The existing Descriptor File is shown below.

File Name: Ubuntu-5GB.vmdk

#Disk Descriptor File
version=1
encoding="windows-1252"
CID=86c08749
parentCID=ffffffff
createType="monolithicFlat"
# Extent description
RW 10485760 FLAT "Ubuntu-5GB-flat.vmdk" 0
# The Disk Data Base
#DDB
ddb.adapterType = "lsilogic"
ddb.geometry.cylinders = "652"
ddb.geometry.heads = "255"
ddb.geometry.sectors = "63"
ddb.longContentID = "06042b12bc30b7187008416b86c08749"
ddb.uuid = "60 00 C2 98 c0 d3 5e 02-6a d0 ad e1 c9 e6 05 97"
ddb.virtualHWVersion = "16"

Disk Extent Descriptors

With reference to the VMDK Specification, and the existing Disk Descriptor file contents above (under section # Extent description) , we learn the following details in relation to the associated raw/data disk, Ubuntu-5GB-flat.vmdk:

  • Access: RW (readable/writeable)
  • Size in sectors: 10,485,760 with each sector being made up of 512 bytes (as taken from the official spec)
  • Filename: Ubuntu-5GB-flat.vmdk (filename containing extent/raw data)
  • Offset: 0 (with reference to the spec :”For pre-allocated virtual disks, this number is zero”).

We now know that the VMDK contains a single “FLAT” extent file ( Ubuntu-5GB-flat.vmdk) with 10,485,760 sectors, and that each sector is made up of 512 bytes. So, our existing single-extent FLAT drive's exact size can be calculated as:

  • 10485760 sectors * 512 bytes/sector = 5,368,709,120 bytes
  • 5,368,709,120/(1024³) = 5GB

Disk Geometry (Cylinders, Heads, Sectors)

According to the VMWare Virtual Disk Specification, disk geometry (cylinders, heads, sectors) are dependent on a couple of factors including the adapter type. By looking under the “# The Disk Data Base” section in the Descriptor File, our adapter type is:

ddb.adapterType = "lsilogic"

The specification did not seem to contain any reference/parameters for these values (Cylinders/Heads/Sectors), however, an excerpt from the this link shows that the following parameters/calculation can be used for lsilogic SCSI drives (>1GB):

Value for Heads = 255, and for Sectors = 63. To calculate Cylinders, we use the equation above:

<number of cylinders> = <size in sectors> / 16065

Applying this equation, with the total size of the extent for existing disk ( Ubuntu-5GB-flat.vmdk) being equal to10,485,760 sectors, we get:

Cylinders = 10485760/16065 = 652

which resolves to 652 Cylinders. The same value as that is contained in the Descriptor file.

Create New Disk Descriptor File (Ubuntu-3GB.vmdk) for 3GB Target Disk

Now that we have some understanding of a relevant Descriptor File structure/parameters, we can create a similar file for our target 3GB disk.

Our target 3GB disk needs to contain a single extent with the following byte count:

3GB Target Disk -> 3GB * (1024^3) = 3,221,225,472 bytes

Converting total bytes to sectors:

(3,221,225,472 bytes / 512 bytes per sector) = 6291456 sectors

To calculate disk geometry, we use Heads = 255, Sectors = 63 and calculate Cylinders as follows:

<number of cylinders> = <size in sectors> / 16065 
Number of Cylinders = 6291456/16065 = 391

Make a copy of the existing Descriptor File ( Ubuntu-5GB.vmdk) and save it as /mnt/hgfs/vm-disks/Ubuntu-3GB.vmdk.

Replace contents of the new file ( Ubuntu-3GB.vmdk) with the contents shown below.

#Disk Descriptor File
version=1
encoding="windows-1252"
CID=86c08749
parentCID=ffffffff
createType="monolithicFlat"
# Extent description
RW 6291456 FLAT "Ubuntu-3GB-flat.vmdk" 0
# The Disk Data Base
#DDB
ddb.adapterType = "lsilogic"
ddb.geometry.cylinders = "391"
ddb.geometry.heads = "255"
ddb.geometry.sectors = "63"
ddb.longContentID = "06042b12bc30b7187008416b86c08749"
ddb.uuid = "60 00 C2 98 c0 d3 5e 02-6a d0 ad e1 c9 e6 05 97"
ddb.virtualHWVersion = "16"

Custom Libguestfs-tools Docker Image

The purpose of creating a docker image is to have a solution that will allow us to rapidly perform VMDK maintenance from a variety of hosts running Docker. The docker image packages the libguestfs-tools, required for Virtual Disk manipulation.

Create Dockerfile Image for libguestfs-tools

The following Dockerfile is used to build the docker image.

FROM ubuntu:19.10ARG username=docker_devRUN apt-get update && apt-get install -y libguestfs-tools \
sudo linux-image-generic \
&& apt-get autoremove -yqq --purge \
&& apt-get clean \
&& rm -rf \
/var/lib/apt/lists/*
RUN useradd -ms /bin/bash -d /home/${username} ${username} \
&& usermod -aG sudo $username \
&& echo "$username ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
ENV HOME=/home/${username}
USER ${username}

Build Docker Image

Run the following to build the docker image, named guest-fs-tools.

sudo docker build -t guest-fs-tools .

Establish Shared Mount between Docker Container and Host

For my setup, VMDK Descriptor Files, and accompanying pre-allocated “FLAT/extent” files, are all located on the host at path /mnt/hgfs/vm-disks.

ls /mnt/hgfs/vm-disksUbuntu-5GB-flat.vmdk
Ubuntu-5GB.vmdk
Ubuntu-3GB.vmdk

We will run our docker image, specifying the above host path be mounted at /mnt/vm-disks within the container.

Run the Docker Image

Run the following command to bring up the container with the mount/mapping, as described above.

sudo docker run -it --rm --privileged -v /mnt/hgfs/vm-disks:/mnt/vm-disks guest-fs-tools

Query Existing (Ubuntu-5GB-flat.vmdk) Disk Details

With our docker container up and running, we can now starting issuing commands from within the container itself.

Existing Partition/Filesytem Information

Information regarding current partitioning and block devices for disk Ubuntu-5GB-flat.vmdk can be obtained by running virt-filesystems:

Using virt-filesystems:

docker_dev@48fbd2885d25:/mnt/vm-disks$ sudo virt-filesystems --long --parts --blkdevs -a Ubuntu-5GB-flat.vmdkName       Type       MBR  Size        Parent
/dev/sda1 partition 83 5367660544 /dev/sda
/dev/sda device - 5368709120 -

Using virt-list-partitions:

docker_dev@d8785ff73766:/mnt/vm-disks$ sudo virt-list-partitions --long -t  Ubuntu-5GB-flat.vmdk/dev/sda1 ext4 5367660544
/dev/sda device 5368709120

For file system utilisation, we can run virt-df.

To obtain output in Human readable format (MB/GB), use -h option):

docker_dev@48fbd2885d25:/mnt/vm-disks$ sudo virt-df -h -a Ubuntu-5GB-flat.vmdkFilesystem                      Size       Used  Available  Use%
Ubuntu-5GB-flat.vmdk:/dev/sda1 4.9G 130M 4.5G 3%

or to display output formatted in 1K blocks:

docker_dev@aaed9d58c24d:/mnt/vm-disks$ sudo virt-df -a Ubuntu-5GB-flat.vmdkFilesystem                     1K-blocks       Used  Available  Use%
Ubuntu-5GB-flat.vmdk:/dev/sda1 5094016 133112 4692908 3%

From the above, we can see that the existing filesystem utilisation is only 130M. This confirms that resizing/shrinking down the file system to 3GB won’t be any issue.

Prepare to Resize Existing Disk (Ubuntu-5GB-flat.vmdk)

The basic (conservative) approach I used to calculate the down-sized filesystem size for our existing disk content (with a reasonable level of precision) is as follows:

  • The calculated target 3GB disk size in sectors = 6291456, but I’m going to assume the start point is at sector 1, not 0. Therefore, we’ll use 6291455 sectors in calculations.
  • An offset of 2048 Sectors needs to be accounted for (sectors reserved at beginning of partition)

With the above in mind, we need to shrink the file system down to:

  • (6,291,455–2048) = 6,289,407 sectors, and at 512 bytes/sector, we get (6,289,407 sectors * 512) = 3,220,176,384 bytes = 3,144,703 KB
  • Rounded down (to be conservative) to 3,140,000 KB

So we need to resize the existing file system on Ubuntu-5GB-flat.vmdk down to 3,140,000 KB

Resize Filesystem for Ubuntu-5GB-flat.vmdk

We will now perform the shrinking of the filesystem on existing disk Ubuntu-5GB-flat.vmdk, to the size calculated above: 3,140,000 KB

Launch guestfish:

docker_dev@aaed9d58c24d:/mnt/vm-disks$ sudo guestfish -a Ubuntu-5GB-flat.vmdk

Once at the ><fs> prompt, run the following:

><fs> run
100% [#######################################################################] --:--
><fs> list-filesystems
/dev/sda1: ext4
><fs> e2fsck-f /dev/sda1
><fs> resize2fs-size /dev/sda1 3140000K
><fs> e2fsck-f /dev/sda1
><fs> quit

Check Post-Resize File System

Run the following to retrieve post-resize filesystem details:

docker_dev@aaed9d58c24d:/mnt/vm-disks$ sudo virt-df -a Ubuntu-5GB-flat.vmdk

Filesystem 1K-blocks Used Available Use%
Ubuntu-5GB-flat.vmdk:/dev/sda1 3025072 130560 2727416 5%

From the above, we can confirm that the filesystem has been down-sized:

3025072 * 1k-blocks  = 3025072 * 1024 = 3,097,673,728 Bytes

Create Target 3GB Disk (Ubuntu-3GB-flat.vmdk) and Migrate

Using truncate, we create a "blank" target 3GB file ( Ubuntu-3GB-flat.vmdk):

docker_dev@aaed9d58c24d:/mnt/vm-disks$ truncate -s 3G Ubuntu-3GB-flat.vmdk

Run the following to migrate/copy over the “shrunken” filesystem/partition from Ubuntu-5GB-flat.vmdk to new 3GB target disk, Ubuntu-3GB-flat.vmdk:

docker_dev@aaed9d58c24d:/mnt/vm-disks$ sudo virt-resize --shrink  /dev/sda1 Ubuntu-5GB-flat.vmdk Ubuntu-3GB-flat.vmdk

Sample output is as follows:

[   0.0] Examining Ubuntu-5GB-flat.vmdk
100% [####################################################] --:--
**********
Summary of changes:/dev/sda1: This partition will be resized from 5.0G to 3.0G. The
filesystem ext4 on /dev/sda1 will be expanded using the ‘resize2fs’
method.
**********
[ 12.4] Setting up initial partition table on Ubuntu-3GB-flat.vmdk
[ 15.3] Copying /dev/sda1
100% [####################################################] 00:00
100% [####################################################] --:--
[ 66.4] Expanding /dev/sda1 using the ‘resize2fs’ method
Resize operation completed with no errors. Before deleting the old disk,
carefully check that the resized disk boots and works correctly.

Update Virtual Machine Configuration

We need to update the VM configuration to remove (not physically delete, yet) the existing 5G disk, and attach the new 3G disk.

Add New 3GB Drive to VM Hardware

Add new Ubuntu-3GB.vmdk by following the steps below.

Go to your Guest Virtual Machine Settings:

  • Click "Edit Virtual Machine Settings"
  • Click the "Hardware" tab
  • Select "Hard Disk", then "Next"
  • Select "Use an Existing Virtual Disk", then "Next"
  • Select the Descriptor File for the new drive (Ubuntu-3GB.vmdk)
  • Click "Finish"

After disk has been added to the VM. Run a disk Defragment as follows:

Verification Checks

Launch the VM OS with the new configuration.

Once logged on,

  • Check the new partition/disk details via fdisk/parted or gparted:
  • Select to run a “Check” on new disk’s partition, and apply changes:
  • You can now mount the partition and check that all files are intact:
sudo mount /dev/sda1 <mountpoint>

Final Points

As you might have discovered, shrinking virtual disks can get fiddly and is, by far, more complicated than the process for expansion.

Of course, several other methods could be used to accomplish the same task, however, the write-up was focused on the use of libguestfs-tools, specifically for the case of shrinking.

Here are some points worth noting/considering:

  • The docker image can be run from any guest/host OS that’s running docker
  • You could achieve the same result from within your guest OS by attaching a new virtual disk and using opensource/third-party partition tools to do the shrinking/migration from old to new disk, all from within the guest
  • Only a single, non-OS partition was dealt with in the article but libguest-fs supports just about every partitioning scenario
  • Using libguest-fs is by far a more exciting approach and is suited for those that want more insight into what’s under the covers when it comes to sysadmin

Originally published at http://github.com.

--

--

Tony Tannous

Learner. Interests include Cloud and Devops technologies.