マシンを長期間連続で稼働させていると、ディスクが壊れてデータが失われる可能性があります。ストレージの冗長性を確保する方法として、LinuxのLVMを用いたソフトウェアRAIDが手軽そうだったので手元の仮想環境で試してみました。

テスト環境

  • VirtualBox 7.0.10
  • Windows 11 Home 22H2 (ホストOS)
  • Debian 12 (ゲストOS)

目標とする環境

  • ディスクが1つ壊れてもシステムが稼働し続けるようにしたい
  • ディスク故障後や交換後も正常にブートできるようにしたい

これらを実現すべく、ブートに必要なデータはブート専用のディスクに配置し、I/Oの多いOS本体のデータはRAID1で別のディスクにもミラーリングする構成にしたいと思います。なおブート用ディスクはブート時にしか使用しないため故障しない前提で考えます。

sda
└─sda1      LVM
sdb
└─sdb1      LVM
sdc
├─sdc1      /boot/efi
└─sdc2      /boot

セットアップ

VMのセットアップ

VirtualBoxでDebianのVMを作成し、ストレージに10GBのディスクを割り当てます。EFIを使用したいので Hardware 画面にて Enable EFI (special OSes only) にチェックを入れておきます。またLVMを使用するので、Debianのインストール画面の Partition disks では use entire disk and set up LVM を選択します。

VirtualBoxのVMの 設定 -> ストレージ タブで、ミラー用に10GBのディスクとブート用に5GBのディスクをそれぞれ作成して割り当てます。

起動後の構成はこんな感じです。

root@debian:~# lsblk
NAME                  MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda                     8:0    0   10G  0 disk
├─sda1                  8:1    0  512M  0 part /boot/efi
├─sda2                  8:2    0  488M  0 part /boot
└─sda3                  8:3    0    9G  0 part
  ├─debian--vg-root   254:0    0  8.1G  0 lvm  /
  └─debian--vg-swap_1 254:1    0  980M  0 lvm  [SWAP]
sdb                     8:16   0   10G  0 disk
sdc                     8:32   0    5G  0 disk
sr0                    11:0    1 1024M  0 rom

スワップパーティションの削除

スワップパーティションは使わない予定なので削除します。fstabのマウント設定もコメントアウトしておきます。

root@debian:~# swapoff -a

root@debian:~# lvremove debian-vg/swap_1
Do you really want to remove active logical volume debian-vg/swap_1? [y/n]: y
  Logical volume "swap_1" successfully removed.

root@debian:~# vi /etc/fstab
#/dev/mapper/debian--vg-swap_1 none            swap    sw              0       0

ミラー用ディスクの設定

LVM用のパーティションを作成します。

root@debian:~# fdisk /dev/sdb

Welcome to fdisk (util-linux 2.38.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS (MBR) disklabel with disk identifier 0xe2fbe83b.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p):
Partition number (1-4, default 1):
First sector (2048-20971519, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-20971519, default 20971519):

Created a new partition 1 of type 'Linux' and of size 10 GiB.

Command (m for help): t
Selected partition 1
Hex code or alias (type L to list all): 8e
Changed type of partition 'Linux' to 'Linux LVM'.

Command (m for help): p
Disk /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xe2fbe83b

Device     Boot Start      End  Sectors Size Id Type
/dev/sdb1        2048 20971519 20969472  10G 8e Linux LVM

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

PVを作成し、VGに追加します。

root@debian:~# pvcreate /dev/sdb1
  Physical volume "/dev/sdb1" successfully created.

root@debian:~# vgextend debian-vg /dev/sdb1
  Volume group "debian-vg" successfully extended

root@debian:~# pvs
  PV         VG        Fmt  Attr PSize   PFree
  /dev/sda3  debian-vg lvm2 a--   <9.02g      0
  /dev/sdb1  debian-vg lvm2 a--  <10.00g <10.00g

ブート用ディスクの設定

EFIシステムパーティションを作りたいのでディスクのパーティションテーブルをGPT形式にします。

root@debian:~# gdisk /dev/sdc
GPT fdisk (gdisk) version 1.0.9

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries in memory.

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sdc.
The operation has completed successfully.

/boot/efi/boot 用にそれぞれパーティションを作成します。

root@debian:~# fdisk /dev/sdc

Welcome to fdisk (util-linux 2.38.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): n
Partition number (1-128, default 1):
First sector (34-10485726, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-10485726, default 10483711): +200M

Created a new partition 1 of type 'Linux filesystem' and of size 200 MiB.

