LVM: Flexible Storage
- Explain the PV → VG → LV model and the indirection that makes LVM beat raw partitions
- Read any machine's storage stack with lsblk, pvs, vgs, and lvs before touching it
- Build LVM storage from disks: pvcreate → vgcreate → lvcreate → mkfs → mount → fstab
- Grow a logical volume and its filesystem online, non-destructively — the headline skill
- Use snapshots for consistent backups, sized by change rate
- Evacuate a failing disk online with pvmove, then retire it with vgreduce and pvremove
Why LVM: the problem with promises carved in stone
Understand the PV → VG → LV model and what the indirection buys
A raw partition is a promise carved in stone: when you make
/dev/sdb150 GB, you're predicting — on day one, knowing the least you'll ever know — how big that data will grow. Guess low and you're migrating data off a full partition at the worst possible time; guess high and the surplus is stranded where nothing else can use it. Repartitioning to fix either mistake is slow, risky, and usually needs downtime.LVM (Logical Volume Manager) breaks the stone by inserting a layer of indirection between disks and filesystems. Think of it as a pantry, in three layers:
- Physical Volume (PV) — a disk or partition handed over to LVM (
/dev/sdb). The raw ingredients. - Volume Group (VG) — a pool combining one or more PVs into one undifferentiated space. The shared pantry — capacity lives here, unassigned.
- Logical Volume (LV) — a slice carved from the VG, which you format and mount exactly like a partition. The portion you're cooking with — and you can go back to the pantry for more whenever it has room.
The indirection buys three things stone can't do:
1. Resize on demand — an LV grows whenever the VG has free space, while mounted and in use.
2. Span disks — a VG made of three 1 TB disks can carve one 2.5 TB LV; the filesystem never knows it's straddling hardware.
3. Snapshots — freeze a point-in-time view of an LV for consistent backups, instantly.
One naming convention to internalize now: an LV's device path is
/dev/<vg>/<lv>— e.g./dev/vg_data/lv_home. When you see that shape in an fstab or a mount table, you know you're looking at LVM.Check yourself
In the pantry model, where does UNASSIGNED free capacity live — and why does that placement matter when an LV fills up?
- Physical Volume (PV) — a disk or partition handed over to LVM (
Read the stack before you build
Survey any machine's storage in four commands
Storage work punishes assumptions harder than any other domain — the command that formats the wrong disk doesn't offer an undo. So the LVM discipline starts with reading, and there's one summary command per layer, plus the block-device tree that shows everything at once:
lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 64G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 63G 0 part ├─rhel-root 253:0 0 40G 0 lvm / └─rhel-swap 253:1 0 4G 0 lvm [SWAP] sdb 8:16 0 100G 0 disk sdc 8:17 0 100G 0 disk
The tree view of every block device. Read the TYPE column: disk → part → lvm shows the layering — sda2 is a partition serving as a PV, with the root and swap LVs carved from it (that's the installer's default LVM layout). And the two prizes: sdb and sdc are bare disks, no partitions, no mountpoints — the raw material for the stack we're about to build. Confirming a disk is genuinely unused in lsblk BEFORE pvcreate is the habit that prevents storage tragedies. sudo pvs; sudo vgs; sudo lvsPV VG Fmt Attr PSize PFree /dev/sda2 rhel lvm2 a-- <63.00g 0 VG #PV #LV #SN Attr VSize VFree rhel 1 2 0 wz--n- <63.00g 0 LV VG Attr LSize Pool Origin Data% root rhel -wi-ao---- 40.00g swap rhel -wi-ao---- 4.00g
- pvs / vgs / lvs
- compact per-layer summaries; the -display variants (vgdisplay) give full detail
- VFree
- (vgs) unallocated space in the pool — the number that decides your grow strategy
One summary per layer: pvs (which disks are in), vgs (the pools and — the column you'll check constantly — VFree, the unassigned headroom), lvs (the carved volumes). This VG shows VFree 0: every byte is allocated, so growing any LV here would first require feeding the pool. That one glance at VFree tells you whether a grow is a one-command job or a two-command job — which is exactly the decision the next steps walk through. Check yourself
vgs shows VFree 0 for vg_data, and /dev/vg_data/lv_app is full. What does that VFree tell you about the fix?
Build the stack
Go from bare disks to a mounted, persistent filesystem
The build always flows up the layers — ingredients into the pantry, pantry onto the plate: initialize PVs, pool them into a VG, carve an LV, then format and mount it like any partition. Five commands, and the first two do the LVM-specific half:
sudo pvcreate /dev/sdb /dev/sdc sudo vgcreate vg_data /dev/sdb /dev/sdcPhysical volume "/dev/sdb" successfully created. Physical volume "/dev/sdc" successfully created. Volume group "vg_data" successfully created
- pvcreate
- initialize disks/partitions as LVM physical volumes — destroys existing data
- vgcreate NAME PVs...
- pool the listed PVs into a named volume group
pvcreate stamps LVM metadata onto each disk — the moment of no return for any data on them, which is why the lsblk check came first. vgcreate pools both into one 200 GB space named vg_data. Naming conventions (vg_ and lv_ prefixes) are optional but make every later command self-documenting. Example — not runnable on your lab box
sudo lvcreate -L 50G -n lv_home vg_data sudo mkfs.xfs /dev/vg_data/lv_home sudo mount /dev/vg_data/lv_home /homeLogical volume "lv_home" created. meta-data=/dev/vg_data/lv_home isize=512 agcount=4, agsize=3276800 blks ...
- -L 50G
- absolute size; -l 100%FREE takes all remaining pool space instead
- -n lv_home
- the LV's name — becomes part of its /dev/<vg>/<lv> path
Carve 50 GB from the pool (leaving 150 GB of VFree for the future — remember, starting modest and growing later is the LVM way), then format and mount the LV's device path exactly as you would a partition. From the filesystem's point of view, /dev/vg_data/lv_home IS a partition; the LVM magic is invisible above this line. Check yourself
Which LVM layer sits between the physical volumes and the logical volumes, and what single job does it do?
Grow it without downtime — the headline skill
Extend an LV and its filesystem online, choosing the right path by VFree
The payoff scene:
/homeis at 95% and users are complaining. Your first move is the one you drilled —vgs, read VFree. That number picks between the one-command path (pool has room) and the two-command path (pool is empty, feed it first). Here's the full version, worst case first:sudo pvcreate /dev/sdd sudo vgextend vg_data /dev/sddPhysical volume "/dev/sdd" successfully created. Volume group "vg_data" successfully extended
Only needed when VFree is 0: initialize a fresh disk and pour it into the pool. Note what did NOT happen — no LV changed, no filesystem grew; the pantry just got restocked. In a virtualized environment this pairs beautifully with hot-added virtual disks: attach in the hypervisor, pvcreate + vgextend in the guest, no reboot anywhere. sudo lvextend -L +50G -r /dev/vg_data/lv_homeSize of logical volume vg_data/lv_home changed from 50.00 GiB to 100.00 GiB. Logical volume vg_data/lv_home successfully resized. meta-data=/dev/mapper/vg_data-lv_home isize=512 ... data blocks changed from 13107200 to 26214400
- -L +50G
- grow BY 50G (the + matters; without it you're setting absolute size)
- -l +100%FREE
- consume all remaining pool space instead of a fixed amount
- -r
- also resize the filesystem — the flag whose absence causes the df mystery
The two-layer truth in one command: -L +50G grows the VOLUME, and -r grows the FILESYSTEM inside it (calling xfs_growfs or resize2fs for you). Skip the -r and you hit the classic confusion — lvs shows 100G, df still shows 50G, because the container grew and the contents didn't. And the quiet miracle: /home stayed mounted, users kept writing, nothing paused. That last output line — data blocks doubled — is your proof for df. Check yourself
After lvextend -L +20G (without -r), lvs shows the new size but df doesn't. What happened, and what fixes it?
Snapshots for consistent backups
Freeze a point-in-time view, back up from it, size it by change rate
One more trick the indirection enables. Backing up a live filesystem has a subtle flaw: the backup takes an hour, files change during that hour, and the archive ends up capturing file A from 2:00 and file B from 2:40 — a combination that never existed. For a database, that inconsistency can mean an unrestorable backup.
An LVM snapshot solves it: a frozen, point-in-time view of an LV, created in under a second, that stays consistent while the original keeps changing. The mechanism is copy-on-write: the snapshot stores only the original versions of blocks that change after its creation — so it starts near-empty and grows with the origin's change rate, not its size.
Example — not runnable on your lab box
sudo lvcreate -L 10G -s -n lv_snap /dev/vg_data/lv_home sudo mount -o ro /dev/vg_data/lv_snap /mnt/snap sudo tar czf /backup/home-$(date +%F).tgz -C /mnt/snap .Logical volume "lv_snap" created.
- -s
- snapshot (copy-on-write) of the given origin LV, not a normal volume
- -L 10G
- reserved space for original copies of blocks that change — size by change rate
- -o ro
- (mount) read-only — a backup source shouldn't be writable
Three beats: freeze (instant, -s makes it a snapshot; the 10G is space reserved for copied-on-write originals), mount read-only, and back up from the frozen view while real /home stays live and changing. Then tear down promptly: umount /mnt/snap && lvremove /dev/vg_data/lv_snap. Snapshots are a backup TOOL, not a backup — they live on the same disks as the origin and die with them. Check yourself
Why is a snapshot created in under a second even for a 2 TB volume?
Evacuate a failing disk: pvmove, then vgreduce
Move every extent off one PV while the volumes stay mounted, then retire the disk
The same indirection that lets a volume grow lets it move. Say
/dev/sdbstarts logging SMART warnings. Its extents belong tovg_data, not to any filesystem directly, so LVM can copy them to another PV in the pool underneath the mounted LVs — users keep working while the data migrates.The tool is `pvmove SOURCE [DESTINATION]`. The first device you name is the source: the disk being emptied. The optional second one is where its extents go; leave it out and LVM picks free extents anywhere else in the VG. Before you start, read
pvs: the other PVs' combinedPFreemust be at least what the source has allocated, or there is nowhere to put the data.Example — not runnable on your lab box
sudo pvmove /dev/sdb /dev/sdd/dev/sdb: Moved: 12.50% /dev/sdb: Moved: 57.81% /dev/sdb: Moved: 100.00%
- /dev/sdb
- SOURCE — the PV whose extents are moved off (the disk you're evacuating)
- /dev/sdd
- DESTINATION (optional) — where the extents land; omit it to let LVM use any free space in the VG
- -i 10
- print a progress line every 10 seconds (the default interval is 15)
Each progress line names the source device and how much of it has been moved so far — so on a long migration, the device in those Moved:lines is the disk being evacuated, never the destination. The copy runs online: LVs on/dev/sdbstay mounted and writable throughout. If the session drops, the move survives; runningpvmovewith no arguments resumes any interrupted move.When the move reaches 100%,
/dev/sdbstill belongs to the VG — it just holds no data. Two more steps retire it: take it out of the pool, then wipe its LVM label so nobody mistakes it for a PV later.Example — not runnable on your lab box
sudo vgreduce vg_data /dev/sdb sudo pvremove /dev/sdbRemoved "/dev/sdb" from volume group "vg_data" Labels on physical volume "/dev/sdb" successfully wiped.
- vgreduce VG PV
- remove an EMPTY physical volume from a volume group
- pvremove PV
- erase the LVM label so the disk is no longer a physical volume
vgreduceshrinks the pool by one disk; it refuses (still in use) if any extent remains, which is your proof thatpvmovefinished.pvremovethen clears the label, and the disk can be pulled. The order is the point: pvmove → vgreduce → pvremove.Check yourself
An operator ran
pvmove /dev/sdc /dev/sdeand the output shows/dev/sdc: Moved: 40.0%. Which disk is being emptied?Now do it — read the stack and rehearse the grow
Walk the storage layers on the sandbox and stage the exam's most common fix
Storage practice has a special rule: the read is always safe, the write needs a spare disk. Walk the reads for real — they're the first two minutes of every storage task you'll ever do — and rehearse the grow command's exact shape so it's finger-memory when a graded lab (or a 2 a.m. page) hands you a full volume.
lsblkThe tree first, always: find the disk → part → lvm layering, spot which mountpoints ride on LVM, and check for bare disks. Say out loud what each TYPE row is — if you can narrate lsblk, you understand the machine's storage. sudo pvs; sudo vgs; sudo lvsThe three-layer summary. Find VFree in vgs and state the verdict it implies: is a grow on this box a one-command or a two-command job? That single glance-then-verdict is the professional reflex this lesson exists to build. sudo vgdisplay 2>/dev/null | grep -E 'VG Name|Alloc|Free'The long-form pool report: allocated vs free extents, spelled out. vgdisplay/lvdisplay are the detail views behind the vgs/lvs summaries — you'll want them when the summary raises a question. echo 'the fix: sudo lvextend -l +100%FREE -r /dev/<vg>/<lv>'Type the real command shape out (with your VG/LV names from lvs) even though you're not running it here: the +100%FREE form plus -r is the single most common real-world resolution to 'the disk is full' — pool to plate, filesystem grown, users unblocked, zero downtime.
- Raw partitions are promises carved in stone; LVM's indirection — PV (disk) → VG (pool) → LV (resizable slice at /dev/<vg>/<lv>) — lets storage follow demand instead of day-one guesses.
- Read before building: lsblk for the tree, pvs/vgs/lvs per layer. VFree in vgs decides everything — nonzero means grow is one command, zero means feed the pool first (pvcreate + vgextend).
- Build upward: pvcreate → vgcreate → lvcreate (-L size or -l 100%FREE) → mkfs → mount, then fstab by UUID and prove it with mount -a before any reboot.
- The headline skill: lvextend -L +N -r — grows volume AND filesystem, online, mounted, zero downtime. Without -r, lvs grows but df doesn't (the filesystem never claimed the space).
- XFS never shrinks (lvreduce on XFS = corruption) — size modestly, grow into demand. Snapshots (lvcreate -s) freeze a consistent backup view via copy-on-write: size by change rate, watch Data% in lvs, keep them short-lived.
- pvmove SOURCE [DEST] migrates extents OFF the first PV named, online, while LVs stay mounted; then vgreduce the empty disk out of the VG and pvremove its label.
| Task | Command |
|---|---|
| Survey the stack | lsblk · pvs · vgs (VFree!) · lvs |
| Init disk as PV | pvcreate /dev/sdb |
| Create / feed the pool | vgcreate vg /dev/sdb · vgextend vg /dev/sdd |
| Carve a volume | lvcreate -L 50G -n lv vg (or -l 100%FREE) |
| Grow volume + filesystem | lvextend -L +50G -r /dev/vg/lv |
| Snapshot for backup | lvcreate -L 10G -s -n snap /dev/vg/lv → mount ro → tar → lvremove |
| Evacuate a failing disk | pvmove /dev/old [/dev/new] → vgreduce vg /dev/old → pvremove /dev/old |
| Persist a mount | blkid → fstab by UUID → mount -a to prove it |
