LVM stands for Logical Volume Manager, and it provides logical volume management for Linux kernels. It enables us to manage multiple physical disks from a single manager and to create logical volumes that take profit from having multiple disks (e.g. RAID, thin provisioning, volumes that span across disks, etc.).
I needed using LVM multiple times, and in special it is of interest when dealing with LVM backed cinder in OpenStack.
So this time I learned…
How to use LVM
LVM is installed in multiple Linux distros and they are usually LVM-aware, to be able to boot from LVM volumes.
For the purpose of this post, we’ll consider LVM as a mechanism to manage multiple physical disks and to create logical volumes on them. Then LVM will show the operating system the logical volumes as if they were disks.
Testlab
LVM is intended for physical disks (e.g. /dev/sda, /dev/sdb, etc.). But we are creating a test lab to avoid the need of buying physical disks.
We are creating 4 fake disks of size 256Mb each. To create each of them we simply create a file of the proper size (that will store the data), and then we attach that file to a loop device:
root@s1:/tmp# dd if=/dev/zero of=/tmp/fake-disk-256.0 bs=1M count=256 ... root@s1:/tmp# dd if=/dev/zero of=/tmp/fake-disk-256.1 bs=1M count=256 ... root@s1:/tmp# dd if=/dev/zero of=/tmp/fake-disk-256.2 bs=1M count=256 ... root@s1:/tmp# dd if=/dev/zero of=/tmp/fake-disk-256.3 bs=1M count=256 ... root@s1:/tmp# losetup /dev/loop0 ./fake-disk-256.0 root@s1:/tmp# losetup /dev/loop1 ./fake-disk-256.1 root@s1:/tmp# losetup /dev/loop2 ./fake-disk-256.2 root@s1:/tmp# losetup /dev/loop3 ./fake-disk-256.3
And now you have 4 working disks for our tests:
root@s1:/tmp# fdisk -l Disk /dev/loop0: 256 MiB, 268435456 bytes, 524288 sectors ... Disk /dev/loop1: 256 MiB, 268435456 bytes, 524288 sectors ... Disk /dev/loop2: 256 MiB, 268435456 bytes, 524288 sectors ... Disk /dev/loop3: 256 MiB, 268435456 bytes, 524288 sectors ... Disk /dev/sda: (...)
For the system, these devices can be used as regular disks (e.g. format them, mount, etc.):
root@s1:/tmp# mkfs.ext4 /dev/loop0 mke2fs 1.44.1 (24-Mar-2018) ... Writing superblocks and filesystem accounting information: done root@s1:/tmp# mkdir -p /tmp/mnt/disk0 root@s1:/tmp# mount /dev/loop0 /tmp/mnt/disk0/ root@s1:/tmp# cd /tmp/mnt/disk0/ root@s1:/tmp/mnt/disk0# touch this-is-a-file root@s1:/tmp/mnt/disk0# ls -l total 16 drwx------ 2 root root 16384 Apr 16 12:35 lost+found -rw-r--r-- 1 root root 0 Apr 16 12:38 this-is-a-file root@s1:/tmp/mnt/disk0# cd /tmp/ root@s1:/tmp# umount /tmp/mnt/disk0
Concepts of LVM
LVM has simple actors:
- Physical volume: which is a physical disk.
- Volume group: which is a set of physical disks managed together.
- Logical volume: which is a block device.
Logical Volumes (LV) are stored in Volume Groups (VG), which are backed by Physical Volumes (PV).
PVs are managed using pv* commands (e.g. pvscan, pvs, pvcreate, etc.). VGs are managed using vg* commands (e.g. vgs, vgdisplay, vgextend, etc.). LGs are managed using lg* commands (e.g. lvdisplay, lvs, lvextend, etc.).
Simple workflow with LVM
To have an LVM system, we have to first initialize a physical volume. That is somehow “initializing a disk in LVM format”, and that wipes the content of the disk:
root@s1:/tmp# pvcreate /dev/loop0 WARNING: ext4 signature detected on /dev/loop0 at offset 1080. Wipe it? [y/n]: y Wiping ext4 signature on /dev/loop0. Physical volume "/dev/loop0" successfully created.
Now we have to create a volume group (we’ll call it test-vg):
root@s1:/tmp# vgcreate test-vg /dev/loop0 Volume group "test-vg" successfully created
And finally, we can create a logical volume
root@s1:/tmp# lvcreate -l 100%vg --name test-vol test-vg Logical volume "test-vol" created.
And now we have a simple LVM system that is built from one single physical disk (/dev/loop0) that contains one single volume group (test-vg) that holds a single logical volume (test-vol).
Examining things in LVM
- The commands to examine PVs: pvs and pvdisplay. Each of them offers different information. pvscan also exists, but it is not needed in current versions of LVM.
- The commands to examine VGs: vgs and vgdisplay. Each of them offers different information. vgscan also exists, but it is not needed in current versions of LVM.
- The commands to examine PVs: lvs and lvdisplay. Each of them offers different information. lvscan also exists, but it is not needed in current versions of LVM.
Each command has several options, which we are not exploring here. We are just using the commands and we’ll present some options in the next examples.
At this time we should have a PV, a VG, and LV, and we can see them by using pvs, vgs and lvs:
root@s1:/tmp# pvs PV VG Fmt Attr PSize PFree /dev/loop0 test-vg lvm2 a-- 252.00m 0 root@s1:/tmp# vgs VG #PV #LV #SN Attr VSize VFree test-vg 1 1 0 wz--n- 252.00m 0 root@s1:/tmp# lvs LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert test-vol test-vg -wi-a----- 252.00m
Now we can use the test-vol as if it was a partition:
root@s1:/tmp# mkfs.ext4 /dev/mapper/test--vg-test--vol mke2fs 1.44.1 (24-Mar-2018) ... Writing superblocks and filesystem accounting information: done root@s1:/tmp# mount /dev/mapper/test--vg-test--vol /tmp/mnt/disk0/ root@s1:/tmp# cd /tmp/mnt/disk0/ root@s1:/tmp/mnt/disk0# touch this-is-my-file root@s1:/tmp/mnt/disk0# df -h Filesystem Size Used Avail Use% Mounted on (...) /dev/mapper/test--vg-test--vol 241M 2.1M 222M 1% /tmp/mnt/disk0
Adding another disk to grow the filesystem
Imagine that we have filled our 241Mb volume in our 256Mb disk (/dev/loop0), and we need some more storage space. We could buy an extra disk (i.e. /dev/loop1) and add it to the LVM (using command vgextend).
root@s1:/tmp# pvcreate /dev/loop1 Physical volume "/dev/loop1" successfully created. root@s1:/tmp# vgextend test-vg /dev/loop1 Volume group "test-vg" successfully extended
And now we have two physical volumes added to a single volume group. The VG is of size 504Mb and there are 252Mb free.
root@s1:/tmp# pvs PV VG Fmt Attr PSize PFree /dev/loop0 test-vg lvm2 a-- 252.00m 0 /dev/loop1 test-vg lvm2 a-- 252.00m 252.00m root@s1:/tmp# vgs VG #PV #LV #SN Attr VSize VFree test-vg 2 1 0 wz--n- 504.00m 252.00m
We could think as if the VG was somehow a disk and the LV are the partitions. So we can make grow the LV in the VG and then grow the filesystem:
root@s1:/tmp/mnt/disk0# lvscan ACTIVE '/dev/test-vg/test-vol' [252.00 MiB] inherit root@s1:/tmp/mnt/disk0# lvextend -l +100%free /dev/test-vg/test-vol Size of logical volume test-vg/test-vol changed from 252.00 MiB (63 extents) to 504.00 MiB (126 extents). Logical volume test-vg/test-vol successfully resized. root@s1:/tmp/mnt/disk0# resize2fs /dev/test-vg/test-vol resize2fs 1.44.1 (24-Mar-2018) Filesystem at /dev/test-vg/test-vol is mounted on /tmp/mnt/disk0; on-line resizing required old_desc_blocks = 2, new_desc_blocks = 4 The filesystem on /dev/test-vg/test-vol is now 516096 (1k) blocks long. root@s1:/tmp/mnt/disk0# df -h Filesystem Size Used Avail Use% Mounted on (...) /dev/mapper/test--vg-test--vol 485M 2.3M 456M 1% /tmp/mnt/disk0 root@s1:/tmp/mnt/disk0# ls -l total 12 drwx------ 2 root root 12288 Apr 22 16:46 lost+found -rw-r--r-- 1 root root 0 Apr 22 16:46 this-is-my-file
Now we have the new LV with double size.
Downsize the LV
Now we have obtained some free space and we want to keep only 1 disk (e.g. /dev/loop0). So we can downsize the filesystem (e.g. to 200Mb), and then downsize the LV.
This method needs unmounting the filesystem. So if you want to resize the root partition, you would need to use a live system or pivoting root to an unused filesystem as described [here](https://unix.stackexchange.com/a/227318).
First, unmount the filesystem and check it:
root@s1:/tmp# umount /tmp/mnt/disk0 root@s1:/tmp# e2fsck -ff /dev/mapper/test--vg-test--vol e2fsck 1.44.1 (24-Mar-2018) 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/test--vg-test--vol: 12/127008 files (0.0% non-contiguous), 22444/516096 blocks
Then change the size of the filesystem to the desired size:
root@s1:/tmp# resize2fs /dev/test-vg/test-vol 200M resize2fs 1.44.1 (24-Mar-2018) Resizing the filesystem on /dev/test-vg/test-vol to 204800 (1k) blocks. The filesystem on /dev/test-vg/test-vol is now 204800 (1k) blocks long.
And now, we’ll reduce the logical volume to the new size and re-check the filesystem:
root@s1:/tmp# lvreduce -L 200M /dev/test-vg/test-vol WARNING: Reducing active logical volume to 200.00 MiB. THIS MAY DESTROY YOUR DATA (filesystem etc.) Do you really want to reduce test-vg/test-vol? [y/n]: y Size of logical volume test-vg/test-vol changed from 504.00 MiB (126 extents) to 200.00 MiB (50 extents). Logical volume test-vg/test-vol successfully resized. root@s1:/tmp# lvdisplay --- Logical volume --- LV Path /dev/test-vg/test-vol LV Name test-vol VG Name test-vg LV UUID xGh4cd-R93l-UpAL-LGTV-qnxq-vvx2-obSubY LV Write Access read/write LV Creation host, time s1, 2020-04-22 16:26:48 +0200 LV Status available # open 0 LV Size 200.00 MiB Current LE 50 Segments 1 Allocation inherit Read ahead sectors auto - currently set to 256 Block device 253:0 root@s1:/tmp# resize2fs /dev/test-vg/test-vol resize2fs 1.44.1 (24-Mar-2018) The filesystem is already 204800 (1k) blocks long. Nothing to do!
And now we are ready to use the disk with the new size:
root@s1:/tmp# mount /dev/test-vg/test-vol /tmp/mnt/disk0/ root@s1:/tmp# df -h Filesystem Size Used Avail Use% Mounted on (...) /dev/mapper/test--vg-test--vol 190M 1.6M 176M 1% /tmp/mnt/disk0root@s1:/tmp# cd /tmp/mnt/disk0/ root@s1:/tmp/mnt/disk0# ls -l total 12 drwx------ 2 root root 12288 Apr 22 16:46 lost+found -rw-r--r-- 1 root root 0 Apr 22 16:46 this-is-my-file
Removing a PV
Now we want to remove /dev/loop0 (which was our original disks) and keep the replacement (/dev/loop1).
We just need to free /dev/loop0 from VGs and remove it from the VG, for being able to safely remove it from the system. First, we check the PVs:
root@s1:/tmp/mnt/disk0# pvs -o+pv_used PV VG Fmt Attr PSize PFree Used /dev/loop0 test-vg lvm2 a-- 252.00m 52.00m 200.00m /dev/loop1 test-vg lvm2 a-- 252.00m 252.00m 0
We can see that /dev/loop0 is used, so we need to move its data to another PV:
root@s1:/tmp/mnt/disk0# pvmove /dev/loop0 /dev/loop0: Moved: 100.00% root@s1:/tmp/mnt/disk0# pvs -o+pv_used PV VG Fmt Attr PSize PFree Used /dev/loop0 test-vg lvm2 a-- 252.00m 252.00m 0 /dev/loop1 test-vg lvm2 a-- 252.00m 52.00m 200.00m
Now /dev/loop0 is 100% free and we can remove it from the VG:
root@s1:/tmp/mnt/disk0# vgreduce test-vg /dev/loop0 Removed "/dev/loop0" from volume group "test-vg" root@s1:/tmp/mnt/disk0# pvremove /dev/loop0 Labels on physical volume "/dev/loop0" successfully wiped.
Thin provisioning with LVM
Thin provisioning consists of providing the user with the illusion of having an amount of storage space, but only storing the amount of space that is actually used. It is similar to qcow2 or vmdk disk formats for virtual machines. The idea is that, if you have 1 Gb of storage space used, the backend will only store these data, even if the volume is 10Gb. The total amount of storage space will be used as it is requested.
First, you need to reserve an effective storage space in the form of a “thin pool”. The next example reserves 200M (-L 200M) as a thin storage (-T) as the thin pool thinpool in VG test-vg:
root@s1:/tmp/mnt# lvcreate -L 200M -T test-vg/thinpool Using default stripesize 64.00 KiB. Thin pool volume with chunk size 64.00 KiB can address at most 15.81 TiB of data. Logical volume "thinpool" created.
The result is that we’ll have a volume of size 200M, which is like a volume but is marked as a thin pool.
Now we can create two thin-provisioned volumes of the same size:
root@s1:/tmp/mnt# lvcreate -V 200M -T test-vg/thinpool -n thin-vol-1 Using default stripesize 64.00 KiB. Logical volume "thin-vol-1" created. root@s1:/tmp/mnt# lvcreate -V 200M -T test-vg/thinpool -n thin-vol-2 Using default stripesize 64.00 KiB. WARNING: Sum of all thin volume sizes (400.00 MiB) exceeds the size of thin pool test-vg/thinpool and the amount of free space in volume group (296.00 MiB). WARNING: You have not turned on protection against thin pools running out of space. WARNING: Set activation/thin_pool_autoextend_threshold below 100 to trigger automatic extension of thin pools before they get full. Logical volume "thin-vol-2" created. root@s1:/tmp/mnt# lvs -o name,lv_size,data_percent,thin_count LV LSize Data% #Thins thin-vol-1 200.00m 0.00 thin-vol-2 200.00m 0.00 thinpool 200.00m 0.00 2
The result is that we have 2 volumes of 200Mb each, while actually having only 200Mb. But each of the volumes is empty. Now as we use the volumes, the space will be occupied:
root@s1:/tmp/mnt# mkfs.ext4 /dev/test-vg/thin-vol-1 (...) Writing superblocks and filesystem accounting information: done root@s1:/tmp/mnt# mount /dev/test-vg/thin-vol-1 /tmp/mnt/disk0/ root@s1:/tmp/mnt# dd if=/dev/random of=/tmp/mnt/disk0/randfile bs=1K count=1024 dd: warning: partial read (94 bytes); suggest iflag=fullblock 0+1024 records in 0+1024 records out 48623 bytes (49 kB, 47 KiB) copied, 0.0701102 s, 694 kB/s root@s1:/tmp/mnt# lvs -o name,lv_size,data_percent,thin_count LV LSize Data% #Thins thin-vol-1 200.00m 5.56 thin-vol-2 200.00m 0.00 thinpool 200.00m 5.56 2 root@s1:/tmp/mnt# df -h Filesystem Size Used Avail Use% Mounted on (...) /dev/mapper/test--vg-thin--vol--1 190M 1.6M 175M 1% /tmp/mnt/disk0
In this example, we have used 49kb, which means 5.56%. If we repeat the process for the other volume:
root@s1:/tmp/mnt# mkfs.ext4 /dev/test-vg/thin-vol-2 (...) Writing superblocks and filesystem accounting information: done root@s1:/tmp/mnt# mkdir -p /tmp/mnt/disk1 root@s1:/tmp/mnt# mount /dev/test-vg/thin-vol-2 /tmp/mnt/disk1/ root@s1:/tmp/mnt# dd if=/dev/random of=/tmp/mnt/disk1/randfile bs=1K count=1024 dd: warning: partial read (86 bytes); suggest iflag=fullblock 0+1024 records in 0+1024 records out 38821 bytes (39 kB, 38 KiB) copied, 0.0473561 s, 820 kB/s root@s1:/tmp/mnt# lvs -o name,lv_size,data_percent,thin_count LV LSize Data% #Thins thin-vol-1 200.00m 5.56 thin-vol-2 200.00m 5.56 thinpool 200.00m 11.12 2 root@s1:/tmp/mnt# df -h Filesystem Size Used Avail Use% Mounted on (...) /dev/mapper/test--vg-thin--vol--1 190M 1.6M 175M 1% /tmp/mnt/disk0 /dev/mapper/test--vg-thin--vol--2 190M 1.6M 175M 1% /tmp/mnt/disk1
We can see that the space is being consumed, but we still see the 200Mb for each of the volumes.
Using RAID with LVM
Apart from using LVM as RAID-0 (i.e. stripping an LV across multiple physical devices), it is possible to create other types of raids using LVM.
Some of the most popular types of raids are RAID-1, RAID-10, and RAID-5. In the context of LVM, they intuitively mean:
- RAID-1: mirror an LV in multiple PV
- RAID-10: mirror an LV in multiple PV, but also stripping parts of the volume in different PVs.
- RAID-5: distributing the LV in multiple PV, and using some parity data to be able to continue working if PV fails.
You can check more specific information on RAIDs in this link.
You can use other RAID utilities (such as on-board RAIDs or software like mdadm), but using LVM you will also be able to take profit from LVM features.
Mirroring an LV with LVM
The first use case is to get a volume that is mirrored in multiple PV. We’ll get back to the 2 PV set-up:
root@s1:/tmp/mnt# pvs PV VG Fmt Attr PSize PFree /dev/loop0 test-vg lvm2 a-- 252.00m 252.00m /dev/loop1 test-vg lvm2 a-- 252.00m 252.00m root@s1:/tmp/mnt# vgs VG #PV #LV #SN Attr VSize VFree test-vg 2 0 0 wz--n- 504.00m 504.00m
And now we can create an LV that is in RAID1. But we can also set the number of copies that we want for the volume (using flag -m). In this case, we’ll create LV lv-mirror (-n lv-mirror) that is mirrored once (-m 1) with a size of 100Mb (-L 100M).
root@s1:/tmp/mnt# lvcreate --type raid1 -m 1 -L 100M -n lv-mirror test-vg Logical volume "lv-mirror" created. root@s1:/tmp/mnt# lvs -a -o name,copy_percent,devices LV Cpy%Sync Devices lv-mirror 100.00 lv-mirror_rimage_0(0),lv-mirror_rimage_1(0) [lv-mirror_rimage_0] /dev/loop1(1) [lv-mirror_rimage_1] /dev/loop0(1) [lv-mirror_rmeta_0] /dev/loop1(0) [lv-mirror_rmeta_1] /dev/loop0(0)
As you can see, LV lv-mirror is built from lv-mirror_rimage_0, lv-mirror_rmeta_0, lv-mirror_rimage_1 and lv-mirror_rmeta_1 “devices”, and in which PV is located each part.
For the case of RAID1, you can convert it to and from linear volumes. This feature is not (yet) implemented for other types of raid such as RAID10 or RAID5.
You can change the number of mirror copies for an LV (even getting the volume to linear), by using command lvconvert:
root@s1:/tmp/mnt# lvconvert -m 0 /dev/test-vg/lv-mirror Are you sure you want to convert raid1 LV test-vg/lv-mirror to type linear losing all resilience? [y/n]: y Logical volume test-vg/lv-mirror successfully converted. root@s1:/tmp/mnt# lvs -a -o name,copy_percent,devices LV Cpy%Sync Devices lv-mirror /dev/loop1(1) root@s1:/tmp/mnt# pvs PV VG Fmt Attr PSize PFree /dev/loop0 test-vg lvm2 a-- 252.00m 252.00m /dev/loop1 test-vg lvm2 a-- 252.00m 152.00m
In this case, we have converted the volume to linear (i.e. zero copies).
But you can also get mirror capabilities for a linear volume:
root@s1:/tmp/mnt# lvconvert -m 1 /dev/test-vg/lv-mirror Are you sure you want to convert linear LV test-vg/lv-mirror to raid1 with 2 images enhancing resilience? [y/n]: y Logical volume test-vg/lv-mirror successfully converted. root@s1:/tmp/mnt# lvs -a -o name,copy_percent,devices LV Cpy%Sync Devices lv-mirror 100.00 lv-mirror_rimage_0(0),lv-mirror_rimage_1(0) [lv-mirror_rimage_0] /dev/loop1(1) [lv-mirror_rimage_1] /dev/loop0(1) [lv-mirror_rmeta_0] /dev/loop1(0) [lv-mirror_rmeta_1] /dev/loop0(0)
More on RAIDs
Using command lvcreate it is also possible to create other types of RAID (e.g. RAID10, RAID5, RAID6, etc.). The only requirement is to have enough PVs for the type of RAID. But you must also have in mind that it is not possible to convert from these other types of raid to or from LV.
You can find much more information on LVM and RAIDs in this link.
More on LVM
There are a lot of features and tweaks to adjust in LVM, but this post shows the basics (and a bit more) to deal with LVM. You are advised to check the file /etc/lvm.conf and the man page.