RHEL Intermediate #2: LVM — PV/VG/LV, Snapshots, Expansion

13 min read

The /dev/mapper/rhel-root that showed up in Basics #6’s automatic partitioning was LVM. This post covers LVM hands-on. First locking in the PV / VG / LV structure, then the flow of attaching a new disk to expand an LV, how to capture a pre-backup state with snapshots, and thin provisioning and RAID options. A post that brings flexible disk management in operations into focus.

The position of this post in the RHEL Intermediate series:

  • #1 Intro to SELinux — Enforcing/Permissive, labels, troubleshooting
  • #2 LVM — PV/VG/LV, snapshots, expansion ← this post
  • #3 Advanced storage — Stratis, NFS, Samba
  • #4 Networking — NetworkManager (nmcli), bonding, teaming
  • #5 Log management — journald, rsyslog, log rotation
  • #6 Job scheduling — cron, systemd timer, at
  • #7 Intro to containers — Podman/Buildah/Skopeo (differences from Docker)

The problem LVM solves #

Traditional partitions are fixed in size once set. What if /home is full but /var is sitting idle? You’d need a major operation: back up the entire disk, recreate the partition table, and restore. The machine has to go down during all of that.

LVM (Logical Volume Manager) solves this by inserting one more layer between physical disks and the filesystem. When a disk fills up, you add a new disk, expand the LV in two or three commands, and grow the filesystem in place — with no downtime.

LVM's three layers
   /dev/sdb1   /dev/sdc1   /dev/sdd1     ← physical partitions
       │           │           │
       ▼           ▼           ▼
     [PV]        [PV]        [PV]        ← Physical Volume
       └───────────┼───────────┘
            ┌─────────────┐
            │     VG      │              ← Volume Group (pool)
            │  data_vg    │
            └──┬───┬───┬──┘
               │   │   │
               ▼   ▼   ▼
            [LV] [LV] [LV]               ← Logical Volume
             │    │    │
             ▼    ▼    ▼
            xfs  ext4 swap                ← filesystem / swap
             │    │
             ▼    ▼
           /data /home

The role of the three layers:

LayerAbbrMeaning
Physical VolumePVA disk / partition marked for LVM use
Volume GroupVGA pool of multiple PVs. “Available storage pool”
Logical VolumeLVA logical volume cut from VG. Filesystem goes on this

The key is that VG is the abstraction layer. VG’s total capacity is the sum of its PVs; LVs are carved freely from within it; adding a disk grows the VG; and when you expand an LV, it pulls from that pool.

Direct creation — PV → VG → LV #

Let’s take the empty disk /dev/vdb from Basics #6 and set it up with LVM.

Verify
$ lsblk
NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
vda             ...          40G  0 disk
├─vda1                       ...
└─vda2                       ...
  ├─rhel-root  ...
  └─rhel-swap  ...
vdb             252:16   0   20G  0 disk          ← one empty disk

1) Create PV — pvcreate #

Create physical volume
$ sudo pvcreate /dev/vdb
  Physical volume "/dev/vdb" successfully created.

$ sudo pvs
  PV         VG   Fmt  Attr PSize   PFree
  /dev/vda2  rhel lvm2 a--  39.00g     0
  /dev/vdb        lvm2 ---  20.00g 20.00g

pvs gives a one-line PV summary; for more detail use pvdisplay /dev/vdb. Here the whole disk was used as a PV, but partition-level is also possible (pvcreate /dev/vdb1).

2) Create VG — vgcreate #

Create volume group
$ sudo vgcreate data_vg /dev/vdb
  Volume group "data_vg" successfully created

$ sudo vgs
  VG      #PV #LV #SN Attr   VSize   VFree
  data_vg   1   0   0 wz--n- <20.00g <20.00g
  rhel      1   2   0 wz--n- <39.00g      0

A new VG named data_vg was created and holds one PV (/dev/vdb). The name is up to you, but descriptive names (data_vg, app_vg, db_vg) are a good habit in operations.

3) Create LV — lvcreate #

Cut LV out of VG. Partial, or whole.

Create logical volume
# One 10GB LV
$ sudo lvcreate -L 10G -n data_lv data_vg
  Logical volume "data_lv" created.

$ sudo lvs
  LV       VG      Attr       LSize   ...
  data_lv  data_vg -wi-a-----  10.00g
  home     rhel    -wi-ao---- <10.00g
  root     rhel    -wi-ao----  25.00g
  swap     rhel    -wi-ao----   4.00g

