In this recipe, we are going to examine how to resize an existing raw image, the partitions hosted on it and the filesystem on top of the partitions. We are going to be using the raw image that we build in the previous recipes, which contains a swap and a root partition with an EXT4 filesystem formatted on it.
Resizing an image
Getting ready
For this recipe, we are going to use the following tools:
- qemu-img
- losetup
- tune2fs
- e2fsck
- kpartx
- fdisk
- resize2fs
Most of the utilities should already be installed on Ubuntu with the exception of kpartx. To install it, run the following:
root@kvm:~# apt install kpartx
How to do it...
The next steps demonstrate how to add additional space to the raw image we created earlier, extend the root partition, and resize the filesystem. By the end of this recipe, the original raw image filesystem size should have changed from 10G to 20G.
- Obtain the current size of the image:
root@kvm:~# qemu-img info debian.img
image: debian.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 848M
root@kvm:~#
- Add additional 10 GB to the image:
root@kvm:~# qemu-img resize -f raw debian.img +10GB
Image resized.
root@kvm:~#
- Check the new size of the image:
root@kvm:~# qemu-img info debian.img
image: debian.img
file format: raw
virtual size: 20G (21474836480 bytes)
disk size: 848M
root@kvm:~#
- Print the name of the first unused loop device:
root@kvm:~# losetup -f
/dev/loop0
root@kvm:~#
- Associate the first unused loop device with the raw image file:
root@kvm:~# losetup /dev/loop1 debian.img
root@kvm:~#
- Read the partition information from the associated loop device and create the device mappings:
root@kvm:~# kpartx -av /dev/loop1
add map loop1p1 (252:0): 0 1024 linear 7:1 2048
add map loop1p2 (252:1): 0 20967424 linear 7:1 4096
root@kvm:~#
- Examine the new device maps, representing the partitions on the raw image:
root@kvm:~# ls -la /dev/mapper
total 0
drwxr-xr-x 2 root root 100 Mar 9 19:10 .
drwxr-xr-x 20 root root 4760 Mar 9 19:10 ..
crw------- 1 root root 10, 236 Feb 10 23:25 control
lrwxrwxrwx 1 root root 7 Mar 9 19:10 loop1p1
lrwxrwxrwx 1 root root 7 Mar 9 19:10 loop1p2
root@kvm:~#
- Obtain some information from the root partition mapping:
root@kvm:~# tune2fs -l /dev/mapper/loop1p2
tune2fs 1.42.13 (17-May-2015)
Filesystem volume name: <none>
Last mounted on: /
Filesystem UUID: 96a73752-489a-435c-8aa0-8c5d1aba3e5f
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 655360
Block count: 2620928
Reserved block count: 131046
Free blocks: 2362078
Free inodes: 634148
First block: 0
Block size: 4096
Fragment size: 4096
Reserved GDT blocks: 639
Blocks per group: 32768
Fragments per group: 32768
Inodes per group: 8192
Inode blocks per group: 512
Flex block group size: 16
Filesystem created: Fri Feb 10 23:29:01 2017
Last mount time: Thu Mar 9 19:09:25 2017
Last write time: Thu Mar 9 19:08:23 2017
Mount count: 12
Maximum mount count: -1
Last checked: Fri Feb 10 23:29:01 2017
Check interval: 0 (<none>)
Lifetime writes: 1621 MB
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 256
Required extra isize: 28
Desired extra isize: 28
Journal inode: 8
Default directory hash: half_md4
Directory Hash Seed: f101cccc-944e-4773-8644-91ebf4bd4f2d
Journal backup: inode blocks
root@kvm:~#
- Check the filesystem on the root partition of the mapped device:
root@kvm:~# e2fsck /dev/mapper/loop1p2
e2fsck 1.42.13 (17-May-2015)
/dev/mapper/loop1p2: recovering journal Setting free blocks count to 2362045 (was 2362078) /dev/mapper/loop1p2: clean, 21212/655360 files, 258883/2620928 blocks
root@kvm:~#
- Remove the journal from the root partition device:
root@kvm:~# tune2fs -O ^has_journal /dev/mapper/loop1p2
tune2fs 1.42.13 (17-May-2015)
root@kvm:~#
- Ensure that the journaling has been removed:
root@kvm:~# tune2fs -l /dev/mapper/loop1p2 | grep "features"
Filesystem features: ext_attr resize_inode dir_index filetype extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
root@kvm:~#
- Remove the partition mappings:
root@kvm:~# kpartx -dv /dev/loop1
del devmap : loop1p2
del devmap : loop1p1
root@kvm:~#
- Detach the loop device from the image:
root@kvm:~# losetup -d /dev/loop1
root@kvm:~#
- Associate the raw image with the network block device:
root@kvm:~# qemu-nbd --format=raw --connect=/dev/nbd0 debian.img root@kvm:~#
- Using fdisk, list the available partitions, then delete the root partition, recreate it, and write the changes:
root@kvm:~# fdisk /dev/nbd0
Command (m for help): p
Disk /dev/nbd0: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders, total 41943040 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
Disk identifier: 0x00000000
Device Boot Start End Blocks Id System
/dev/nbd0p1 1 16450559 8225279+ 82 Linux swap / Solaris
/dev/nbd0p2 16450560 20964824 2257132+ 83 Linux
Command (m for help): d
Partition number (1-4): 2
Command (m for help): n
Partition type:
p primary (1 primary, 0 extended, 3 free)
e extended
Select (default p): p
Partition number (1-4, default 2): 2
First sector (16450560-41943039, default 16450560):
Using default value 16450560
Last sector, +sectors or +size{K,M,G} (16450560-41943039, default 41943039):
Using default value 41943039
Command (m for help): w
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.
root@kvm:~#
- Associate the first unused loop device with the raw image file, like we did in step 5:
root@kvm:~# losetup /dev/loop1 debian.img
- Read the partition information from the associated loop device and create the device mappings:
root@kvm:~# kpartx -av /dev/loop1
add map loop1p1 (252:2): 0 1024 linear 7:1 2048
add map loop1p2 (252:3): 0 41938944 linear 7:1 4096
root@kvm:~#
- After the partitioning is complete, perform a filesystem check:
root@kvm:~# e2fsck -f /dev/mapper/loop1p2
e2fsck 1.42.13 (17-May-2015)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/mapper/loop1p2: 21212/655360 files (0.2% non-contiguous), 226115/2620928 blocks
root@kvm:~#
- Resize the filesystem on the root partition of the mapped device:
root@kvm:~# resize2fs /dev/nbd0p2
resize2fs 1.42.13 (17-May-2015)
Resizing the filesystem on /dev/mapper/loop1p2 to 5242368 (4k) blocks.
The filesystem on /dev/mapper/loop1p2 is now 5242368 (4k) blocks long.
root@kvm:~#
- Create the filesystem journal because we removed it earlier:
root@kvm:~# tune2fs -j /dev/mapper/loop1p2
tune2fs 1.42.13 (17-May-2015)
Creating journal inode: done
root@kvm:~#
- Remove the device mappings:
root@kvm:~# kpartx -dv /dev/loop1
del devmap : loop1p2
del devmap : loop1p1
root@kvm:~# losetup -d /dev/loop1
root@kvm:~#
How it works...
Resizing an image for VM can be somewhat involving, as we saw from all the steps in the previous section. Things can get complicated when there are multiple Linux partitions inside the same image, even more so if we are not using Logical Volume Management (LVM). Let's step through all the commands we ran earlier and explain in more details why we ran them and what they do.
In step 1, we confirmed the current size of the image being 10 GB.
In step 2, we added 10 GB at the end of the image and confirm the new image size in step 3.
Recall that the image we built from earlier recipes contains two partitions, swap and root. We need a way to manipulate them individually. Particularly, we would like to allocate the extra space we added in step 2 to the root partition. To do that we need to expose it as a block device that we can easily manipulate with standard disk and filesystem utilities. We accomplished that using the losetup command in step 5, resulting in a mapping between the image and a new block device named /dev/loop1. In step 6, we exposed the individual partitions as two new device mappings. The /dev/mapper/loop1p2 is the root partition that we would like to append the unused disk space to.
Before we can resize the partitioned on the loop device, we need to check the integrity of the filesystem on it, and this is what we did in step 9. Because we are using a journaling filesystem, we need to remove the journal prior to resizing. We do that in step 10 and made sure that the has_journal attribute is not showing after running the tune2fs command in step 11.
Now, we need to work directly on the main block device and not the individual partitions. We remove the mappings in steps 12 and 13 and associated a new block device with the image file using the qemu-nbd command in step 14. The new /dev/nbd0 block device now represents the entire disk of the guest VM and it's a direct mapping to what's inside the raw image. We can use this block device just like any other regular disk, most importantly we can use tools such as fdisk to examine and manipulate the partitions residing on it.
In step 15, we use the fdisk utility to delete the root partition and recreate it. This does not destroy any filesystem data, but changes the metadata, allocating the extra space we added earlier as part of the root partition.
Now that the block device has all the disk space allocated to the root partition, we need to extend the filesystem that is on top of it. We do that by first recreating the individual partition mappings like we did earlier, to expose the root partition directly so that we can yet again manipulate it. We do that in steps 16 and 17.
In steps 18 and 19, we check the integrity of the root file system, then we resize it to the maximum available disk space on the root partition that it resides.
Finally, in step 20, we remove the mappings again. Now the image, the root partition inside the image, and the EXT4 filesystem on top of the Linux partition have been resized to 20 GB.
You can check the new root partition size by starting a new QEMU instance using the image. We are going to do just that in a separate recipe in this chapter.