Raspberry PiのルートにRAIDボリュームをマウントしてみた

## はじめに

Raspberry Piを24時間稼働のサーバーとして運用するにあたって、頻繁に読み書きがある領域をSDカードに任せるのは耐久性に難ありです。
そこで、以下の作業によってシステムの耐久性を向上させよう思います。

- ソフトウェアRAID(mdadm)によりUSBドライブ2枚でRAID1を組む
- そのRAIDボリュームを起動時にルートディレクトリ`/`にマウントする

### 概要

0. まっさらなRaspberry Piに対する初期設定
1. USBドライブのフォーマット
2. RAID構築
3. SDカードの`/`をRAIDボリュームにコピー
4. RAIDボリュームから起動するための設定

USBドライブはHDD/SDD等々、中のデータがどうなってもよいものをご用意ください。

### 筆者の環境

SDカードにOSイメージを焼き、bootパーテーションに空の`ssh`ファイルを書き込んだだけのまっさらなラズパイにssh接続して設定します。

- Raspberry Pi 4B (4GBモデル), Raspberry Pi OS Buster (kernel release: 5.4.79-v7l+)
- microSDカード (32GB)
- 2.5インチHDD x 2 (どちらも1TB)

Raspberry PiのバスパワーはHDD2台に電源供給するには不十分なので、別途電源付きUSBハブを使っています。
構築するRAIDレベルは1（ミラーリング）です。

## 0. 初期設定

初期起動後の各種設定です。済んでいる方は読み飛ばしてください。

### rootパスワードの設定

```bash
sudo passwd root
```

### `raspi-config` による設定

```bash
sudo raspi-config
```

- "1 System Options" > "S3 Password" > ユーザー"pi"のパスワード設定
- "3 Interface Options" > "P2 SSH" > "Enable"
- "5 Localization Options" > "L2 Timezone" > "Asia/Tokyo"
- "8 Update"

### パッケージ更新などなど

パッケージの更新、OSの更新。

```bash
sudo apt-get update && sudo apt-get upgrade
sudo apt-get dist-upgrade
```

## 1. USBドライブのフォーマット

### ディスク確認

接続したUSBドライブのデバイス名を確認します。

```bash
$ sudo fdisk -l
Disk /dev/mmcblk0: 29.8GiB, ...

Device ...
/dev/mmcblk0p1 ...
/dev/mmcblk0p2 ...


Disk /dev/sda: 931.5GiB, ...
Disk model: ...

Device ...
/dev/sda1 ...


Disk /dev/sdb: 931.5GiB, ...
Disk model: ...

Device ...
/dev/sdb1 ...
```

実際はRAMについても出力されるため、もっと多く情報が出力さます。上から順に、こんな情報が書かれています。

- SDカード: `/dev/mmcblk0`
 - パーテーション1: `/dev/mmcblk0p1`
 - パーテーション2: `/dev/mmcblk0p2`
- USBドライブ1: `/dev/sda`
 - パーテーション1: `/dev/sda1`
- USBドライブ2: `/dev/sdb`
 - パーテーション1: `/dev/sdb1`

### パーテーションの削除と作成

`fdisk`を使って、すべてのUSBドライブを単一のLinux用パーテーションからなるデバイスにします。
今回は、各USBドライブにもともと存在するパーテーション`/dev/sda1`、`/dev/sdb1`を削除して新たにパーテーションを作成します。

```bash
$ sudo fdisk /dev/sda

Command (m for help):
```

1. "p"で現在の状態を表示
2. パーテーションがあれば"d"で削除
3. "n"でパーテーションを作成、（Partition Type, Partition Number, First Sector, Last Sectorは全てデフォルト値）
4. "w"でコマンドの反映 (入力ミスしたら"q"で反映せずに終了)

もう一つのUSBドライブについても同様に設定。

## 2. RAID構築

2つのUSBドライブから1つのRAID1ボリュームを作成します。

### RAIDソフトウェアのインストール

`mdadm`を使用します。

```bash
sudo apt-get install mdadm
```

### RAIDボリュームの構築

今回はRAID1(ミラーリング)ボリュームを`/dev/md0`として、さきほど作成したパーテーション`/dev/sda1`と`/dev/sdb1`を対象に構築します。

```bash
sudo mdadm --create --verbose /dev/md0 --level=1 --raid-devices=2 /dev/sda1 /dev/sdb1
```

確認メッセージに対して"y"を入力すると構築が始まります。
構築後、以下のコマンドで同期の進捗が確認できます。

```bash
$ sudo mdadm --detail /dev/md0
/dev/md0:
...
   Resync Status: 1% complete
```

### RAIDボリュームの登録

このままでも構築したRAIDは有効ですが、デバイスファイル名`/dev/md1`に永続性がありません。
あとで設定するfstabではマウント対象をデバイスファイル名によって指定するため固定したいです。
そこで、`mdadm`の設定ファイルにをRAIDボリュームと名称の対応を登録します。

`mdadm --detail --scan`コマンドにより現在有効なRAIDが全てリストアップされるので、それを設定ファイル`/etc/mdadm/mdadm.conf`に追記します。
これは、以下のコマンドをrootユーザーとして実行することと等価です。

```bash
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
```