Option breakdown:

  • -L 10G — absolute size. With -l 100%FREE you use all remaining space in the VG; with -l 50%VG you take 50% of the total VG. Percentage specs are also accepted.
  • -n data_lv — LV name.
  • The last data_vg — which VG to cut from.

4) Make filesystem and mount #

LV is accessed via /dev/<VG>/<LV> or /dev/mapper/<VG>-<LV> like a regular block device.

Format / mount
$ sudo mkfs.xfs /dev/data_vg/data_lv
$ sudo mkdir -p /data
$ sudo mount /dev/data_vg/data_lv /data
$ df -hT /data
Filesystem                   Type Size Used Avail Use% Mounted on
/dev/mapper/data_vg-data_lv  xfs  10G  175M 10G   1%   /data

Permanently register in /etc/fstab with UUID, same flow as Basics #6:

Check UUID then /etc/fstab
$ sudo blkid /dev/data_vg/data_lv
/dev/mapper/data_vg-data_lv: UUID="..." TYPE="xfs"
/etc/fstab
UUID=...  /data  xfs  defaults,nofail  0  0

That’s the standard flow for putting a new disk under LVM. Once you learn it, the same pattern repeats every time.

LV expansion — daily life of operations #

LVM’s real value shows itself here. Suppose /data is 80% full.

1) Add new PV #

Suppose a new disk /dev/vdc (10GB) is attached to the machine.

Add PV
$ sudo pvcreate /dev/vdc
  Physical volume "/dev/vdc" successfully created.

2) Add PV to VG — vgextend #

VG expansion
$ sudo vgextend data_vg /dev/vdc
  Volume group "data_vg" successfully extended.

$ sudo vgs data_vg
  VG      #PV #LV #SN Attr   VSize  VFree
  data_vg   2   1   0 wz--n- 29.99g 19.99g    ← 20G + 10G

VG total capacity grew. LV is still as is (10GB).

3) LV expansion — lvextend #

LV expansion
$ sudo lvextend -L +10G /dev/data_vg/data_lv
  Size of logical volume data_vg/data_lv changed from 10.00 GiB to 20.00 GiB.
  Logical volume data_vg/data_lv successfully resized.

-L +10G means “10GB more than now”; -L 20G means “set the final size to 20GB.” Don’t confuse the two — the + matters. To use all remaining free space, -l +100%FREE.

4) Filesystem expansion — xfs_growfs #

LV grew, but the filesystem on top is still old size. For XFS:

XFS expansion
$ sudo xfs_growfs /data
$ df -hT /data
Filesystem                   Type Size Used Avail Use% Mounted on
/dev/mapper/data_vg-data_lv  xfs  20G  175M 20G   1%   /data    ← grew to 20G

For ext4, use sudo resize2fs /dev/data_vg/data_lv. The key point is that expansion is possible while mounted — the disk grows without any downtime.

lvextend’s convenience option — attaching -r (or --resizefs) does LV expansion + filesystem expansion at once.

$ sudo lvextend -r -L +10G /dev/data_vg/data_lv

In operations this is almost always used. However, the fs must be of a recognizable kind (XFS, ext2/3/4, reiserfs, etc.).

Reduction — XFS no, ext4 yes #

As noted in Basics #6, XFS can’t be shrunk. ext4 can be.

Flow of reducing ext4 LV
# 1. Unmount required (different from XFS — ext4 reduction also needs unmount)
$ sudo umount /data

# 2. Filesystem check
$ sudo e2fsck -f /dev/data_vg/data_lv

# 3. Filesystem reduction
$ sudo resize2fs /dev/data_vg/data_lv 8G

# 4. LV reduction (must be at least the size of fs)
$ sudo lvreduce -L 8G /dev/data_vg/data_lv

# 5. Mount again
$ sudo mount /dev/data_vg/data_lv /data

Order matters — shrink the filesystem first, then reduce the LV. Reverse the order and data gets truncated. This is a risky operation, so always take a backup first, and when possible, creating a new LV and migrating the data is the safer path.

Snapshot — locking a moment whole #

LVM’s most powerful feature. It captures a point-in-time copy of an LV almost instantly and tracks changes separately from that point, leaving the original state intact for recovery.

Snapshot shape
   original LV (data_lv) ─── time →
        ├── snapshot moment (T0) — lvcreate -s
        T0 │ blocks changed after T0 are stored separately in snapshot
           │ reading original gives current, reading snapshot gives T0 moment

Creation #

Snapshot creation
# T0 snapshot of data_lv, accommodating 1GB of changes
$ sudo lvcreate -L 1G -s -n data_snap /dev/data_vg/data_lv
  Logical volume "data_snap" created.

