Filesystem Diffs Via ZFS Snapshots - 2022-09-11
The issue
Today I needed a to find a way to run a freebsd-update
command exactly once
on a certain jail.
Background
Let’s take a step back shortly to understand why I need this.
Previously the process to build jails looked as follows:
Do once:
- Download
base.txz
Do for every new jail:
- Create a ZFS dataset
- Extract base.txz into the dataset
- Run
freebsd-update
on the jail - Configure sshd and set
authorized_keys
- Copy
resolv.conf
- Configure
jails.conf
with IPv6/IPv4 addresses - Start jail
This felt tedious and seemed like something that can be improved. Since ZFS supports very fast and supposedly lightweight snapshots I figured they could be the right tool. After a quick search I found a blog post called “FreeBSD jails the hard way” which describes exactly this scenario. Funny thing: This post is from 2015 and still works like a charm. Do you remember the last time a seven year old blog post helped you solve an issue with kubernetes? It was probably seven years ago.
After a short refactoring I was able to change the process to this:
Do once:
- Download
base.txz
- Create a ZFS dataset
- Extract base.txz into the dataset
- Run
freebsd-update
for the jail - Configure sshd and set
authorized_keys
- Copy
resolv.conf
- Create a ZFS snapshot
Do for every new jail:
- Create a ZFS dataset clone from previous snapshot
- Configure
jails.conf
with IPv6/IPv4 addresses - Start jail
Check the commit if you’re interested in the details..
The solution
And since I do not want to run the slow freebsd-update
command on every
invocation of ansible, I needed to find a way to only run it once.
This command obviously changes files on the file system. To find these files, I
made use of ZFS snapshots and the diff
functionality of ZFS.
It’s as easy as it sounds.
- Create snapshot of dataset
- Run command that alters files
- Create another snapshot of the same dataset
- Run
zfs diff snapshot1 snapshot2
to get a diff
And of course, after searching through the list of files that were changed, I found this thing:
root@w0:~ # zfs diff zroot/jails/test2/root@pre-install zroot/jails/test2/root@post-install
[..]
+ /usr/local/jails/test2/root/boot/kernel.old/.freebsd-update
[..]
The +
at the beginning of the line suggests that this is a file that is
present in the second but not the first snapshot. A perfect candidate for
our task.
Finally, the ansible task to run the update looks as follows:
- name: Update
ansible.builtin.command:
cmd: "freebsd-update -b /usr/local/jails/{{ jails_version }}/root fetch install --not-running-from-cron"
creates: "/usr/local/jails/{{ jails_version }}/root/boot/kernel.old/.freebsd-update"
Someone on the FreeBSD Discord recently mentioned that they’re making use of ZFS deduplication for all of their full-jails. While I do not really want to go the route of using deduplication yet I liked the idea to save a lot of space when having multiple jails. Maybe on another day.