Command (m for help): t
Selected partition 1
Partition type or alias (type L to list all): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.

Command (m for help): n
Partition number (2-128, default 2):
First sector (411648-10485726, default 411648):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (411648-10485726, default 10483711): +500M

Created a new partition 2 of type 'Linux filesystem' and of size 500 MiB.

Command (m for help): p
Disk /dev/sdc: 5 GiB, 5368709120 bytes, 10485760 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 0557C8E1-7528-4658-B2FC-14464F190345

Device      Start     End Sectors  Size Type
/dev/sdc1    2048  411647  409600  200M EFI System
/dev/sdc2  411648 1435647 1024000  500M Linux filesystem

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

それぞれのパーティションをフォーマットします。

root@debian:~# mkfs.vfat /dev/sdc1
mkfs.fat 4.2 (2021-01-31)

root@debian:~# mkfs.ext2 /dev/sdc2
mke2fs 1.47.0 (5-Feb-2023)
Creating filesystem with 512000 1k blocks and 128016 inodes
Filesystem UUID: 61901504-35b9-4d88-bf4d-95142719ec91
Superblock backups stored on blocks:
        8193, 24577, 40961, 57345, 73729, 204801, 221185, 401409

Allocating group tables: done
Writing inode tables: done
Writing superblocks and filesystem accounting information: done

/dev/sdc1 にGRUBをインストールします。

root@debian:~# umount /boot/efi

root@debian:~# mount /dev/sdc1 /boot/efi

root@debian:~# grub-install --efi-directory=/boot/efi
Installing for x86_64-efi platform.
Installation finished. No error reported.

root@debian:~# update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.1.0-12-amd64
Found initrd image: /boot/initrd.img-6.1.0-12-amd64
Found linux image: /boot/vmlinuz-6.1.0-10-amd64
Found initrd image: /boot/initrd.img-6.1.0-10-amd64
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
done

/dev/sdc2 に現在の /boot 配下の内容をコピーします。

root@debian:~# mkdir /mnt/boot

root@debian:~# mount /dev/sdc2 /mnt/boot

root@debian:~# cp -r /boot/* /mnt/boot

これでブート用ディスクのセットアップができました。次回からこのディスクでブートできるようにfstabのUUIDを修正します。

root@debian:~# blkid /dev/sdc1
/dev/sdc1: SEC_TYPE="msdos" UUID="E559-D6C3" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="6a744d6a-90ce-4742-baca-a9f134f14247"

root@debian:~# blkid /dev/sdc2
/dev/sdc2: UUID="61901504-35b9-4d88-bf4d-95142719ec91" BLOCK_SIZE="1024" TYPE="ext2" PARTUUID="ccee7610-5edc-854c-9fef-46870659b11d"

root@debian:~# vi /etc/fstab
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# systemd generates mount units based on this file, see systemd.mount(5).
# Please run 'systemctl daemon-reload' after making changes here.
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
/dev/mapper/debian--vg-root /               ext4    errors=remount-ro 0       1
# /boot was on /dev/sdc2 during installation
UUID=61901504-35b9-4d88-bf4d-95142719ec91 /boot           ext2    defaults        0       2
# /boot/efi was on /dev/sdc1 during installation
UUID=E559-D6C3  /boot/efi       vfat    umask=0077      0       1
#/dev/mapper/debian--vg-swap_1 none            swap    sw              0       0
/dev/sr0        /media/cdrom0   udf,iso9660 user,noauto     0       0

一度VMを再起動してみて、正常に起動するか確認します。

root@debian:~# reboot

RAIDのメタデータ用の領域を確保

RAID1のボリュームを作成するに当たって、イメージが配置されるPVにはメタデータボリュームが作成されるため、その分の空き領域を確保しておく必要があります。今回は /dev/sda3 の使用率が100%なので、LVを縮小して空き領域を作ります。

root@debian:~# pvs
  PV         VG        Fmt  Attr PSize   PFree
  /dev/sda3  debian-vg lvm2 a--  <9.00g    0

# 1エクステント分縮小
root@debian:~# lvreduce --resizefs --extents -1 debian-vg/root
resize2fs 1.47.0 (5-Feb-2023)
Filesystem at /dev/mapper/debian--vg-root is mounted on /; on-line resizing required
old_desc_blocks = 2, new_desc_blocks = 2
The filesystem on /dev/mapper/debian--vg-root is now 2619392 (4k) blocks long.

  Size of logical volume debian-vg/root changed from <9.00 GiB (2304 extents) to 8.99 GiB (2303 extents).
  Logical volume debian-vg/root successfully resized.