$ sudo lvs
  LV         VG      Attr       LSize  Origin   Data%
  data_lv    data_vg owi-aos--- 20.00g
  data_snap  data_vg swi-a-s---  1.00g data_lv   0.01

-s is the snapshot flag. -L 1G is the size of the change (COW) area — not the original’s total size, but the space allocated to store data that changes in the original after T0. Set it larger if many changes are expected.

Backup pattern #

The most common use case for snapshots.

Snapshot → backup → delete
# 1. Snapshot (instant, original doesn't stop)
$ sudo lvcreate -L 5G -s -n data_snap /dev/data_vg/data_lv

# 2. Mount snapshot to back up
$ sudo mkdir -p /mnt/snap
$ sudo mount -o ro,nouuid /dev/data_vg/data_snap /mnt/snap
$ sudo tar czf /backup/data-$(date +%F).tar.gz -C /mnt/snap .

# 3. Unmount and remove snapshot
$ sudo umount /mnt/snap
$ sudo lvremove /dev/data_vg/data_snap

The original never stopped during the backup, and the backup is a consistent snapshot of the T0 moment. Consistent backup without stopping the original — the operational standard pattern.

-o nouuid — XFS prevents mounting fs of the same UUID twice. The snapshot has the same UUID as the original, so this option is needed. Not needed for ext4.

Recovery (rollback) #

To revert original to T0 moment:

Rollback to snapshot
# 1. Unmount original LV if mounted
$ sudo umount /data

# 2. Merge snapshot to original
$ sudo lvconvert --merge /dev/data_vg/data_snap
  Merging of volume data_vg/data_snap started.
  Merge of snapshot into logical volume data_vg/data_lv has finished.

# 3. Remount and it's at T0 state
$ sudo mount /dev/data_vg/data_lv /data

--merge absorbs the snapshot into the original to revert to T0. After merge, the snapshot disappears automatically.

Merging an active LV — if the original is in use and can’t be unmounted, the merge happens on the next activation (next boot): reboot and it auto-rolls back. Keep this in mind when working in production.

Snapshot limits #

  • A snapshot is invalidated when the change area fills up. Setting -L too small risks corruption during backup. Usually 10–20% of the original is a safe minimum.
  • Not for long-term storage. Snapshots are for backup, temporary testing, and rollback. For permanent storage, copy data to a separate backup (tar, rsync, AWS S3, etc.).

One step further — Thin Provisioning #

Traditional LVs immediately claim their full size from the VG at creation. Creating two 100GB LVs deducts 200GB from the VG right away, even if the actual data stored is minimal.

Thin LVs only consume space as needed. Even with just 100GB in the VG, you can create two 200GB thin LVs on a thin pool — as long as actual usage stays within the pool’s capacity.

thin pool + thin LV
# 100GB thin pool
$ sudo lvcreate -L 100G -T data_vg/thin_pool

# Two 200GB thin LVs on pool (overprovisioning)
$ sudo lvcreate -V 200G -T data_vg/thin_pool -n thin_a
$ sudo lvcreate -V 200G -T data_vg/thin_pool -n thin_b

$ sudo lvs
  LV          VG      Attr       LSize   Pool      Data%
  thin_pool   data_vg twi-aotz-- 100.00g
  thin_a      data_vg Vwi-a-tz-- 200.00g thin_pool   0.01
  thin_b      data_vg Vwi-a-tz-- 200.00g thin_pool   0.01

Strengths: flexible disk allocation, instant creation, and efficient snapshots (no need to pre-allocate a change area). Weakness: when the pool fills up, all thin LVs go read-only. Monitoring (lvs, alerts) is essential.

In production it’s commonly used for workloads that grow dynamically, such as container storage or virtual machine disks. Detailed design decisions belong to the advanced series.

Other options — Striping and RAID #

Striping (RAID 0) #

Distributes data across multiple PVs to spread I/O. Fast, but losing one PV loses all data.

2-way striping
$ sudo lvcreate -L 10G -i 2 -I 64 -n stripe_lv data_vg
  # -i 2: stripe across 2 PVs
  # -I 64: 64KB chunk

Mirroring (RAID 1) #

Synchronously replicates data across two PVs. Even if one PV fails, the data is safe.

2-way mirror
$ sudo lvcreate -L 10G -m 1 -n mirror_lv data_vg
  # -m 1: 1 additional copy

RAID5 / RAID6 #

