Declarative Storage Management on RHEL 9 with the Ansible Storage System Role
Use the Red Hat-supported rhel-system-roles.storage Ansible role to declaratively manage LVM, RAID, and filesystems on RHEL 9.2+ with idempotent convergence, systemd mount persistence, and safe-mode defaults that prevent accidental data loss.
06 Jul 2026, 20:54 UTC

Desired Outcome
Define LVM volume groups, logical volumes, RAID arrays, and filesystems in Ansible variables and have the rhel-system-roles.storage role converge target hosts to that state idempotently — creating, resizing, or removing devices as needed, with persistent systemd mount units.
Prerequisites
- Control node: Ansible Core 2.14+ or Ansible Automation Platform 2.4+, Python 3.9+
- Managed nodes: RHEL 9.2 or later (role version 1.10+), subscribed to AppStream
- Packages on managed nodes:
lvm2,mdadm,xfsprogs(for XFS),e2fsprogs(for ext4) - Privilege escalation: Inventory configured with
become: yes(root via sudo) - Role installation:
dnf install rhel-system-roleson control node, or install theredhat.rhel_system_rolescollection from Automation Hub
Role Variables Overview
The role consumes two primary variable structures:
storage_pools— defines volume groups (physical disks, RAID level, VG name)storage_volumes— defines logical volumes (size, filesystem type, mount point, mount options)
Safety defaults: storage_safe_mode: true (blocks destructive actions) and storage_force: false (avoids forced filesystem recreation). Override only with explicit change-control approval.
Procedure: Create a RAID-1 Volume Group with Two Logical Volumes
This example provisions a mirrored VG on two disks, then creates an XFS volume for /data/app and an ext4 volume for /var/log/audit.
1. Inventory and Playbook
# inventory.yml
all:
hosts:
storage-node-01:
ansible_host: 192.168.10.20
vars:
ansible_become: yes
ansible_become_method: sudo
# site.yml
- name: Configure declarative storage
hosts: all
vars:
storage_pools:
- name: vg_app
disks:
- /dev/nvme1n1
- /dev/nvme2n1
raid_level: raid1
storage_volumes:
- name: lv_appdata
pool: vg_app
size: 200G
fs_type: xfs
mount_point: /data/app
mount_options: "defaults,noatime"
- name: lv_audit
pool: vg_app
size: 50G
fs_type: ext4
mount_point: /var/log/audit
mount_options: "defaults,noatime"
roles:
- rhel-system-roles.storage
2. Dry-Run Validation
Run in check mode to preview changes without applying:
ansible-playbook -i inventory.yml site.yml --check --diff
Review the diff output. The role will show planned VG creation, LV creation, filesystem formatting, and systemd mount unit generation. No changes are made.
3. Apply Configuration
ansible-playbook -i inventory.yml site.yml
Expected runtime: 30–120 seconds depending on disk initialization and filesystem creation.
Expected Checks After Run
Verify LVM State
# On managed node
vgs --reportformat json
lvs --reportformat json
lsblk -f
Confirm vg_app exists with two PVs, RAID-1 personality, and two LVs (lv_appdata, lv_audit) at requested sizes.
Verify Filesystems and Mounts
findmnt /data/app /var/log/audit
blkid | grep -E 'lv_appdata|lv_audit'
systemctl list-units 'mnt-*.mount' --state=active
systemctl is-enabled mnt-data-app.mount
systemctl is-enabled mnt-var-log-audit.mount
Mount units are named mnt-<path>.mount with path separators replaced by dashes. Both should be active and enabled.
Test Idempotence
ansible-playbook -i inventory.yml site.yml
Second run should report changed=0 for all storage tasks. Any non-zero changes indicate drift or a variable mismatch.
Recovery and Rollback Options
Prevent Accidental Data Loss
Keep storage_safe_mode: true (default). The role will refuse to wipe signatures, shrink volumes, or reformat existing filesystems. To allow destructive changes in a controlled window, set storage_safe_mode: false only for that playbook run.
Back Up LVM Metadata Before Changes
# On managed node, before first run or major changes
vgcfgbackup -f /root/vg_app_backup_$(date +%F).vg vg_app
# Archives also stored automatically in /etc/lvm/archive/
Rollback to Prior LVM State
If a playbook run produces an undesired configuration, restore the previous VG metadata:
# List available archives
ls -l /etc/lvm/archive/vg_app_*
# Restore specific archive (example name)
vgcfgrestore -f /etc/lvm/archive/vg_app_00001-1234567890.vg vg_app
# Re-scan and reactivate
vgscan && vgchange -ay vg_app
Alternatively, revert the playbook variables to the prior committed state and re-run — the role's idempotence will converge back.
Limitations and Gotchas
- XFS cannot shrink. Reducing
sizeon an XFS volume causes task failure. Ext4 supports offline shrink only (unmount first). - RAID member disks must be unpartitioned and identical in size. Mixing sizes wastes capacity to the smallest disk.
- No direct multipath management. Configure
/etc/multipath.conffirst, then present multipath devices (e.g.,/dev/mapper/mpatha) asdisksinstorage_pools. - SELinux contexts. New mounts receive default contexts. If custom policies apply, run
restorecon -Rv /data/app /var/log/auditafter first mount. - Version skew. RHEL 9.0/9.1 ship older role versions lacking
resizefsandraid_levelenhancements. Use RHEL 9.2+ for full feature parity.
Integration Note: RHEL Image Builder
The same storage_pools and storage_volumes variables can be passed as blueprint customizations to Image Builder, producing pre-configured disk images (QCOW2, AMI, ISO) for edge or VM deployments. This enables identical storage topology across installed and image-based provisioning.
Verification Checklist
rpm -qi rhel-system-rolesshows version 1.10+- Check mode run shows expected changes only
vgs/lvsJSON output matches playbook variables- Mount units active and enabled via systemd
- Second playbook run reports
changed=0 - LVM metadata backup exists in
/etc/lvm/archive/
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.