root@debian:~# pvs
  PV         VG        Fmt  Attr PSize   PFree
  /dev/sda3  debian-vg lvm2 a--  <9.00g 4.00m

RAID1でミラーリング

-m1 でミラーの数を1に指定してRAID1ボリュームを作成します。

root@debian:~# lvconvert --type raid1 -m1 debian-vg/root
Are you sure you want to convert linear LV debian-vg/root to raid1 with 2 images enhancing resilience? [y/n]: y
  Logical volume debian-vg/root successfully converted.

root@debian:~# lvs -a -o name,copy_percent,devices debian-vg
  LV              Cpy%Sync Devices
  root            38.19    root_rimage_0(0),root_rimage_1(0)
  [root_rimage_0]          /dev/sda3(0)
  [root_rimage_1]          /dev/sdb1(1)
  [root_rmeta_0]           /dev/sda3(2303)
  [root_rmeta_1]           /dev/sdb1(0)

これで目標としていた構成の完成です。

root@debian:~# lsblk
NAME                         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda                            8:0    0   10G  0 disk
├─sda1                         8:1    0  512M  0 part
├─sda2                         8:2    0  488M  0 part
└─sda3                         8:3    0    9G  0 part
  ├─debian--vg-root_rmeta_0  254:1    0    4M  0 lvm
  │ └─debian--vg-root        254:0    0    9G  0 lvm  /
  └─debian--vg-root_rimage_0 254:2    0    9G  0 lvm
    └─debian--vg-root        254:0    0    9G  0 lvm  /
sdb                            8:16   0   10G  0 disk
└─sdb1                         8:17   0   10G  0 part
  ├─debian--vg-root_rmeta_1  254:3    0    4M  0 lvm
  │ └─debian--vg-root        254:0    0    9G  0 lvm  /
  └─debian--vg-root_rimage_1 254:4    0    9G  0 lvm
    └─debian--vg-root        254:0    0    9G  0 lvm  /
sdc                            8:32   0    5G  0 disk
├─sdc1                         8:33   0  200M  0 part /boot/efi
└─sdc2                         8:34   0  500M  0 part /boot
sr0                           11:0    1 1024M  0 rom

テスト

実際にディスクを取り外し・交換したいケースを想定して色々いじってみます。

ディスクが故障しそうなとき

完全には壊れていないが、S.M.A.R.T.などの出力から故障が近いと考えられる場合です。RAID1ボリュームをリニアボリュームに変換してもう片方のPVに残し、対象のPVを無効化します。ここでは /dev/sda を交換したい対象とします。

root@debian:~# lvconvert -m0 debian-vg/root /dev/sda3
Are you sure you want to convert raid1 LV debian-vg/root to type linear losing all resilience? [y/n]: y
  Logical volume debian-vg/root successfully converted.

root@debian:~# lvs -a -o name,copy_percent,devices debian-vg
  LV   Cpy%Sync Devices
  root          /dev/sdb1(1)

root@debian:~# vgreduce debian-vg /dev/sda3
  Removed "/dev/sda3" from volume group "debian-vg"

root@debian:~# pvremove /dev/sda3
  Labels on physical volume "/dev/sda3" successfully wiped.

これでディスクを取り外してもシステムは正常に動作します。

また、ミラーに使用していない別のPVがある場合はすぐに置き換えることも可能です。

root@debian:~# lvconvert --replace /dev/sda1 debian-vg/root /dev/sdd1

root@debian:~# lvs -a -o name,copy_percent,devices debian-vg
LV              Cpy%Sync Devices
  root            48.54    root_rimage_0(0),root_rimage_1(0)
  [root_rimage_0]          /dev/sdb1(1)
  [root_rimage_1]          /dev/sdd1(1)
  [root_rmeta_0]           /dev/sdb1(0)
  [root_rmeta_1]           /dev/sdd1(0)

ディスクが故障したとき

完全に壊れてディスクとして機能しなくなった場合です。実際にシステム稼働中にディスクを取り外してテストしてみます。VirtualBoxでVMが電源オフの状態で、VMの 設定 -> ストレージ タブにてディスクを選択すると ホットプラグ可能 という欄があるのでこれにチェックをいれます。

故障する想定のディスク (/dev/sda) をホットプラグ可能にして、システム稼働中にこのディスクを取り外してみます。

