Root on ZFS構築手順

基本的には [Ubuntu 16.04 Root on ZFS](https://github.com/zfsonlinux/zfs/wiki/Ubuntu-16.04-Root-on-ZFS) の通りに実施した。
ZFSの構造や各項目の意味については下記が参考になった。

* [ZFS - ArchWiki](https://wiki.archlinuxjp.org/index.php/ZFS)
* [Oracle Solaris 11 ZFSを使ってみよう (概要・設計ガイド) (PDF)](https://www.fujitsu.com/jp/documents/products/computing/servers/unix/sparc/technical/document/00_ZFS.pdf)

## 環境

構築したマシン環境は個人向けPCベースのスペック。

項目 | 値
--- | ---
M/B | ASRock H97 Pro4
CPU | Core i7 4790K
MEM | 32GB
SSD | Sandisk SDSSDXPS-480G-J25

## 手順詳細

Live CD環境とホストOS共にopenssh-serverをインストールしてリモートから実行した。

### ZFSパーティションの作成

ホストOSをインストールするディスクをパーティショニングする。

```bash
# sgdisk -n3:1M:+512M -t3:EF00 /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161857400901
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.

# sgdisk -n9:-8M:0 -t9:BF07 /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161857400901
Information: Moved requested sector from 937686670 to 937684992 in
order to align on 2048-sector boundaries.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.

# sgdisk -n1:0:0 -t1:BF01 /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161857400901
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.
```

成功していればこんな感じになる。

```bash
# fdisk /dev/sda

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


Command (m for help): p
Disk /dev/sda: 447.1 GiB, 480103981056 bytes, 937703088 sectors
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: 109D5615-3B2D-4B68-8409-1F4D140C1B5F

Device         Start       End   Sectors   Size Type
/dev/sda1    1050624 937684991 936634368 446.6G Solaris /usr & Apple ZFS
/dev/sda3       2048   1050623   1048576   512M EFI System
/dev/sda9  937684992 937703054     18063   8.8M Solaris reserved 1

Partition table entries are not in disk order.
```

パーティションがすでに作成されていた場合は削除しておかないと下記のようなエラーが発生する。

```bash
# sgdisk -n3:1M:+512M -t3:EF00 /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161857400901
Could not create partition 3 from 2048 to 1050623
Error encountered; not saving changes.
```

上記エラーが発生した場合はfdiskで削除する。
エラーが出てるけど削除できているのでOK。

```bash
# fdisk /dev/sda

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

Command (m for help): p
Disk /dev/sda: 447.1 GiB, 480103981056 bytes, 937703088 sectors
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: 109D5615-3B2D-4B68-8409-1F4D140C1B5F

Device      Start       End   Sectors  Size Type
/dev/sda1      34      2047      2014 1007K BIOS boot
/dev/sda2    2048    262143    260096  127M EFI System
/dev/sda3  262144 937703054 937440911  447G Linux LVM

Command (m for help): d
Partition number (1-3, default 3): 1

Partition 1 has been deleted.

Command (m for help): d
Partition number (2,3, default 3): 2

Partition 2 has been deleted.

Command (m for help): d
Selected partition 3
Partition 3 has been deleted.
Command (m for help): p           

Disk /dev/sda: 447.1 GiB, 480103981056 bytes, 937703088 sectors
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: 109D5615-3B2D-4B68-8409-1F4D140C1B5F

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy

The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8).
```

partprobeを実行して新しいパーティションを認識させる。
以前のパーティションを削除した場合はOSの再起動が必要かも。

### zpoolを作成する。

ディスクの最後のpart1に注意

```bash
# zpool create -o ashift=12 \
      -O atime=off -O canmount=off -O compression=lz4 -O normalization=formD \
      -O mountpoint=/ -R /mnt \
      rpool /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161857400901-part1
```

### OSをインストールする

ディレクトリの作成

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

# zfs create                 -o setuid=off              rpool/home
# zfs create -o mountpoint=/root                        rpool/home/root
# zfs create -o canmount=off -o setuid=off  -o exec=off rpool/var
# zfs create -o com.sun:auto-snapshot=false             rpool/var/cache
# zfs create                                            rpool/var/log
# zfs create                                            rpool/var/spool
# zfs create -o com.sun:auto-snapshot=false -o exec=on  rpool/var/tmp
```

ホストOSのインストール。
もしdebootstrapでdownload系のエラーが出た場合はコマンドの再実行でOK。

```bash
# chmod 1777 /mnt/var/tmp
# debootstrap xenial /mnt
# zfs set devices=off rpool
```

### ホストOSの環境設定

最初にホスト名とネットワークを設定しておく。
そうしないとchrootしても通信できない。
/mnt/etc/hostsで指定するIPアドレスがなぜか127.0.0.1ではなく127.0.1.1。

```bash
# echo server > /mnt/etc/hostname

# vi /mnt/etc/hosts
127.0.0.1	localhost
::1		localhost ip6-localhost ip6-loopback
ff02::1		ip6-allnodes
ff02::2		ip6-allrouters
127.0.1.1       server
```

ネットワークの設定

```bash
# vi /etc/network/interfaces.d/enp0s25
auto enp0s25
iface enp0s25 inet dhcp
```

その後chrootでホストOS環境に乗り込んで設定する。

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

source.listにURLを追加してロケールも設定。
英語圏でなくともen_US.UTF-8しとけとのこと。

```bash
# vi /etc/apt/sources.list
deb http://archive.ubuntu.com/ubuntu xenial main universe
deb-src http://archive.ubuntu.com/ubuntu xenial main universe

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

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

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

# locale-gen en_US.UTF-8
```

タイムゾーンはAsia -> Tokyoを選択。

```bash
# echo 'LANG="en_US.UTF-8"' > /etc/default/locale

# dpkg-reconfigure tzdata

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

GRUB関連では私の環境はUEFIなので4.6bのやり方を実行。

```bash
# apt install dosfstools
# mkdosfs -F 32 -n EFI /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161857400901
# mkdir /boot/efi
# echo PARTUUID=$(blkid -s PARTUUID -o value \
      /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161857400901-part3) \
      /boot/efi vfat defaults 0 1 >> /etc/fstab
# mount /boot/efi
# apt install --yes grub-efi-amd64
```

最後にシステムグループを追加してrootのパスワードを設定

```bash
# addgroup --system lpadmin
# addgroup --system sambashare
# passwd
```

### GRUBをインストール

ファイルシステムが認識されているか確認してinitrdをリフレッシュ。

```bash
# grub-probe /
zfs

# update-initramfs -c -k all
update-initramfs: Generating /boot/initrd.img-4.4.0-57-generic
```

デバッグのためgrubを編集。

* GRUB_HIDDEN_TIMEOUT=0をコメントアウト
* GRUB_CMD_LINUX_DEFAULTの左辺を””に変更
* GRUB_TERMINALをアンコメント

```bash
# vi /etc/default/grub

# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
#   info -f grub -n 'Simple configuration'

# GRUB_DEFAULT=0
#GRUB_HIDDEN_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT_QUIET=true
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX=""

# Uncomment to enable BadRAM filtering, modify to suit your needs
# This works with Linux (no patch required) and with any kernel that obtains
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"

# Uncomment to disable graphical terminal (grub-pc only)
GRUB_TERMINAL=console

# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'
#GRUB_GFXMODE=640x480

# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true

# Uncomment to disable generation of recovery mode menu entries
#GRUB_DISABLE_RECOVERY="true"

# Uncomment to get a beep at grub start
#GRUB_INIT_TUNE="480 440 1"
```

grubの設定を更新。

```bash
# update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-4.4.0-57-generic
Found initrd image: /boot/initrd.img-4.4.0-57-generic
Adding boot menu entry for EFI firmware configuration
done
```

GRUBをインストール。
UEFIなので5.5bを実行。

```bash
 # grub-install --target=x86_64-efi --efi-directory=/boot/efi \
      --bootloader-id=ubuntu --recheck --no-floppy
Installing for x86_64-efi platform.
Installation finished. No error reported.
```

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

```bash
# ls /boot/grub/*/zfs.mod
/boot/grub/x86_64-efi/zfs.mod
```

### 初回起動

初期状態をsnapshotして chrootを抜ける。

```bash
# zfs snapshot rpool/ROOT/ubuntu@install
# exit
```

マウントしていたすべてをアンマウントしてリブート。

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

再起動後ホストOSにrootでログインしてユーザーを作成する。
暗号化はせず。
キーボードが英語になっているので注意。

```bash
# zfs create rpool/home/tomo
# adduser tomo
# cp -a /etc/skel/.[!.]* /home/tomo
# chown -R tomo:tomo /home/tomo
# usermod -a -G adm,cdrom,dip,lpadmin,plugdev,sambashare,sudo tomo
```

sudoが使えるようになったのでここでopenssl-serverをインストール。

```bash
# apt update
# apt install —yes openssh-server
```

ここからsshでアクセス。

### Swapの設定

とりあえず4GBで作成。

```bash
# 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
      
# mkswap -f /dev/zvol/rpool/swap
Setting up swapspace version 1, size = 4 GiB (4294963200 bytes)
no label, UUID=dcb8b368-7a48-4c22-9658-15ce4088e742

# echo /dev/zvol/rpool/swap none swap defaults 0 0 >> /etc/fstab

# swapon -av
swapon /dev/zd0
swapon: /dev/zd0: found swap signature: version 1d, page-size 4, same byte order
swapon: /dev/zd0: pagesize=4096, swapsize=4294967296, devsize=4294967296
```

### パッケージの最新化

GUIはいらないんでstandardをインストール。

```bash
# apt dist-upgrade --yes
# apt install --yes ubuntu-standard
```

ログの圧縮を無効化。

```bash
# 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
```

再起動

```bash
# reboot
```

### クリーンアップ

rootのパスワードを無効化

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

セットアップ完了した状態でsnapshot

```bash
sudo zfs snapshot rpool/ROOT/ubuntu@install
```