Skip to main content
RunBook Academy

LinuxXVII · Software RAIDRAID operations

md RAID monitoring and failure detection

Intermediate⏱ ~14 minmdadmsystemctlbash

What you'll learn

  • Persist an array in mdadm.conf and the initramfs so it assembles at boot
  • Configure mdadm --monitor alerting and prove it fires
  • Schedule periodic scrubs and interpret mismatch_cnt
  • Explain what a write-intent bitmap changes after an unclean shutdown
  • Alert on md health from node_exporter metrics

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11

Not yet marked complete on this device.

Building the array is the easy part. A RAID1 that loses a leg keeps serving reads and writes exactly as before: no error, no slowdown a user would notice, nothing in the application log. The array is now a single disk, and it will stay that way until someone looks.

This lesson closes that gap. Four things have to be true before an array counts as production-ready: it assembles at boot, it tells you when a member fails, it finds bad sectors before a rebuild needs them, and its health is a metric you can alert on.

Persist the array before you do anything else

mdadm --create builds an array in the running kernel. It does not write anything that survives a reboot. Without the two commands below, the array may come back with a different name (/dev/md127 is the classic symptom) or fail to assemble at all — and if it holds the root filesystem, the host does not boot.

Configuration changerecord the array
$ sudo mdadm --detail --scan | sudo tee -a /etc/mdadm/mdadm.conf
ARRAY /dev/md0 metadata=1.2 name=host:0 UUID=8f3a1c2e:9b4d5e6f:1a2b3c4d:5e6f7a8b

Illustrative output

Configuration changerebuild the initramfs
$ sudo update-initramfs -u        # Debian-family. RHEL-family: sudo dracut -f
update-initramfs: Generating /boot/initrd.img-6.8.0-40-generic

Illustrative output

Alerting: mdadm —monitor

mdadm --monitor is a daemon that polls arrays and fires an event on Fail, FailSpare, DegradedArray, SparesMissing and RebuildFinished. The distribution ships a unit for it, but it does nothing useful until you tell it where to send the event.

# /etc/mdadm/mdadm.conf   (Debian-family; /etc/mdadm.conf on RHEL-family)
MAILADDR storage-oncall@example.com
PROGRAM  /usr/local/sbin/md-alert

MAILADDR needs a working local MTA. In a fleet that has no mail path — most modern fleets — PROGRAM is the one that matters: mdadm runs it with the event name, the array and, when relevant, the device as arguments, so it can push straight into your alerting system.

Configuration changeenable the monitor
$ sudo systemctl enable --now mdmonitor.service
Created symlink /etc/systemd/system/multi-user.target.wants/mdmonitor.service -> /usr/lib/systemd/system/mdmonitor.service.

Illustrative output

Scrubbing: find bad sectors before the rebuild does

This is the mechanism that decides whether a rebuild succeeds.

A RAID5 rebuild has to read every sector of every surviving member. Sectors that were written years ago and never read since may have decayed. Nobody has noticed, because nobody has read them. The rebuild reads them, hits an unrecoverable read error on a surviving disk, and the array — already down one member — cannot reconstruct that stripe. That is the mechanism behind “the rebuild failed and we lost the array”.

A scrub reads the whole array on a schedule, while it is still redundant, so a decayed sector is found at a moment when the parity or mirror can still rewrite it.

Read-only / Safestart a scrub
$ cat /sys/block/md0/md/sync_action; echo check | sudo tee /sys/block/md0/md/sync_action
idle
check

Illustrative output

Read-only / Saferead the result
$ cat /sys/block/md0/md/mismatch_cnt
0

Illustrative output

Distributions already ship a schedule; enable it rather than writing your own cron entry:

# Debian-family: /etc/cron.d/mdadm runs checkarray on the first Sunday monthly
sudo /usr/share/mdadm/checkarray --cron --all --idle --quiet

# RHEL-family: systemd timers
sudo systemctl enable --now mdcheck_start.timer
sudo systemctl enable --now mdcheck_continue.timer
  1. Confirm a schedule exists: systemctl list-timers | grep -i mdcheck on RHEL-family, or cat /etc/cron.d/mdadm on Debian-family
  2. Run one scrub by hand during a maintenance window and time it, so you know the real duration on your disks
  3. Read mismatch_cnt after every scrub and record it - a count that grows month on month is a failing member, not noise
  4. Throttle the scrub if it hurts production: sysctl dev.raid.speed_limit_max caps rebuild and scrub speed in KB/s per device
  5. Alert on a scrub that has not completed in over 35 days as well as on a non-zero mismatch count

Write-intent bitmaps

mdadm --detail prints an Intent Bitmap field. It is worth understanding because it changes the cost of an unclean shutdown by orders of magnitude.

Without a bitmap, an unclean shutdown means the kernel does not know which regions were mid-write, so it resyncs the entire array: hours on large disks, with degraded performance throughout. With an internal bitmap, the array records which regions have writes in flight, so recovery touches only those regions — seconds instead of hours.

# Add a bitmap to an existing array
sudo mdadm --grow /dev/md0 --bitmap=internal

# Confirm it
sudo mdadm --detail /dev/md0 | grep -i 'intent bitmap'

The cost is a small write amplification, since the bitmap is updated as writes are issued. For arrays large enough for a full resync to matter — which is most of them — the trade is worth it. Latency-critical small arrays are the exception.

Metrics and alerts

node_exporter’s mdadm collector reads /proc/mdstat and exposes it, which turns “someone must remember to look” into a normal alerting rule.

# A member has failed
- alert: MdArrayDegraded
  expr: node_md_disks{state="failed"} > 0
  for: 5m
  labels: { severity: critical }
  annotations:
    summary: "md array {{ $labels.device }} on {{ $labels.instance }} has a failed member"

# Fewer active disks than the array requires - degraded even with no hard failure
- alert: MdArrayMissingDisks
  expr: (node_md_disks_required - node_md_disks{state="active"}) > 0
  for: 15m
  labels: { severity: critical }

# The array is not fully in sync
- alert: MdArrayNotSynced
  expr: node_md_state{state="active"} == 0
  for: 15m
  labels: { severity: warning }

Knowledge check

Knowledge check · 5 questions

  1. Q1. You created /dev/md0 with mdadm --create and mounted it. What must you still do before the array is safe across a reboot?

  2. Q2. A periodic scrub costs performance while it runs, and its value is finding decayed sectors while redundancy still exists to repair them.

  3. Q3. Which of these actually tell an operator that an array has lost a member? Select all that apply.

  4. Q4. A 4-disk RAID5 has been degraded for six weeks. Nobody noticed because the service was unaffected. You replace the disk and the rebuild aborts with a read error on a surviving member. What went wrong, and which single control would most likely have prevented it?

  5. Q5. You enable mdmonitor.service and systemctl status shows it active. What is the next thing you do?

Passing score: 75%. Answers are checked in this browser.