root@debian:~# lvs -a -o name,copy_percent,devices debian-vg
  WARNING: Couldn't find device with uuid bXBHE0-PzXh-gv9Z-Rdlm-K3pQ-bdW0-vcQujE.
  WARNING: VG debian-vg is missing PV bXBHE0-PzXh-gv9Z-Rdlm-K3pQ-bdW0-vcQujE (last written to /dev/sda3).
  WARNING: Couldn't find all devices for LV debian-vg/root_rmeta_1 while checking used and assumed devices.
  WARNING: Couldn't find all devices for LV debian-vg/root_rimage_1 while checking used and assumed devices.
  LV              Cpy%Sync Devices
  root            0.00     root_rimage_0(0),root_rimage_1(0)
  [root_rimage_0]          /dev/sdb1(1)
  [root_rimage_1]          [unknown](1)
  [root_rmeta_0]           /dev/sdb1(0)
  [root_rmeta_1]           [unknown](0)

PVが認識不可能となってしまったのでVGから取り除きます。

root@debian:~# vgreduce --removemissing debian-vg --force
  WARNING: Couldn't find device with uuid bXBHE0-PzXh-gv9Z-Rdlm-K3pQ-bdW0-vcQujE.
  WARNING: VG debian-vg is missing PV bXBHE0-PzXh-gv9Z-Rdlm-K3pQ-bdW0-vcQujE (last written to /dev/sda3).
  WARNING: Couldn't find device with uuid bXBHE0-PzXh-gv9Z-Rdlm-K3pQ-bdW0-vcQujE.
  WARNING: Couldn't find device with uuid bXBHE0-PzXh-gv9Z-Rdlm-K3pQ-bdW0-vcQujE.
  Wrote out consistent volume group debian-vg.

root@debian:~# lvs -a -o name,copy_percent,devices debian-vg
  LV              Cpy%Sync Devices
  root            100.00   root_rimage_0(0),root_rimage_1(0)
  [root_rimage_0]          /dev/sdb1(1)
  [root_rimage_1]
  [root_rmeta_0]           /dev/sdb1(0)
  [root_rmeta_1]

この後、前のケースと同様にリニアボリュームに変換することもできますが、新しいディスクに交換してRAID1の状態を復旧させることも可能です。

# パーティションを作成
root@debian:~# fdisk /dev/sda

root@debian:~# pvcreate /dev/sda1
  Physical volume "/dev/sda1" successfully created.

root@debian:~# vgextend debian-vg /dev/sda1
  Volume group "debian-vg" successfully extended

root@debian:~# lvconvert --repair debian-vg/root
Attempt to replace failed RAID images (requires full device resync)? [y/n]: y
  Faulty devices in debian-vg/root successfully replaced.

root@debian:~# lvs -a -o name,copy_percent,devices debian-vg
  LV              Cpy%Sync Devices
  root            19.10    root_rimage_0(0),root_rimage_1(0)
  [root_rimage_0]          /dev/sdb1(1)
  [root_rimage_1]          /dev/sda1(1)
  [root_rmeta_0]           /dev/sdb1(0)
  [root_rmeta_1]           /dev/sda1(0)

電源が落ちてディスクが故障したとき

電源オフの状態からミラーに用いているディスクが認識しない状態で起動しようとすると、LVMの中にあるルートファイルシステムをマウントできず、initramfsのシェルが起動します。この場合はシェルで手動でLVをアクティベートしてOSを起動する必要があります。

Gave up waiting for suspend/resume device
Gave up waiting for root file system device.  Common problems:
 - Boot args (cat /proc/cmdline)
   - Check rootdelay= (did the system wait long enough?)
 - Missing modules (cat /proc/modules: ls /dev)
ALERT! /dev/mapper/debian--vg-root does not exist.  Dropping to a shell!

BusyBox v1.35.0 (Debian 1:1.35.0-4+b3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

(initramfs) vgchange -ay
  WARNING: Couldn't find device with uuid 3RqGP2-2w3s-BIv-7UMy-7v2j-FmE9k1.
  WARNING: VG debian-vg is missing PV 3RqGP2-2w3s-BIv-7UMy-7v2j-FmE9k1 (last written to /dev/sda1).
  /sbin/dmeventd: stat failed: No such file or directory
  WARNING: Failed to monitor debian-vg/root.
  1 logical volume(s) in volume group "debian-vg" now active
(initramfs) exit

まとめ

ディスクの故障に強く、復旧も簡単な環境がLVMだけで作れてしまうのは凄いですね。今回は仮想環境での実験となりましたが、近いうちに自宅のマシンにもセットアップしていきたいと思います。

参考