Dumping LVM volumes for debugging purposes
This post covers a possible procedure to “snapshot” a Linux installation based on a boot partition and three LVM logical volumes for root, var and swap, which proves useful when an identical environment must be reproduced on a different machine sharing the same hardware configuration. One of the requirements is to obtain the smallest possible “image”, so that it can be easily transferred and rewritten on the second machine. Such a procedure does entail a number of issues: all the machine specific parameters (e.g. /etc/hostname, MAC addresses in /etc/sysconfig/network-scripts) are deployed to the second machine and need to be adapted accordingly (not covered in this post). Bearing this in mind, all the following commands have been executed from a live image based on RedHat Linux.
Initial setup
First off, lvm2
package needs to be installed. In this reference systems, the
situation is the following:
/dev/sda2
is the partition which is hosting a LVM physical volume/dev/sda1
is the boot partition
vg1 is the only volume group on the system, consisting of the PV created on /dev/sda2.
The volume group must be first activated with vgchange
.
Resizing filesystems
All logical volumes created on top of vg1 must be shrunk to the minimum size possible, but before doing so, the filesystems need to be resized. The test system contains four LVs. The notes that follow will take /dev/vg1/var as a reference.
df
shows the available space on the LV as seen from userspace:
There is clearly a significant margin of unused space on vg1/var
and
resize2fs
, from e2fsprogs
, can be used to find out the minimum allowed size of
the ext filesystem on top of it. It is important
to notice that resize2fs uses the filesystem blocksize as default unit (normally 4K for ext),
and that the space reported by df
is the usable space as seen by the user. The actual
minimum size, metadata included, of an ext filesystem is not trivial to calculate:
there is a detailed article
on TLDP which covers the layout of ext2. The structure basically
consists of block groups, each one being divided as follows:
- Superblock
- FS descriptors
- Block bitmap
- inode bitmap
- Subset of the Inode table
- Data blocks
Taking into consideration all contributions of the metadata requires an in-depth
understanding of the filesystem, but resize2fs comes to the rescue. In fact,
this calculation is done by function calculate_minimum_resize_size
in resize/resize2fs.c
.
If resize2fs is invoked with a command line argument which is smaller than the value returned
by calculate_minimum_resize_size
, it raises an error followed by the minimum
allowed size of the filesystem, as the number of 4K blocks. To this value, I usually
add a small safety margin.
This roughly corresponds to 3.3GB.
Resizing the LVs
The LV can be resized accordingly. The procedure outlined so far applies to all the LVs, with the exception of the swap volume.
The swap filesystem is a special case, as the underlying LV can be shrunk straight away and a new swap filesystem created on top.
Modifying the mapping of the Extents
The situation after resizing the LVs is the following:
The LVs occupy around 30GB altogether, hence the underlying physical volume could be
resized to match this value with the usual safety margin. Unfortunately, this operation
is not immediately straightforward because the physical extents (PE) which map
the logical extends (LE) are normally fragmented throughout the whole physical
volume. pvresize
will refuse to shrink the physical volume if there are extends
allocated beyond the point where the new end would be. The documentation however
states that future versions of lvm2 will support automatic relocation, so this workflow
might change. For now, the physical extents must be collected at the beginning of the PV.
pvdisplay
and pvs
can be used to verify how many PEs are used and how these are
mapped on the volume.
PEs belonging to the same LVs are normally clustered together as shown in the output above.
PEs from 22494 to 22838 (345 PEs) are allocated for tmp and swap. These should be moved right
after var, starting from PE 473 until 817. This operation can be accomplished with
pvmove
command.
There are now 818x32 MiB Physical Extents allocated, which roughly corresponds to 25GB.
The PV can be resized taking into consideration a safety margin.
Resizing the partition
The partition /dev/sda2 is now larger than the physical volume requires.
Resizing a partition basically means redefining its boundaries
in the partition table, i.e. start and end coordinates: this is a critical step,
which requires much attention. fdisk
can be used to obtain the current
layout of the disk.
Pre-GPT partition tables identify partitions boundaries both in CHS and LBA coordinates. fdisk default displaying unit is cylinders. This comes from the DOS era when partitions had to be aligned to cylinder boundaries. As a matter of fact, cylinders are identified in the partition table with 10 bits, so a value higher than 1024 is just an abstraction implemented by fdisk (also known as DOS-compatible mode). Nowadays this mode is deprecated and logical sectors values are highly recommended. Furthermore, on this machine the underlying device is a Solid State Drive, so CHS addressing does not make any sense at all. And there is more! This is a 4KB pages device so not even 512 bytes logical block addressing makes sense: it is purely an abstraction implemented by the firmware. For the remainder of these notes, I have used cylinder values. This is really not a good a idea, as the resulting partition is not aligned with the optimal I/O size of the device, 4KB, which impacts both performance and flash wearout, the latter maybe not being that critical anymore in 2016. A flawless redefinition of the partition boundaries must take into consideration the optimal I/O size.
Anyhow, I will be continuing considering cylinder coordinates.
fdisk reports that a cylinder corresponds to ~7.84MiB. /dev/sda2 must be deleted and recreated with the same Start cylinder. The End cylinder is obviously defined based on the desired size, 35GB in this case or ~4571 cylinders. LVM type must also be set with fdisk. The layout is now the following:
To obtain a compressed image of the disk, the LVs must be first disabled and /dev/sda dumped for around ~40GB from the beginning of the disk: everything else beyond this area is unallocated space and therefore not interesting.
The live environment must provide enough in-memory space for dumping the whole compressed file. My configuration led to a 14GB image, and RAM was large enough to host it. With more careful rounding, the compression ratio can be improved significantly. To test the restore procedure, the image can be simply decompressed and written back to the disk.
If everything went well, the system should boot into the exact same environment as before.