Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Use Docker and libguestfs-tools to Shrink Virtual Machine Disks (VMDKs)

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

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

resize_Existing_VM_Settings

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 details. Information such as geometry, adapter type and other identifiers are also contained within the descriptor 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 the existing Descriptor File as a template to create one 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:

5GB-VMDK_Descriptor

  • 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)
  • Type of extent: FLAT
  • 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^3) = 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):

lsi_logic_CHS

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 extent size, in bytes:

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 existing Descriptor File (Ubuntu-5GB.vmdk) and save it as /mnt/hgfs/vm-disks/Ubuntu-3GB.vmdk.

Replace contents of this new file (Ubuntu-3GB.vmdk) with what is 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.

File Name: Dockerfile

FROM ubuntu:19.10

ARG username=docker_dev

RUN 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, along with other equivalent pre-allocated "FLAT" files, are all located on the host at path /mnt/hgfs/vm-disks.

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

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

Run the Docker Image

With the host path mentioned above (/mnt/hgfs/vm-disks) and target mount point within the container (/mnt/vm-disks), we run the following command to bring up the container with the shared mount.

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 docker 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.vmdk

Name       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.vmdk

Filesystem                                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.vmdk

Filesystem                           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 the disk extent starts at sector 1, not 0. Therefore, we'll use 6291455 sectors in calculations.
  • An offset of 2048 Sectors needs to be accounted for. The partition starts at this sector.

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
  • Round 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.

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

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.

  • Remove existing Ubuntu-5GB.vmdk

remove_5gb_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
  • Click "Add"
  • Select "Hard Disk", then "Next"
  • Select "SCSI", 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:

defrag_before_boot_guest

Post Verification Checks

Launch the VM OS with the new configuration.

Once logged on,

  • Check the new partition/disk details via fdisk/parted or gparted:

post_virt_resize_verify_from_guest

  • Select to run a "Check" on new disk's partition, and apply changes:

post_virt_resize_repair_partition_from_guest

  • You can now mount the partition and check 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 expanding.

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 within the guest

  • Only a single non-OS partition was dealt 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment