Hey,

I remember back when I was introduced to Arch Linux this thing called LVM but I never really used it and knew what were its capabilities.

I simply couldn’t understand why someone would want to resize a partition on the fly (guess what, I’ve done that so many times in AWS nowadays). Why would someone want to snapshot? Why LVM?

Now many years after my first Arch Linux installation I understand what are its benefits but I’ve never really set up on a Linux machine myself though. Here in this article, I go through the process of how one can play around with LVM without needing to have an extra disk or format anything.

ps.: If you’re looking for high-level concepts of LVM, this is not really the place you’re looking for. This is just hands-on on how to get it running with a loopback device (/dev/loop*).

ps.: in all of the code below the “hashtags” before the command (#) indicates that we’re executing it with a privileged user.

ps.: this is an area I’m not expert at all! Please be kind of pointing to me the places I got stuff wrong and I’ll update right away!

Getting real

First, let’s verify that we have some loopback devices

# ls /dev | grep loop
loop0
loop1
loop2
loop3
loop4
loop5
loop6
loop7
loop-control

Now create a file to hold the data that will be written to the first loopback device. This is important because we’re wanting to simulate a real disk so we have to reserve some space for it.

# dd if=/dev/zero of=./lvm0.img bs=50 count=1M
1048576+0 records in
1048576+0 records out
52428800 bytes (52 MB, 50 MiB) copied, 1.06171 s, 49.4 MB/s

The next step is linking the loopback devices with the file we just created. This is important because the file itself is just another regular file in the OS. LVM can’t get set up on a regular file though - it demands a special block device (like those you have under /dev).

# losetup /dev/loop0 ./lvm0.img

Verify that the loopback device has been recognized as an actual disk

# fdisk -l
Disk /dev/loop0: 50 MiB, 52428800 bytes, 102400 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 /dev/sda: 10 GiB, 10737418240 bytes, 20971520 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
Disklabel type: dos
Disk identifier: 0x14f750bf

# lsblk
lsblk
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0    7:0    0  50M  0 loop          <<<<<<< (not mounted yet)
sda      8:0    0  10G  0 disk 
└─sda1   8:1    0  10G  0 part /
sdb      8:16   0  10M  0 disk 

Check all the partitions that we have in our disks - we should see 0 partitions in /dev/loop0. For doing so we can use GNU parted (parted command in ubuntu zesty).

# man parted

NAME
       GNU Parted - a partition manipulation program

SYNOPSIS
       parted [options] [device [command [options...]...]]

...
      -l, --list
              lists partition layout on all block devices
...

Now, let’s execute it:

# parted -l

Model: VBOX HARDDISK (scsi)
Disk /dev/sda: 10.7GB                           <<<< NOT THE LOOPBACK
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags: 

Number  Start   End     Size    Type     File system  Flags
 1      1049kB  10.7GB  10.7GB  primary  ext4         boot

So now what we need to do is format the device and set it to use LVM. For doing so we can make use of fdisk. As we don’t want to go through the process using the interactive section we can make use of sfdisk.

From man sfdisk you can see that it’s a “script-oriented tool for partitioning any block device”. Here we only have to submit the same inputs we’d use with fdisk but in the stdin of the executable:

# echo ",,8e,," | sfdisk /dev/loop0
Checking that no-one is using this disk right now ... OK

Disk /dev/loop0: 50 MiB, 52428800 bytes, 102400 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

>>> Created a new DOS disklabel with disk identifier 0x9f5773d1.
/dev/loop0p1: Created a new partition 1 of type 'Linux LVM' and of size 49 MiB.
/dev/loop0p2: Done.

New situation:

Device       Boot Start    End Sectors Size Id Type
/dev/loop0p1       2048 102399  100352  49M 8e Linux LVM

The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Invalid argument
The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8).
Syncing disks.

What does ,,8e,, means? It’s the same as “create a partition starting at the default start, with the default size, being LVM and using the default value for the bootable property”.

We can understand that better by looking at man sfdisk:

# man sfdisk.8
...
       Unnamed-fields format

                     start size type bootable

LIKE WHAT WE DID
           >>>>>         ,     , 8e ,       ,

              where each line fills one partition descriptor.

You can check that 8e is the type of LVM by using fdisk manually, typing m to look for help, and then l to list known partition types:

#fdisk

press m
press l

Command (m for help): l

 ...
 7  HPFS/NTFS/exFAT 4d  QNX4.x          88  Linux plaintext de  Dell Utility   
                                        ++++++++++++++
 8  AIX             4e  QNX4.x 2nd part 8e  Linux LVM|      df  BootIt         
                                        /\++++++++++++
                                        || 
                                        the 8e we used!
 ...

Cool, we have our /dev/loop0 device partitioned with LVM. Using the same commands we used before we can make sure that the statement is true:

# fdisk -l

fdisk -l
Disk /dev/loop0: 50 MiB, 52428800 bytes, 102400 sectors
...

Device       Boot Start    End Sectors Size Id Type
/dev/loop0p1       2048 102399  100352  49M 8e Linux LVM

But if you go back to the output of the sfdisk command you can see there was an error:

The kernel still uses the old table. 
The new table will be used at the next reboot or 
after you run partprobe(8) or kpartx(8).

We can confirm that indeed there’s something wrong if we make use of a command that relies on the mentioned table.

We can be faced with such problem by using our first LVM command: lvmdiskscan

# lvmdiskscan 
  /dev/loop0 [      50.00 MiB] 
  /dev/sda1  [      10.00 GiB] 
  /dev/sdb   [      10.00 MiB] 
  1 disk
  2 partitions
  0 LVM physical volume whole disks
  0 LVM physical volumes

What? We just created an LVM partition on a disk (on the loopback device) and it says that there’s no LVM devices? That’s the mentioned problem in place.

To fix it we can make use of partx to force an update in the kernel table:

# partx --update /dev/loop0

and then see the new output:

# lvmdiskscan

  /dev/loop0p1 [      49.00 MiB] 
  /dev/loop0   [      50.00 MiB] 
  /dev/sda1    [      10.00 GiB] 
  /dev/sdb     [      10.00 MiB] 
  1 disk
  3 partitions
  0 LVM physical volume whole disks
  0 LVM physical volumes

But we still don’t have a physical volume that LVM can use.

# lvmdiskscan -l
  WARNING: only considering LVM devices
  0 LVM physical volume whole disks
  0 LVM physical volumes

To create that we now make use of pvcreate:

PVCREATE(8)                     System Manager's Manual                    PVCREATE(8)

NAME
       pvcreate — initialize a disk or partition for use by LVM

...

Examples
       Initialize  partition  #4 on the third SCSI disk and the entire fifth SCSI disk
       for later use by LVM:

       pvcreate /dev/sdc4 /dev/sde

So that’s cool, we can just follow the example provided and do ourselves:

# lvmdiskscan -l
  WARNING: only considering LVM devices
  0 LVM physical volume whole disks
  0 LVM physical volumes

# pvcreate /dev/loop0
WARNING: dos signature detected on /dev/loop0 at offset 510. Wipe it? [y/n]: y
  Wiping dos signature on /dev/loop0.
  Physical volume "/dev/loop0" successfully created.

# lvmdiskscan  -l
  WARNING: only considering LVM devices
  /dev/loop0   [      50.00 MiB] LVM physical volume
  0 LVM physical volume whole disks
  1 LVM physical volume

So now we have our first physical volume. Next step is creating a volume group:

VGCREATE(8)                     System Manager's Manual                    VGCREATE(8)

NAME
       vgcreate — create a volume group

SYNOPSIS
       vgcreate [opts...] VolumeGroupName PhysicalDevicePath

DESCRIPTION
       vgcreate creates a new volume group called VolumeGroupName using the block spe‐
       cial device PhysicalDevicePath.


# vgcreate myvg /dev/loop0
  Volume group "myvg" successfully created

With vgdisplay we can then check that everything went fine:

# vgdisplay 
  --- Volume group ---
  VG Name               myvg
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  ...
  VG Size               48.00 MiB
  ...

Now that we have a volume group we can create a logical volume using a desired amount of size from the volume group:

# lvcreate --size 10M --name lv1 myvg
 Rounding up size to full physical extent 12.00 MiB
  Logical volume "lv1" created.

Having the logical volume created we can inspect the before and after of vgdisplay:

--- initial	2017-11-11 18:54:11.000000000 -0200
+++ after	2017-11-11 18:54:23.000000000 -0200
@@ -3,11 +3,11 @@
   System ID             
   Format                lvm2
   Metadata Areas        1
-  Metadata Sequence No  1
+  Metadata Sequence No  2
   VG Access             read/write
   VG Status             resizable
   MAX LV                0
-  Cur LV                0
+  Cur LV                1
   Open LV               0
   Max PV                0
   Cur PV                1
@@ -15,6 +15,6 @@
   VG Size               48.00 MiB
   PE Size               4.00 MiB
   Total PE              12
-  Alloc PE / Size       0 / 0   
-  Free  PE / Size       12 / 48.00 MiB
+  Alloc PE / Size       3 / 12.00 MiB
+  Free  PE / Size       9 / 36.00 MiB
   VG UUID               TmGsRi-3SZg-kU9Q-FTOI-72rG-XaYB-xkxXYB

Clearly, we can see that we took some space from the pool of all available space of the volume group (as it was allocated to the logical volume).

To finish, just see that our logical volume is not listed under lsblk:

# lsblk
NAME       MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0        7:0    0  50M  0 loop 
├─myvg-lv1 253:0    0  12M  0 lvm  
└─loop0p1  259:0    0  49M  0 loop 
sda          8:0    0  10G  0 disk 
└─sda1       8:1    0  10G  0 part /
sdb          8:16   0  10M  0 disk 

Having that we’re set to use the partition for anything we want - mounting XFS, EXT4 .. any filesystem you want there and have all the benefits that LVM gives us (which is not the focus of this article).

Closing thoughts

Compared to just set up up a block device with a different filesystem, setting up LVM takes a big number of steps. Besides this, it’s pretty cool to see that because the interface (a special block device) is so simple we can totally skip the need for a real device and work in the same manner. Once meet the needs of the interface we’re done. That’s pretty cool IMHO.

What I plan to do next is script that process and set up multiple loopback devices so that I can experiment with the concept of having multiple volumegroups together with docker volumes.

Let’s see if that works out well 🙌.

If you’re struggling with a concept related to this or do something related to your work, your side project .. make sure you subscribe to the newsletter - you’ll be interested in other content similar to this.

Have a good one!

Resources

I used two resources (beside man pages) that really helped me along the way. I want to thank these two for the great resources they provided:

finis