Ubuntu Server 16.04+UEFI+GPT+LVMで、ソフトウェアRAID1する

# Ubuntu ServerでソフトウェアRAID1したい

ただ、それだけだった。はずなのに。
調べれば調べるほど、どれが正しい方法か、わからなくなった…(今でもわからない)。
やりたいことや環境に応じた選択肢が用意されている、と前向きに考えてみる。

# やったことまとめ

- Ubuntu 16.04 Serverで、mdadmでなくLVMミラーリングしてみた
- ほぼ普通にインストールした後に、ミラーを作成できて、楽だった
- UEFI環境で、2台目ディスクからも起動できた
    - 起動まわりの設定は、UEFIだからか、少し面倒だった
- 性能未測定

# 手順まとめ

- Ubuntu ServerをLVMでインストール時に、rootのLVを少しだけ小さくする
- GPTのパーティションテーブルを`sgdisk`でコピー
    - 2台目のGUIDは生成し直し
- ESPと/bootパーティションを`dd`でコピー
- `update-grub`で、/bootのgrub設定をアップデート
    - ESP内のgrub設定は手動編集
- `efibootmgr`で、2台目ESPのエントリーを、UEFIのNVRAMに登録
- `lvconvert -m 1`でミラー作成
- swapは2つ作ってミラーせず
- `fstab`で、ESPと/bootとswapのオプションに、`nofail`指定

ここにたどり着くまでが長かった…。
以下はその過程。

# 環境

事前に決めていたのは、Ubuntu Server 16.04と、ソフトウェアRAID1を使うことだけ。
あと、なるべく楽したい…。

# 調査
## RAID1実装とファイルシステム

RAID1するにも、環境がいろいろある。

- mdadm
- LVM
- Btrfs
- ZFS

どちらのディスクでも起動できるように設定すると、問題が起こった時に楽だが、起動に関連する要素もいろいろある。

- BIOS
- UEFI
- MBR
- GPT

通常、起動関連はハードウェアに紐づいて選べないが、今回は仮想マシン上に新規構築するため、何でも選べる。
物理マシンへのインストールも想定し、起動はUEFIで、ファイルシステムはデフォルトのext4。
最近LVMに救われているので、UEFI+GPT+mdadm(+LVM+ext4)と仮決め。

## mdadmとLVM

この2つを同時に使う場合、理論的には以下の選択肢がある。

- mdadmで作成したmdデバイス上でLVMする(LVM on RAID1)
- LVMで作成したLVデバイス上でmdadmする

