LVM — Logical Volume Manager
- What LVM is: the three-layer model
- Why you use it
- Inspect what's already there
- Build a volume from scratch
- Mount and persist in /etc/fstab
- Grow a volume online
- Shrink a volume (ext4 only)
- Snapshots
- Thin provisioning
- Rename, remove, move between PVs
- Remediate a full volume
- LVM RAID
- Ansible patterns
- Common errors
What LVM is: the three-layer model
LVM sits between your physical disks and the filesystem you mount. Instead of formatting a disk partition directly and losing the ability to resize it later, you give the raw block device to LVM and carve it into logical volumes that can be grown, shrunk, snapshotted, or moved between disks — most of the time without unmounting anything.
Three layers, bottom to top:
┌──────────────────────────────────────────────┐
│ Logical Volumes (LV) data, logs, home │ <-- what you mkfs and mount
├──────────────────────────────────────────────┤
│ Volume Group (VG) vg_data │ <-- pool of extents
├──────────────────────────────────────────────┤
│ Physical Volumes (PV) /dev/sdb /dev/sdc │ <-- raw block devices
└──────────────────────────────────────────────┘
- Physical Volume (PV)
- A block device (whole disk, partition, RAID array, or iSCSI LUN) initialised with
pvcreateso LVM can use it. - Volume Group (VG)
- A pool of storage built from one or more PVs. Logical volumes are allocated from this pool in fixed-size physical extents (PE, typically 4 MiB).
- Logical Volume (LV)
- A virtual block device carved from the VG, e.g.
/dev/vg_data/lv_home. You format and mount this. It can be linear, striped, mirrored, RAID, thin, or a snapshot.
Why you use it
- Online extend. Add a disk, extend the VG, extend the LV, resize the filesystem — all without unmounting.
- Snapshots. Freeze a point-in-time copy of an LV for consistent backups, then merge it back or drop it.
- Striping. Spread a single LV across multiple PVs for higher throughput (
lvcreate -i N). - Thin provisioning. Over-allocate on paper; physical extents are consumed only when data is actually written.
- RAID without mdadm.
lvcreate --type raid1/5/6/10integrates redundancy into LVM directly (see LVM RAID). - Migrate between disks.
pvmoverelocates data off a failing disk while services stay online.
Inspect what's already there
First commands on any new system — find out what LVM layout exists before touching anything.
# Block devices and mountpoints
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,UUID
# PVs (physical volumes)
pvs # compact summary
pvdisplay /dev/sdb # detailed single-PV view
# VGs (volume groups)
vgs # summary: VG name, PVs, LVs, free space
vgdisplay vg_data # detailed: PE size, total/free extents, policies
# LVs (logical volumes)
lvs # summary
lvs -a -o +devices # show underlying PVs and segment types
lvdisplay /dev/vg_data/lv_home
# Everything in one view
lsblk /dev/sdb /dev/sdc # tree view of a specific disk
If a disk was previously partitioned with LVM, pvs will detect the PV metadata automatically — there is no "mount LVM" step.
Build a volume from scratch
Typical case: a new disk /dev/sdb (64 GiB) and you want to add /data backed by it.
1. Initialise the PV
# Wipe any old signatures first (skip if disk is definitely blank)
wipefs -a /dev/sdb
# Create the physical volume
pvcreate /dev/sdb
# → Physical volume "/dev/sdb" successfully created.
You can also pvcreate on a partition (/dev/sdb1). Using the whole disk is simpler and recommended when the disk is dedicated to LVM.
2. Create or extend the VG
# New VG
vgcreate vg_data /dev/sdb
# Or add the PV to an existing VG
vgextend vg_data /dev/sdb
3. Create the LV
# Linear LV, fixed size
lvcreate -n lv_data -L 50G vg_data
# Use a percentage of free space
lvcreate -n lv_data -l 100%FREE vg_data
# Striped across 2 PVs for throughput
lvcreate -n lv_data -L 50G -i 2 -I 64 vg_data /dev/sdb /dev/sdc
# name size stripes stripe-size-KiB PVs
-L takes absolute size (50G, 500M). -l takes extents or percentages (100%FREE, 50%VG).
Mount and persist in /etc/fstab
# Format (pick xfs or ext4)
mkfs.xfs /dev/vg_data/lv_data
# or
mkfs.ext4 -L data /dev/vg_data/lv_data
# Mount it
mkdir -p /data
mount /dev/vg_data/lv_data /data
# Capture the filesystem UUID for fstab (more stable than the device path)
blkid /dev/vg_data/lv_data
# /etc/fstab — use UUID or the LVM device path
UUID=… /data xfs defaults 0 0
# or
/dev/vg_data/lv_data /data xfs defaults 0 0
# Tell systemd about the new fstab entry and verify
systemctl daemon-reload
mount -a
findmnt /data
mount -a catches most problems while the system is still up. Use nofail on any mount that isn't critical for boot.
Grow a volume online
This is the killer feature. With -r (or --resizefs), lvextend resizes the filesystem in the same step — no unmount.
# One-shot: add 20 GiB and resize the filesystem
lvextend -r -L +20G /dev/vg_data/lv_data
# Absolute target size
lvextend -r -L 100G /dev/vg_data/lv_data
# Use all remaining free space in the VG
lvextend -r -l +100%FREE /dev/vg_data/lv_data
# Verify
df -h /data
lvs /dev/vg_data/lv_data
If the VG is out of free extents, add another PV first:
# Attach another disk
pvcreate /dev/sdc
vgextend vg_data /dev/sdc
# If the underlying disk itself grew (resized SAN LUN, expanded cloud disk)
pvresize /dev/sdb # LVM re-reads the new size
# Then extend the LV as above
lvextend -r -l +100%FREE /dev/vg_data/lv_data
xfs_growfs and resize2fs are what -r invokes internally. You can call them manually if you forgot -r: xfs_growfs /data or resize2fs /dev/vg_data/lv_data.
Shrink a volume (ext4 only)
# ext4 shrink — MUST be offline and fsck'd first
umount /data
e2fsck -f /dev/vg_data/lv_data
resize2fs /dev/vg_data/lv_data 40G # shrink filesystem first
lvreduce -L 40G /dev/vg_data/lv_data # then shrink the LV to match
mount /data
# Safer one-shot (does the fsck + resize2fs + lvreduce)
lvreduce -r -L 40G /dev/vg_data/lv_data
Always shrink the filesystem before the LV. The reverse order destroys data.
Snapshots
A snapshot is a writable copy-on-write view of an LV at a point in time. Initially it takes almost no space — blocks are only copied when the origin LV diverges. Main use: take a consistent backup while the service keeps writing.
# Thick snapshot, 10 GiB buffer for changes
lvcreate --size 10G --snapshot --name lv_data_snap /dev/vg_data/lv_data
# Mount read-only to back it up
mkdir -p /mnt/snap
mount -o ro,nouuid /dev/vg_data/lv_data_snap /mnt/snap
tar -C /mnt/snap -cf /backup/data-$(date +%F).tar .
umount /mnt/snap
# Drop the snapshot when done
lvremove -f /dev/vg_data/lv_data_snap
# Or merge the snapshot back (roll origin back to snapshot-time state)
umount /data
lvconvert --merge /dev/vg_data/lv_data_snap
mount /data
lvs — the Data% column shows consumption.
Thin-pool snapshots don't need a pre-sized buffer; they draw from the same pool (see thin provisioning).
Thin provisioning
In a thin pool, LVs are created with a virtual size that can exceed the pool's physical size. Physical extents are only allocated on write. Useful for dense snapshots and scenarios where most LVs don't actually fill up.
# Create the thin pool itself (physical, fixed size)
lvcreate -L 500G --thinpool tp_data vg_data
# Create a thin LV inside the pool (virtual size can exceed the pool)
lvcreate -V 100G --thin -n lv_tenantA vg_data/tp_data
lvcreate -V 100G --thin -n lv_tenantB vg_data/tp_data
lvcreate -V 100G --thin -n lv_tenantC vg_data/tp_data
# Total virtual: 300 GiB. Pool physical: 500 GiB. Safe — for now.
# Snapshot of a thin LV (cheap, no pre-sized buffer)
lvcreate -s -n lv_tenantA_snap vg_data/lv_tenantA
# Monitor pool usage
lvs -o name,data_percent,metadata_percent vg_data/tp_data
data_percent aggressively and configure thin_pool_autoextend_threshold in /etc/lvm/lvm.conf so the pool grows before it hits the wall.
Rename, remove, move between PVs
# Rename
lvrename vg_data lv_old lv_new
vgrename vg_old vg_new
# Remove (unmount first!)
umount /data
lvremove /dev/vg_data/lv_data
# Evacuate a PV before pulling the disk (online, data stays available)
pvmove /dev/sdc # move everything off /dev/sdc
pvmove /dev/sdc /dev/sdd # move everything off /dev/sdc onto /dev/sdd
pvmove -n /dev/vg_data/lv_data /dev/sdc # move a specific LV off /dev/sdc
# Then remove the PV from the VG and wipe it
vgreduce vg_data /dev/sdc
pvremove /dev/sdc
Remediate a full volume
Ticket says "disk is full on prod01". Your workflow:
# 1. Confirm which filesystem is full
df -h
df -i # inodes can exhaust before blocks do
# 2. Find out if the VG has free extents
vgs # look at VFree
# 3a. VG has free space — grow the LV online
lvextend -r -l +100%FREE /dev/vg_data/lv_data
# 3b. VG is also full — add a disk, then extend
pvcreate /dev/sdd
vgextend vg_data /dev/sdd
lvextend -r -l +100%FREE /dev/vg_data/lv_data
# 4. Verify
df -h /data
If the answer is "not actually full, just one process filling /var/log", don't extend — find and fix the writer first. See Service Troubleshooting.
LVM RAID
LVM can create RAID LVs directly, without stacking LVM on top of mdadm. One command, one admin interface, and the redundancy lives inside the VG. Good fit for single-host servers where you already manage storage with LVM.
Create RAID LVs
# RAID1 (mirror) across 2 PVs — data redundancy, same write throughput
lvcreate --type raid1 -m 1 -L 50G -n lv_mirror vg_data /dev/sdb /dev/sdc
# ^^ 1 mirror = 2 copies total
# RAID5 across 3+ PVs — parity, survives one disk loss
lvcreate --type raid5 -i 2 -L 50G -n lv_parity vg_data /dev/sdb /dev/sdc /dev/sdd
# RAID6 across 4+ PVs — double parity, survives two disk losses
lvcreate --type raid6 -i 2 -L 50G -n lv_parity2 vg_data /dev/sdb /dev/sdc /dev/sdd /dev/sde
# RAID10 (stripe of mirrors) across 4 PVs — performance + redundancy
lvcreate --type raid10 -m 1 -i 2 -L 50G -n lv_fast vg_data /dev/sdb /dev/sdc /dev/sdd /dev/sde
Monitor sync status and health
# Quick status
lvs -a -o +devices,raid_sync_action,sync_percent
# Cpy%Sync shows rebuild/resync progress
# raid_sync_action: idle / resync / recover / check / repair
# Watch a rebuild in progress
watch 'lvs -a -o name,attr,cpy%sync,raid_sync_action vg_data'
# The hidden sub-LVs (rmeta/rimage) hold per-leg metadata and data
lvs -a vg_data
Scrub for silent corruption
# Read every block and compare mirrors / check parity
lvchange --syncaction check /dev/vg_data/lv_mirror
# Same, but fix any mismatches found
lvchange --syncaction repair /dev/vg_data/lv_mirror
# Count of mismatches from the last check
lvs -o name,raid_mismatch_count vg_data
Replace a failing PV
# A disk is reporting errors. Replace the failing leg online.
# 1. Add the replacement PV to the VG
pvcreate /dev/sdf
vgextend vg_data /dev/sdf
# 2. Tell LVM to rebuild the array onto the new disk
lvconvert --replace /dev/sdc /dev/vg_data/lv_mirror /dev/sdf
# failing PV LV replacement
# 3. Watch the resync complete
lvs -a -o name,cpy%sync,raid_sync_action /dev/vg_data/lv_mirror
# 4. Remove the old disk from the VG once sync is at 100%
vgreduce vg_data /dev/sdc
pvremove /dev/sdc
LVM RAID vs mdadm + LVM stacked
| LVM RAID | mdadm + LVM | |
|---|---|---|
| Management surface | One tool (lv*) | Two tools (mdadm + lv*) |
| Distro defaults | RHEL/Rocky since 7 | Debian/Ubuntu historical default |
| Boot/root on RAID | Trickier | Well-trodden with /boot on md |
| Monitoring maturity | Good, via dmeventd + lvs | Very mature, mdadm --monitor, email alerts |
| RAID levels | 0, 1, 4, 5, 6, 10 | Same |
| Best when | Single-host, LVM-first shops | You want RAID under everything, including /boot |
Either way, hardware RAID controllers with battery-backed cache are still a valid choice for databases. LVM RAID is a software solution and costs CPU + parity writes.
Ansible patterns
Three modules cover most LVM work: community.general.lvg (VGs), community.general.lvol (LVs), and ansible.posix.mount (fstab + mount).
---
- name: Ensure /data is provisioned with LVM
hosts: db_servers
become: true
tasks:
- name: Ensure volume group vg_data spans the data disks
community.general.lvg:
vg: vg_data
pvs: /dev/sdb,/dev/sdc
pesize: 4
- name: Ensure lv_data exists and is grown to fit the VG
community.general.lvol:
vg: vg_data
lv: lv_data
size: 100%FREE
resizefs: true # xfs_growfs / resize2fs after extend
shrink: false # never shrink without explicit opt-in
- name: Ensure filesystem on lv_data
community.general.filesystem:
fstype: xfs
dev: /dev/vg_data/lv_data
- name: Ensure /data is mounted and in fstab
ansible.posix.mount:
path: /data
src: /dev/vg_data/lv_data
fstype: xfs
opts: defaults,nofail
state: mounted
Set shrink: false on lvol in production. The default behaviour will shrink a volume if the variable is smaller than the current size — easy to cause data loss by accident.
Common errors
Device /dev/sdb excluded by a filter- LVM's
global_filterorfilterin/etc/lvm/lvm.confis rejecting the device. Common cause: multipath or a stale filter line. Runlvmconfig --type full devices/filterto see the active filter. Can't open /dev/sdb exclusively. Mounted filesystem?- Something else is holding the device — a mounted partition, a running
mdadmarray, multipath, or an older PV signature. Uselsof /dev/sdb,lsblk, andwipefs /dev/sdbto investigate. Insufficient free space: N extents needed, only M available- The VG doesn't have enough room. Extend the VG with another PV (
vgextend) or resize an underlying disk and runpvresize. xfs_growfs: ... is not a mounted XFS filesystem- You ran
lvextend -rbut the LV wasn't mounted. XFS can only be grown while mounted. Mount it first, then rerun with-r. cannot resize to N extents as later ones are allocated(on shrink)- The extents at the tail of the LV you're shrinking are in use. With xfs this is fatal (no shrink possible). With ext4 you need to fsck and
resize2fsto a size smaller than the target beforelvreduce. Snapshot ... is invalid- The snapshot's COW buffer filled up. The snapshot has been auto-dropped; remove it with
lvremove -fand retake with a bigger--size.
lsblk/blkid/findmnt, and Service Troubleshooting for the full "disk is full" workflow.