Updating FreeBSD with ZFS Root File System

This weekend I wanted to update my FreeBSD box and saw that the ZFS tools fundamentally changed. The UPDATING file has a corresponding note:

20090520:
        Update ZFS to version 13. ZFS users will need to re-build
        and install both kernel and world simultaneously in order 
        for the ZFS tools to work. Existing pools will continue to work
        without upgrade. If a pool is upgraded it will no longer be
        usable by older kernel revs. ZFS send / recv between 
        pool version 6 and pool version 13 is not supported.

Installing kernel and world simultaneously is scary. If something breaks you are left with a broken system, no way to roll back, and uncertain repair possibilities. Sure, you should have backups, but restoring a whole system is still something that takes a lot of time.

But is this not exactly one of ZFS’ strength? ZFS provides easy snapshots and clones. When I search for “FreeBSD ZFS update” I stumbled of this Mail: Solaris live upgrade like FreeBSD ZFS-rootfs update, which gave me the idea to snapshot my root and usr file system. This should give me a reasonably easy rollback path.

Disclaimer

Here is what I did (with some minor tweaks). It might or might not work for you. You might loose all your data following my steps. I cannot be held reliable for that. I also probably made a few mistakes while writing this up. Back up your system!

Prerequistes

I have my FreeBSD with encrypted ZFS set-up like I described in my blog post FreeBSD: Encrypted ZFS Root with Geli. It should also work with a different encryption mechanism or without encryption.

Creating the Snapshots

Let us assume that the ZFS pool is called tank and that the root file system is under tank/root and the usr file system is under tank/usr.

First, let us create a snapshot of the usr file system and of the root file system:

$ sudo zfs snapshot tank/usr@pre_update
$ sudo zfs snapshot tank/root@pre_update

A snapshot is not writable. So let us create clones:

$ sudo zfs clone tank/usr@pre_update tank/usr_pre_update
$ sudo zfs clone tank/root@pre_update tank/root_pre_update

We need two versions of the root file system. The normal one (tank/usr) where usr points to the normal tank/usr and the pre update one (tank/root_pre_update) that points to the pre update snapshot tank/usr_pre_update. We will use a symlink for this.

Unfortunately, you cannot change the mountpoint of your usr file system while the system is running. So let us first finalize and boot into the pre update set-up to see that it is working.

$ echo Temporary mounting the pre update root
$ sudo mdkir -p /pre_update/root
$ sudo zfs set mountpoint=/pre_update/root tank/root_pre_update
$ echo Setting the mountpoint of the pre update usr file system
$ sudo zfs set mountpoint=/usr_pre_update tank/usr_pre_update
$ echo Symlinking usr to the pre update usr file system
$ cd /pre_update/root
$ sudo rmdir usr
$ sudo ln -s usr_pre_update usr
$ echo Setting the pre update root mountpoint to legacy
$ sudo zfs set mountpoint=legacy tank/root_pre_update

We need a similar set-up in the normal root file system where the symlink points to usr_post_update. In order to set the mountpoint of tank/usr to /usr_post/update we need to reboot into single user mode with ZFS disabled. We go to the boot loader prompt (normally by choosing 6 in the boot menu).

# unset vfs.root.mountfrom
# disable-module zfs
# set boot_single
# boot

Once we are in single user mode, we can import zpool and set the mountpoint.

# zpool import -d /boot/zfs
# zpool import -f -R /tank tank
# cd /tank/root
# rmdir usr
# ln -s usr_post_update usr
# zfs set mountpoint=/usr_post_update tank/usr_post_update

Now we reboot the system into the pre update environment. We go to the boot loader prompt and change the root file system to the pre update root file system.

# set vfs.root.mountfrom=zfs:tank/root_pre_update
# boot

If everything goes well, the machine boots up into the cloned environment. We can verify this by looking that /usr points to /usr_pre_update.

Let us reboot again into the normal environment (by doing nothing special during boot-up). Now /usr should point to /usr_post_update. With this system we can build and install the world and the kernel as mentioned in the Handbook - Rebuilding “world”, without the reboot into single user mode (which will not work, anyway).

Rollback

If anything goes wrong during the update, we can boot our pre update environment by going to the boot loader prompt, selecting the pre update root file system and booting the old kernel.

# set vfs.root.mountfrom=zfs:tank/root_pre_update
# boot kernel.old

Here we can copy over the backup kernel to correct destination and roll back to the snapshot with the comfort of the system in a fully functional state that we know.

Should the update work out, we can destroy the pre update environment in order to safe disk space. Or we can leave it around to mitigate problems of the update we are not aware of just yet.

Resources