← Start of tutorial

WIUNIX ACADEMY

Grow an LVM logical volume and XFS/ext4 filesystem online

Page 2 of 3. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

AlmaLinux 9Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment3 editable values
05

LET’S BUILD

Recheck the mapping and calculate an absolute extent target

Keep this Bash shell for the remaining panels. The function below only reads identity and capacity. It rejects a wrong mount/LV match, a non-root filesystem subtree, multiple mounts of the same device, read-only mounts, unsupported filesystem/LV layouts and a shared-lock VG. It is a scope guard, not a proof of hardware health or a lock against concurrent administrators.

The final GiB value becomes a rounded absolute extent count. Subtracting the existing extent count gives the additional allocation; a repeated run at the same completed LV target requires zero additional extents. Freeze concurrent mount/storage reconfiguration operationally with the storage owner. If you open a new shell, load this function again; later calls pass the current form values explicitly.

bash

Same Bash shell on the approved storage host

export LC_ALL=C
set -o pipefail
plan_growth() {
    mount=$1
    lv=$2
    target_gib=$3
    test -d "$mount" && test -b "$lv" || return 1
    source=$(findmnt -rn --mountpoint "$mount" -o SOURCE) || return 1
    test "$(readlink -f -- "$source")" = "$(readlink -f -- "$lv")" || return 1
    test "$(findmnt -rn --mountpoint "$mount" -o FSROOT)" = / || return 1
    fs=$(findmnt -rn --mountpoint "$mount" -o FSTYPE) || return 1
    case "$fs" in xfs|ext4) ;; *) return 1 ;; esac
    options=$(findmnt -rn --mountpoint "$mount" -o OPTIONS) || return 1
    case ",$options," in *,rw,*) ;; *) return 1 ;; esac
    devno=$(findmnt -rn --mountpoint "$mount" -o MAJ:MIN) || return 1
    mount_count=$(findmnt -rn -o MAJ:MIN | awk -v d="$devno" '$1==d {n++} END {print n+0}') || return 1
    test "$mount_count" -eq 1 || return 1
    attr=$(sudo lvs --noheadings -o lv_attr "$lv" | tr -d '[:space:]') || return 1
    case "$attr" in -w??a*) ;; *) return 1 ;; esac
    types=$(sudo lvs --segments --noheadings -o segtype "$lv" | awk 'NF {print $1}' | sort -u) || return 1
    test "$types" = linear || return 1
    vg=$(sudo lvs --noheadings -o vg_name "$lv" | tr -d '[:space:]') || return 1
    test -n "$vg" || return 1
    locktype=$(sudo vgs --noheadings -o vg_locktype "$vg" | tr -d '[:space:]') || return 1
    case "$locktype" in ''|none) ;; *) return 1 ;; esac
    vgattr=$(sudo vgs --noheadings -o vg_attr "$vg" | tr -d '[:space:]') || return 1
    case "$vgattr" in ??x*|???p*|?????[cs]*) return 1 ;; esac
    current_b=$(sudo lvs --noheadings --units b --nosuffix -o lv_size "$lv" | awk 'NF {printf "%.0f\n",$1}') || return 1
    extent_b=$(sudo vgs --noheadings --units b --nosuffix -o vg_extent_size "$vg" | awk 'NF {printf "%.0f\n",$1}') || return 1
    free_extents=$(sudo vgs --noheadings -o vg_free_count "$vg" | tr -d '[:space:]') || return 1
    case "$current_b:$extent_b:$free_extents:$target_gib" in *[!0-9:]*|:*|*::*) return 1 ;; esac
    test "$extent_b" -gt 0 && test "$target_gib" -ge 1 && test "$target_gib" -le 1024 || return 1
    test $((current_b % extent_b)) -eq 0 || return 1
    target_gib=$((10#$target_gib))
    requested_b=$((target_gib * 1024 * 1024 * 1024))
    target_extents=$(((requested_b + extent_b - 1) / extent_b))
    current_extents=$((current_b / extent_b))
    test "$target_extents" -ge "$current_extents" || return 1
    needed_extents=$((target_extents - current_extents))
    test "$free_extents" -ge "$needed_extents" || return 1
    printf 'mount=%s LV=%s filesystem=%s VG=%s\n' "$mount" "$lv" "$fs" "$vg"
    printf 'current_bytes=%s extent_bytes=%s target_extents=%s additional_extents=%s free_extents=%s\n' "$current_b" "$extent_b" "$target_extents" "$needed_extents" "$free_extents"
}
plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || {
    printf '%s\n' 'STOP: mapping, supported layout, target size or free extents did not pass.'
    exit 1
}
How do I know it worked?

The printed mount, device, filesystem, VG and extent calculation match the approved change; a failed guard means stop and investigate, not remove the check.

06

LET’S BUILD

Keep a private before-record and the VG metadata

After confirming the separate data backup, save the current VG metadata and a few focused reports in a new root-owned private directory. The command names the one VG discovered from the selected LV; it does not export all volume groups. Retain the printed directory with the change record and copy the needed evidence to the team’s existing protected storage.

The temporary record location must itself have room. If it shares the affected filesystem, it is especially not a disaster-recovery copy. Do not proceed merely because vgcfgbackup succeeded: that file describes LV mappings and cannot restore application data or reverse a grown filesystem.

bash

Same Bash shell on the approved storage host

plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
record=$(sudo mktemp -d /var/tmp/academy-lvm-growth.XXXXXXXX) || exit 1
sudo vgcfgbackup --file "$record/vg-before.conf" "$vg" || exit 1
sudo lvs --segments -o lv_path,lv_uuid,lv_attr,segtype,lv_size,vg_name,devices "$lv" | sudo tee "$record/lvs-before.txt" >/dev/null || exit 1
findmnt --mountpoint "$mount" -o TARGET,SOURCE,FSTYPE,FSROOT,OPTIONS | sudo tee "$record/mount-before.txt" >/dev/null || exit 1
df -B1 -- "$mount" | sudo tee "$record/df-before.txt" >/dev/null || exit 1
printf 'Private change record: %s\n' "$record"
How do I know it worked?

A private before-record exists and the separate application-data restore path has been checked; an error stops before capacity changes.

07

LET’S BUILD

Check the allocation plan without confusing it with execution

Re-read identity and free extents, then ask LVM to test the requested absolute target. Test mode disables metadata writes; a success is not proof that the real allocation or subsequent filesystem growth will succeed. Another storage operation can consume extents after this check.

Read the complete report. If the LV already has the target size, do not add another increment: continue by comparing the filesystem layer with that LV. If the current size exceeds this form’s target, reconcile the plan with the owner rather than attempting a reduction.

bash

Same Bash shell on the approved storage host

plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
if test "$needed_extents" -eq 0; then
    printf '%s\n' 'LV already has the reviewed target size; inspect the filesystem layer next.'
else
    sudo lvextend --test --extents "$target_extents" "$lv" || exit 1
fi
How do I know it worked?

The test reports the intended LV/extent target or explicitly skips an already-sized LV; no filesystem has grown yet.

08

LET’S BUILD

Extend only the logical-volume layer

This is the first capacity-changing panel. It repeats the identity/capacity guard immediately before requesting the absolute extent count. The command deliberately does not combine filesystem resizing, so a partial result has a clear boundary. LVM also owns its allocation locking; the earlier read-only check does not reserve extents.

Keep the terminal and error output. After a successful return, compare LVM’s bytes with the device-mapper block-device size. The filesystem can still have its old size; that is an unfinished second step, not a reason to extend the LV again.

bash

Same Bash shell on the approved storage host

plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
if test "$needed_extents" -eq 0; then
    printf '%s\n' 'LV already has the reviewed target size; no repeated extension.'
else
    sudo lvextend --extents "$target_extents" "$lv" || exit 1
fi
sudo lvs --units b --nosuffix -o lv_path,lv_size,vg_name "$lv"
sudo blockdev --getsize64 "$lv"
How do I know it worked?

The LV and its block device expose the approved rounded target; the filesystem action remains to be completed.

09

LET’S BUILD

For XFS only: grow through the mounted filesystem

Execute this branch only when the mapping probe reports xfs. The guard requires the LV to have reached the reviewed target, then xfs_growfs grows the data section through the existing mountpoint. The filesystem must remain mounted; an ordinary LV path is not a replacement for identifying its actual mount.

Read geometry and filesystem capacity after the operation. No external log/realtime-device topology is handled here. If you have ext4, skip this entire branch and use the next one instead.

bash

Same Bash shell on the approved storage host

plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
test "$needed_extents" -eq 0 && test "$fs" = xfs || exit 1
sudo xfs_growfs -d "$mount" || exit 1
sudo xfs_info "$mount"
df -B1 -- "$mount"
How do I know it worked?

The mounted XFS data section uses the newly available LV capacity, or the command reports the exact reason growth did not complete.

10

LET’S BUILD

For ext4 only: grow to the existing LV size

Execute this branch only when the probe reports ext4. With no size argument, resize2fs grows to the underlying device’s current size. The guard requires that device to be the approved LV and already at the target. Mounted online growth depends on the installed kernel and filesystem features; a refusal is not permission to add force flags.

If the tool requests an offline check or reports unsupported growth/features, stop and arrange the distribution’s offline maintenance procedure with the application owner. Do not run a filesystem checker or repair tool against this mounted filesystem, and do not unmount a live application blindly. Skip this branch for XFS.

bash

Same Bash shell on the approved storage host

plan_growth '/srv/data' '/dev/vg_data/lv_data' '20' || exit 1
test "$needed_extents" -eq 0 && test "$fs" = ext4 || exit 1
sudo resize2fs "$lv" || exit 1
df -B1 -- "$mount"
How do I know it worked?

resize2fs completes the ext4 growth and the mounted filesystem reports the new capacity, or a specific unsupported/error state remains open.

Explore all tutorials ↗