Shrinking qcow2 image in Libvirt KVM

Have you ever reduced the size of the qcow2 disk image used in KVM-QEMU virtual machines? As you know, the process of increasing the size of an image is quite simple and fast, but what about the reduction?



In this article I will tell you about the situation when you need to reduce the qcow2 image of the KVM virtual machine as quickly as possible.



Block device format - qcow2



But, nevertheless, this scheme can be done with raw images, it is enough to convert it to qcow2. In general, the instructions will be relevant for everything that is converted to qcow2.



An easy way to shrink disk on KVM is as follows:



  1. Compressing a qcow2 block device (VM disk)
  2. Making a smaller disk
  3. Mounting gparted image, old and new disk
  4. Preparing to boot the OS
  5. VM and host machine specifications:


Host: CentOS 7 + QEMU 2.12 + LIBVIRT 4.5.0 + Kernel UEK5 v. 4.14

VM: CentOS 7 + 80GB HDD + Kernel std v. 3.10

The donor will be a CentOS 7 virtual machine with an 80GB hard disk image, actually occupied by 20GB and physically 80GB. We will reduce it to 40GB.



What is the difference between image size, actual and physical size?

Let's say the qcow2 image was created with a size of 80GB and we know about it. During operation, the image was clogged with data, some data was deleted. In general terms, due to the peculiarities of the process of writing and deleting data, for the OS, the deleted data does not seem to exist, but remains recorded in the image until it is overwritten by other data. Accordingly, even in the OS you will see 20GB of actually occupied data, the KVM host will show such a wonderful picture (we use the qemu-img utility to get information):



qemu-img info qcow2_image.img
image: qcow2_image.img
file format: qcow2
virtual size: 80G (85899345920 bytes)
disk size: 80G
cluster_size: 65536
Format specific information:
compat: 0.10
refcount bits: 16


You can see that virtual size = disk size, as well as du -sh of the image will show that it occupies real 80G:



du -sh qcow2_image.img
80G    qcow2_image.img


And since we need to reduce the image size to 40GB, let's start the process.



Stage 1 - Compressing the block device (image)



Before starting the disk reduction procedure, you need to make sure that the occupied space inside the OS is less than the volume to which the disk will be reduced in one way or another.



df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda2        80G   18G   63G  22% /


As we can see, 18GB is occupied, which is less than 40GB. Turn off the VM with the command



shutdown -h now


And go to the host machine, check:



  • how much does the image take physically
  • how much actually


# qemu-img info qcow_shrink
image: qcow_shrink
file format: qcow2
virtual size: 80G (85899345920 bytes)
disk size: 80G
cluster_size: 65536
Format specific information:
    compat: 0.10
    refcount bits: 16
# virt-df -h qcow_shrink
du -sh Filesystem                                Size       Used  Available  Use%
qcow_shrink:/dev/sda1                     488M       101M       351M   21%
qcow_shrink:/dev/sda2                      79G        17G        62G   22%
# du -sh qcow_shrink
80G     qcow_shrink


To compress the image, we need a simple utility called virt-sparsify . Make sure the VM is down and execute the command in the directory along with the disk image (Important note: before starting virt-sparsify , make sure that there is enough free space in / tmp and in the image storage to perform the operation)



virt-sparsify qcow_shrink qcow_shrink-new


Successful completion of the operation will result in the following output:



[   0.0] Create overlay file in /tmp to protect source disk
[   0.1] Examine source disk
[   1.2] Fill free space in /dev/sda1 with zero
[   1.5] Fill free space in /dev/sda2 with zero
 100% ⟦▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒⟧ 00:00
[  72.5] Copy to destination and make sparse
[  81.9] Sparsify operation completed with no errors.
virt-sparsify: Before deleting the old disk, carefully check that the
target disk boots and works correctly.


Then we swap the disk (move qcow_shrink somewhere to the side, for example, qcow_shrink-old , and qcow_shrink-new in its place - qcow_shrink ).



mv qcow_shrink qcow_shrink-old && mv qcow_shrink-new qcow_shrink


We start the VM. If everything starts, we extinguish the VM and continue working.



Stage 2 - Create a smaller disk



A simple procedure with just one command:



qemu-img create -f qcow2 -o preallocation=metadata qcow_shrinked 40G


qcow_shrinked - new image name

40G - new size



Stage 3 - connecting gparted



Since sometimes admins prefer easier ways of solving the issue, the tambourine is put aside (kpartx) and ISO and VNC come in its place. Fortunately, it is not very difficult to connect it in KVM.



What are we doing:



  • Connect the ISO GParted image
  • Connect qcow2_shrinked to VM
  • Launch VM, boot from ISO