### RAIDボリュームのフォーマット

構築したRAIDボリュームをLinux用のファイルシステム"ext4"でフォーマットします。

```bash
sudo mkfs.ext4 /dev/md0
```

## 3. SDカードの`/`をRAIDボリュームにコピー

SDカードにRaspberry Pi OSを焼いた場合は、`/dev/mmcblk0p2`が`/`にあたるデバイスです。

### RAIDボリュームに`/`をコピー

`dd`コマンドにより、RAIDボリュームを`/`の複製に変換します。

```bash
sudo dd if=/dev/mmcblk0p2 of=/dev/md0 bs=32M conv=noerror,sync status=progress
```

ifとofに指定するデバイスを逆にしてしまうとSDカードへのOSイメージ作成からからやり直しになります。注意して実行してください。
"i"fはin、"o"fはoutです。

### ファイルシステムのチェック

```bash
sudo e2fsck -f -y /dev/md0
```

### パーテーションの拡張

このままではコピー元のSDカードと同じサイズのパーテーションなので、USBドライブ本来のサイズまで拡張します。

```bash
sudo resize2fs -p /dev/md0
```

## 4. RAIDボリュームから起動するための設定

RAIDをつかさどるmdadmが`/boot`の外にいるため、このままfstabでRAIDボリュームを`/`にマウントしてもうまくいきません。
そこで、起動時にRAIDが使用できる`initiramfs` (initial ram file system)のイメージを作成します。

### initramfsイメージの作成

```bash
sudo vi /etc/initramfs-tools/modules
```

このファイルの最後に以下の3行を追加します。

```conf
raid1
md_mod
ext4
```

そして、イメージ作成します。実行結果として、生成されたイメージファイル名が返ってきます。

```bash
$ sudo update-initramfs -c -k `uname -r`
update-initramfs: Generating /boot/initrd.img-5.4.79-v7l+
```

### ブートイメージの設定

```bash
sudo vi /boot/config.txt
```

このファイルの最後にさきほど生成したイメージファイルを読み込むよう設定します。

```conf
initramfs initrd.img-5.4.79-v7l+ followkernel
```

### cmdline.txtの設定

起動時にRAIDボリュームを`/`とするよう設定します。

```bash
sudo vi /boot/cmdline.txt
```

```diff
- console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
+ console=serial0,115200 console=tty1 root=/dev/md0 rootfstype=ext4 elevator=deadline fsck.repair=yes rootdelay=10 rootwait
```

変更は2点だけです。

- `root=`以降にRAIDボリュームを指定する
- `rootdelay=10`によりRAIDボリュームがマウントされるまで待つようにする

### fstabの更新

起動時にRAIDボリュームが`/`にマウントされるように設定します。
ここで編集すべきは、SDカード内の`/etc/fstab`ではなく、RAIDボリュームの中にあるものです。

読み書きできるようにするために、一時的にRAIDボリューム`/dev/md0`をどこかへマウントします。今回は`/mnt/md0`ディレクトリを作成してそこへマウントしました。

```bash
sudo mkdir /mnt/md0
sudo mount /dev/md0 /mnt/md0
```

そして、RAIDボリューム内のfstabを書き換えます。ついでに、SDカードを変更できるように`/boot`に対するデバイスをPARTUUIDによる紐づけからデバイスファイル名称に変えます。

```bash
sudo vi /mnt/md0/etc/fstab
```

```diff
- PARTUUID=...     /boot  vfat  defaults          0  2
- PARTUUID=...     /      ext4  defaults,noatime  0  1
+ /dev/mmcblk0p1   /boot  vfat  defaults          0  2
+ /dev/md0         /      ext4  defaults,noatime  0  1
```

### The moment of truth

設定に間違いがなければ、再起動してください。
RAIDボリュームが`/`としてマウントされた状態で起動します。

```bash
sudo reboot
````

最後に`df`コマンドで、期待通りか確認できます。

```bash
$ df -h
Filesystem      Size Used Avail Use% Mounted on
/dev/mmcblk0p1  253M  58M  195M  24% /boot
/dev/md0        917G 1.9G  878G   1% /
```

## さいごに

`/boot`の中身はSDカード内にあるためSDカードなしにこのRaspberry Piは起動しません。
視点を変えると、このSDカードの役割は、起動ディスクのみ言えます。
さらに、このSDカードのbootパーテーションを別のFAT32でフォーマットされたSDカードにコピーすれば、それが起動ディスクになります。

そこで、以下のようにメンテナンスすることでシステムの耐久性が向上すると思います。

- bootパーテーションの中身をバックアップ、定期的に他のSDカードに交換
- それ以外のデータはRAIDでUSBドライブを定期交換

これでうまくいくのかは、運用しつつ見ていきます。

## 先行研究

- [How to boot into RAID array (mdadm) - Raspberry Pi Forums](https://www.raspberrypi.org/forums/viewtopic.php?t=29992&start=25)
- [Raspberry PiをUSB(HDD)で起動させる | 純規の暇人趣味ブログ](https://jyn.jp/raspberrypi-usb-boot/)
- [[RasPi] ルートパーテーションの外部ストレージ化 - my glasses](http://mukachan.hateblo.jp/entry/2018/03/01/000000)