RHEL Intermediate #2: LVM — PV/VG/LV, Snapshots, Expansion
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.
/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 /homeThe role of the three layers:
| Layer | Abbr | Meaning |
|---|---|---|
| Physical Volume | PV | A disk / partition marked for LVM use |
| Volume Group | VG | A pool of multiple PVs. “Available storage pool” |
| Logical Volume | LV | A 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.
$ 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 disk1) Create PV — pvcreate
#
$ 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.00gpvs 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
#
$ 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 0A 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.
# 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.00gOption breakdown:
-L 10G— absolute size. With-l 100%FREEyou use all remaining space in the VG; with-l 50%VGyou 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.
$ 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% /dataPermanently register in /etc/fstab with UUID, same flow as Basics #6:
$ sudo blkid /dev/data_vg/data_lv
/dev/mapper/data_vg-data_lv: UUID="..." TYPE="xfs"UUID=... /data xfs defaults,nofail 0 0That’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.
$ sudo pvcreate /dev/vdc
Physical volume "/dev/vdc" successfully created.2) Add PV to VG — vgextend
#
$ 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 + 10GVG total capacity grew. LV is still as is (10GB).
3) LV expansion — lvextend
#
$ 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:
$ 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 20GFor 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_lvIn 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.
# 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 /dataOrder 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.
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 momentCreation #
# 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.
# 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_snapThe 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:
# 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
-Ltoo 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.
# 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.01Strengths: 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.
$ sudo lvcreate -L 10G -i 2 -I 64 -n stripe_lv data_vg
# -i 2: stripe across 2 PVs
# -I 64: 64KB chunkMirroring (RAID 1) #
Synchronously replicates data across two PVs. Even if one PV fails, the data is safe.
$ sudo lvcreate -L 10G -m 1 -n mirror_lv data_vg
# -m 1: 1 additional copyRAID5 / RAID6 #
$ sudo lvcreate -L 10G --type raid5 -i 3 -n raid5_lv data_vg
# -i 3: 3 data PVs + auto 1 parity = 4 PVs neededIn 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 #
$ sudo pvs # PV list
$ sudo vgs # VG list
$ sudo lvs # LV list
$ sudo lvs -a # including hidden internal LVs (snapshot, thin metadata, etc.)$ sudo pvdisplay /dev/vdb
$ sudo vgdisplay data_vg
$ sudo lvdisplay /dev/data_vg/data_lvAuto 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 #
| Command | What 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 -a | LV 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:
pvcreate→vgcreate/vgextend→lvcreate→mkfs.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.