zfs
ZFS is one of the most advanced file system with volume management capabilities available today with many useful features. ZFS is one of the file systems offered by FreeBSD and best of all it has support for jails, so we can delegate a dataset inside a jail and use it as we use a dataset on the host.
To delegate a dataset we need to mark one with the jailed
property set to on
. I recommend doing this task at creation time because it avoids creating a directory indicated by the mountpoint
property, so our host will not make a mess.
zfs create -o jailed=on -o mountpoint=/jailed zroot/jailed
If we check /jailed
:
# ls /jailed
ls: /jailed: No such file or directory
Perfect! The directory is not created.
In AppJail the configuration file used by each jail is called Template, we need one to attach or detach a dataset and also to mount those datasets inside the jail. (I will explain this a bit better in a moment.)
exec.start: "/bin/sh /etc/rc"
exec.stop: "/bin/sh /etc/rc.shutdown jail"
allow.mount
allow.mount.zfs
enforce_statfs: 1
${dataset}: zroot/jailed
exec.poststart: "zfs jail ${name} ${dataset}"
exec.poststart+: "appjail cmd jexec ${name} zfs list -Hro name ${dataset} | xargs -L 1 appjail cmd jexec ${name} zfs mount"
exec.prestop: "appjail cmd jexec ${name} zfs list -Hro name ${dataset} | tail -r | xargs -L 1 appjail cmd jexec ${name} zfs umount"
exec.prestop+: "zfs unjail ${name} ${dataset}"
We already have all the requirements to delegate a ZFS dataset, so we can proceed to create the jail, but with some important options.
appjail quick jzfs \
mount_devfs \
device='include $devfsrules_hide_all' \
device='include $devfsrules_unhide_basic' \
device='include $devfsrules_unhide_login' \
device='path zfs unhide' \
template=template.conf \
overwrite=force \
start
overwrite=force
is not necessary if you do not want to remove an existing jail named jzfs
. The rest of the options are important because with
-
mount_devfs
we mount adevfs(5)
filesystem, to be able to manage devices inside the jail; -
template
to indicate the template we will use; -
start
to start the jail; - and each
device
option to specify dynamically the devices needed by the jail. The highlighted device iszfs
because it is very important for thezfs(8)
command.
Now that we have our jail created, we can log into it and issue some ZFS commands.
# appjail cmd jexec jzfs
root@jzfs:~ # zfs list -r
NAME USED AVAIL REFER MOUNTPOINT
zroot 79.1G 204G 96K none
zroot/jailed 96K 204G 96K /jailed
root@jzfs:~ # zfs mount
zroot/appjail/jails/jzfs/jail /
zroot/jailed /jailed
The attentive reader has noticed that there is a dataset named zroot/appjail/jails/jzfs/jail
that does not exist on our jail. This is very important to note because zfs umount -a
will fail, this is the reason why we explicitly mount or unmount (all of them recursively) the datasets specified in our template.
Let's create a new dataset and assign it a quota of 1g
.
root@jzfs:~ # zfs create -o quota=1g zroot/jailed/users
root@jzfs:~ # zfs list -r
NAME USED AVAIL REFER MOUNTPOINT
zroot 79.1G 204G 96K none
zroot/jailed 192K 204G 96K /jailed
zroot/jailed/users 96K 1024M 96K /jailed/users
root@jzfs:~ # pw useradd -n op -m -d /jailed/users/op
root@jzfs:~ # su - op
Sometimes a single slow HDD can cripple the performance of your entire system. You can spot one like this:
# gstat -I5s | sort -rn -k9 | head
-- Alan Somers <asomers@FreeBSD.org>
op@jzfs:~ $ dd if=/dev/random of=randomfile status=progress bs=1m
dd: randomfile: Disc quota exceedediB) transferred 62.471s, 17 MB/s
1024+0 records in
1023+1 records out
1073086464 bytes transferred in 62.848765 secs (17074106 bytes/sec)