RAID5 — distributed parity
$ sudo lvcreate -L 10G --type raid5 -i 3 -n raid5_lv data_vg
  # -i 3: 3 data PVs + auto 1 parity = 4 PVs needed

In production, hardware RAID or mdadm is more commonly used, but LVM RAID is useful when you just need a simple mirror across two disks.

Monitoring / inspection #

Capacity and status #

Summary
$ sudo pvs           # PV list
$ sudo vgs           # VG list
$ sudo lvs           # LV list
$ sudo lvs -a        # including hidden internal LVs (snapshot, thin metadata, etc.)
Detail
$ sudo pvdisplay /dev/vdb
$ sudo vgdisplay data_vg
$ sudo lvdisplay /dev/data_vg/data_lv

Auto activation #

VG/LV activation
$ sudo vgchange -a y data_vg     # activate
$ sudo vgchange -a n data_vg     # deactivate (after unmount)

Auto-activation at boot is controlled by the auto_activation_volume_list setting in /etc/lvm/lvm.conf. The defaults are sufficient for most operations.

Frequently encountered traps #

“lvextend done but df is the same” #

The LV was expanded but the filesystem expansion (xfs_growfs / resize2fs) wasn’t done. Using the -r option to do both at once is safer.

“Snapshot suddenly disappeared” #

When the snapshot’s change area fills up, it auto-invalidates. When Data% in lvs approaches 100%, immediately switch to a larger snapshot or finish the backup and remove it.

“Can’t put PV in VG — ‘already in volume group’” #

The PV already belongs to another VG. Check where with pvs. If it’s truly unused, remove it from that VG first using vgreduce.

“Made disk whole as PV but not recognized after boot” #

The /etc/fstab entry was registered using the device name (/dev/vdb), which can change between boots. Use the VG/LV path (/dev/data_vg/data_lv) or UUID instead — both are stable across reboots.

“thin pool filled up — all thin LVs read-only” #

The biggest risk of thin provisioning. Always set a pool-at-80% alert, and expand the pool itself via lvextend before it fills up.

AlmaLinux / Rocky differences #

Every command in this post works as is. LVM runs on top of the kernel and the lvm2 package, which AlmaLinux and Rocky take directly from RHEL.

Frequently used commands in one table #

CommandWhat it does
pvcreate <device>Create PV
pvs / pvdisplay <pv>PV list / detail
vgcreate <vg> <pv>...Create VG
vgs / vgdisplay <vg>VG list / detail
vgextend <vg> <pv>Add PV to VG
vgreduce <vg> <pv>Remove PV from VG
lvcreate -L <size> -n <lv> <vg>Create LV (absolute size)
lvcreate -l 100%FREE -n <lv> <vg>Create LV with all remaining space in VG
lvcreate -L <size> -s -n <snap> <orig>Create snapshot
lvcreate -L <size> -T <vg>/<pool>Create thin pool
lvcreate -V <size> -T <vg>/<pool> -n <lv>Create thin LV
lvextend [-r] -L +<size> <lv>Expand LV (+ fs together)
lvreduce -L <size> <lv>Reduce LV (risky)
lvconvert --merge <snap>Merge snapshot to original (rollback)
lvremove <lv>Delete LV
vgchange -a y/n <vg>VG activate / deactivate
xfs_growfs <mount>XFS expansion
resize2fs <device>ext4 expansion / reduction
lvs / lvs -aLV list (+ internal LVs)

Summary #

Flow organized in this post:

  • LVM abstracts disk operations in three layers PV (physical) → VG (pool) → LV (logical).
  • Standard for adding a new disk: pvcreatevgcreate / vgextendlvcreatemkfs.xfs → mount.
  • Expand an LV with a single lvextend -r -L +<size> command (LV + filesystem together), safely while mounted.
  • XFS cannot be shrunk; ext4 can but it’s risky. When possible, migrate to a new LV instead.
  • Snapshot instantly created with lvcreate -s, standard pattern for backup / rollback. Mind the change area size.
  • Thin provisioning allows over-provisioning, but monitoring pool fill is essential.
  • Striping / Mirroring / RAID is supported natively by LVM, but in production hardware RAID or mdadm is more common.

Next — Advanced storage #

LVM remains the standard in RHEL, but since RHEL 8 the next-generation Stratis is also available. And beyond a single machine, NFS / Samba for sharing storage over the network is another area you’ll run into regularly.

#3 Advanced storage — Stratis, NFS, Samba looks at the management model Stratis provides on top of LVM + XFS, the configuration flow for an NFS server and client, and Windows-compatible Samba. A post that organizes how to handle network storage beyond the local disk.

X