【root on ZFS】Ubuntu 18.04.2 LTSをLet's noteにインストールする

# この記事は何か
Ubuntu 18.04.2のrootをZFS+LUKS上に構築した実録である。

## ZFSに至るまで
個人的に最近Linux熱が高まっているのだが、筆者の萌えポイントはZFSである。Solarisに実装された時には非常に興奮したことを覚えているが、当時悩んだ末に、グラフィック性能が必要だったため結局SUNではなくSilicon GraphicsのIndigoを購入した。

そんなわけでZFSを利用する機会は失ったのだが、つい最近、LinuxのカーネルモジュールとしてZFSが使えるようになっていることを知った。

## 参考にした記事
この記事は以下のドキュメントを参考に筆者が実際にノートPCにインストールした実録記事である。タイトルにあるとおりroot、すなわちシステム全体をZFSに乗せることを目的としている。
またLUKS（Linux Unified Key Setup）を組み合わせてファイルシステムを暗号化している。

* [Ubuntu 18.04 Root on ZFS](https://github.com/zfsonlinux/zfs/wiki/Ubuntu-18.04-Root-on-ZFS)

上記記事は英語である上に非暗号化、暗号化の両方について説明しているので、ストレートに暗号化ZFSのHOWTOが、それも日本語で必要な人に参考としてもらえればありがたい。

# 環境及び前提条件
この記事は全てのPCへのインストール方法を記述したものでは無い。筆者の環境及びインストール要件、前提とした条件を整理する。

## インストール環境
インストールした環境は以下のようなものである。

|Item           |Configuration                      |
|:--------------|:----------------------------------|
|PC             |Let's note CF-SZ6                  |
|CPU            |Intel Core™ i5-7300U vPro™ 2.60GHz |
|Memory         |8GB LPDDR3 SDRAM                   |
|Disk           |256GB SSD                          |
|System Software|UEFI                               |
|OS             |Ubuntu 18.04.2 LTS Desktop         |
|Encryption     |LUKS                               |

## インストールの前提条件
* ディスク（SSD）全体をZFSでフォーマットする。デュアルブートは一切考慮していない。
* ディスクは1台でミラーリングやRAIDZは考慮していない。
* 起動OSはUbuntuのDesktop版を用いる。Server版ではないので注意のこと。

## 暗号化について
オリジナルのZFSはそれ自体に暗号化機能を含んでいるが、Linux版はまだ暗号化機能は実装途中で正式版としてはリリースされていない。
このため本記事ではLinuxで広く用いられているLUKSを利用してディスク全体を暗号化した。このチュートリアルでは、ブートローダー、カーネル、initrd以外の全て、すなわちOS、スワップ、ホームディレクトリなど全てを暗号化する。
なおLUKSでは起動時に復号のためのパスワードを求められるため、コンソールを操作できないリモート環境では利用できない。

# インストール手順

## インストール環境の準備
1.1. UbuntuのLiveイメージからブートする。筆者はUSBメモリにインストールしたイメージから起動した。メニューが現れたら**インストールではなくTry Ubuntuを選択**する。デスクトップが起動したらCtrl-Alt-Tを押下してターミナルを開く。

1.2. リポジトリを設定する。

```
$ sudo apt-add-repository universe
$ sudo apt update
```

1.3. 他の端末からインストール操作を行うためSSHのインストールを行う。

```
$ passwd
$ sudo apt install --yes openssh-server
```
コンソールで```# ip a | grep inet```としてIPアドレスを調べ、他の端末から```# ssh ubuntu@IPADDRESS```で接続する。

1.4. ルートになってLive環境にZFS関連のツールのインストールを行う。

```
$ sudo -i
# apt install --yes debootstrap gdisk zfs-initramfs
```
## ディスクのフォーマット

2.1. ディスクが再利用の場合には既存のパーティションを削除しておく。

```
# sgdisk --zap-all /dev/sda
```

2.2. ディスクのUUIDを調べ、環境変数にセットする。

```
# ls -la /dev/disk/by-id | grep sda
# export DISK=ata-SAMSUNG_MZNLN256HMHQ-XXXXXX
```
2.3. パーティションを切る。

```
# sgdisk     -n2:1M:+512M   -t2:EF00 /dev/disk/by-id/$DISK
# sgdisk     -n3:0:+512M    -t3:BF01 /dev/disk/by-id/$DISK
# sgdisk     -n4:0:0        -t4:8300 /dev/disk/by-id/$DISK
```

2.4. boot用のプールを作成する。

```
# zpool create -f -o ashift=12 -d \
       -o feature@async_destroy=enabled \
       -o feature@bookmarks=enabled \
       -o feature@embedded_data=enabled \
       -o feature@empty_bpobj=enabled \
       -o feature@enabled_txg=enabled \
       -o feature@extensible_dataset=enabled \
       -o feature@filesystem_limits=enabled \
       -o feature@hole_birth=enabled \
       -o feature@large_blocks=enabled \
       -o feature@lz4_compress=enabled \
       -o feature@spacemap_histogram=enabled \
       -o feature@userobj_accounting=enabled \
       -O acltype=posixacl -O canmount=off -O compression=lz4 -O devices=off \
       -O normalization=formD -O relatime=on -O xattr=sa \
       -O mountpoint=/ -R /mnt \
       bpool /dev/disk/by-id/$DISK-part3
```

2.5. root用のプールを作成する。

```
# cryptsetup luksFormat -c aes-xts-plain64 -s 256 -h sha256 \
       /dev/disk/by-id/$DISK-part4
# cryptsetup luksOpen /dev/disk/by-id/$DISK-part4 luks1
# zpool create -o ashift=12 \
       -O acltype=posixacl -O canmount=off -O compression=lz4 \
       -O dnodesize=auto -O normalization=formD -O relatime=on -O xattr=sa \
       -O mountpoint=/ -R /mnt \
       rpool /dev/mapper/luks1
```

## システムのインストール

3.1. コンテナとなるデータセットを作成する。

```
# zfs create -o canmount=off -o mountpoint=none rpool/ROOT
# zfs create -o canmount=off -o mountpoint=none bpool/BOOT
```
3.2. ルートとブートファイルシステムのためのデータセットを作成する。

```
# zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/ubuntu
# zfs mount rpool/ROOT/ubuntu

# zfs create -o canmount=noauto -o mountpoint=/boot bpool/BOOT/ubuntu
# zfs mount bpool/BOOT/ubuntu
```

3.3. 各ディレクトリ用のデータセットを作成する。

```
# zfs create                                 rpool/home
# zfs create -o mountpoint=/root             rpool/home/root
# zfs create -o canmount=off                 rpool/var
# zfs create -o canmount=off                 rpool/var/lib
# zfs create                                 rpool/var/log
# zfs create                                 rpool/var/spool
```
スナップショット作成から除外するデータセットの作成。

```
# zfs create -o com.sun:auto-snapshot=false  rpool/var/cache
# zfs create -o com.sun:auto-snapshot=false  rpool/var/tmp
# chmod 1777 /mnt/var/tmp
```

3.4. 最小システムをインストールする。

```
# debootstrap bionic /mnt
# zfs set devices=off rpool
```
## システムの設定

4.1. ホストネームを設定する。HOSTNAME、FQDNはそれぞれホストネーム、ドメインネームとする。

```
# echo $HOSTNAME > /mnt/etc/hostname
# echo "127.0.1.1 $FQDN $HOSTNAME" >> /mnt/etc/hosts
```

4.2. ネットワークインタフェースの設定を行う。```# ip a```としてインタフェース名を調べる。以下、INTERFACEは調べたインタフェース名とする。

```
# nano /etc/netplan/01-netcfg.yaml
network:
  version: 2
  ethernets:
    $INTERFACE:
      dhcp4: true
```

4.3. パッケージ取得のソースリストを設定する。

```
# nano /mnt/etc/apt/sources.list
deb http://archive.ubuntu.com/ubuntu bionic main universe
deb-src http://archive.ubuntu.com/ubuntu bionic main universe

deb http://security.ubuntu.com/ubuntu bionic-security main universe
deb-src http://security.ubuntu.com/ubuntu bionic-security main universe

deb http://archive.ubuntu.com/ubuntu bionic-updates main universe
deb-src http://archive.ubuntu.com/ubuntu bionic-updates main universe
```

4.4. ライブ環境から新しいシステムの環境へ移行する。

```
# mount --rbind /dev  /mnt/dev
# mount --rbind /proc /mnt/proc
# mount --rbind /sys  /mnt/sys
# chroot /mnt /bin/bash --login
```

4.5. 基本的な環境を設定する。

```
# ln -s /proc/self/mounts /etc/mtab
# apt update

# dpkg-reconfigure locales
# dpkg-reconfigure tzdata
```
日本語ロケールを選択するにしても、**必ず```en_US.UTF-8```も選択**する。
viは辛いのでnanoをインストールしておいた。

```
# apt install --yes nano
```

4.6. chrootした新しい環境にZFS関連のツールをインストールする。

```
# apt install --yes --no-install-recommends linux-image-generic
# apt install --yes zfs-initramfs
```

4.7. LUKSのセットアップを行う。

```
# apt install --yes cryptsetup
# echo luks1 UUID=$(blkid -s UUID -o value \
      /dev/disk/by-id/$DISK-part4) none \
      luks,discard,initramfs > /etc/crypttab
```

4.8. ブートローダーをインストールする。

```
# apt install dosfstools
# mkdosfs -F 32 -s 1 -n EFI /dev/disk/by-id/$DISK-part2
# mkdir /boot/efi
# echo PARTUUID=$(blkid -s PARTUUID -o value \
      /dev/disk/by-id/$DISK-part2) \
      /boot/efi vfat nofail,x-systemd.device-timeout=1 0 1 >> /etc/fstab
# mount /boot/efi
# apt install --yes grub-efi-amd64-signed shim-signed
```

4.9. ルートパスワードを設定する。

```
passwd
```

4.10. bpoolが常に読み込まれることを確実にする。

```
# cat >> /etc/systemd/system/zfs-import-bpool.service << EOF
    [Unit]
    DefaultDependencies=no
    Before=zfs-import-scan.service
    Before=zfs-import-cache.service
    
    [Service]
    Type=oneshot
    RemainAfterExit=yes
    ExecStart=/sbin/zpool import -N -o cachefile=none bpool
    
    [Install]
    WantedBy=zfs-import.target
EOF
    # systemctl enable zfs-import-bpool.service
```
4.11. tmpfs（RAMファイルシステム）を/tmpにマウントする。

```
# cp /usr/share/systemd/tmp.mount /etc/systemd/system/
# systemctl enable tmp.mount
```

## ブートローダーのインストール

5.1. ZFSがブートシステムとして認識されていることを確認する。

```
# grub-probe /boot
zfs
```

5.2. initrdファイルをリフレッシュする。

```
# update-initramfs -u -k all
```
このときWARNINGがでるが、気にしなくて良い。

5.3. デバッグしやすくするためにGRUBの設定を変更する。

```
Set: GRUB_CMDLINE_LINUX="root=ZFS=rpool/ROOT/ubuntu"
Comment out: GRUB_TIMEOUT_STYLE=hidden
Set: GRUB_TIMEOUT=5
Below GRUB_TIMEOUT, add: GRUB_RECORDFAIL_TIMEOUT=5
Remove quiet and splash from: GRUB_CMDLINE_LINUX_DEFAULT
Uncomment: GRUB_TERMINAL=console
```

5.4. GRUBの設定をアップデートする。

```
# update-grub
```

5.5. GRUBをインストールする。

```
# grub-install --target=x86_64-efi --efi-directory=/boot/efi \
      --bootloader-id=ubuntu --recheck --no-floppy
```

5.6. ZFSモジュールがインストールされていることを確認する。

```
ls /boot/grub/*/zfs.mod
```

5.7. ファイルシステムのマウント順序を修正する。

```
# umount /boot/efi
# zfs set mountpoint=legacy bpool/BOOT/ubuntu
# echo bpool/BOOT/ubuntu /boot zfs \
       nodev,relatime,x-systemd.requires=zfs-import-bpool.service 0 0 >> /etc/fstab
# zfs set mountpoint=legacy rpool/var/log
# echo rpool/var/log /var/log zfs nodev,relatime 0 0 >> /etc/fstab
# zfs set mountpoint=legacy rpool/var/spool
# echo rpool/var/spool /var/spool zfs nodev,relatime 0 0 >> /etc/fstab
# zfs set mountpoint=legacy rpool/var/tmp
# echo rpool/tmp /tmp zfs nodev,relatime 0 0 >> /etc/fstab
```

## 最初の起動

6.1. これまでの状態のスナップショットを取っておく。

```
# zfs snapshot bpool/BOOT/ubuntu@install
# zfs snapshot rpool/ROOT/ubuntu@install
```

6.2. chroot環境から抜ける。

```
# exit
```

6.3. 全てのファイルシステムをアンマウントする。

```
# mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
# zpool export -a
```

6.4. リブート！

```
# reboot
A dependency job for reboot.target failed. See 'journalctl -xe' for details.
```

ところが筆者の環境では上のようなメッセージが表示されリブートが止まってしまった。```journalctl -xe```で詳細を見てもよくわからなかったので、電源長押しで強制的にシャットダウンを行ったが何事もなくブートしてくれた。

6.5. LUKSのパスワードを入力して起動後、rootでログインする。

6.6. ユーザーアカウントを作成する。$YOURUSERNAMEはアカウント名である。

```
# zfs create rpool/home/$YOURUSERNAME
# adduser $YOURUSERNAME
# cp -a /etc/skel/.[!.]* /home/$YOURUSERNAME
# chown -R $YOURUSERNAME:$YOURUSERNAME /home/$YOURUSERNAME
```

6.7. アカウントを管理者グループに追加する。グループはお好みで。

```
# usermod -a -G adm,cdrom,dip,lpadmin,plugdev,sambashare,sudo YOURUSERNAME
```

## スワップの設定

7.1. スワップのためのデータセットを作成する。

```
# zfs create -V 4G -b $(getconf PAGESIZE) -o compression=zle \
      -o logbias=throughput -o sync=always \
      -o primarycache=metadata -o secondarycache=none \
      -o com.sun:auto-snapshot=false rpool/swap
```

7.2. スワップを設定する。

```
# mkswap -f /dev/zvol/rpool/swap
# echo /dev/zvol/rpool/swap none swap defaults 0 0 >> /etc/fstab
# echo RESUME=none > /etc/initramfs-tools/conf.d/resume
```

7.3. スワップを有効化する。

```
# swapon -av
```

7.4. 設定したZFSはファイルシステムそのものが圧縮を行っているため、logrotateが圧縮を行わないように設定する。

```
# for file in /etc/logrotate.d/* ; do
    if grep -Eq "(^|[^#y])compress" "$file" ; then
        sed -i -r "s/(^|[^#y])(compress)/\1#\2/" "$file"
    fi
done
```

7.5. リブートする。

```
# reboot
```

## 最後のクリーンアップ

8.1. インストール時に作成したスナップショットを削除する。

```
$ sudo zfs destroy bpool/BOOT/ubuntu@install
$ sudo zfs destroy rpool/ROOT/ubuntu@install
```

8.2. rootのパスワードを無効化する。

```
sudo usermod -p '*' root
```

8.3. GRUBのグラフィカルインタフェースを有効にする。

```
$ sudo nano /etc/default/grub
Uncomment: GRUB_TIMEOUT_STYLE=hidden
Add quiet and splash to: GRUB_CMDLINE_LINUX_DEFAULT
Comment out: GRUB_TERMINAL=console
Save and quit.

$ sudo update-grub
```

# おわりに

とても長い道のりだったがこれで最小構成のubuntu on ZFSがインストールできたことになる。あとはサーバー系のソフトウェアを入れるなり、デスクトップ環境を構築するなり、各自でカスタマイズして欲しい。