Signal9Signal9 — home
online
all lessons

LPIC-2 Advanced Storage: Software RAID (mdadm), iSCSI and NBD

Advanced45 min9 steps
What you'll learn
  • Assemble a software RAID array with mdadm and read its health and rebuild progress from /proc/mdstat
  • Persist an array so it reassembles automatically after a reboot
  • Choose a RAID level by reasoning about its speed, capacity, and how many disks it can lose
  • Export a LUN from an iSCSI target with targetcli (backstore, IQN, LUN, ACL)
  • Attach a remote LUN as a local block device with the iscsiadm initiator and mount it safely
  • Attach a network block device (NBD) export with nbd-client and persist it with /etc/nbdtab and a _netdev fstab entry
  1. Why combine disks at all

    Frame RAID as turning several fragile disks into one device that is faster, bigger, or survivable.

    A single disk is a single point of failure. When it dies, everything on it dies with it — and meanwhile, one disk can only read and write so fast. RAID (Redundant Array of Independent Disks) glues several physical disks together so the operating system sees one block device that is faster, larger, more resilient, or some blend of the three.

    Think of it like a delivery company. One van (one disk) is slow and, if it breaks down, the parcel is stuck. Run several vans and you can either split each order across them to deliver faster (striping), send a duplicate van behind every order so a breakdown loses nothing (mirroring), or keep a spare van loaded with enough information to reconstruct any one lost load (parity). RAID levels are just named recipes for which of those tricks you use.

    On Linux, you build these arrays in software with `mdadm` ("multiple devices admin"). The kernel exposes the result as a `/dev/mdN` device you partition, format, and mount like any disk.

  2. Build an array with mdadm

    Create a RAID device and understand the metadata mdadm writes onto each member.

    mdadm --create takes a list of member block devices and bonds them into a new /dev/mdN. You tell it the level (the recipe) and how many devices participate. Here we build a RAID 5 across three disks.

    Example — not runnable on your lab box

    sudo mdadm --create /dev/md0 --level=5 --raid-devices=3 /dev/sdb /dev/sdc /dev/sdd
    mdadm: Defaulting to version 1.2 metadata
    mdadm: array /dev/md0 started.
    --create
    Make a brand-new array at the named device (/dev/md0).
    --level=5
    The RAID recipe: 5 = striping with one disk's worth of distributed parity.
    --raid-devices=3
    How many active members make up the array — must match the device list that follows.
    array /dev/md0 started means the kernel has assembled the device and it is usable immediately — even though, for a parity level, it begins syncing in the background (next step). The version 1.2 metadata note matters: mdadm writes a superblock near the start of each member disk recording the array's UUID, level, and role. That superblock is what lets the array reassemble later without you retyping any of this.

    Check yourself

    What does mdadm write onto each member disk so the array can be rebuilt on different hardware later?

  3. Read array health from /proc/mdstat

    Check status, member health, and live rebuild progress without guessing.

    Once an array exists, `/proc/mdstat` is your single window into its health. The kernel keeps this file live: which disks are members, whether any are missing, and — crucially — how far along a rebuild or initial sync has progressed. After creating a parity array, or after replacing a failed disk, watch this file until it reports clean.

    Example — not runnable on your lab box

    cat /proc/mdstat
    Personalities : [raid6] [raid5] [raid4]
    md0 : active raid5 sdd[3] sdc[1] sdb[0]
          209584128 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/3] [UUU]
          [=========>...........]  recovery = 47.3% (49574912/104792064) finish=4.2min speed=216000K/sec
    
    unused devices: <none>
    Read it line by line. `md0 : active raid5` names the array and level. `[3/3]` means 3 of 3 expected members are present; `[UUU]` shows each member is Up (a _ would mark a missing or failed disk — [UU_] means one is gone and you are running degraded). The `recovery = 47.3% ... finish=4.2min` line is the live rebuild progress bar — the array is usable now but not yet redundant, and you should wait for it to finish before trusting it to survive a failure.

    Example — not runnable on your lab box

    sudo mdadm --detail /dev/md0
    /dev/md0:
            Version : 1.2
         Raid Level : raid5
         Array Size : 209584128 (199.87 GiB)
       Raid Devices : 3
        Total Devices : 3
              State : clean
      Active Devices : 3
      Working Devices : 3
      Failed Devices : 0
    --detail
    Print the full status report for one assembled array (state, sizes, per-device roles, array UUID).
    Where /proc/mdstat is the quick glance, --detail is the full chart for a single array. `State : clean` with `Failed Devices : 0` is the healthy resting state. This is also where you read the array UUID that you will persist in the next step.

    Check yourself

    In a /proc/mdstat line the array shows [3/3] [UU_]. What is the state of the array?

  4. Persist the array so it survives a reboot

    Record the array in mdadm.conf so the system reassembles it automatically at boot.

    Here is the step learners forget — and the one labs love to grade. Creating an array assembles it for the running system only. At the next boot, the system scans for arrays to bring up, and it consults `/etc/mdadm.conf` (on some distros /etc/mdadm/mdadm.conf) to know which arrays it should expect. If your array is not listed there, it may not assemble — or may come back under a different device name like /dev/md127, breaking any /etc/fstab entry that named /dev/md0.

    mdadm --detail --scan prints exactly the ARRAY line the config file wants — UUID, level, and device count. You append it to the config so the description is on record.

    sudo mdadm --detail --scan | sudo tee -a /etc/mdadm.conf
    ARRAY /dev/md0 metadata=1.2 name=server:0 UUID=9f8a1c44:3e2b07d5:1a6f9e22:8c4d51ab
    --detail
    Report on assembled arrays.
    --scan
    Scan all active arrays and emit them in config-file (ARRAY-line) format, ready to save.
    -a (tee)
    Append to the file instead of overwriting it — never clobber an existing mdadm.conf.
    The single ARRAY line printed (and appended) ties the array's UUID to its device name. At boot the system matches the on-disk superblock UUID against this line and assembles /dev/md0 consistently every time. The UUID= is the durable identifier — disk ordering or device-name changes won't fool it.

    Check yourself

    Which command produces the exact ARRAY line you append to mdadm.conf so the array reassembles at boot?

  5. Choose the right RAID level

    Reason about speed, usable capacity, and fault tolerance to pick a level on purpose.

    Picking a level is a trade between three things: speed, usable capacity, and how many disks you can lose without losing data. Here is the mental model for each common level:

    • RAID 0 (stripe) — data split across all disks for maximum speed and full capacity, but zero redundancy. Lose any one disk and the whole array is gone. Pure performance, for data you can afford to lose.
    • RAID 1 (mirror) — every disk holds an identical copy. Survives losing all but one disk, but you pay half (or more) your capacity for it. Simple and very safe.
    • RAID 5 (single parity) — striping plus one disk's worth of parity spread across the set. Survives one disk failure; usable capacity is (N−1) disks. A common balance of capacity and safety, but a rebuild after a failure stresses the remaining disks.
    • RAID 6 (double parity) — like 5 but with two parity blocks, so it survives two simultaneous failures; usable capacity is (N−2). The safer choice for large, slow disks where a rebuild takes so long a second failure is plausible.
    • RAID 10 (stripe of mirrors) — mirror pairs, then stripe across them. Fast and redundant: survives at least one disk per mirror pair. The premium choice for databases — you pay half your capacity, like RAID 1.

    The one-line rule of thumb: *0 = speed, 1 = safety, 5 = balance, 6 = balance with a wider safety margin, 10 = speed and safety (at a price).*

    Check yourself

    You need an array that keeps working even if two disks fail at the same time. Which RAID level meets that requirement?

  6. Export a LUN: the iSCSI target with targetcli

    Stand up the server side — turn a block device into a network-accessible LUN protected by an ACL.

    RAID gives you bigger, safer storage on one machine. iSCSI lets a machine serve raw block storage over the network so another machine can use it as if it were a local disk. The serving side is the target; the consuming side is the initiator.

    Think of it as renting a storage unit by mail. The target is the storage facility; a LUN (Logical Unit Number) is one specific unit it rents out; the IQN (iSCSI Qualified Name, like iqn.2026-06.com.example:storage.disk0) is the globally unique address printed on the door; and the ACL is the access list at the front desk — only an initiator whose own IQN is on that list gets the key.

    You configure the target with `targetcli`, an interactive shell. The build order is always: backstore → target (IQN) → LUN → ACL.

    Example — not runnable on your lab box

    sudo targetcli
    targetcli shell version 2.1.fb49
    Copyright 2011-2013 by Datera, Inc and others.
    
    /> /backstores/block create name=disk0 dev=/dev/md0
    Created block storage object disk0 using /dev/md0.
    /> /iscsi create iqn.2026-06.com.example:storage.target0
    Created target iqn.2026-06.com.example:storage.target0.
    Created TPG 1.
    /backstores/block create
    Register a real local block device (here our RAID array /dev/md0) as an exportable object.
    /iscsi create <iqn>
    Create the target and its IQN — the unique network address initiators will connect to.
    Two foundations are laid. The backstore is the actual storage being shared — we point it at /dev/md0, so the RAID array becomes the thing served over the wire. The `/iscsi create` line mints the target IQN; that string is what an initiator must name to find this storage. TPG 1 (Target Portal Group) is the default container the LUN and ACL go into next.

    Example — not runnable on your lab box

    /iscsi/iqn.2026-06.com.example:storage.target0/tpg1/luns create /backstores/block/disk0
    Created LUN 0.
    This maps the backstore into the target as LUN 0 — the actual unit number the initiator will see and attach. No LUN means the target answers but offers no storage.

    Example — not runnable on your lab box

    /iscsi/iqn.2026-06.com.example:storage.target0/tpg1/acls create iqn.2026-06.com.example:client01
    Created Node ACL for iqn.2026-06.com.example:client01
    Created mapped LUN 0.
    The ACL authorizes exactly one initiator — the one whose IQN is iqn.2026-06.com.example:client01 — to log in and see LUN 0. This IQN must exactly match the initiator's configured name (you'll see where that's set in the next step). Anyone whose IQN isn't on this list is refused at login. Finish with saveconfig and exit in targetcli so the configuration persists, and make sure the target is reachable on its portal (here the documentation address 192.0.2.10, TCP 3260).

    Check yourself

    In targetcli, which object decides which initiator IQN is allowed to connect to a target?

  7. Attach it: the iSCSI initiator with iscsiadm

    Discover, log in, and mount the remote LUN as a local block device — with the IQN matched correctly.

    On the client (the initiator) you use `iscsiadm`. The workflow is two beats: discovery (ask the target portal what it offers) then login (actually attach a LUN). After login, the remote LUN appears in lsblk as an ordinary new disk like /dev/sdX — at which point you partition, format, and mount it like any local device.

    First, the initiator's own identity. Its IQN lives in /etc/iscsi/initiatorname.iscsi, and it must match the ACL you set on the target — otherwise login is refused.

    Example — not runnable on your lab box

    cat /etc/iscsi/initiatorname.iscsi
    InitiatorName=iqn.2026-06.com.example:client01
    This is the name the initiator presents at login. It reads `iqn.2026-06.com.example:client01` — identical to the ACL we created on the target. If you change this file, restart iscsid so the new name takes effect. A mismatch here is the number-one reason a login is rejected.

    Example — not runnable on your lab box

    sudo iscsiadm -m discovery -t sendtargets -p 192.0.2.10
    192.0.2.10:3260,1 iqn.2026-06.com.example:storage.target0
    -m discovery
    Discovery mode — ask a portal which targets it exposes, without attaching anything yet.
    -t sendtargets
    The discovery method: ask the portal to send its list of target IQNs.
    -p 192.0.2.10
    The portal address (IP[:port]) of the target server to query.
    Discovery returns the target IQN available at the portal 192.0.2.10:3260 — proof the target is reachable and that you're naming it correctly. Nothing is attached yet; you've only learned what's on offer. Getting a result here but failing at login almost always means an ACL/IQN mismatch, not a network problem.

    Example — not runnable on your lab box

    sudo iscsiadm -m node -T iqn.2026-06.com.example:storage.target0 -p 192.0.2.10 --login
    Logging in to [iface: default, target: iqn.2026-06.com.example:storage.target0, portal: 192.0.2.10,3260]
    Login to [iface: default, target: iqn.2026-06.com.example:storage.target0, portal: 192.0.2.10,3260] successful.
    -m node
    Node mode — operate on a discovered target record.
    -T <iqn>
    Which target IQN to act on (from the discovery output).
    --login
    Attach the target's LUN(s) now, creating local block devices.
    `Login ... successful` means the target accepted this initiator's IQN against its ACL and attached the LUN. Run lsblk now and a new disk (e.g. /dev/sdb) appears — that's the remote LUN presented locally. If instead you see an authorization failure, the initiator IQN doesn't match the target ACL: fix one to equal the other.

    Now treat it like a disk — but mount it with one special option. Because the device only exists after the network is up and login succeeds, a normal /etc/fstab line would make the system try to mount it too early at boot and hang. The `_netdev` option tells the system to wait for the network first.

    echo 'UUID=... /mnt/iscsi ext4 _netdev 0 0' | sudo tee -a /etc/fstab
    UUID=... /mnt/iscsi ext4 _netdev 0 0
    _netdev
    Marks the mount as requiring the network — the system defers it until networking (and iSCSI login) is available, and unmounts it before networking goes down.
    The key token is `_netdev`. Without it, the boot sequence tries to mount the iSCSI filesystem before the initiator has logged in, the device isn't there yet, and the boot stalls in emergency mode — the network-storage equivalent of the missing-mdadm.conf trap from earlier. With it, the mount waits its turn. tee echoes what it appended (that's how the sudo-write idiom shows its work). The _netdev option is load-bearing: it holds the mount until the network is up, so an iSCSI mount never hangs the boot.

    Check yourself

    Which fstab mount option must an iSCSI filesystem use so the system doesn't try to mount it before the network is up?

  8. A lighter network disk: NBD with nbd-client

    List a server's NBD exports, attach one by name to a free /dev/nbdN, and make it come back after a reboot with /etc/nbdtab and a _netdev fstab line.

    iSCSI is not the only way to put a remote disk on a local device node. NBD (network block device) is a much smaller protocol built into the Linux kernel: a server (nbd-server, TCP port 10809) offers one or more named exports, and the client (nbd-client) attaches one of them to a local `/dev/nbdN` device. From then on it is an ordinary block device: blkid, mount, mkfs and fsck all work on it. Every read and write travels over the network to the server.

    There are no IQNs or ACLs to match here. You need three things: the server's name, the export name, and a free /dev/nbdN on the client. The nbd kernel module creates /dev/nbd0 upwards (modprobe nbd if they are missing).

    Example — not runnable on your lab box

    nbd-client -l nas01
    Negotiation: ..
    scratch
    backups
    media
    -l <host>
    List the export names the server offers, without attaching anything.
    Like iSCSI discovery: you learn what is on offer before you attach. Each line after Negotiation is one export name. Attaching the wrong one puts another team's disk on your box, so read the list against your ticket.

    Example — not runnable on your lab box

    nbd-client -c /dev/nbd0; echo "exit: $?"
    exit: 1
    -c <device>
    Check a device: prints the PID of the client holding it and exits 0 if it is in use; exits 1 if it is free.
    Exit 1 with no PID means /dev/nbd0 is free. Exit 0 and a PID means another export is already attached there, so pick the next device. Attaching over a device that is in use fails, or worse, hides the disk that was there.

    Example — not runnable on your lab box

    sudo nbd-client nas01 -N scratch /dev/nbd0 && sudo blkid /dev/nbd0
    Negotiation: ..size = 64MB
    Connected /dev/nbd0
    /dev/nbd0: LABEL="scratch" UUID="0b6c…" BLOCK_SIZE="4096" TYPE="ext4"
    -N <export>
    Attach the export with this name. Without -N the client asks for the server's default export, which is usually not the one you want.
    /dev/nbd0
    The local device to attach it to; it must be free.
    Connected /dev/nbd0 means the export is attached. blkid shows the filesystem already on it, so you mount it as it is: mount /dev/nbd0 /mnt/scratch. Don't run mkfs on a disk you were asked to attach. That erases data that lives on the server. nbd-client -d /dev/nbd0 detaches it again (unmount first).

    Surviving a reboot takes two files. /etc/nbdtab tells the system what to attach: one line per device, <device> <host> <export> [options], with the device written without /dev/. With that line in place, nbd-client nbd0 attaches it by name, and on a systemd host systemctl enable nbd@nbd0 does that at boot. Then /etc/fstab mounts the filesystem, with `_netdev` for the same reason as iSCSI: the device does not exist until the network is up and the client has connected.

    Example — not runnable on your lab box

    echo 'nbd0 nas01 scratch' | sudo tee -a /etc/nbdtab
    echo "UUID=$(sudo blkid -o value -s UUID /dev/nbd0) /mnt/scratch ext4 defaults,_netdev 0 0" | sudo tee -a /etc/fstab
    nbd0 nas01 scratch
    UUID=0b6c… /mnt/scratch ext4 defaults,_netdev 0 0
    tee -a
    Append to the file instead of overwriting it, and echo what was written. Run through sudo, it can write a root-owned file that a plain >> redirect can't.
    blkid -o value -s UUID
    Print only the UUID value of the device's filesystem, ready to paste into fstab.
    _netdev
    Mount only once the network is up; unmount before it goes down.
    The nbdtab line maps nbd0 to export scratch on nas01. The fstab line mounts it by filesystem UUID, which still matches if the export ends up on a different /dev/nbdN someday. To check both files without rebooting: unmount, nbd-client -d /dev/nbd0, then nbd-client nbd0 and mount /mnt/scratch. If those two commands bring the disk back, boot will too.

    Check yourself

    Which nbd-client option attaches an export by its name (rather than the server's default export)?

  9. Try it in your live terminal

    Inspect real storage state on the sandbox to your right — read-only, nothing is created or destroyed.

    You have a real root shell. These commands are read-only — they report on whatever storage exists without building or breaking any array. Run them to make the concepts concrete; if there are no md arrays here, the first command says so plainly.

    cat /proc/mdstat 2>/dev/null || echo 'no md arrays'
    The live health window for software RAID. If any mdN arrays exist you'll see their level, member health ([UUU] vs [UU_]), and any rebuild progress; otherwise you get a plain no md arrays.
    lsblk
    The block-device tree: physical disks, partitions, any mdN arrays, and what's mounted where. This is how you'd confirm an attached iSCSI LUN shows up as a new disk after login.
    mdadm --version 2>/dev/null || echo 'mdadm not installed'
    Confirms the RAID toolset is present and prints its version — the tool you'd use to create, inspect (--detail), and persist (--detail --scan) arrays.
Recap
  • mdadm --create builds the array, but persistence is a separate, mandatory step: append mdadm --detail --scan to /etc/mdadm.conf (and rebuild the initramfs) or the array won't return cleanly after a reboot.
  • `/proc/mdstat` is the live health window — [UUU] is healthy, a _ means a degraded member, and the recovery line shows rebuild progress while the array runs unprotected.
  • Pick a RAID level by what you can afford to lose: 0 none, 1/5 one, 6 two, 10 one per mirror pair — capacity and safety always trade against each other.
  • iSCSI splits into target (targetcli: backstore → IQN → LUN → ACL) and initiator (iscsiadm: discovery → login); the initiator IQN must match the target ACL or login is refused.
  • Mount network block storage with `_netdev` so the system waits for the network instead of hanging at boot.
RAID level → redundancy and speed
LevelDisks it can loseUsable capacitySpeedUse it for
0 (stripe)0 — any loss kills it100% (all disks)FastestScratch/temp data you can rebuild
1 (mirror)All but one50% (half)Read-fast, write-normalBoot/system disks; simple safety
5 (single parity)1(N−1)/NGood read, slower writeBalanced general-purpose storage
6 (double parity)2(N−2)/NSlower write than 5Large slow disks; long rebuilds
10 (stripe+mirror)1 per mirror pair50% (half)Fast read and writeDatabases needing speed + safety