Installing Ubuntu 16.04 on a ZFS root filesystem
Re-publishing this from my old blog since it is bound to be useful for Plex servers.
One of the major new pieces of functionality in Ubuntu 16.04 (Xenial) is built in support for ZFS filesystems. I was disappointed to learn that ZFS support is not actually built into the installer itself, leaving you to piece things together yourself. I did find a few good tutorials online for this, but they all seem to be missing a few pieces. I’m hoping that this guide will be a bit more complete for people.
It is based on a combination of the following two guides:
- https://github.com/zfsonlinux/pkg-zfs/wiki/HOWTO-install-Ubuntu-16.04-to-a-Native-ZFS-Root-Filesystem
- http://dotfiles.tnetconsulting.net/articles/2016/0327/ubuntu-zfs-native-root.html
Booting Into the Install Environment
apt-get update
apt-get install –yes zfsutils-linux debootstrap
Partitioning the Disks
Next, we partition the disks. For now we’re going to assume a two disk system which is just doing striping, but you can change as desired:
parted — /dev/sda mklabel msdos Y mkpart primary zfs 0% 100%
parted /dev/sdb mklabel msdos Y mkpart primary zfs 0% 100%
Device naming during bootup on Linux isn’t always static, so we want to reference the disks through a static identifier like disk ID (/dev/disk/by-id), unfortunately Grub fails to properly identify the devices from the zfs command output, so we need this hack for now to make sure that update-grub will correctly identify the disks. Specifically, it forces creation of symlinks in /dev which map to the device names in /dev/disk/by-id. Some additional details about the issue can be found in this grub2 launchpad bug.
echo ‘KERNEL==”sd*[!0-9]”, IMPORT{parent}==”ID_*”, SYMLINK+=”$env{ID_BUS}-$env{ID_SERIAL}”
> KERNEL==”sd*[0-9]”, IMPORT{parent}==”ID_*”, SYMLINK+=”$env{ID_BUS}-$env{ID_SERIAL}-part%n”‘ > /etc/udev/rules.d/90-zfs.rules
udevadm trigger
Create Your Zpool
Now we actually create the zpool and import it to /mnt:
zpool create -m none -o ashift=12 -O compression=lz4 rpool /dev/sda1 /dev/sdb1
zfs create -o mountpoint=/ rpool/root
zpool export rpool
zpool import -d /dev/disk/by-id -R /mnt rpool
So here we create the zpool by device name, and then re-import it by device ID while mounting at /mnt.
And then create various partitions off of the root filesystem:
zfs create -o setuid=off rpool/root/home
zfs create -o mountpoint=/root rpool/root/home/root
zfs create -o canmount=off -o setuid=off -o exec=off rpool/root/var
zfs create -o com.sun:auto-snapshot=false rpool/root/var/cache
zfs create rpool/root/var/log
zfs create rpool/root/var/spool
zfs create -o com.sun:auto-snapshot=false -o exec=on rpool/root/var/tmp
We break out these various subdirectories so we have the ability to optionally enable/disable compression, support for setuid/exec, and various other options. We can also choose to limit the maximum size of each mount independently.
Install Ubuntu
debootstrap the OS
cp /etc/udev/rules.d/90-zfs.rules /mnt/etc/udev/rules.d/90-zfs.rules
You’ll notice at the end here we copy our hacky udev rule to the new filesystem. The “devices=off” option we set here disables the ability to create device nodes in the filesystem. This works since /dev is actually a devtmpfs partition of its own.