I will omit how to do this from this article, since it is assumed that the person doing all this already knows how it happens, but the result will be the following:



image



Start the VM and see the GParted boot screen:



image



Select the first item and follow the instructions on the screen. I usually press enter all the way.



image



Seeing GParted itself, let's get down to action. Quickly check which partition table / dev / vda has - msdos or gpt . This is important:



image



Switch to the second disk / dev / vdb and create the partition table:



image



image



When creating the table, select the msdos type as we learned earlier.



Then switch back to / dev / vdaand sequentially, from the first disks, we begin to copy partitions, switching between vda and vdb :



image



The end result will be:



image



Press Apply and wait for the result to complete:



image



As a result:



image



That already looks like the truth. But since we have done some manipulation that will lead to a change in the UUID of the disks, we will potentially not boot into the OS. Why? CentOS 7 uses disk UUIDs in fstab, Grub2 uses disk UUIDs, so jump into the console and do some black magic.



Gparted works initially as a user, so we jump under root with the command sudo su - root :



image



Let's blkid to make sure that the UUID of the partitions has changed



image



It can be seen that the UUID vda1 = vdb1, but for vdb2 it has changed. It's okay - you can live with it.



Mount vdb completely, along with the / boot partition, and mount some partitions for our convenience.



mkdir vdb2
mount /dev/vdb2 vdb2
mount /dev/vdb1 vdb2/boot
cd vdb2
mount --bind /dev dev
mount --bind /sys sys
mount --bind /proc proc
chroot .


Let's start with fstab - since it is not very convenient to type the UUID in VNC, we will replace it with the familiar device name.



image



We replace the line with UUID = ... by, attention : we



specify / dev / vdb2 , if the old disk is not planned to be disconnected, we

specify / dev / vda2 , if the old disk is disconnected



Since we disconnect the old disk before loading the OS, then we write / dev / vda2



Next let's change the bootloader, put it in order. Let's assume that everything is in / boot / grub2, grub.cfg is in the same place, but efi is not (msdos table, which is efi :)):



grub2-install /dev/vdb
cd /boot/grub2
grub2-mkconfig -o grub.cfg


On this you can be happy for yourself and by disabling gparted, boot into the OS.



Stage 4 - boot the OS



Before loading the OS, I still recommend disconnecting the old disk from the server. Therefore, at the previous stage, vda2 had to be registered in fstab, but if you are an attentive PC user and did not disconnect anything, then there should be no problems. With an old disk, it is very likely that you will boot from it.



There were no problems during the boot process, the server booted up as expected. Let's check this:



[root@shrink ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        484M     0  484M   0% /dev
tmpfs           496M     0  496M   0% /dev/shm
tmpfs           496M  6.7M  489M   2% /run
tmpfs           496M     0  496M   0% /sys/fs/cgroup
/dev/vdb2        40G   18G   23G  44% /
/dev/vdb1       488M  101M  352M  23% /boot
tmpfs           100M     0  100M   0% /run/user/0

[root@shrink ~]# blkid
/dev/vda1: UUID="ea505196-32fb-4df6-8bed-0a0ab2d0b726" TYPE="ext4"
/dev/vda2: UUID="30ec1bc6-658f-4611-8708-5e3b7ebaa467" TYPE="xfs"
/dev/vdb1: UUID="ea505196-32fb-4df6-8bed-0a0ab2d0b726" TYPE="ext4"
/dev/vdb2: UUID="c8548834-272b-4331-a9bf-aa99fb41a434" TYPE="xfs"
/dev/sr0: UUID="2019-03-21-13-42-32-00" LABEL="GParted-live" TYPE="iso9660" PTTYPE="dos"


We see that / boot and / are required, 40GB in size, the OS is running. Happiness, not otherwise!



Bonus



Something that you will have to face in some situations.



  1. VM Windows, virt-sparsify . , , ( blkid), Windows , . ( ) fixmbr + rebuildbcd. — man
  2. — xfs Superblock has unknown read-only compatible features (0x4) enabled. read-only, . :


Most likely, when everything was done in gparted or another environment, the version of the kernel of this environment was too new, in which xfs was slightly changed, namely the metadata and their version differ. As a result, xfs made in the new kernel turns into a pumpkin on the old one. What we do is boot back into rescue gparted, raise the network in this rescue environment and install the freshest kernel in the OS. I installed 5.x on CentOS 7, maybe 4.x will do, I haven't tested it, but in the end everything worked. Moreover, without any problems.



That's all!



As you can see, there is nothing complicated about it. Of course, you can use LVM, resize2fs and other things, but still qcow2 is used somewhere else and even needs someone.



If you know an even simpler method - write about it in the comments.



All Articles