この選択肢については、[こちら](http://d.hatena.ne.jp/den8/20100727/1280232047)が詳しい。[こちら](https://serverfault.com/questions/217666/what-is-better-lvm-on-raid-or-raid-on-lvm)にも議論がある。

[こちら](https://qiita.com/TsutomuNakamura/items/c9c277049e0032ed6116)では前者、LVMとRAIDに関する[ArchWikiの記事](https://wiki.archlinux.jp/index.php/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2_RAID_%E3%81%A8_LVM)や[Linux RAIDの記事](https://raid.wiki.kernel.org/index.php/Partitioning_RAID_/_LVM_on_RAID)、[Ubuntu Community Helpの記事](https://help.ubuntu.com/community/Installation/RAID1%2BLVM)も前者。
とりあえず、前者のLVM on RAID1を選択。

この場合、RAIDパーティション全体がミラーされることになる。
swapはどうなる?

## RAID1とswap

swapをミラーするかどうかも、議論がある。

- [こちら](https://www.linuxmania.jp/raid_md_grub.html#swap)によると、意味はあまりない、とある
- [こちら](https://askubuntu.com/questions/245976/should-swap-and-raid-1-mix)や[Arch Wiki](https://wiki.archlinux.jp/index.php/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2_RAID_%E3%81%A8_LVM#.E3.82.B9.E3.83.AF.E3.83.83.E3.83.97.E9.A0.98.E5.9F.9F)では、異常発生時のメリットが挙げられている

クリティカルな環境ではないので、swapのミラーは不要としよう。

一方、インストーラーの自動パーティショニングに頼ると、md上にswapが作られ、自動的にミラーされてしまう気がする。
インストール前にmdを作るのだろうから、インストール前に手動パーティショニングすることになる?

## インストールとパーティショニング

[UbuntuインストールガイドのSoftware RAIDの項目](https://help.ubuntu.com/lts/serverguide/advanced-installation.html)に "Select Manual as the partition method" とあるので、インストール時にマニュアルでパーティショニングするものなのだろう。
[こちら](https://qiita.com/tagosaku324/items/553776c50e48131efd19)にmdadmでの詳細手順例があるが、なかなか大変そう…。
今回はUEFIなので、ESPも作成する必要がある。

なるべく楽な方法は?

## 方針変更

Ubuntu16.04のmdadmに、degrade時の起動に問題があり、パッケージを16.10のものに更新する必要があるという[記事](https://askubuntu.com/questions/789953/how-to-enable-degraded-raid1-boot-in-16-04lts)を見つけた。うーむ。

[先ほどのRAID1とLVMの記事](http://d.hatena.ne.jp/den8/20100727/1280232047)では、LVMにミラーリング機能があるとのこと。
こちらのほうが、自動パーティショニングでインストールした後、簡単にミラーを作れそうな気がしてきた。mdadmの問題も回避できるかも?

## LVMのミラーリング機能

[先ほどの記事](http://d.hatena.ne.jp/den8/20100727/1280232047)では、トータル性能や電源断耐性は、mdadmのほうがよさそうだ。

でも、クリティカルな用途でないので、とりあえずLVMでやってみよう。
[こちら](http://kimitakeblog.net/item/688)に実施例があるが、楽そうに見える。(フラグ)
ただ、RAID1を開始する`lvconvert -m1`でエラーが出る、との情報が気になる。
[こちらの記事](https://qiita.com/yuya_presto/items/66e9ac0cb180be3aa84b)の、外部のディスクがいる、というのも気になる。

## LVMのextent不足

試しにVGにsdb3を足して、`lvconvert`してみる。

```shell-session
# lvconvert -m1 /dev/hoge-vg/root /dev/sdb3
  Insufficient free space: 1 extents needed, but only 0 available
```

エラーが出た。文字通り、RAID1化に必要なLVMの空きextentが足らないのだろう。

[こちら](http://www.nslabs.jp/raid-logical-volumes.rhtml)では、ext4とLVを縮めてVGに空きを作っている。(ちなみにXFSはオフラインでも縮められないのか…)
[こちら](http://neet.waterblue.net/2011/03/27/lvm%E3%81%A7%E3%83%9F%E3%83%A9%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0/)によると、`lvconvert`時のオプション`--alloc anywhere`で3台目を不要にできるとある。3台目の代わりに、空きextentを使うということ?

今回は新規インストールなので、パーティショニング時のLVの容量をちょっと小さくすれば、空きextentを使って2台でRAID1にできると予想。
[ArchWikiのRAIDの項目](https://wiki.archlinux.jp/index.php/RAID)でも、RAID1では100MBくらい残しておけば?と書いてある。(今回は仮想化環境なので条件が異なり、意味ないかもだが)
幸い、Ubuntu ServerではLVMでのインストール時にrootのLVのサイズを聞かれるので、0.1GBだけ減らしてみる。[インストールガイドのStep 12.](https://help.ubuntu.com/lts/serverguide/installing-from-cd.html)参照。

## Ubuntuの自動パーティション構成

LVMでインストールした後に、`gdisk`でパーティションを確認すると、

```shell-session:gdiskでpした結果(抜粋)
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1050623   512.0 MiB   EF00
   2         1050624         2050047   488.0 MiB   8300
```

ESPと/bootが別だ。

`mount`でも確認すると、

```shell-session
# mount | grep sd
/dev/sda2 on /boot type ext2 (rw,relatime,block_validity,barrier,user_xattr,acl)
/dev/sda1 on /boot/efi type vfat (rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)
```

ESPはvfat、/bootはext2だ。

これらがsdbにも無いと、2台目で起動できない。
また、これらはLVMの外なので、ミラーされない。
パーティション情報と中身の両方を、手動でコピーする必要がある?

## GPTのコピー

[こちら](https://askubuntu.com/questions/57908/how-can-i-quickly-copy-a-gpt-partition-scheme-from-one-hard-drive-to-another)に、`sgdisk`を使ったパーティションテーブルのコピー例がある。
`sfdisk`の例も見かけるが、`sfdisk`はMBR用の模様。GPT用は`sgdisk`か。[関連記事1](
https://wiki.archlinux.jp/index.php/GUID_Partition_Table#GPT_fdisk) [関連記事2](https://wiki.archlinux.jp/index.php/Fdisk#sfdisk_.E3.82.92.E4.BD.BF.E3.81.86)

```shell-session
# sgdisk /dev/sda -R /dev/sdb
# sgdisk -G /dev/sdb
```

`sgdisk -G`で、GUIDが再生成され、ユニークになる。
そうすると、`efibootmgr -v`で表示されるGUIDも、1台目と2台目で別のものになり、わかりやすい。

## ESPのコピー

[こちら](https://askubuntu.com/questions/660023/how-to-install-ubuntu-14-04-16-04-64-bit-with-a-dual-boot-raid-1-partition-on-an)では、`dd`でESPをコピーしている。やはりESPは手動でコピーする必要があるということか。
上記の通りUbuntuでは/bootはESPと別パーティションなので、/bootもコピーする必要がある。
grubやカーネルのアップデート時に自動でコピーすることもできると思うが、アップデート回数はそんなに多くないだろうから、手動でもいいか。

```shell-session
# dd if=/dev/sda1 of=/dev/sdb1
# dd if=/dev/sda2 of=/dev/sdb2
```

## fstabによるESPと/bootのmount

ESPと/bootは`fstab`で自動的にmountされるが、これだと、1台目か2台目のどちらかしかmountできないことになる。
つまり、1台目と2台目の両方で起動できるようにできない? 詰んだかも…。
そこで、試しに`fstab`からESPと/bootのエントリーを削除してみると、普通に起動できた。
起動時は、UEFIのNVRAMや`grub.cfg`でパーティションなどを指定しているので、`fstab`にエントリーが無くても起動できるのは、理論的には当然か。
`fstab`に記載してmountするのは、起動後にアップデートできるようにするため?

[こちら](https://terrydactyl10.wordpress.com/2015/11/04/centos-7-with-mdadm-software-raid-and-uefi-boot/)の似た例では、`fstab`でUUIDの代わりにPARTUUIDを指定しつつ、2台目を`/boot/esp2`にmountしている。何で?

## UUIDとPARTUUID

fstabに書かれているUUIDは、`gdisk`の`Disk identifier (GUID)`にも`Partition GUID code`にも`Partition unique GUID`にも出て来ない。何を指定しているの? [関連記事](http://d.hatena.ne.jp/incarose86/20150131/1422723062)

[こちら](https://help.ubuntu.com/community/UsingUUID)によると、`blkid`に出てくるUUIDとのこと。そこで`blkid`を実行してみる。

```shell-session
$ blkid | grep sd[ab][12]
/dev/sda1: UUID="0088-9E70" TYPE="vfat" PARTUUID="25a2fe63-0350-442c-a32e-7e393862c81e"
/dev/sda2: UUID="cc044c77-ec4b-4a4f-94db-7718e1afc315" TYPE="ext2" PARTUUID="7b3f10af-13a1-46ad-bb1b-5f8b0bffef9f"
/dev/sdb1: UUID="0088-9E70" TYPE="vfat" PARTUUID="69a02c5e-3042-409a-b323-f570e7c8f1e2"
/dev/sdb2: UUID="cc044c77-ec4b-4a4f-94db-7718e1afc315" TYPE="ext2" PARTUUID="c28968f1-4b66-411d-be4b-d7d91453bdac"
```

sdaとsdbでUUIDがまるっきり同じだ。`sgdisk -G`でGUIDを再生成しているのに…。
つまり、`dd`でコピーしているパーティションの中身に、UUIDが入っているということか。
実際、[こちら](http://pman0214.github.io/blog/debian-efi-raid.html)では、バイナリエディタでESPのUUIDを書き換えている。すごい。

`fstab`でPARTUUIDを指定すれば、UUIDは`dd`のコピーのままにしても、マウント元を一意に指定できるので、1台目と2台目をそれぞれmountできる。
しかし、ESPと/bootのアップデート時に`dd`でコピーするなら、2台目のESPと/bootをmountしておいたところで、unmountしてからコピーすることになる。

そこで、`fstab`では、1台目をmountすること明確にするためにPARTUUIDで指定しつつ、2台目のESPと/bootのエントリーは記載せず、mountしないでおくことにする。
UUIDが重複すると、`/dev/disk/by-uuid/`に片方のディスクしか出て来ないようで、不安だが…。

## grub.cfg

ESPも/bootも`dd`でコピーすると、中にある`grub.cfg`もコピーされるはず。
すると、`grub.conf`の中身によっては、コピーでは2台目で起動できない可能性に気づく。

`/boot/grub/grub.cfg`を確認すると、`hd0`という指定やUUIDが書いてある。

```/boot/grub/grub.cfg
  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2  cc044c77-ec4b-4a4f-94db-7718e1afc315
```

2台目にhd0と書いてあると、1台目が無い時に、2台目で起動できない。
`hd1`に修正しようと思ったが、ご丁寧にファイルの先頭に`DO NOT EDIT THIS FILE`と書かれている。

[こちら](https://askubuntu.com/questions/355727/how-to-install-ubuntu-server-with-uefi-and-raid1-lvm)では`grub-install`で2台目にgrubをインストールしているが、grub自体は`dd`でコピーしているので、[こちら](https://askubuntu.com/questions/660023/how-to-install-ubuntu-14-04-16-04-64-bit-with-a-dual-boot-raid-1-partition-on-an)にあるような`update-grub`による`grub.cfg`の更新で済みそうだ。
`man update-grub`によると、コマンドラインオプションで場所は指定できないので、2台目を`/boot`と`/boot/esp`にmountし直してから、実行する。

```shell-session
# umount /boot/efi
# umount /boot
# mount /dev/sdb2 /boot
# mount /dev/sdb1 /boot/efi
# update-grub
```
## 2つのgrub.cfg

2台目で`update-grub`後、1台目の`/boot/grub/grub.cfg`を比較すると、

```diff
67c67
< set root='hd0,gpt2'
---
> set root='hd1,gpt2'
69c69
<   search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2  cc044c77-ec4b-4a4f-94db-7718e1afc315
---
>   search --no-floppy --fs-uuid --set=root --hint-bios=hd1,gpt2 --hint-efi=hd1,gpt2 --hint-baremetal=ahci1,gpt2  cc044c77-ec4b-4a4f-94db-7718e1afc315
(以下略)
```

`hd0`が`hd1`になっている。いい感じ。
`cc044c77`のUUIDは、/bootを`dd`でコピーしているので、同じままでいいはず。

ただ、よく調べると、ESPにも`/boot/efi/EFI/ubuntu/grub.cfg`というファイルがある。

```/boot/efi/EFI/ubuntu/grub.cfg
search.fs_uuid cc044c77-ec4b-4a4f-94db-7718e1afc315 root hd0,gpt2
set prefix=($root)'/grub'
configfile $prefix/grub.cfg
```

ここにも`hd0`があり、これは/bootを指定しているようだが、`update-grub`でも`hd1`にはならなかった。
詳細未調査だが、2台目のESPの`/boot/efi/EFI/ubuntu/grub.cfg`は、手動で`hd1`に変更。

```shell-session
# sed -i -e "s/hd0/hd1/g" /boot/efi/EFI/ubuntu/grub.cfg
```

## 2つのswap

swapをミラーしないことにしたので、2台目のswapは手動で作る必要がある。
LVMのVGにswapのLVを作ればよいので、これは簡単。

```shell-session
# lvcreate -L 2GiB -n swap_2 hoge-vg /dev/sdb3
# mkswap /dev/hoge-vg/swap_2
```

ただ、2つ作ったswapを`fstab`に書いたとして、1つがダメだったら、起動しなくなってしまうのでは?

### 実験1: `fstab`で指定したLVはあるが、`mkswap`されていない状態で起動

起動時に一瞬エラーが表示されるが、そのまま起動した。
`swapon`で確認しても、片方だけがswapとして利用されていた。

### 実験2: `fstab`で指定したLVが無い状態で起動

90秒待った後、`Welcome to maintenance mode!`で、起動せず。[画面例](https://askubuntu.com/questions/646414/welcome-to-emergency-mode-think-it-is-a-fsck-problem)
ESPも/bootも、パーティションが存在せずにmountできないと、同じように起動しなかった。
片方のディスクが死んで取り外したら、起動しないことになる…。

## `fstab`のオプション

パーティションが無くても起動する`fstab`オプションを探すために、`man mount`を見る。
唯一それっぽかったのが、`nofail`。

> Do not report errors for this device if it does not exist.

`fstab`の`/boot`, `/boot/efi`, `/dev/mapper/hoge--vg-swap_1`, `/dev/mapper/hoge--vg-swap_2`の`<options>`に`nofail`を付けたら、片方のディスクのパーティションが無くても起動した。[関連記事](https://qiita.com/nishemon/items/a967b950c4bbca08fd88)

## 脱線: swap priority

今回の自分の環境では、性能の異なるディスク間でのミラーとなる。
[こちら](https://wiki.archlinux.jp/index.php/%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97#Priority)によると、swapには優先度が付けられる。
`fstab`の編集ついでにやってみよう。

# ようやく手順

## Ubuntu Server 16.04のインストール

省略。
LVMのLVのサイズを、0.1MB小さくしてみた。

## パーティションテーブルのコピー

```shell-session
# sgdisk /dev/sda -R /dev/sdb
# sgdisk -G /dev/sdb
# gdisk /dev/sdb
pとiでパーティション状態とGUIDを確認
# partprobe
```

`partprobe`で、コピー後のパーティションテーブルを、カーネルに読み込ませる。

## ESPと/bootをコピー

```shell-session
# dd if=/dev/sda1 of=/dev/sdb1
# dd if=/dev/sda2 of=/dev/sdb2
```

## 2つ目の`grub.cfg`修正

```shell-session
# umount /boot/efi
# umount /boot
# mount /dev/sdb2 /boot
# mount /dev/sdb1 /boot/efi
# update-grub
# sed -i -e "s/hd0/hd1/g" /boot/efi/EFI/ubuntu/grub.cfg
```

## 2台目のESPをUEFIに登録

```shell-session
# efibootmgr -c -d /dev/sdb -p 1 -l '\EFI\ubuntu\shimx64.efi' -L 'ubuntu2'
(以下抜粋)
BootCurrent: 0005
BootOrder: 0007,0005,0001,0002,0003,0004,0000,0006
Boot0005* ubuntu
Boot0007* ubuntu2
```

次の起動時は、`BootOrder`上、2台目(`0007`)から起動される。

## LVMのPVとVGの準備

```
# pvcreate /dev/sdb3
# pvdisplay
# vgextend hoge-vg /dev/sdb3
# vgdisplay
```

`VG Size`と`Free PE`が増えていることを確認。

## LVをミラー化

```shell-session
# lvconvert -m 1 /dev/hoge-vg/root /dev/sdb3
# lvs -a -o +devices
```

`--alloc anywhere`しなくても、2台でできるようだ。

```shell-session:lvsの結果(抜粋)
  [root_rmeta_0]  hoge-vg ewi-aor---   4.00m                                                     /dev/sda3(50925)
  [root_rmeta_1]  hoge-vg ewi-aor---   4.00m                                                     /dev/sdb3(0)
```

`Cpy%Sync`がじわじわ増えて行く。

## sdbにswapを作成

```shell-session
# lvcreate -l 511 -n swap_2 hoge-vg /dev/sdb3
# lvdisplay
# mkswap /dev/hoge-vg/swap_2
# echo '/dev/mapper/hoge--vg-swap_2 none swap sw 0 0' >> /etc/fstab
# swapon -a
# swapon
```

`lvdisplay`で確認した時の`swap_1`の`Current LE`が、512ではなく511だったので、swap_2は、サイズ指定ではなくextent数指定で`lvcreate`してみた。
`fstab`の`<options>`に`pri=100`などを付けると、優先順位が付き、`swapon`でも確認できる。

## fstabのPARTUUID化とnofail追加

```shell-session
# blkid
(PARTUUIDを確認)
# vi /etc/fstab
(ESPと/bootをPARTUUID指定に置き換え、swapも含め<options>にnofailを追加)
```

## 再起動

```shell-session
# reboot
:(再起動)
# efibootmgr -v
# efibootmgr -o 0005,0007,0001,0002,0003,0004,0000,0006
BootCurrent: 0007
BootOrder: 0005,0007,0001,0002,0003,0004,0000,000
(後略)
```

2台目で起動した。(`BootCurrent: 0007`)
`efibootmgr -o`で起動順を1台目に戻し、さらに再起動。

# 感想

- 結局mdadmと同じくらいの手間になった気がする…(無事フラグ回収)
- LVMはまだ使われそうという[ニュース](https://news.mynavi.jp/article/20170804-a056/)もあるし、勉強になった
- UEFIとRAID1の相性悪し
    - [RAID for the EFI System Partition](https://wiki.debian.org/UEFI#RAID_for_the_EFI_System_Partition)

> This is arguably a mis-design in the UEFI specification

- みなさん…本当にこんなこと…してるんでしょうか…

# 聞こえた囁き

- BIOS…ESPから解放
- mdadm…情報多い
- ハードウェアRAID…お金で解決
- CentOS…インストーラーが楽?
- Btrfs…統一的管理の夢

# 宿題

- grubとカーネルのアップデート時の、ESPと/bootの自動コピー
- /bootのミラーリング
    - [こちら](https://www.ibm.com/developerworks/jp/linux/library/l-grub2/index.html)によると、GRUB2はLVMとRAIDをサポートしている。その他[関連記事](https://unix.stackexchange.com/questions/156719/ext2-filesystem-for-boot-partition)
- 性能測定

# (おまけ)戻す時

```shell-session
# swapoff -a
# vi /etc/fstab
swap_2を削除
# swapon -a
# lvremove /dev/hoge-vg/swap_2
# lvconvert -m 0 /dev/hoge-vg/root
# vgreduce hoge-vg /dev/sdb3
# pvremove /dev/sdb3
# efibootmgr -B -b 7
UEFIのNVRAMから2台目のエントリーを削除
# gdisk /dev/sdb
dで/bootとESPを削除
# partprobe
```

# Ubuntu Server 20.04での確認 (2021年4月追記)
基本的に今までの方法が使えた。
差分は2点。

- /etc/fstabでの/boot/efiのマウントに、デフォルトでは/dev/disk/by-uuid/を使用していた
    - -> /dev/disk/by-partuuid/を使用するように変えて、動作した
- GPTでLVMパーティションのType Codeが8E00ではなく8300になっている([バグ?](https://bugs.launchpad.net/maas/+bug/1682204))
    - -> gdiskを使って手動で直した