Tag Archives: jails

NFSv4 Server Inside FreeBSD VNET Jail

Not so long ago I wrote an article about running NFS Server Inside FreeBSD VNET Jail which was possible by then with using the net/unfs3 NFS server from the FreeBSD Ports that run in the userspace. The kernel solution was not possible by then and while this little daemon comes handy – it is limited to NFSv3 only. The status quo changed recently and mainly thanks to Rick Macklem who made the changes and created needed patches. The FreeBSD 2022 Q4 Status Report had even it described as one of the ongoing projects and it looked like that:

status

You can check Rick work and commits about this topic in the usual places:

He even created a short HOWTO on how to test this new feature.

The good thing is that You do not longer need to patch and rebuild FreeBSD to have this ability to run nfsd(8) NFSv4 server inside FreeBSD Jail. Its already ‘upstream’ in the 14-CURRENT and 13-STABLE branches.

Today I am going to test how that new feature works and I will show you how to configure it within a VNET Jail. If you would like to know how the VNET Jails work take a look at my recent FreeBSD Jails Containers article for the details.

The Table of Contents for this article is listed below.

  • Test Environment
  • FreeBSD Host Setup
  • FreeBSD VNET Jail Setup
  • FreeBSD Client Setup
  • Important Rick Notes
  • Summary

Test Environment

The host machine is a typical FreeBSD server installed on a physical or virtual machine. The exact ISO image I used was FreeBSD-13.2-STABLE-amd64-20230615-894492f5bf4e-255597-disc1.iso from 2023/06/15 so anything newer then that should also work. The nfsd is a FreeBSD VNET Jail that will run on the host system. The client will be running FreeBSD 13.2-RELEASE where we will mount the NFSv4 share.

Below you will find the list of systems that we will use in this guide.

       IP  ROLE    VERSION
20.0.0.10  client  13.2-RELEASE @254617
20.0.0.20  host    13.2-STABLE  @255597
20.0.0.30  nfsd    13.2-STABLE  @255597

FreeBSD Host Setup

First we will install host machine with typical ZFS install – nothing special about that – just pick Auto (ZFS) option from bsdinstall(8) installer.

Below you will find content of needed configuration files – such as /etc/rc.conf file.

host # cat /etc/rc.conf
# NETWORK
  hostname="host"
  cloned_interfaces="bridge0"
  ifconfig_em0="up"
  ifconfig_bridge0="inet 20.0.0.20/24 up addm em0"
  defaultrouter="20.0.0.1"
  gateway_enable=YES

# DAEMONS
  sshd_enable=YES
  zfs_enable=YES

# JAILS
  jail_enable=YES
  jail_parallel_start=YES
  jail_list="nfsd"

We will keep our Jails under the /jail path. Lets create these datasets now.

host # zfs create -o mountpoint=/jail -p zroot/jail
host # zfs create                     -p zroot/jail/BASE
host # zfs create                     -p zroot/jail/nfsd

I also created the /jail/BASE and /jail/nfsd dirs. We will keep various FreeBSD versions *-base.txz files in the first one and the /jail/nfsd will be used as a place for our nfsd(8) server VNET Jail.

host # find /jail -maxdepth 1
/jail
/jail/BASE
/jail/nfsd

As we already have the 13.2-STABLE ISO file we can just copy the needed base.txz file from there.

host # mdconfig -a -t vnode -f FreeBSD-13.2-STABLE-amd64-20230615-894492f5bf4e-255597-disc1.iso
md0

host # mkdir ISO

host # mount -t cd9660 /dev/md0 ISO

host # cp ISO/usr/freebsd-dist/base.txz /jail/BASE/13.2-STABLE-255597-base.txz

host # umount ISO

host # rm -r ISO

host # mdconfig -d -u /dev/md0

Lets not forget about DNS – we will use /etc/hosts file for this purpose.

host # cat /etc/hosts
127.0.0.1       localhost localhost.my.domain
::1             localhost localhost.my.domain

20.0.0.10       client
20.0.0.20       host
20.0.0.30       nfsd

This is how the host network interfaces and routes look like.

host # ifconfig em0; ifconfig bridge0; ifconfig epair30a;
em0: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=4810099<RXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,VLAN_HWFILTER,NOMAP>
        ether 08:00:27:09:cc:a8
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        ether 58:9c:fc:10:ff:b6
        inet 20.0.0.20/24 broadcast 20.0.0.255
        id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
        maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200
        root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
        member: epair30a flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
                ifmaxaddr 0 port 4 priority 128 path cost 2000
        member: em0 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
                ifmaxaddr 0 port 1 priority 128 path cost 2000000
        groups: bridge
        nd6 options=9<PERFORMNUD,IFDISABLED>
epair30a: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        description: jail:nfsd
        options=8<VLAN_MTU>
        ether 02:82:ef:85:a1:0a
        groups: epair
        media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

host # route get 0
   route to: default
destination: default
       mask: default
    gateway: 20.0.0.1
        fib: 0
  interface: bridge0
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

host # netstat -Win -f inet
Name      Mtu Network            Address              Ipkts Ierrs Idrop    Opkts Oerrs  Coll
lo0         - 127.0.0.0/8        127.0.0.1                0     -     -        0     -     -
bridge0     - 20.0.0.0/24        20.0.0.20              967     -     -      743     -     -

FreeBSD VNET Jail Setup

In this step we will prepare our VNET Jail.

host # tar -xf /jail/BASE/13.2-STABLE-255597-base.txz -C /jail/nfsd --unlink

Now the /etc/jail.conf.d/nfsd.conf config file for our Jail.

host # cat /etc/jail.conf.d/nfsd.conf
nfsd {
  # STARTUP/LOGGING
    exec.start = "/bin/sh /etc/rc";
    exec.stop  = "/bin/sh /etc/rc.shutdown";
    exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
    allow.raw_sockets;
    mount.devfs;
    exec.clean;

  # PATH/HOSTNAME
    path = "/jail/${name}";
    host.hostname = "${name}";

  # VNET/VIMAGE
    vnet;
    vnet.interface = "${if}b";

  # NFSD/VNET
    allow.nfsd;
    enforce_statfs = 1;

  # NETWORKS/INTERFACES
    $id = "30";
    $ip = "20.0.0.${id}/24";
    $gw = "20.0.0.1";
    $br = "bridge0";
    $if = "epair${id}";

  # ADD TO bridge0 INTERFACE
    exec.prestart += "ifconfig ${if} create up";
    exec.prestart += "ifconfig ${if}a up descr jail:${name}";
    exec.prestart += "ifconfig ${br} addm ${if}a up";
    exec.start    += "ifconfig ${if}b ${ip} up";
    exec.start    += "route add default ${gw}";
    exec.poststop += "ifconfig ${if}a destroy";
}

We can now start our nfsd VNET Jail.

host # service jail start nfsd

host # jls
   JID  IP Address      Hostname                      Path
     1                  nfsd                          /jail/nfsd

host # jexec nfsd

root@nfsd:/ # ifconfig | grep 'inet '
        inet 127.0.0.1/8
        inet 20.0.0.30/24 broadcast 20.0.0.255

Our VNET Jail seems to work properly. Lets check its network connectivity.

root@nfsd:/ # ifconfig epair30b
epair30b: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=8<VLAN_MTU>
        ether 02:54:54:dd:f0:0b
        inet 20.0.0.30/24 broadcast 20.0.0.255
        groups: epair
        media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

root@nfsd:/ # route get 0
   route to: default
destination: default
       mask: default
    gateway: 20.0.0.1
        fib: 0
  interface: epair30b
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

root@nfsd:/ # ping -c 3 20.1
PING 20.1 (20.0.0.1): 56 data bytes
64 bytes from 20.0.0.1: icmp_seq=0 ttl=255 time=0.480 ms
64 bytes from 20.0.0.1: icmp_seq=1 ttl=255 time=0.612 ms
64 bytes from 20.0.0.1: icmp_seq=2 ttl=255 time=0.357 ms

--- 20.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.357/0.483/0.612/0.104 ms

root@nfsd:/ # nc -v -u 1.1.1.1 53
Connection to 1.1.1.1 53 port [udp/domain] succeeded!
^C

Works as it should.

To make the network setup complete we will now put the same contents into the /etc/hosts file as they are on the host system.

root@nfsd:/ # cat /etc/hosts
127.0.0.1       localhost localhost.my.domain
::1             localhost localhost.my.domain

20.0.0.10       client
20.0.0.20       host
20.0.0.30       nfsd

We need to add two lines to the /etc/sysctl.conf file.

root@nfsd:/ # cat /etc/sysctl.conf

# VNET/NFSD
vfs.nfs.enable_uidtostring=1
vfs.nfsd.enable_stringtouid=1

The main /etc/rc.conf file in the nfsd VNET Jail looks as follows.

root@nfsd:/# cat /etc/rc.conf
# DAEMONS
  sshd_enable=YES
  nfs_server_enable=YES
  nfsv4_server_only=YES
  nfs_server_flags="-t"

I enabled and force only the NFSv4 version. That will allow us to server NFSv4 share with only single 2049 TCP port – which is very firewall friendly 🙂

Now we will create our NFS share under /share dir and start the nfsd(8) NFS server. We will also populate the /etc/exports with config to share that /share dir.

root@nfsd:/ # mkdir /share

root@nfsd:/ # cat /etc/exports
V4: /           -sec=sys               -network 20.0.0.0/24
/share          -sec=sys -maproot=root -network 20.0.0.0/24

We will now restart our FreeBSD VNET Jail to make these changes take effect.

host # service jail restart nfsd
Stopping jails: nfsd.
Starting jails: nfsd.

root # jls
   JID  IP Address      Hostname                      Path
     2                  nfsd                          /jail/nfsd

host # jexec nfsd

root@nfsd:/ # sockstat -l4
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
root     sendmail   66969 4  tcp4   127.0.0.1:25          *:*
root     sshd       66195 4  tcp4   *:22                  *:*
root     nfsd       61939 5  tcp4   *:2049                *:*

Our VNET Jail setup is complete. The nfsd(8) server listens at its default 2049 port. We will not move on to the client setup.

FreeBSD Client Setup

Our client system is FreeBSD 13.2-RELEASE system – again – installed in a simple Auto (ZFS) way chosen in the FreeBSD installer.

We will start with the /etc/hosts file.

client # cat /etc/hosts
127.0.0.1       localhost localhost.my.domain
::1             localhost localhost.my.domain

20.0.0.10       client
20.0.0.20       host
20.0.0.30       nfsd

Next is the main FreeBSD /etc/rc.conf config file.

client # cat /etc/rc.conf
# NETWORK
  hostname="client"
  ifconfig_em0="inet 20.0.0.10/24"
  defaultrouter="20.0.0.1"
  sshd_enable=YES
  zfs_enable=YES
  nfs_client_enable=YES

The important part for NFS here is the nfs_client_enable=YES part.

We may now try to mount our NFSv4 share from the nfsd VNET Jail on our client system.

client # service nfsclient start
NFS access cache time=60

client # ping -c 3 nfsd
PING nfsd (20.0.0.30): 56 data bytes
64 bytes from 20.0.0.30: icmp_seq=0 ttl=64 time=0.540 ms
64 bytes from 20.0.0.30: icmp_seq=1 ttl=64 time=0.465 ms
64 bytes from 20.0.0.30: icmp_seq=2 ttl=64 time=0.589 ms

--- nfsd ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.465/0.531/0.589/0.051 ms

client # nc -v nfsd 2049
Connection to nfsd 2049 port [tcp/nfsd] succeeded!
^C

client # mount -o nfsv4 nfsd:/share /mnt

client # mount -t nfs
nfsd:/share on /mnt (nfs, nfsv4acls)

client # cd /mnt

client # echo asd > asd

client # ls -l asd
-rw-r--r--  1 root  wheel  4 Jun 30 03:06 asd

client # rm asd

Viola! Works as advertised 🙂

We can also make that NFS mount permanent and mounted automatically at boot using the /etc/fstab file.

client # tail -3 /etc/fstab

# NFSD @ VNET nfsv4
nfsd:/share             /mnt                    nfs     rw,nfsv4,late    0 0

How it looks live.

network

Important Rick Notes

While the above setup works properly I would like to share some of Rick notes from his HOWTO.

To make such a VNET Jail serve the NFS shares these requirements need to be met:

  • Jail must be on a separate filesystem.
  • Jail root directory must be a filesystem mount point.
  • Jail config needs allow.nfsd; option added.
  • Jail config needs enforce_statfs = 1; to export shares mounted below root of Jail.Most of the vfs.nfsd.* settings must be done on the /etc/sysctl.conf host config.

    Currently the only vfs.nfsd.* settings available within VNET Jail are:

  • vfs.nfsd.server_min_nfsvers
  • vfs.nfsd.server_max_nfsvers
  • vfs.nfs.enable_uidtostring
  • vfs.nfsd.enable_stringtouid
  • vfs.nfsd.fha.enable
  • vfs.nfsd.fha.read
  • vfs.nfsd.fha.write
  • vfs.nfsd.fha.bin_shift
  • vfs.nfsd.fha.max_nfsds_per_fh
  • vfs.nfsd.fha.max_reqs_per_nfsd

    Summary

    As you can see Rick did a very solid job here – the NFS server works really well in the FreeBSD VNET Jails.

    Let me know in the comments if I forgot about anything.

    EOF

FreeBSD Jails Containers

FreeBSD networking and containers (Jails) stacks are very mature and provide lots of useful features … yet for some reason these features are not properly advertised by the FreeBSD project … or not even documented at all. I remember when Solaris was still under Sun before ‘fatal’ 2008 Oracle acquisition and one of the advertised Solaris features were its networking capabilities – along with virtual switches etc. that were administrated with the ipadm(1M) and dladm(1M) commands. FreeBSD – while having technologies like Netgraph or Jails lightweight containers – along with VNET Jails that have full independent of the host virtual network stack … are almost not advertised at all. The VNET Jails – while being production ready and used by thousands of sysadmins for about a decade – are still not documented in the FreeBSD Handbook or FreeBSD FAQ at all … you will not be able to find a single VNET mention in the FreeBSD Handbook. Even the FreeBSD Man Pages like jail.conf(5) does not mention it – only jail(8)partially mentions VNET feature.

There are however two FreeBSD Books dedicated to Jails … one is free – FreeBSD Jails Using VNETs (from 2020) and one is non-free – FreeBSD Mastery – Jails (from 2019).

The Table of Contents for this article is:

  • FreeBSD Host Setup
  • Classic Jails
  • VNET Jails
  • Thin Provisioning Jails
  • Single Process Jails
  • Removing Jails
  • Summary

This guide aims to make VNET Jails networking little closer and simple. While one can use Netgraph bridge for this purpose – we will use the simpler and more obvious classic network bridge supported by if_bridge(4) driver on FreeBSD. I also encourage you to check the FreeBSD Handbook – Jails chapter.

jails

FreeBSD Host Setup

The first thing we will do is to prepare the host networking. This is the typical static network configuration on FreeBSD with single IPv4 IP address without any VLANs in the /etc/rc.conf file. We will also enable the Jails subsystem.

host # cat /etc/rc.conf
# NETWORK
  hostname="host"
  ifconfig_em0="inet 20.0.0.20/24"
  defaultrouter="20.0.0.1"

# JAILS
  jail_enable="YES"
  jail_parallel_start="YES"
  jail_list="classic vnet shadow"

With that setup – your classic Jails will be able to connect to the outside World.

It can be visualized more or less like that.

              +--------+    +-----------------+
              |GATEWAY |    |20.0.0.20    HOST|
(Internet)<==>|        |<==>|em0              |
              |20.0.0.1|    |                 |
              +--------+    +-----------------+

Below we will create a classic Jail for a start. Each such setup requires certain decisions to be made – one of them will be using /jail as our Jails root.

We will create one with ZFS datasets now.

host # zfs create -o mountpoint=/jail -p zroot/jail
host # zfs create                     -p zroot/jail/BASE
host # zfs create                     -p zroot/jail/classic

I have also created the /jail/BASE and /jail/classic dirs. The first one will be used as a placeholder for various FreeBSD versions *-base.txz files. The latter will be used for our first ‘classic’ FreeBSD Jail.

host # find /jail -maxdepth 1
/jail
/jail/BASE
/jail/classic

Classic Jails

I will be using FreeBSD 13.2-RELEASE for the host system so this is the Jail version I would use for ‘classic’ Jail.

host # fetch -o /jail/BASE/13.2-RELEASE-base.txz http://ftp.freebsd.org/pub/FreeBSD/releases/amd64/13.2-RELEASE/base.txz

The FreeBSD host can run any other FreeBSD version in a Jail as long as its not newer then the host system version. That means that while we use FreeBSD 13.2-RELEASE you are able to run and consolidate a farm of any older FreeBSD versions – along with legendary 4.11-RELEASE … or one of the most problematic 5.0-RELEASE that was the first one that introduced the SMP with its M:N threading model.

We will now create the ‘classic’ Jail.

host # tar -xf /jail/BASE/13.2-RELEASE-base.txz -C /jail/classic --unlink

… and now its configuration. Usually the jail.conf(5) file is used for that … but as you grow more Jails it becomes less practical to scroll through this file to ‘find’ your desired Jail that you want to modify. This is where the /etc/jail.conf.d dir comes handy. You will be able to place each Jail config as /etc/jail.conf.d/JAILNAME.conf file. This is what we will use here – leaving /etc/jail.conf file empty or non-existent.

This is the config we will use for the ‘classic’ Jail.

host # cat /etc/jail.conf.d/classic.conf
classic {
  # STARTUP/LOGGING
    exec.start = "/bin/sh /etc/rc";
    exec.stop  = "/bin/sh /etc/rc.shutdown";
    exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
    allow.raw_sockets;
    exec.clean;

  # PATH/HOSTNAME
    path = "/jail/${name}";
    host.hostname = "${name}";

  # NETWORK
    ip4.addr = 20.0.0.50;
    interface = em0;
}

Now we will start that Jail.

host # service jail start classic
Starting jails: classic.

host # jls
   JID  IP Address      Hostname                      Path
    31  20.0.0.50       classic                       /jail/classic

Our ‘classic’ Jail successfully started.

The 20.0.0.50 IP address was added to the host em0 interface as shown below.

host # ifconfig em0
em0: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=481009b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,VLAN_HWFILTER,NOMAP>
        ether 08:00:27:09:cc:a8
        inet 20.0.0.20/24 broadcast 20.0.0.255
        inet 20.0.0.50/32 broadcast 20.0.0.50
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

The ‘classic’ Jails networking can be visualized like that.

              +--------+    +-----------------------------+
              |GATEWAY |    |20.0.0.20                HOST|
(Internet)<==>|        |<==>|em0          +-------------+ |
              |20.0.0.1|    |20.0.0.50<==>|____jail0____| |
              +--------+    |20.0.0.51<==>|____jail1____| |
                            |(.......)<==>|    (...)    | |
                            |             +-------------+ |
                            +-----------------------------+

We can also ping(8) the ‘classic’ Jail IP address from the host system.

host # ping -c 3 20.50
PING 20.50 (20.0.0.50): 56 data bytes
64 bytes from 20.0.0.50: icmp_seq=0 ttl=64 time=0.114 ms
64 bytes from 20.0.0.50: icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from 20.0.0.50: icmp_seq=2 ttl=64 time=0.046 ms

--- 20.50 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.046/0.069/0.114/0.031 ms

Lets login into the ‘classic’ Jail.

host # jls
   JID  IP Address      Hostname                      Path
     1  20.0.0.50       classic                       /jail/classic

host # jexec classic

root@classic:/ # hostname
classic

Inside the ‘classic’ Jail we are able to ping(8) the host gateway.

root@classic:/ # ping -c 3 20.1
PING 20.1 (20.0.0.1): 56 data bytes
64 bytes from 20.0.0.1: icmp_seq=0 ttl=255 time=0.083 ms
64 bytes from 20.0.0.1: icmp_seq=1 ttl=255 time=0.314 ms
64 bytes from 20.0.0.1: icmp_seq=2 ttl=255 time=0.256 ms

--- 20.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.083/0.218/0.314/0.098 ms

We can also reach outside World popular DNS public servers.

root@classic:/ # nc -v -u 1.1.1.1 53
Connection to 1.1.1.1 53 port [udp/domain] succeeded!
^C

Lets configure one of those DNS servers for our ‘classic’ Jail and test it.

root@classic:/ # echo nameserver 1.1.1.1 > /etc/resolv.conf

root@classic:/ # drill freebsd.org | grep '^[^;]'
freebsd.org.    240     IN      A       96.47.72.84

Seems to work properly. Lets enable sshd(8) service on it.

root@classic:/ # service sshd enable
sshd enabled in /etc/rc.conf

root@classic:/ # service sshd start
Generating RSA host key.
3072 SHA256:mSVNDUSi14S+GiaFJgNHNLCqQi6ndFG9JaSyA/wev1k root@classic (RSA)
Generating ECDSA host key.
256 SHA256:Hij315+3C/IMVJ1RX+hNJynGtVSU7ALYN0AS9/lxpJY root@classic (ECDSA)
Generating ED25519 host key.
256 SHA256:qzQnJCHjhHB7jQzmimSLayBfOc3dkLzIVhmrL2r9qxM root@classic (ED25519)
Performing sanity check on sshd configuration.
Starting sshd.

root@classic:/ # sockstat -l4
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
root     sshd       15049 3  tcp4   20.0.0.50:22          *:*
root     sendmail   18689 3  tcp4   20.0.0.50:25          *:*
root     syslogd    84689 5  udp4   20.0.0.50:514         *:*

Seems to work properly. We will now try to login to it from other host on the 20.0.0.0/24 network.

laptop % ssh 20.50
The authenticity of host '20.0.0.50 (20.0.0.50)' can't be established.
ED25519 key fingerprint is SHA256:qzQnJCHjhHB7jQzmimSLayBfOc3dkLzIVhmrL2r9qxM.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '20.0.0.50' (ED25519) to the list of known hosts.
(root@20.0.0.50) Password for root@classic:
(root@20.0.0.50) Password for root@classic:
(root@20.0.0.50) Password for root@classic:
root@20.0.0.50: Permission denied (publickey,keyboard-interactive).

Works. I was not able to login as by default logins to root account are disabled.

VNET Jails

On the other hand the VNET Jails (with options VIMAGE in the kernel) are quite different beasts. While on the file/dir/config level they work the same – on the network part they are a lot different. They come with separate network stack and do not add their IP to the host network interface.

Also – the current configuration of the host system (as repeated below) would also not work for the VNET Jails network connectivity with the outside World.

host # cat /etc/rc.conf
# NETWORK
  hostname="host"
  ifconfig_em0="inet 20.0.0.20/24"
  defaultrouter="20.0.0.1"

To allow VNET Jails entry to the World outside of the host system we need to use – for example – if_bridge(4) interface – and move our host IP address there. Below is the not working host network configuration – VNET Jails will not be able to access outside World.

host # ifconfig
em0: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=481009b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,VLAN_HWFILTER,NOMAP>
        ether 08:00:27:09:cc:a8
        inet 20.0.0.20/24 broadcast 20.0.0.255
        inet 20.0.0.50/32 broadcast 20.0.0.50
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
        inet 127.0.0.1/8
        groups: lo
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>


host # netstat -Win -f inet
Name      Mtu Network            Address              Ipkts Ierrs Idrop    Opkts Oerrs  Coll
em0         - 20.0.0.0/24        20.0.0.20           164406     -     -    91934     -     -
em0         - 20.0.0.50/32       20.0.0.50              207     -     -      168     -     -
lo0         - 127.0.0.0/8        127.0.0.1                6     -     -        6     -     -

host # route get 0
   route to: default
destination: default
       mask: default
    gateway: 20.0.0.1
        fib: 0
  interface: em0
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

This is how the rc.conf(5) file needs to look like now. This config will allow VNET Jails to leave host system and access outside World.

host # cat /etc/rc.conf

# NETWORK
  hostname="host"
  cloned_interfaces="bridge0"
  ifconfig_em0="up"
  ifconfig_bridge0="inet 20.0.0.20/24 up addm em0"
  defaultrouter="20.0.0.1"
  gateway_enable=YES

# JAILS
  jail_enable="YES"
  jail_parallel_start="YES"
  jail_list="classic"

… and this is how it looks in the ifconfig(8) and netstat(8) commands.

host # ifconfig
em0: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=481009b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,VLAN_HWFILTER,NOMAP>
        ether 08:00:27:09:cc:a8
        inet 20.0.0.50/32 broadcast 20.0.0.50
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
        inet 127.0.0.1/8
        groups: lo
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        ether 58:9c:fc:10:ff:b6
        inet 20.0.0.20/24 broadcast 20.0.0.255
        id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
        maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200
        root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
        member: em0 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
                ifmaxaddr 0 port 1 priority 128 path cost 20000
        groups: bridge
        nd6 options=9<PERFORMNUD,IFDISABLED>

host # netstat -Win -f inet
Name      Mtu Network            Address              Ipkts Ierrs Idrop    Opkts Oerrs  Coll
em0         - 20.0.0.50/32       20.0.0.50              207     -     -      168     -     -
lo0         - 127.0.0.0/8        127.0.0.1                6     -     -        6     -     -
bridge0     - 20.0.0.0/24        20.0.0.20           164576     -     -    92109     -     -

host # route get 0
   route to: default
destination: default
       mask: default
    gateway: 20.0.0.1
        fib: 0
  interface: bridge0
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

The ‘classic’ Jail IP address is still bound to the em0 interface while the host IP address has been moved to the bridge0 bridge. If needed you can also move all the ‘classic’ Jails IP addresses to the bridge0 interface but its not mandatory.

It can be visualized like that. Keep in mind that em0 and epair*a interfaces are members of bridge0 interface.

                            +--------------------------------------------+
                            |                        +-------------+ HOST|
                            |       em0 20.0.0.50<==>|____jail0____|     |
                            |      /    20.0.0.51<==>|____jail1____|     |
              +--------+    |     /     (.......)<==>|    (...)    |     |
              |GATEWAY |    |20.0.0.20               +-------------+     |
(Internet)<==>|        |<==>|bridge0                                     |
              |20.0.0.1|    |  \ \ \           +-----------------------+ |
              +--------+    |   \ \ epairXa<==>|epairXb 20.0.0.60 vnet0| |
                            |    \ \           +-----------------------+ |
                            |     \ epairYa<==>|epairYb 20.0.0.61 vnet1| |
                            |      \           +-----------------------+ |
                            |       (....)a<==>|(....)b (.......) (...)| |
                            |                  +-----------------------+ |
                            +--------------------------------------------+

Lets create now our first VNET Jail. The beginning is the same – we will extract base.txz file from the 13.2-RELEASE system.

host # zfs create -p zroot/jail/vnet

host # tar -xf /jail/BASE/13.2-RELEASE-base.txz -C /jail/vnet --unlink

We will now also need the VNET Jail config.

host # cat /etc/jail.conf.d/vnet.conf
vnet {
  # STARTUP/LOGGING
    exec.start = "/bin/sh /etc/rc";
    exec.stop  = "/bin/sh /etc/rc.shutdown";
    exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
    allow.raw_sockets;
    exec.clean;

  # PATH/HOSTNAME
    path = "/jail/${name}";
    host.hostname = "${name}";

  # VNET/VIMAGE
    vnet;
    vnet.interface = "${if}b";

  # NETWORKS/INTERFACES
    $id = "60";
    $ip = "20.0.0.${id}/24";
    $gw = "20.0.0.1";
    $br = "bridge0";
    $if = "epair${id}";

  # ADD TO bridge0 INTERFACE
    exec.prestart += "ifconfig ${if} create up";
    exec.prestart += "ifconfig ${if}a up descr jail:${name}";
    exec.prestart += "ifconfig ${br} addm ${if}a up";
    exec.start    += "ifconfig ${if}b ${ip} up";
    exec.start    += "route add default ${gw}";
    exec.poststop += "ifconfig ${if}a destroy";
}

Lets now start the VNET Jail.

host # service jail start vnet
Starting jails: vnet.

host # jls
   JID  IP Address      Hostname                      Path
     1  20.0.0.50       classic                       /jail/classic
     2                  vnet                          /jail/vnet

One thing to notice here is that the jls(8) tool does not show the VNET Jails IP addresses.

You can of course overcome that limitation with jexec(8) – but IMHO after 10+ years of VNET Jails being production ready its PITA to say the least.

host # jexec vnet ifconfig | grep 'inet '
        inet 127.0.0.1/8
        inet 20.0.0.60/24 broadcast 20.0.0.255

Lets now try our VNET Jail network connectivity to the outside World.

host # jexec vnet

root@vnet:/ # ping -c 3 20.1
PING 20.1 (20.0.0.1): 56 data bytes
64 bytes from 20.0.0.1: icmp_seq=0 ttl=255 time=0.853 ms
64 bytes from 20.0.0.1: icmp_seq=1 ttl=255 time=0.474 ms
64 bytes from 20.0.0.1: icmp_seq=2 ttl=255 time=1.355 ms

--- 20.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.474/0.894/1.355/0.361 ms

root@vnet:/ # nc -v -u 1.1.1.1 53
Connection to 1.1.1.1 53 port [udp/domain] succeeded!
^C

Works as desired.

This is how the networking looks like inside the VNET Jail.

root@vnet:/ # ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
        inet 127.0.0.1/8
        groups: lo
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
epair60b: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=8<VLAN_MTU>
        ether 02:47:e8:2e:6b:0b
        inet 20.0.0.60/24 broadcast 20.0.0.255
        groups: epair
        media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

root@vnet:/ # netstat -Win -f inet
Name       Mtu Network            Address              Ipkts Ierrs Idrop    Opkts Oerrs  Coll
lo0          - 127.0.0.0/8        127.0.0.1             2848     -     -     2848     -     -
epair60b     - 20.0.0.0/24        20.0.0.60                5     -     -        9     -     -

root@vnet:/ # route get 0
   route to: default
destination: default
       mask: default
    gateway: 20.0.0.1
        fib: 0
  interface: epair60b
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

Same as with ‘classic’ Jail – we will enable the sshd(8) service.

root@vnet:/ # service sshd enable
sshd enabled in /etc/rc.conf

root@vnet:/ # service sshd start
Generating RSA host key.
3072 SHA256:n9sGBV1bmz3bT4+qKuFE5fZHjnBcYlOMxXCq98z7/r0 root@vnet (RSA)
Generating ECDSA host key.
256 SHA256:A+gDtzkkrhNnGRGR4Yf27cqME8/NZk5NCHrxwyEO9oM root@vnet (ECDSA)
Generating ED25519 host key.
256 SHA256:aojc9Kbyd32HkllG9+noKL8GvKMjObuLrUNiq24+OFk root@vnet (ED25519)
Performing sanity check on sshd configuration.
Starting sshd.

root@vnet:/ # sockstat -l4
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
root     sshd       37145 4  tcp4   *:22                  *:*
root     sendmail   99914 4  tcp4   127.0.0.1:25          *:*
root     syslogd    71123 6  udp4   *:514                 *:*

We will try to connect to it from a system other then the host.

laptop % ssh 20.60
The authenticity of host '20.0.0.60 (20.0.0.60)' can't be established.
ED25519 key fingerprint is SHA256:aojc9Kbyd32HkllG9+noKL8GvKMjObuLrUNiq24+OFk.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '20.0.0.60' (ED25519) to the list of known hosts.
(root@20.0.0.60) Password for root@vnet:
(root@20.0.0.60) Password for root@vnet:
(root@20.0.0.60) Password for root@vnet:
root@20.0.0.60: Permission denied (publickey,keyboard-interactive).

Same as with the ‘classic’ Jail – we will not login with root user as its disabled by default – but that is not the task of this exercise.

Thin Provisioning Jails

While this will not be possible on the UFS filesystem – the ZFS allows that without any hassle.

We will now create a template for the ‘classic’ Jails on ZFS.

host # zfs snapshot zroot/jail/classic@template

The only things we did with the ‘classic’ Jail was to setup DNS server in /etc/resolv.conf file and we enabled sshd(8) service. We generally want that from any of our future Jails – so its a good candidate for a template Jail.

As we created the classic@template ZFS snapshot – we can not create unlimited thin provisioning ‘classic’ Jails from it. Just make sure to create needed /etc/jail.conf.d Jail config files.

Lets create a new thin provisioning Jail from it now.

host # zfs list -t snapshot
NAME                          USED  AVAIL     REFER  MOUNTPOINT
zroot/jail/classic@template   224K      -      503M  -

host # zfs clone zroot/jail/classic@template zroot/jail/shadow

host # zfs list -t all
NAME                          USED  AVAIL     REFER  MOUNTPOINT
zroot                        3.31G  15.6G       96K  none
zroot/ROOT                    700M  15.6G       96K  none
zroot/ROOT/default            699M  15.6G      699M  /
zroot/jail                   1.86G  15.6G      194M  /jail
zroot/jail/BASE               191M  15.6G      191M  /jail/BASE
zroot/jail/classic            503M  15.6G      503M  /jail/classic
zroot/jail/classic@template   224K      -      503M  -
zroot/jail/shadow               8K  15.6G      503M  /jail/shadow
zroot/jail/vnet               503M  15.6G      503M  /jail/vnet
zroot/tmp                      96K  15.6G       96K  /tmp
zroot/usr                     780M  15.6G       96K  /usr
zroot/usr/home                 96K  15.6G       96K  /usr/home
zroot/usr/ports                96K  15.6G       96K  /usr/ports
zroot/usr/src                 780M  15.6G      780M  /usr/src
zroot/var                     684K  15.6G       96K  /var
zroot/var/audit                96K  15.6G       96K  /var/audit
zroot/var/crash                96K  15.6G       96K  /var/crash
zroot/var/log                 204K  15.6G      204K  /var/log
zroot/var/mail                 96K  15.6G       96K  /var/mail
zroot/var/tmp                  96K  15.6G       96K  /var/tmp

host # zfs list -r -o space zroot/jail
NAME                AVAIL   USED  USEDSNAP  USEDDS  USEDREFRESERV  USEDCHILD
zroot/jail          15.6G  1.88G        0B    212M             0B      1.67G
zroot/jail/BASE     15.6G   191M        0B    191M             0B         0B
zroot/jail/classic  15.6G   503M      260K    503M             0B         0B
zroot/jail/nfsd     15.6G   513M        0B    513M             0B         0B
zroot/jail/shadow   15.6G   392K        0B    392K             0B         0B
zroot/jail/vnet     15.6G   505M        0B    505M             0B         0B

host # cp /etc/jail.conf.d/classic.conf /etc/jail.conf.d/shadow.conf

host # sed -i '' -e 's|classic|shadow|g' -e 's|20.0.0.50|20.0.0.51|g' /etc/jail.conf.d/shadow.conf

host # cat /etc/jail.conf.d/shadow.conf
shadow {
  # STARTUP/LOGGING
    exec.start = "/bin/sh /etc/rc";
    exec.stop  = "/bin/sh /etc/rc.shutdown";
    exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
    allow.raw_sockets;
    exec.clean;

  # PATH/HOSTNAME
    path = "/jail/${name}";
    host.hostname = "${name}";

  # NETWORK
    ip4.addr = 20.0.0.51;
    interface = em0;
}

host # service jail start shadow
Starting jails: shadow.

host # jls
   JID  IP Address      Hostname                      Path
     1  20.0.0.50       classic                       /jail/classic
     2                  vnet                          /jail/vnet
     3  20.0.0.51       shadow                        /jail/shadow

host # netstat -Win -f inet
Name      Mtu Network            Address              Ipkts Ierrs Idrop    Opkts Oerrs  Coll
em0         - 20.0.0.50/32       20.0.0.50             2854     -     -     2746     -     -
em0         - 20.0.0.51/32       20.0.0.51                3     -     -        0     -     -
lo0         - 127.0.0.0/8        127.0.0.1                6     -     -        6     -     -
bridge0     - 20.0.0.0/24        20.0.0.20           195544     -     -   150555     -     -

While the ‘classic’ Jail uses about 500 MB of space the Thin Provisioning shadow Jail uses less then 1 MB of space.

We can now test its sshd(8) daemon connection.

laptop % ssh 20.51
The authenticity of host '20.0.0.51 (20.0.0.51)' can't be established.
ED25519 key fingerprint is SHA256:qzQnJCHjhHB7jQzmimSLayBfOc3dkLzIVhmrL2r9qxM.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '20.0.0.51' (ED25519) to the list of known hosts.
(root@20.0.0.51) Password for root@shadow:
(root@20.0.0.51) Password for root@shadow:
(root@20.0.0.51) Password for root@shadow:
root@20.0.0.51: Permission denied (publickey,keyboard-interactive).

Yep. Works as it should.

You can create a template for VNET Jail in the same manner.

Single Process Jails

The Docker is mostly know from its single process execution. You choose a process/daemon that you want to run isolated – copy it into the Docker container with its dependencies and you start it.

Exactly the same can be achieved with FreeBSD Jails containers.

For the purposes of this guide we will execute /bin/sh POSIX shell interpreter in a separated FreeBSD Jail. To omit copying additional dependency files we will use the statically compiled version from the well known FreeBSD Rescue System.

host # mkdir -p /jail/shell/dev

host # cp /rescue/sh /rescue/hostname /jail/shell/

host # jail -n shell \
            -c path=/jail/shell \
               mount.devfs \
               host.hostname=shell \
               ip4.addr=20.0.0.111 \
               command=/sh

shell # /hostname
shell

shell # /sh
Cannot read termcap database;
using dumb terminal settings.

shell # for I in 1 2 3; do echo ${I}; done
1
2
3

shell # echo /*
/dev /hostname /sh

You can login to the host system from other SSH session to see that Thin Provisioning shell Jail is running among our other FreeBSD Jails.

host # jls
   JID  IP Address      Hostname                      Path
     1  20.0.0.50       classic                       /jail/classic
     2                  vnet                          /jail/vnet
     3  20.0.0.51       shadow                        /jail/shadow
     4  20.0.0.111      shell                         /jail/shell

Removing Jails

For that purpose we will remove the ‘classic’ Jail.

We will first stop our Jail.

host # service jail stop classic
host # rm -rf /jail/classic
rm: /jail/classic/var/empty: Operation not permitted
rm: /jail/classic/var: Directory not empty
rm: /jail/classic/libexec/ld-elf32.so.1: Operation not permitted
rm: /jail/classic/libexec/ld-elf.so.1: Operation not permitted
rm: /jail/classic/libexec: Directory not empty
rm: /jail/classic/lib/libthr.so.3: Operation not permitted
rm: /jail/classic/lib/libcrypt.so.5: Operation not permitted
rm: /jail/classic/lib/libc.so.7: Operation not permitted
rm: /jail/classic/lib: Directory not empty
(...)

Wait … you are root and you can not delete files? 🙂 This is just another FreeBSD feature. The File Flags. They can also be used with FreeBSD Secure Levels feature. By default some files and directories are marked with some of these flags. To be able to remove these files you first need to remove these flags. We can do that with chflags(1) command.

host # chflags -R 0 /jail/classic
host # rm -rf /jail/classic
host # echo ${?}
0

Now as the File Flags have been removed we can delete all files and dirs with usual rm(1) command. As our ‘classic’ Jail files are gone we should also delete the Jail config at /etc/jail.conf.d/classic.conf file.

host # rm -f /etc/jail.conf.d/classic.conf

Summary

I am not sure what else I should add here … but I am sure that you will let me know in the comments section 🙂

Regards.

EOF

Simple FreeBSD Poudriere Harvester Guide

The Poudriere is one of the topics I kinda omitted. I thought that official pkg(8) packages are more then enough and that occasional needed package recompilations are not the reasons to use a harvester such as Poudriere for that task. What was my needed recompilations? Usually the audio/lame because FreeBSD did not provided package for it for more then a decade while OpenBSD did … and multimedia/ffmpeg just to include that lame(1) support. Also at the beginning of exFAT format the sysutils/exfat-utils and sysutils/fusefs-exfat needed to be built by hand because of additional license agreement.

logo-freebsd

The Table of Contents for this article:

  • Poudriere Name
  • Poudriere Features
  • FreeBSD Host Setup
  • Poudriere Setup
  • Poudriere Jails
  • Poudriere Ports Tree
  • Poudriere Packages to Build List
  • Poudriere Options
  • Nginx
  • Memcached
  • Ccache
  • Poudriere Packages Build Process
  • Our Repository Client Setup
  • Next Builds
  • Summary

There was as time when I had a dedicated script that would do just that – rebuild several ports as packages after the pkg upgrade cycle.

% cat pkg-recompile.sh
#! /bin/sh

# OPTIONS
  PORTS='audio/lame multimedia/ffmpeg sysutils/exfat-utils sysutils/fusefs-exfat'

# ONLY root CAN BUILD PACKAGES/PORTS
if [ "$( whoami )" != "root" ]
then
  echo "ER: only 'root' may use this script"
  exit 1
fi

# BUILD PACKAGES
case ${1} in
  # REBUILD PACKAGES
  (b|build)
    for PORT in ${PORTS}
    do
      pkg unlock -y ${PORT} 1> /dev/null 2> /dev/null
      env BATCH=yes DISABLE_VULNERABILITIES=yes \
        make -C /usr/ports/${PORT} build deinstall install clean &
      MAKE=${!}
      rctl -a process:${MAKE}:pcpu:deny=40
      wait ${MAKE}
      pkg lock -y ${PORT}
    done
    ;;

  # CLEAN
  (c|clean)
    for PORT in ${PORTS}
    do
      make -C /usr/ports/${PORT} clean
    done
    ;;

  # USAGE
  (*)
    echo "usage: ${0##*/} b|c|build|clean"
    exit 1
    ;;

esac

Now none of that is needed anymore … unless you want to Connect FreeBSD to FreeIPA/IDM server … this is the case where Poudriere comes pretty handy. Here you may find my latest attempt with FreeBSD on FreeIPA/IDM with Poudriere Repo guide. You may configure/rebuild needed packages by hand or use a tool that will do that for you and you will then just use its custom build packages repository to install them on multiple systems. Scale often changes many things and this is not different with Poudriere tool.

Poudriere Name

… its quite unfortunate to say the least. I needed some time to actually learn to remember that name properly. Not sure I have any useful tips here – I just split it in half to make it easier to remember – as poud and riere parts. In the beginning I interpreted the pond part as British Pound … uncorrected of course. I really wish the author would name it simpler – like Rebuild or Bob the Builder for example 🙂

So what does Poudriere really means? Its a French translation of Powder Keg – which means a place where gunpowder was stored. There is some logic in that … as all the power (gun power) is in the packages … and Poudriere creates that place.

UPDATE: Dan Langille just reminded me that Poudriere replaced Powder Keg which was a similar tool – that explains the French translation for the name 🙂

Poudriere Features

The Poudriere is a bulk package builder and port tester. It uses a ‘clean’ FreeBSD Jails containers to build packages for defined FreeBSD version (supported or not) and a ‘snapshot’ of a FreeBSD Ports tree. Most of the time it will be the latest FreeBSD Ports tree ‘snapshot’ but nothing prevents you from using older ‘snapshots’ with older packages versions when needed.

Then the results (and logs) of these builds are available as HTML pages and you can (and probably should) host them as some WWW server.

All of this seems scary, complicated and pointless bloat to some … but it gets simple and obvious once you try it. You know me – I have an allergy for bullshit and bloat and Poudriere is really far from both of them.

Its kinda like with the famous UNIX co-creator Dennis Ritchie quote – “UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity.” – just kidding – Poudriere actually is quite simple once you start using it – and that is probably the biggest obstacle to knowing it better. Just start using it (in a VM or in a Jail) to get to know it better and once you know it better – setup it properly and just use. Its far from over-complicated solutions such as SELinux, systemd(1) or Kubernetes.

FreeBSD Host Setup

First things first. The ‘three kings’ of and FreeBSD system configuration:

  • /boot/loader.conf
  • /etc/rc.conf
  • /etc/sysctl.conf

Here they are:

The /boot/loader.conf file.

# cat /boot/loader.conf
security.bsd.allow_destructive_dtrace=0
kern.geom.label.disk_ident.enable=0
kern.geom.label.gptid.enable=0
cryptodev_load=YES
zfs_load=YES

The /etc/rc.conf file.

# cat /etc/rc.conf
  clear_tmp_enable=YES
  syslogd_flags="-ss"
  sendmail_enable=NONE
  hostname=fbsdpr
  ifconfig_em0="inet 10.0.10.123/24"
  defaultrouter="10.0.10.1"
  sshd_enable=YES
  dumpdev=AUTO
  zfs_enable=YES
  nginx_enable=YES
  memcached_enable=YES
  memcached_flags="-l localhost -m 8192"

The /etc/sysctl.conf file.

# cat /etc/sysctl.conf
vfs.zfs.min_auto_ashift=12

From the three above only the /etc/rc.conf is important as the other two only have settings from the bsdinstall(8) installer – as used with the Auto ZFS option.

We will also need to populate /etc/resolv.conf file to have DNS configured.

# echo nameserver 1.1.1.1 > /etc/resolv.conf

Pick your personal DNS server favorite here if the Cloudflare one does not suit your needs.

Poudriere Setup

First we need to install some packages – especially the Poudriere package – to make them more up-to-date we would prefer the latest branch of pkg(8) packages.

# sed -i '' -e 's|quarterly|latest|g' /etc/pkg/FreeBSD.conf

# grep latest /etc/pkg/FreeBSD.conf
  url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",

Now we can add needed packages.

# env ASSUME_ALWAYS_YES=yes \
    pkg install -y \
      poudriere \
      portmaster \
      screen \
      tmux \
      zsh \
      beadm \
      lsblk \
      nginx \
      git-lite \
      htop \
      tree \
      ccache-memcached \
      memcached \
      groff

Some of them are less needed but they definitely does not hurt for workflow. Absolute minimum are:

  • poudriere
  • nginx
  • git-lite
  • ccache-memcached
  • groff

Now … lets prepare some dirs and certs to make our packages signed.

# SSL=/usr/local/etc/ssl
# mkdir -p   ${SSL}/{keys,certs} /usr/ports/distfiles
# chmod 0600 ${SSL}/keys
# openssl genrsa -out ${SSL}/keys/poudriere.key 4096
# openssl rsa -in ${SSL}/keys/poudriere.key -pubout -out ${SSL}/certs/poudriere.cert

As we will be using ZFS for Poudriere we will now create dedicated dataset for it.

# zfs create -o mountpoint=/usr/local/poudriere zroot/poudriere

Now we will create the Poudriere config file.

# cat << EOF > /usr/local/etc/poudriere.conf
ZPOOL=zroot
BASEFS=/usr/local/poudriere
ZROOTFS=/usr/local/poudriere
FREEBSD_HOST=ftp://ftp.freebsd.org
POUDRIERE_DATA=/usr/local/poudriere/data
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key
URL_BASE=http://10.0.10.123/
USE_TMPFS=yes
TMPFS_LIMIT=8
MAX_MEMORY=8
MAX_FILES=2048
DISTFILES_CACHE=/usr/ports/distfiles
KEEP_OLD_PACKAGES=yes
KEEP_OLD_PACKAGES_COUNT=3
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
CCACHE_DIR=/var/ccache
RESTRICT_NETWORKING=no
EOF

Its quite basic – yet it will do the job.

Poudriere Jails

Next we will create Poudriere Jails for each FreeBSD version we want to create pkg(8) repositories with packages.

I will create Jails for all FreeBSD 13.x versions – supported or not.

# poudriere jail -c -j 13-2-R-amd64 -v 13.2-RELEASE
# poudriere jail -c -j 13-1-R-amd64 -v 13.1-RELEASE
# poudriere jail -c -j 13-0-R-amd64 -v 13.0-RELEASE -m ftp-archive

Keep in mind that you need to specify additional -m ftp-archive argument for unsupported FreeBSD versions.

After some time you will end up with ready to use Poudriere FreeBSD Jails containers as shown below.

# poudriere jail -l
13-0-R-amd64 13.0-RELEASE-p13 amd64 ftp-archive 2023-04-28 03:22:05 /usr/local/poudriere/jails/13-0-R-amd64
13-1-R-amd64 13.1-RELEASE-p7  amd64 http        2023-04-27 23:17:13 /usr/local/poudriere/jails/13-1-R-amd64
13-2-R-amd64 13.2-RELEASE     amd64 http        2023-04-27 23:15:30 /usr/local/poudriere/jails/13-2-R-amd64

Poudriere Ports Tree

You may already have an up-to-date FreeBSD Ports tree on your disk at usual /usr/ports location – but we need Poudriere to get its own one too.

# poudriere ports -c

After fetching one you can list it like that.

# poudriere ports -l
default   git+https 2023-04-27 06:16:42 /usr/local/poudriere/ports/default

Poudriere Packages to Build List

Here is the best part – you do not need to build all 33000+ ports – you may specify just several of them. This is what we would do now.

# cat << EOF > /usr/local/etc/poudriere.d/list
sysutils/beadm
sysutils/lsblk
devel/m4
EOF

Accepting all possible licenses is also a good idea.

# echo DISABLE_LICENSES=yes >> /usr/local/etc/poudriere.d/make.conf

We will also specify which options should (and should not) be included in our built packages.

# cat << EOF > /usr/local/etc/poudriere.d/13-0-R-amd64-make.conf
ALLOW_UNSUPPORTED_SYSTEM=yes
DISABLE_LICENSES=yes
EOF

# cat << EOF > /usr/local/etc/poudriere.d/13-1-R-amd64-make.conf
ALLOW_UNSUPPORTED_SYSTEM=yes
DISABLE_LICENSES=yes
EOF

# cat << EOF > /usr/local/etc/poudriere.d/13-2-R-amd64-make.conf
ALLOW_UNSUPPORTED_SYSTEM=yes
DISABLE_LICENSES=yes
EOF

Poudriere Options

You may choose the Poudriere packages options the same interactive way you do when you use the FreeBSD Ports tree.

To do that – here are the needed spells.

# poudriere options -c -j 13-0-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere options -c -j 13-1-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere options -c -j 13-2-R-amd64 -f /usr/local/etc/poudriere.d/list

Nginx

To make most of the Poudriere you will also need some web server. I have chosen Nginx for that task as its currently ‘the’ standard for the Internet.

Its setup is not complicated – just repeat steps below and you are done.

# service nginx enable

# sed -i '' -E 's|text/plain[\t\ ]*txt|text/plain txt log|g' /usr/local/etc/nginx/mime.types

# cat << EOF > /usr/local/etc/nginx/nginx.conf
events {
  worker_connections 1024;
}

http {
  include      mime.types;
  default_type application/octet-stream;

  server {
    listen 80 default;
    server_name ${IP};
    root /usr/local/share/poudriere/html;

    location /data {
      alias /usr/local/poudriere/data/logs/bulk;
      autoindex on;
    }

    location /packages {
      root /usr/local/poudriere/data;
      autoindex on;
    }
  }
}
EOF

# service nginx restart

The /usr/local/etc/nginx/mime.types part will allow you to display the *.log files in the browser instead of ‘forcing’ the browser to pointlessly download them.

Our web server seems to work properly.

# sockstat -l4
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
www      nginx      95263 7  tcp4   *:80                  *:*
root     nginx      95262 7  tcp4   *:80                  *:*
root     sshd       706   5  tcp4   *:22                  *:*

Memcached

As we will be using devel/ccache-memcached package to speed up builds – we would also need memcached to be running.

We already have needed configuration in /etc/rc.conf file so we only need to start it.

# grep memcached /etc/rc.conf
  memcached_enable="YES"
  memcached_flags="-l localhost -m 8192"

# service memcached restart

# sockstat -l4
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
nobody   memcached  37844 13 tcp4   127.0.0.1:11211       *:*
www      nginx      69727 7  tcp4   *:80                  *:*
root     nginx      69726 7  tcp4   *:80                  *:*
root     sshd       706   5  tcp4   *:22                  *:*

Ccache

We will now configure ccache(1) service.

Like success have many father the same ccache(1) have many configs – to be sure – we will propagate all of them.

We will use /var/ccache dir for the ccache(1) cache dir = but feel free to put it somewhere else or even with a dedicated ZFS dataset.

# mkdir -p /var/ccache

# cat << EOF > /usr/local/etc/ccache.conf
max_size = 0
cache_dir = /var/ccache
base_dir = /var/ccache
memcached_conf = --SERVER=localhost:11211
memcached_only = true
EOF

# cat << EOF > /root/.ccache/ccache.conf
max_size = 0
cache_dir = /var/ccache
base_dir = /var/ccache
memcached_conf = --SERVER=localhost:11211
memcached_only = true
EOF

# cat << EOF > /var/ccache/ccache.conf
max_size = 0
cache_dir = /var/ccache
base_dir = /var/ccache
memcached_conf = --SERVER=localhost:11211
memcached_only = true
EOF

The ccache(1) stats output after several builds below.

# ccache -s
cache directory                     /var/ccache
primary config                      /var/ccache/ccache.conf
secondary config      (readonly)    /usr/local/etc/ccache.conf
stats updated                       Fri Apr 28 04:10:17 2023
cache hit (direct)                  4510
cache hit (preprocessed)             713
cache miss                          2481
cache hit rate                     67.80 %
called for link                     5616
called for preprocessing            1476
multiple source files                 18
compile failed                      1143
preprocessor error                   351
bad compiler arguments                72
unsupported source language            9
autoconf compile/link               3147
no input file                        441
cleanups performed                     0
files in cache                      6303
cache size                          26.6 MB

Poudriere Packages Build Process

Now as you have everything configured and ready – you may build your custom packages.

These next commands will build repositories with your configured packages.

# poudriere bulk -j 13-0-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-1-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-2-R-amd64 -f /usr/local/etc/poudriere.d/list

The Poudriere console output is pretty colorful and nice – here is how it looks.

poudriere-build

Lets check how it look on the browser side.

poudriere-www-01

Lets ‘click’ the 13-2-R-amd64-default name for some details.

poudriere-www-02

It shows that 14 packages has been built in the process and all of them succeed.

Lets now click the date_time build name.

poudriere-www-03

You will now see the details about that build run – with logs and time needed for each package build.

You can ‘click’ on the package name to get the build log details.

poudriere-www-04

Here is how our three repositories look after the build process.

# ls -l /usr/local/poudriere/data/packages
total 26K
drwxr-xr-x 4 root wheel 14 2023-04-28 04:10 13-0-R-amd64-default/
drwxr-xr-x 4 root wheel 14 2023-04-28 04:06 13-1-R-amd64-default/
drwxr-xr-x 4 root wheel 14 2023-04-28 04:07 13-2-R-amd64-default/

# tree /usr/local/poudriere/data/packages
/usr/local/poudriere/data/packages
├── 13-0-R-amd64-default
│   ├── All -> .latest/All
│   ├── Latest -> .latest/Latest
│   ├── meta.conf -> .latest/meta.conf
│   ├── meta.pkg -> .latest/meta.pkg
│   ├── meta.txz -> .latest/meta.txz
│   ├── packagesite.pkg -> .latest/packagesite.pkg
│   └── packagesite.txz -> .latest/packagesite.txz
├── 13-1-R-amd64-default
│   ├── All -> .latest/All
│   ├── Latest -> .latest/Latest
│   ├── meta.conf -> .latest/meta.conf
│   ├── meta.pkg -> .latest/meta.pkg
│   ├── meta.txz -> .latest/meta.txz
│   ├── packagesite.pkg -> .latest/packagesite.pkg
│   └── packagesite.txz -> .latest/packagesite.txz
└── 13-2-R-amd64-default
    ├── All -> .latest/All
    ├── Latest -> .latest/Latest
    ├── meta.conf -> .latest/meta.conf
    ├── meta.pkg -> .latest/meta.pkg
    ├── meta.txz -> .latest/meta.txz
    ├── packagesite.pkg -> .latest/packagesite.pkg
    └── packagesite.txz -> .latest/packagesite.txz

10 directories, 15 files

Our Repository Client Setup

Now we need to configure our FreeBSD clients to start using our Poudriere created repositories.

Here is what we need to do on such client.

# mkdir -p /usr/local/etc/pkg/repos

# cat << EOF > /usr/local/etc/pkg/repos/13-2-R-amd64.conf
13-2-R-amd64: {
    url: "http:/10.0.10.123/packages/13-2-R-amd64-default/",
    mirror_type: "http",
    signature_type: "pubkey",
    pubkey: "/usr/local/etc/ssl/certs/poudriere.cert",
    enabled: yes,
    priority: 100
}
EOF

# pkg update -f
Updating FreeBSD repository catalogue...
Fetching meta.conf: 100%    163 B   0.2kB/s    00:01
Fetching packagesite.pkg: 100%    7 MiB 626.5kB/s    00:11
Processing entries: 100%
FreeBSD repository update completed. 32980 packages processed.
Updating 13-2-R-amd64-HEAD repository catalogue...
Fetching meta.conf: 100%    163 B   0.2kB/s    00:01
Fetching packagesite.pkg: 100%    6 KiB   5.7kB/s    00:01
Processing entries: 100%
13-2-R-amd64-HEAD repository update completed. 14 packages processed.
All repositories are up to date.

We now have our first FreeBSD client system configured against our Poudriere created repository.

Lets install/update the m4 package for a test.

# pkg install m4
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
Updating 13-2-R-amd64-HEAD repository catalogue...
13-2-R-amd64-HEAD repository is up to date.
All repositories are up to date.
The following 2 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        m4: 1.4.19,1 [13-2-R-amd64-HEAD]

Installed packages to be REINSTALLED:
        pkg-1.19.1_1 [13-2-R-amd64-HEAD] (options changed)

Number of packages to be installed: 1
Number of packages to be reinstalled: 1

The process will require 2 MiB more space.
9 MiB to be downloaded.

Proceed with this action? [y/N]: y
[1/2] Fetching pkg-1.19.1_1.pkg: 100%    8 MiB   8.7MB/s    00:01
[2/2] Fetching m4-1.4.19,1.pkg: 100%  214 KiB 218.7kB/s    00:01
Checking integrity... done (0 conflicting)
[1/2] Reinstalling pkg-1.19.1_1...
[1/2] Extracting pkg-1.19.1_1: 100%
[2/2] Installing m4-1.4.19,1...
[2/2] Extracting m4-1.4.19,1: 100%

Now some FreeBSD client notes … if you are a Linux fan you probably know that – for example on Red Hat Linux (and its clones) – its relatively easy to just list the configured repositories with yum repolist command.

# yum repolist
repo id         repo name
appstream       CentOS Linux 8 - AppStream
baseos          CentOS Linux 8 - BaseOS
epel            Extra Packages for Enterprise Linux 8 - x86_64
epel-modular    Extra Packages for Enterprise Linux Modular 8 - x86_64
extras          CentOS Linux 8 - Extras

… unfortunately there is not 1:1 equivalent on FreeBSD side for pkg(8) repositories.

The closest one that is available out of the box are:

# pkg -vv | grep -A 999 '^Repositories:'
Repositories:
  FreeBSD: {
    url             : "pkg+http://pkg.FreeBSD.org/FreeBSD:13:amd64/latest",
    enabled         : yes,
    priority        : 0,
    mirror_type     : "SRV",
    signature_type  : "FINGERPRINTS",
    fingerprints    : "/usr/share/keys/pkg"
  }
  13-2-R-amd64: {
    url             : "http://10.0.10.123/packages/13-2-R-amd64-default/",
    enabled         : yes,
    priority        : 100,
    mirror_type     : "HTTP",
    signature_type  : "PUBKEY",
    pubkey          : "/usr/local/etc/ssl/certs/poudriere.cert"
  }

# grep -h '^[^#]' /etc/pkg/* /usr/local/etc/pkg/repos/*
FreeBSD: {
  url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
  mirror_type: "srv",
  signature_type: "fingerprints",
  fingerprints: "/usr/share/keys/pkg",
  enabled: yes
}
13-2-R-amd64: {
    url: "http://10.0.10.123/packages/13-2-R-amd64-default/",
    mirror_type: "http",
    signature_type: "pubkey",
    pubkey: "/usr/local/etc/ssl/certs/poudriere.cert",
    enabled: yes,
    priority: 100
}

Not very pretty.

After asking at GitHub for potential solution or pkg(8)man page overlook I was informed that there is no such option … so I came with my own POSIX /bin/sh shell scripts/functions for the rescue 🙂

For example pkg-repo-var-en.sh that will list all pkg(8) repositories with their enabled/disabled status.

# ./pkg-repo-var-en.sh
REPO          ENABLED  PRIO  URL
FreeBSD       yes      0     pkg+http://pkg.FreeBSD.org/${ABI}/latest
13-2-R-amd64  yes      100   http://10.0.10.123/packages/13-2-R-amd64

# cat pkg-repo-var-en.sh
#! /bin/sh

( # HEADER
  echo -e "\nREPO ENABLED PRIO URL"
  for REPO in /etc/pkg/* /usr/local/etc/pkg/repos/*
  do
    REPOCUR=$( grep '^[^#]' "${REPO}" )

    # REPO
    echo "${REPOCUR}" | awk -F ':' '/\{[\ \t]*$/ {printf(" %s ",$1)}'

    # ENABLED
    echo "${REPOCUR}" | awk '/enabled:/ {printf(" %s ",$NF)}' | tr -cd '[a-zA-Z ]'

    # PRIO
    if echo "${REPOCUR}" | grep -q priority
    then
      echo "${REPOCUR}" | awk '/priority:/ {printf(" %s ",$NF)}' | tr -cd '[0-9 ]'
    else
      echo -n " 0 "
    fi

    # URL
    echo "${REPOCUR}" | grep '^[^#]' | awk -F'"' '/url:/ {print $2}' | tr -d ','

  done 2> /dev/null
) | column -t 2> /dev/null

Next Builds

So … you have successfully build your custom repository once … these are the steps to create new up-to-date package every next time.

// UPDATE JAILS
# poudriere jail -u -j 13-0-R-amd64
# poudriere jail -u -j 13-1-R-amd64
# poudriere jail -u -j 13-2-R-amd64

// UPDATE PORTS
# poudriere ports -u

// BUILD REPOSITORIES
# poudriere bulk -j 13-0-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-1-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-2-R-amd64 -f /usr/local/etc/poudriere.d/list

For example – if there are not new packages/changes – this is how the Poudriere output would look like.

poudriere-build-not-needed

Summary

Not sure what should I add here more … I definitely need to compare Poudriere against ports-mgmt/synth alternative one day … but it is not this day – as Aragorn once said 🙂

Pick your packages to customize and have fun.

Take care.

NFS Server Inside FreeBSD VNET Jail

FreeBSD Jails is a great piece of container technology pioneered several years before Solaris Zones. Not to mention 15 years before Docker was born. Today they still work great and offer some new features like entire network stack for each Jail called VNET. Unfortunately they also have downsides. For example anything related to NFS is broken inside FreeBSD Jails (especially when they are VNET based Jails) and the relevant 251347 Bug Report remains unsolved.

There is however a way to run NFS server inside VNET based FreeBSD Jail – we will use userspace NFS server implementation instead of using the FreeBSD base system kernel space NFS server. Its available as net/unfs3 package and this is exactly what we will gonna use for this guide.

unfs3


Same in plain text below.

/ % cd /usr/ports/net/unfs3

/usr/ports/net/unfs3 % cat pkg-descr
UNFS3 is a user-space implementation of the NFSv3 server specification. It
provides a daemon for the MOUNT and NFS protocols, which are used by NFS
clients for accessing files on the server.
Since it runs in user-space, you can use it in a jail.

WWW: https://unfs3.github.io/

/usr/ports/net/unfs3 % pkg info -l unfs3           
unfs3-0.9.22_2:
        /usr/local/man/man7/tags.7.gz
        /usr/local/man/man8/unfsd.8.gz
        /usr/local/sbin/unfsd
        /usr/local/share/licenses/unfs3-0.9.22_2/BSD3CLAUSE
        /usr/local/share/licenses/unfs3-0.9.22_2/LICENSE
        /usr/local/share/licenses/unfs3-0.9.22_2/catalog.mk

Its also pity that VNET feature for FreeBSD Jails is not well documented. Search the FreeBSD Handbook or FreeBSD FAQ for the VNET or VIMAGE keywords. Not a single match. There are only man pages and some stuff left in the /usr/share/examples/jails dir. There is also FreeBSD Mastery: Jails book by Michael W. Lucas but its 3 years old already.

Setup

Below you will find the list of systems we will use in this guide.

10.0.10.250  host
10.0.10.251  nfs_server

The host is a common FreeBSD server installed on a physical or virtual machine. We will also use it as out NFS client and mount the NFS share there. The nfs_server is a FreeBSD Jail with VNET separate network stack enabled. We will run NFS server from this host nfs_server system. Both of them run latest FreeBSD 13.1-RELEASE but I suspect that it should also work the same on older versions.

FreeBSD Host and NFS Client (host)

First we will setup the host machine. Its typical default ZFS FreeBSD install – nothing special about that. To use the VNET enabled Jails we will use jib tool from the /usr/share/examples/jails directory as we will need it to automate epair(4) interfaces management.

root@host:/ # install -o root -g wheel -m 0555 /usr/share/examples/jails/jib /usr/sbin/jib

Our next step would be to fetch and setup the nfs_server FreeBSD Jail. We will not waste time in compilation – we will fetch the base.txz directly from FreeBSD page.

root@host:/ # mkdir -p /jail/BASE
root@host:/jail/BASE # cd /jail/BASE
root@host:/jail/BASE # fetch http://ftp.freebsd.org/pub/FreeBSD/releases/amd64/13.1-RELEASE/base.txz
root@host:/jail/BASE # mv base.txz 13.1-base.txz

Now the nfs_server FreeBSD Jail.

root@host:/ # mkdir -p /jail/nfs_server
root@host:/jail/nfs_server # cd /jail/nfs_server
root@host:/jail/nfs_server # tar -xzf /jail/BASE/13.1-base.txz --unlink

The main FreeBSD /etc/rc.conf configuration file does not hold any special setting – pretty usual stuff.

root@host:/ # cat /etc/rc.conf
# NETWORK
  hostname="host"
  ifconfig_em0="inet 10.0.10.250/24 up"
  defaultrouter="10.0.10.1"
  gateway_enable="YES"

# DAEMONS
  dumpdev="AUTO"
  sshd_enable="YES"
  zfs_enable="YES"
  sendmail_enable="NO"
  sendmail_submit_enable="NO"
  sendmail_outbound_enable="NO"
  sendmail_msp_queue_enable="NO"
  update_motd="NO"

# JAILS
  jail_enable="YES"
  jail_parallel_start="YES"
  jail_list="nfs_server"

The nfs_server FreeBSD Jail as configured in the /etc/jail.conf config file.

root@host:/ # cat /etc/jail.conf
nfs_server {
  path = "/jail/${name}";
  host.hostname = "${name}";
  allow.raw_sockets = 1;
  allow.set_hostname = 1;
  allow.sysvipc = 1;
  mount.devfs;
  exec.clean;
  vnet;
  vnet.interface = "e0b_${name}";
  exec.prestart += "/usr/sbin/jib addm -b _bridge0 ${name} em0";
  exec.poststop += "/usr/sbin/jib destroy ${name}";
  exec.start += "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_${name}_console.log";
}

… and last but not least lets make sure the following DNS haiku will not bother us 🙂

dns

Setup the /etc/hosts on the host system.

root@host:/ # tail -3 /etc/hosts
10.0.10.250 host
10.0.10.251 nfs_server

FreeBSD NFS Server VNET Jail (nfs_server)

As our FreeBSD Jail is installed we will now start it and configure it.

root@host:/ # service jail onestart nfs_server

root@host:/ # jls
   JID  IP Address      Hostname                      Path
     1                  nfs_server                    /jail/nfs_server

root@host:/ # jexec 1

root@nfs_server:/ # 

First we will install latest net/unfs3 package – this userspace NFS server is also very minimal and does not have any dependencies.

root@nfs_server:/ # echo nameserver 1.1.1.1 > /etc/resolv.conf

root@nfs_server:/ # sed -i '' s/quarterly/latest/g /etc/pkg/FreeBSD.conf

root@nfs_server:/ # pkg install unfs3

root@nfs_server:/ # pkg info -qoa
ports-mgmt/pkg
net/unfs3

Now we will configure our NFS share under /share dir and start the unfsd(8) userspace NFS server.

root@nfs_server:/ # mkdir /share

root@nfs_server:/ # cat /etc/exports
/share  10.0.10.250(rw,no_root_squash,no_all_squash)

… last but not least – DNS 🙂

root@nfs_server:/ # tail -3 /etc/hosts
10.0.10.250 host
10.0.10.251 nfs_server

As we are using VNET network stack in a FreeBSD Jail we will have to address the network interface in the Jails /etc/rc.conf file. The unfsd(8) daemon does not start without rpcbind service so we will also enable it.

root@nfs_server:/ # cat /etc/rc.conf
# NETWORK
  hostname="nfs_server"
  ifconfig_e0b_nfs_server="10.0.10.251/24 up"
  defaultrouter="10.0.10.1"

# DAEMONS
  sshd_enable="YES"
  rpcbind_enable="YES"
  sendmail_enable="NO"
  sendmail_submit_enable="NO"
  sendmail_outbound_enable="NO"
  sendmail_msp_queue_enable="NO"

We will make unfsd(8) start automatically at Jails start with plain old /etc/rc.local file.

root@nfs_server:/ # cat /etc/rc.local 
/usr/local/sbin/unfsd &

We will not restart our FreeBSD Jail to make these changes take effect.

root@host:/ # service jail onerestart nfs_server

root@host:/ # jls
   JID  IP Address      Hostname                      Path
     2                  nfs_server                    /jail/nfs_server

root@host:/ # jexec 2

root@nfs_server:/ # 

After startup we can see that unfsd(8) is listening on a NFS 2049 port.

root@nfs_server:/ # sockstat -l4
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      
root     sshd       1261  4  tcp4   *:22                  *:*
root     sendmail   1241  5  tcp4   127.0.0.1:25          *:*
root     unfsd      1223  3  udp4   *:2049                *:*
root     unfsd      1223  4  tcp4   *:2049                *:*
root     rpcbind    1196  9  udp4   *:111                 *:*
root     rpcbind    1196  10 udp4   *:842                 *:*
root     rpcbind    1196  11 tcp4   *:111                 *:*
root     syslogd    1188  6  udp4   *:514                 *:*
  

We should have our epair(4) interface called e0b_nfs_server addressed properly.

root@nfs_server:/ # ifconfig 
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
        inet 127.0.0.1 netmask 0xff000000
        groups: lo
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
e0b_nfs_server: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=8<VLAN_MTU>
        ether 0e:27:dd:b3:81:88
        hwaddr 02:30:0d:9f:57:0b
        inet 10.0.10.251 netmask 0xffffff00 broadcast 10.0.10.255
        groups: epair
        media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

Mount /share on NFS Client

I added that NFS entry to the /etc/fstab file on the host machine.

root@host:~ # cat /etc/fstab 
#DEV         #MNT       #TYPE    #OPT  #DUMP/PASS
/dev/ada0p1  /boot/efi  msdosfs  rw    2 2
/dev/ada0p3  none       swap     sw    0 0

#DEV                #MNT  #TYPE  #OPT       #DUMP/PASS
10.0.10.251:/share  /mnt  nfs    rw,noauto  0 0

We will now attempt to mount the /share NFS export on the host machine.

root@host:/ # mount /mnt

root@host:/ # mount | grep share
10.0.10.251:/share on /mnt (nfs)

root@host:/ # cd /mnt

root@host:/mnt # :> FILE

root@host:/mnt # ls -l FILE
-rw-r--r-- 1 root root 0 2022-05-21 22:53 FILE

root@host:/mnt # rm FILE

Seems to work properly.

Here are also network interfaces on the host machine.

root@host:/ # ifconfig 
em0: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=4810099<RXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,VLAN_HWFILTER,NOMAP>
        ether 08:00:27:b3:81:88
        inet 10.0.10.250 netmask 0xffffff00 broadcast 10.0.10.255
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
        inet 127.0.0.1 netmask 0xff000000
        groups: lo
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
em0_bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        ether 58:9c:fc:10:ff:dd
        id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
        maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200
        root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
        member: e0a_nfs_server flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
                ifmaxaddr 0 port 4 priority 128 path cost 2000
        member: em0 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
                ifmaxaddr 0 port 1 priority 128 path cost 20000
        groups: bridge
        nd6 options=9<PERFORMNUD,IFDISABLED>
e0a_nfs_server: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=8<VLAN_MTU>
        ether 02:27:dd:b3:81:88
        hwaddr 02:30:0d:9f:57:0a
        groups: epair
        media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>


Future of NFS Server in Jails

This setup – while allowing to run the NFS server inside FreeBSD Jail with even VNET enabled has its drawbacks unfortunately. First is that it is run in userspace instead of kernel space – which means its slower. Second is that the unfsd(8) only implements NFS version 3 – so no version 4 is not possible.

freebsd-foundation-logo

Where we can go from here? Like with WiFi stuff IMHO the FreeBSD Foundation could step in to sponsor the missing bits of NFS server and VNET to make these native tools work they should. Its up to you to put the pressure on the FreeBSD Foundation when they as what are you missing from the FreeBSD UNIX system that could be improved with one of their projects. You may also join the discussion at the 251347 Bug Report of course.

I think its a big loss that native kernel space NFS server is not currently possible with VNET FreeBSD Jails.

EOF

Secure Containerized Browser

By default Chromium on OpenBSD (not so) recently got OpenBSD’s unveil(2) support. That means that of you run Chromium with --enable-unveil flag then it will be prevented from accessing anything other than the ~/Downloads directory. No such thing on FreeBSD exists. Firefox or Chromium have access to all files user can read – even to your system sshd(8) keys or even worse to your private keys laying in the ~/.ssh dir. On FreeBSD thanks to its FreeBSD Jails technology we can create secure containerized browser with only access to the specified directory. On my system its the ~/download dir.

You may want to check other desktop related articles in the FreeBSD Desktop series on the FreeBSD Desktop page.

Configuration

We will start with /etc/jail.conf file configuration. For the record – we will be using /jail for our FreeBSD Jails main dir. I will also use /jail dir for the ‘base’ FreeBSD versions tarballs as a convenient place. As I use 10.0.0.0/24 address space I will use 10.0.0.200 for our containerized browser. Feel free to pick other IP from which you will be able to reach the Internet. The /etc/jail.conf is shown below. One thing to note here. As I am using WiFi wlan0 interface I have put that into the Jail configuration. If you use LAN interface (for example em0) then put that instead into this Jail config. As you see from the example below we will be using Firefox browser in out example.

root@host # cat /etc/jail.conf

# GLOBAL
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.clean;
  exec.consolelog = "/var/log/jail_${name}_console.log";
  mount.devfs;
  host.hostname = ${name};
  path = /jail/${name};

# JAILS
  firefox {
    devfs_ruleset = 30;
    ip4.addr = 10.0.0.200;
    interface = wlan0;
    allow.raw_sockets;
    allow.sysvipc;
    mount.fstab = "/jail/firefox/etc/fstab";
  }

As you can see we will also be using devfs(8) rules in the /etc/devfs.rules file – shown below. This configuration is needed to have access to sound(4) in our FreeBSD Jail. If you do not need sound then you can delete devfs_ruleset = 30; from the /etc/jail.conf file and also do not add anything in the /etc/devfs.rules file.

root@host # cat /etc/devfs.rules
[sound=30]
add path 'mixer*' unhide
add path 'dsp*'   unhide

If we are about to share the ~/download dir with our containerized browser then we need to somehow add that information to our FreeBSD Jail. We will use the FreeBSD’s mount_nullfs(8) command to mount our currently existing ~/download dir into our FreeBSD Jail. We will use following /jail/firefox/etc/fstab for that purpose.

root@host # cat /jail/firefox/etc/fstab
#SOURCE         #MNT                                      #TYPE   #OPTS       #DUMP/PASS
/data/download  /jail/firefox/usr/home/vermaden/download  nullfs  rw,noatime  0 0

Of course you do not have to share any directory with your containerized browser.

You may as well would want to make this jails start everytime you boot your system. To do that add below lines to the /etc/rc.conf file as shown below.

jail_enable=YES
jail_parallel_start=YES
jail_list="firefox"

Create the Jail

As I use FreeBSD 13.0-RELEASE I would be using also the FreeBSD 13.0-RELEASE Jail for that purpose. If you are running for example FreeBSD 12.3-RELEASE then make sure that you will use FreeBSD 12.3-RELEASE Jail. The Jail version needs to be lower then the host system version. We will now fetch needed FreeBSD ‘base’ file and unpack it within /jail/firefox dir where our container would live. We will also configure several other basic files such as /etc/resolv.conf or /etc/hosts files.

root@host # mkdir -p /jail/BASE /jail/firefox /jail/firefox/usr/home/vermaden/download

root@host # fetch -o /jail/BASE/13.0-RELEASE-base.txz \
    http://ftp.freebsd.org/pub/FreeBSD/releases/amd64/13.0-RELEASE/base.txz

root@host # tar -xvpf /jail/BASE/13.0-RELEASE-base.txz -C /jail/firefox

root@host # echo nameserver 1.1.1.1 > /jail/firefox/etc/resolv.conf

root@host # echo 10.0.0.200 firefox >> /jail/firefox/etc/hosts

root@host # cat << EOF > /jail/firefox/etc/fstab
#SOURCE         #MNT                                      #TYPE   #OPTS       #DUMP/PASS
/data/download  /jail/firefox/usr/home/vermaden/download  nullfs  rw,noatime  0 0
EOF

We will now start our fresh FreeBSD Jail.

root@host # service jail onestart firefox

We can now also see two new mounts in the mount(8) output.

root@host # mount | tail -2
/data/download on /jail/firefox/usr/home/vermaden/download (nullfs, local, noatime)
devfs on /jail/firefox/dev (devfs)

root@host # mount -p | tail -2 | column -t
/data/download /jail/firefox/usr/home/vermaden/download nullfs rw,noatime 0 0
devfs /jail/firefox/dev devfs rw 0 0

You may want to update the FreeBSD version to the most up to date one with freebsd-update(8) commands.

root@host # freebsd-update -b /jail/firefox fetch
root@host # freebsd-update -b /jail/firefox install

Install Needed Packages

Before installing anything we will first switch to the latest branch for the pkg(8) packages to have most up to date software. We will then process to installing the Firefox package. We will also need x11/xauth package for X11 Forwarding process.

root@host # sed -i '' s.quarterly.latest.g /jail/firefox/etc/pkg/FreeBSD.conf

root@host # grep latest /jail/firefox/etc/pkg/FreeBSD.conf
  url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",

root@host # jls
   JID  IP Address      Hostname                      Path
     1  10.0.0.200      firefox                       /jail/firefox

root@host # jexec 1

(root@jail) # pkg install -y firefox xauth

Create Matching User and Configure sshd(8) Daemon

We will now enter our FreeBSD Jail again for several other needed tasks for our containerized browser to be working. First is creating inside similar user as you currently use inside. Especially with the same UID/GID to have files with proper permissions in your real ~/download directory instead of files with other UID/GID that you will have to chown(8) with root user. As my vermaden user uses UID/GID 1000 I will also use that inside. I will also set simple password that You will only use once – to copy your public SSH key there.

root@host # jexec 1

(root@jail) # echo your-username-password-goes-here | pw user add -u 1000 -n vermaden -m -s /bin/sh -h 0

Now we need to run /usr/local/bin/dbus-uuidgen --ensure once to make sure DBUS is initialized properly. Firefox and many other apps would not start if we omit that step.

(root@jail) # /usr/local/bin/dbus-uuidgen --ensure

Now the sshd(8) daemon. The only thing we need to do is to add it to the system startup and also add X11UseLocalhost no option to its config file.

(root@jail) # sysrc sshd_enable=YES
sshd_enable: NO -> YES

(root@jail) # echo X11UseLocalhost no >> /etc/ssh/sshd_config

(root@jail) # service sshd start
Generating RSA host key.
2048 SHA256:VnrvItf0tl738C5Oc2St6T63/6o8zaDlfUskB+NrElo root@firefox (RSA)
Generating ECDSA host key.
256 SHA256:ZAjcAGqlrVwvY+J9MuVzErx9QUOqIOJE3nJX/Oqwtpk root@firefox (ECDSA)
Generating ED25519 host key.
256 SHA256:JdzUql2D2+X8iBn3c1jWDHQRNQMKqWGOcL4J16fIX0E root@firefox (ED25519)
Performing sanity check on sshd configuration.
Starting sshd.

Copy Public SSH Key and Start

Copying your public SSH key is optional but if you omit this step then you would have to type your FreeBSD Jail user password every time you would want to start your secure Firefox instance.

vermaden@host % ssh-copy-id -i ~/.ssh/id_rsa vermaden@10.0.0.200
Password:

Now you can start your containerized browser. I have added some useful flags for ssh(1) client like compression with -C and fastest supported encryption with -c aes128-ctr option. The -X is for X11 Forwarding option. I also added GDK_SYNCHRONIZE=1 to make Firefox yell less 🙂

vermaden@host % ssh -C -c aes128-ctr -X vermaden@10.0.0.200 env GDK_SYNCHRONIZE=1 firefox --new-instance

Now without password you should see fresh Firefox instance.

firefox-fresh

I will now try to play some random video. I can not show you that from an image but the sound also works 🙂

firefox-youtube

Similar setup can be created for other browser if Firefox is not your browser of choice of course. If you are curious how much space it uses its about this:

root@host # du -smx /jail/BASE/13.0-RELEASE-base.txz /jail/firefox 
181 /jail/BASE/13.0-RELEASE-base.txz
1603 /jail/firefox

root@host # du -smx -A /jail/BASE/13.0-RELEASE-base.txz /jail/firefox
181 /jail/BASE/13.0-RELEASE-base.txz
2601 /jail/firefox

I also added the -A flag in second the du(1) command to show you how much more space would be used without the ZFS LZ4 compression.

UPDATE 1 – Use XPRA Instead of X11 Forwarding

Some people complained that this is quite good setup but they were not happy with using X11 Forwarding for the connection method. I decided to add additional XPRA method to connect to our secure containerized browser. First thing you need to do is to install the x11/xpra package on both the host system and also inside the jail container.

root@host # pkg install -y xpra
(root@jail) # pkg install -y xpra

Now – after logging into your user in the Jail container – vermaden in may case – we will use the xpra commands to create new session with Firefox browser.

Lets see if any xpra sessions currently exists.

(vermaden@jail) % xpra list
Warning: XDG_RUNTIME_DIR is not defined
 and '/run/user/1000' does not exist
 using '/tmp'
No xpra sessions found

Seems not. We can not start our Firefox session.

(vermaden@jail) % xpra start --bind-tcp=:14500 --start='firefox --new-instance'
Warning: XDG_RUNTIME_DIR is not defined
 and '/run/user/1000' does not exist
 using '/tmp'
Entering daemon mode; any further errors will be reported to:
  /tmp/xpra/S19958.log
Actual display used: :0
Actual log file name is now: /tmp/xpra/:0.log

We can see in the xpra list command that new session appeared.

(vermaden@jail) % xpra list
Warning: XDG_RUNTIME_DIR is not defined
 and '/run/user/1000' does not exist
 using '/tmp'
Found the following xpra sessions:
/home/vermaden/.xpra:
        LIVE session at :0

We can also see that xpra is now listening on the 14500 port.

(vermaden@jail) % sockstat -l4
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
vermaden python3.8  20781 3  tcp4   10.0.0.200:14500      *:*
root     sshd       58454 3  tcp4   10.0.0.200:22         *:*
root     syslogd    48568 5  udp4   10.0.0.200:514        *:*

We will now move to out host and start graphical xpra client to connect to our FreeBSD Jail with Firefox process.

update1-xpra-main

After clicking the large Connect button we can now enter our Jail address.

update1-xpra-connect

After again clicking the Connect button on the bottom this time we can now se our Firefox browser from our secure environment.

update1-xpra-firefox

After we done our job at more secure Firefox we can now end our xpra session on the jail system.

(vermaden@jail) % xpra stop
Warning: XDG_RUNTIME_DIR is not defined
 and '/run/user/1000' does not exist
 using '/tmp'
xpra initialization error:
 cannot find any live servers to connect to

(vermaden@jail) % xpra list
Warning: XDG_RUNTIME_DIR is not defined
 and '/run/user/1000' does not exist
 using '/tmp'
No xpra sessions found

As XPRA provides OpenGL acceleration you may verify that fact from your host system using below command.

vermaden@host % xpra opengl
Warning: XDG_RUNTIME_DIR is not defined
 and '/run/user/1000' does not exist
 using '/tmp'
Warning: cannot handle window transparency
 screen is not composited
Warning: vendor 'Intel Open Source Technology Center' is greylisted,
 you may want to turn off OpenGL if you encounter bugs
Warning: window 0xffffffff changed its transparency attribute
 from False to True, behaviour is undefined
GLU.version=1.3
GLX=1.4
accelerate=3.1.5
accum-alpha-size=0
accum-blue-size=0
accum-green-size=0
accum-red-size=0
alpha-size=0
aux-buffers=0
blue-size=8
buffer-size=24
depth=24
depth-size=0
direct=True
display_mode=ALPHA, DOUBLE
double-buffered=True
green-size=8
level=0
max-viewport-dims=16384, 16384
opengl=3.0
pyopengl=3.1.5
red-size=8
renderer=Mesa DRI Intel(R) HD Graphics 3000 (SNB GT2)
rgba=True
safe=True
shading-language-version=1.30
stencil-size=0
stereo=False
success=True
texture-size-limit=8192
transparency=True
vendor=Intel Open Source Technology Center
zerocopy=True

You can also use VNC or other methods of course.

Hope that helps 🙂

EOF

Quare FreeBSD?

I really wanted to make this article short … but I failed miserably. At least I tried to organize it well so one may get back to it after ‘some’ reading because its not a short lecture. I wanted to title it Why FreeBSD? but when you type that into your favorite duck.com search engine there are so many similar articles. I wanted it to have distinguished and unique name so I used Latin word for ‘why‘ which is ‘quare‘.

logo-freebsd

What FreeBSD can offer you that other operating systems does not? From all of the operating systems I used I find FreeBSD to suck the least. This post is not here to convince you to use or try FreeBSD – this you will have to do by yourself. This article will show you why FreeBSD is valuable or better alternative to other operating systems and is definitely not dying.

This is the Table of Contents for this article.

  • Base System
  • ZFS Boot Environments
  • Rescue
  • Audio
  • Jails
  • Bhyve
  • FreeBSD Ports Infrastructure
  • Updating/Building from Source
  • Storage
  • Init System
  • Linux Binary Compatibility
  • Simplicity
  • Evolution Instead Rewriting
  • Documentation
  • Community
  • Closing Thoughts
  • External Discussions

Base System

When you install a Linux system its just a bunch of RPM or DEB packages. For example of you install CentOS 7.8 Minimal variant you end up with several hundred RPM packages installed. After a week or month many of these packages will get updates sometimes making this CentOS system unusable or even unbootable (recent GRUB Boothole problem for example). On the contrary FreeBSD comes with a Base System concept. This means that when you install FreeBSD you install a minimal system as a whole. No packages or subsystems to be separately updated. Just whole Base System. That means that /boot /bin /sbin /usr /etc /lib /libexec /rescue directories are untouchable by any packages. When you decide to install packages (or build them using FreeBSD Ports) they will all fall into the /usr/local prefix. That means /usr/local/etc for configuration. The /usr/local/bin and /usr/local/sbin directories for binaries. The /usr/local/lib and /usr/local/libexec for libraries and so on. The FreeBSD Base System kernel modules are kept in the same dir along with the kernel in the /boot/kernel directory. To make things tidy all kernel modules that are provided by packages go into the /boot/modules dir. Everything has its place and its separated.

That is separation between Base System binaries (at /bin /sbin /usr/bin /usr/sbin dirs) and Third Party Packages maintained by pkg(8) and are located at /usr/local/bin and /usr/local/sbin dirs. We all know differences between bin (user) and sbin (root) binaries but in FreeBSD there is also another more UFS related separation. When there was only UFS filesystem in the FreeBSD world the /bin and /sbin binaries were available at boot after the root (/) filesystem was mounted and yet before /usr  filesystem was mounted – this is historical (and still useful in the UFS setups) distinction dating to old UNIX days. In ZFS setups it does not matter as all files are on ZFS pool anyway.

Illustration of the Base System / dir and optional for third party applications /usr/local dir.

base-system-dirs

The FreeBSD Base System separation also helps with another thing – if any package gets the ‘great’ idea to install new compiler named cc and override the default system compiler … or to add libraries/includes in such a way that makes it super hard to get back into a working system. If some random FreeBSD package would add libc.so to /usr/local/lib dir then you are covered and not prevented from running programs as usual because FreeBSD system binaries are linked to stuff in /usr/lib dir. This is why there is PATH variable on UNIX systems (and FreeBSD as well) to set which directories should be searched for binaries first. On FreeBSD by default its set search Base System binaries dirs first and then Third Party Packages later.

You can update (or not) the Base System separately from the installed packages with freebsd-update(8) command when using RELEASE or by recompiling with make buildworld and make installworld commands when using STABLE/CURRENT systems. When it comes to packages you can update them using the pkg(8) tool or portmaster when building from FreeBSD Ports tree under /usr/ports dir. That means that any packages updates will not touch your FreeBSD Base System at all. For example when you mess up (and I have done that in the beginning of my FreeBSD journey) the compiled ports and packages and you want to start over the only thing you have to do is remove /usr/local and /boot/modules and /var/db/pkg directories. That’s it. You are just reverted to your Base System and can start over. This is just not possible when using Linux system. Even with Gentoo that many concepts are based on FreeBSD ideas does not have Base System feature. This Base System also have additional feature. Because its separated from packages version no one stops you from running oldshool FreeBSD 9.0 from 2012 and install there latest Firefox 80 or LibreOffice 7.0. You can not install latest Firefox on Ubuntu from 2012 …

One may be ‘afraid’ that such Base System independent from installed packages would take more space but nothing far more from the truth. The fresh installed FreeBSD 12.1 system uses less then 1 GB of disk space and takes less then 75 MB of RAM with sshd(8) running. For the comparison fresh CentOS 7.8 install with ‘Minimal’ set chosen takes 1.1 GB of disk space and uses more then 100 MB RAM with sshd(8) running. Such CentOS system is really naked and really needs more packages to be usable while FreeBSD with its Base System is far more capable and powerful and comes along with builtin latest version of LLVM/CLANG compiler suite for example.

More on the Base System topic:

ZFS Boot Environments

I have talked about this many times and probably one time too less because Linux world still ignores this bless. Having ZFS Boot Environments its such a game changer that once you realize how powerful it is you will never want to use a system that does not support it. The idea is that you can snapshot a running system at any moment of time and then reboot into that moment (or snapshot) if something happened. Its perfect solution for upgrade or changes to the system. The FreeBSD systems are already well ‘protected’ from problems arising after updating the packages but ZFS Boot Environments takes this to a whole new level.

groundhog

Like in the movie Groundhog Day (1993) with ZFS Boot Environments you will have limitless chances to get your shit together. Even the Base System updates and changes are protected by it. You can even transport that Boot Environment by using zfs send and zfs recv commands to other system … or propagate it on many systems. You can create Jails containers from it … or install new version of FreeBSD in the new Boot Environment and reboot into it while still having your older ‘production’ system untouched.

More on the ZFS Boot Environments topic:

Rescue

When you really mess up to the point that even Base System concept or ZFS Boot Environments feature did not stopped you from killing your FreeBSD installation then there is one more level of rescue … the Rescue subsystem.

rescue

You have about 150 statically linked binaries available at your disposal for the rescue mission of that FreeBSD installation. You probably think now that if its so many binaries then it probably takes a lot of space … nothing far more from the truth. Its actually one static binary with hardlinks … and it takes whooping 11 MB of disk space.

FreeBSD # ls -lh /rescue | head -5
total 1118446
-r-xr-xr-x  146 root  wheel    11M 2020.02.19 21:10 [
-r-xr-xr-x  146 root  wheel    11M 2020.02.19 21:10 bectl
-r-xr-xr-x  146 root  wheel    11M 2020.02.19 21:10 bsdlabel
-r-xr-xr-x  146 root  wheel    11M 2020.02.19 21:10 bunzip2

They Rescue subsystem even contains such binaries as bectl(8) for ZFS Boot Environments management or zfs(8) and zpool(8) commands for the ZFS filesystem. Here is complete list of these binaries.

FreeBSD # ls /rescue
[           dd               fsck_ffs      init       mdmfs          ping      rtsol        unlink
bectl       devfs            fsck_msdosfs  ipf        mkdir          ping6     savecore     unlzma
bsdlabel    df               fsck_ufs      iscsictl   mknod          pkill     sed          unxz
bunzip2     dhclient         fsdb          iscsid     more           poweroff  setfacl      unzstd
bzcat       dhclient-script  fsirand       kenv       mount          ps        sh           vi
bzip2       disklabel        gbde          kill       mount_cd9660   pwd       shutdown     whoami
camcontrol  dmesg            geom          kldconfig  mount_msdosfs  rcorder   sleep        xz
cat         dump             getfacl       kldload    mount_nfs      rdump     spppcontrol  xzcat
ccdconfig   dumpfs           glabel        kldstat    mount_nullfs   realpath  stty         zcat
chflags     dumpon           gpart         kldunload  mount_udf      reboot    swapon       zdb
chgrp       echo             groups        ldconfig   mount_unionfs  red       sync         zfs
chio        ed               gunzip        less       mt             rescue    sysctl       zpool
chmod       ex               gzcat         link       mv             restore   tail         zstd
chown       expr             gzip          ln         nc             rm        tar          zstdcat
chroot      fastboot         halt          ls         newfs          rmdir     tcsh         zstdmt
clri        fasthalt         head          lzcat      newfs_msdos    route     tee          
cp          fdisk            hostname      lzma       nextboot       routed    test         
csh         fsck             id            md5        nos-tun        rrestore  tunefs       
date        fsck_4.2bsd      ifconfig      mdconfig   pgrep          rtquery   umount   

More on the Rescue topic:

Audio

Not many people expect from FreeBSD to shine in that department but it shines a lot here and not from yesterday but from decades. Remember when Linux got rid of the old OSS subsystem with one channel and came up with ‘great’ idea to write ALSA? I remember because I used Linux back then. Disaster is very polite word to describe Linux audio stack back then … and then PulseAudio came and whole Linux audio system got much worse. Back then because of that one OSS channel and many ALSA channels meant that ONLY ONE application with OSS backend could do the sound (for example WINE). But if another application would want to ‘make’ sound using OSS and you already have WINE started then it will be soundless because that one and only OSS channel was already taken. And remember that ALSA was so bad back then that KDE or GNOME made their own sound daemons mixing audio in userspace that were incompatible with each other. That means if you used KDE and GNOME apps back then you could have sound from GNOME apps but not from KDE apps or vice versa. One big fucking audio hell on Linux.

audio

Lets get back to FreeBSD audio then. What FreeBSD offered? A whooping 256 OSS channels mixed live in kernel for low latency. Everything audio related just worked out of the box – and still works today. You could have WINE or KDE/GNOME sound backends attached to their OSS channels and also ALSA apps getting their sound device without a problem. Even when you plugged a 5.1 surround system into FreeBSD it worked out of the box without any configuration and applications were able to use it immediately. That FreeBSD audio supremacy remains today as PulseAudio sound mixing in userspace while generally working incorporates large latency on Linux compared to in kernel FreeBSD mixing with low latency.

Comrade meka suggested that FreeBSD is also the only OS which has virtual_oss that allows mixing/resampling/compressing in user space and allows one to have Bluetooth headphones and USB microphone represented as single sound card.

More on the Audio topic:

Jails

The FreeBSD Jails are one of the oldest OS Level Virtualization implementations dating back to 1999. Even the Solaris Zones/Containers came five years later in 2004.

containers

After Docker was introduced in Linux the term OS Level Virtualization became less used to the Containers term and now the FreeBSD Jails along with Solaris Zones/Containers are named 1st generation containers. But that naming nomenclature change does not make FreeBSD Jails less powerful. They are also really brain dead simple to use. You just need a directory – for example /jail/nextcloud – where you will extract the FreeBSD Base System for desired release version – for example base.txz from 12.1-RELEASE and create the Jail config in the /etc/jail.conf file as shown below.

FreeBSD # mkdir -p /jail/nextcloud
FreeBSD # fetch -o - http://ftp.freebsd.org/pub/FreeBSD/releases/amd64/12.1-RELEASE/base.txz | tar --unlink -xpJf - -C /jail/nextcloud
FreeBSD # cat /etc/jail.conf
nextcloud {
  host.hostname = nextcloud.local;
  ip4.addr = 10.0.0.100;
  path = /jail/nextcloud;
}

Now you can start you Jail right away.

FreeBSD # service jail onestart nextcloud
Starting jails: nextcloud.

Voila! Your FreeBSD Jail is already running.

FreeBSD # jls
   JID  IP Address      Hostname                      Path
     1  10.0.0.100      nextcloud.local               /jail/nextcloud

You can of course have a trimmed down version of FreeBSD Base System in the Jail if that is needed. The ZFS filesystem also helps here greatly because with zfs clone only your ‘base’ Jail will take space and only the changes you make to Jails created from it. Thanks to other FreeBSD subsystem – the Linux Binary Compatibility – you can also create a Linux Jail – for example running Devuan or Ubuntu Jail.

The FreeBSD Jails are also very lightweight. You can boot and use about 1000 FreeBSD Jails on a single FreeBSD system with 4 GB RAM.

They are also very easy to debug and troubleshoot comparing even to plain Docker – not to even mention Kubernetes which requires whole team of highly skilled people to maintain.

The FreeBSD Jails may be configured/managed only by the Base System utilities such as jls(8)/jexec(8) but you can also select from many third party Jail management frameworks. From all available ones I would choose BastilleBSD because of their modern approach and many ready to use templates for all needed use cases.

If you want to check all the Jails features feel free to check the FreeBSD Jails Containers article.

More on the Jails topic:

Bhyve

FreeBSD – same as Linux or any other respected operating system – comes with its own written virtualization hypervisor. On the FreeBSD land this native solution is called Bhyve (and spelled bee hive).

bhyve-logo

In the terms of features its similar to KVM/XEN/VMware/VirtualBox.

The only thing that its lacking is the Live Migration feature. Its in the works as the Save/Resume feature is in the testing and will be merged soon.

Besides that – its performance is on par or similar to the mentioned competition. In the end – they all use the same AMD/Intel CPU features that allow virtualization on x86 land.

If You want to check Bhyve hypervisor in all its glory – feel free to check the FreeBSD Bhyve Virtualization article.

Alternatively FreeBSD also supports XEN (as dom0) and VirtualBox hypervisors if Bhyve does not suit your needs.

More on the Bhyve topic:

FreeBSD Ports Infrastructure

This is one of another examples why FreeBSD rocks that much. When you install Ubuntu or CentOS in some version there is chance that you will end up with not latest versions of packages but with versions that were quite up-to-date when this distribution version was released. Its especially visible in the CentOS world (and its upstream enterprise source system from Red Hat) where packages are quite up-to-date when .0 (dot zero) release is published but are VERY outdated when .8 or .9 incarnation of that release is available. Not to even mention that Firefox for example is released every month …

packages

As I said before when describing the FreeBSD Base System the FreeBSD Ports (and packages built from it available through pkg(8) command) are independent. That means that third party software from FreeBSD Ports is almost always up-to-date (or very close to it). You can even check it on the repology.org site for the details. Below you will find a ‘snapshot’ of the repology.org stats from time of writing this article. The ‘online’ table is very long so I copy/pasted just the systems relevant to the article.

repology

One of the other advantages of FreeBSD Ports is that it offers really MASSIVE amount of software counting 40354 ports when writing this article and still rising. Amount of ready to be installed packages are little smaller with more then 32000 available.

I once migrated for a while to OpenSolaris in 2009 on my Dell Latitude D630 laptop because I really liked all the Solaris features (including ZFS and ZFS Boot Environments that were not available on FreeBSD back then) and the OpenSolaris GNOME based desktop was pretty nice back then even with Time Slider feature for ZFS snapshots in the Nautilus file manager. I got working WiFi connection, sound was working, generally everything on my laptop was supported and working with OpenSolaris … but there was no software. Of course ‘large’ projects like GIMP or OpenOffice was available even in the default pkg(8) repository but not much else. There was less then 4000 packages back then on OpenSolaris while about 25000 packages on FreeBSD if I recall correctly.

You can also easily browse available FreeBSD Ports (and its options) on the web by using the https://freshports.org/ page.

ports-later

The count of FreeBSD Ports is one thing, the features is another. No matter which Linux distribution you are using you will find a software that was compiled and shipped without that needed flag that you desperately need. If you find such software on FreeBSD it ‘hurts’ only for a moment because you can VERY EASILY recompile that software with needed options and replace that ‘default’ package with yours. For example the FreeBSD project is afraid to provide packages of Lame because of existing MP3 patents, so multimedia/ffmpeg package is built without MP3 support (with --disable-libmp3lame flag). That is why I have my own audio/lame and multimedia/ffmpeg packages built with my configure options and that is very easy to achieve. You need to go to the /usr/ports/multimedia/ffmpeg dir type make config and select [x] LAME at the ncurses dialog. Your chosen options will be saved as plain /var/db/ports/multimedia_ffmpeg/options file. If you remove that file (or type make rmconfig) then these custom options will reset to defaults. Then you type make build deinstall install clean and your port with new options is ready and installed as package. Nothing more is needed. You can even lock that package from the pkg(8) upgrades with pkg lock -y ffmpeg command so it will not be modified later but its better to rebuild such packages everytime you do a pkg upgrade procedure because of libraries versions bump and changes. While its very easy and fast to create a script with these commands to make it more automated you can also use other parts of the FreeBSD Ports infrastructure – enter Poudriere (or Synth) – more on that in the next part.

You also do not have to configure each port that way (which could be PITA for large amount of ports) but you may specify your needed (OPTIONS_SET) or unwanted (OPTIONS_UNSET) parameters only once globally using the /etc/make.conf file. You can also specify which default versions of software you want to use, for example Apache 2.2 instead of 2.4 and PHP 7.0 instead of 7.2. You can find all default versions in the /usr/ports/Mk/bsd.default-versions.mk file. Once you setup these options you can build/rebuild or update your packages from FreeBSD Ports by portmaster(8) tool. Like on Gentoo Linux with USE flags. But this is the original. Gentoo took all/most of its ideas from FreeBSD system and its Ports infrastructure.

The Poudriere is a build framework that uses FreeBSD Ports and FreeBSD Jails to build requested packages in clean reproducible way. You can create whole new binary package repository for pkg(8) command to use with it. I mentioned Synth because while Poudriere is often used to produce whole package repository the Synth is usually used just to rebuild several packages that does not fit your needs.

There is one important things about FreeBSD Ports that is often misunderstood by newcomers. What is the difference between the Ports and packages that are fetched and installed by pkg(8) tool? Its quite simple. A package is just a build and installed port. Nothing more or less. When you use the binary packages using pkg(8) command you are using packages that someone (the FreeBSD project in that case) built for you from the FreeBSD Ports in some point in time. While FreeBSD strives to maintain as up-to-date built packages as possible its the nature of FreeBSD Ports that they are always more up-to-date then the built packages. That is why you may build and install a new version of needed packages by yourself using FreeBSD Ports. One may think of such usage when it comes to security holes. When some locally executed commands (like file(1) for example) has a security hole then its not critical for you to update it as fast as possible because that security hole can be harmless for you, but when new version of Firefox fixes very important security hole then its better to update from FreeBSD Ports version faster because waiting 2 days for the package to be built (along with other packages) can be too long.

More on the FreeBSD Ports topic:

Updating/Building from Source

While the FreeBSD Ports infrastructure is for third party software the FreeBSD Base System (or its parts) also can be easily and convenient build from source. The FreeBSD kernel config is also very small and simple. While Linux kernel config contains thousands of options – 4432 for example in the default CentOS 8.2 install the FreeBSD GENERIC config has about 20 times options less – only 260 options. But that does not saturate the topic. You can start with MINIMAL FreeBSD kernel config which has only 75 options specified.

Linux # grep -c '^CONFIG' /boot/config-$( uname -r )
4432

FreeBSD # grep -c -E '^(device|options)' /usr/src/sys/amd64/conf/GENERIC
260

FreeBSD # grep -c -E '^(device|options)' /usr/src/sys/amd64/conf/MINIMAL
75

… and its not only about smaller amount of options. Can you tell my how many steps (and which ones are required) to rebuild CentOS or Ubuntu for example without Bluetooth support?

code

On the contrary its very simple (and fast) on the FreeBSD side. While /etc/make.conf file is used to enable/disable Ports options the /etc/src.conf file is used to enable/disable FreeBSD Base System options while building it from source. To build FreeBSD without Bluetooth support just add WITHOUT_BLUETOOTH=yes to the /etc/src.conf file and type these to build it:

FreeBSD # beadm create safe
FreeBSD # cd /usr/src
FreeBSD # make buildworld kernel
FreeBSD # reboot
FreeBSD # cd /usr/src
FreeBSD # etcupdate -p      # // IN THE PAST: mergemaster -p
FreeBSD # make installworld
FreeBSD # etcupdate -B      # // IN THE PAST: mergemaster -iU
FreeBSD # reboot

Voila! You now have FreeBSD without Bluetooth support … and if any of the steps failed or because of your lack of experience/expertise your FreeBSD system does not boot or is broken you can use tools from /rescue to try to fix it (or at least figure out what is broken) and when you do not want to cope with this jest select safe ZFS Boot Environment at the FreeBSD loader(8) to boot to the system before you started building modified version of FreeBSD. Yes, You are bulletproof here. While having 294 WITHOUT_X options and 125 WITH_X options you can really tune FreeBSD Base System to your needs.

FreeBSD # zgrep -c WITHOUT_ /usr/share/man/man5/src.conf.5.gz
294

FreeBSD # zgrep -c WITH_ /usr/share/man/man5/src.conf.5.gz
125

The big downside of updating FreeBSD by source is that you can not use the freebsd-update tools to do it … but nothing stops you from creating your own FreeBSD Update Server so you will be able to use freebsd-update by adding updates using a CURRENT or STABLE system instead of RELEASE. That process is described in the Build Your Own FreeBSD Update Server article of official FreeBSD documentation.

More on the FreeBSD Source Updates/Builds topic:

Storage

Storage is one of the parts where FreeBSD really shines. Lots of people adore FreeBSD for well integrated ZFS filesystem and its really true. ZFS in FreeBSD has always been first class citizen. Lately OpenZFS 2.0 has been also integrated from the upstream joint FreeBSD and Linux repository. More and more FreeBSD features and solutions are using ZFS features.

openzfs

Most of these people that like integrated ZFS in FreeBSD do not know about the FreeBSD GEOM modular disk transformation framework which provides various storage related features and utilities like software RAID0/RAID1/RAID10/RAID3/RAID5 configurations or transparent encryption of underlying devices with GELI/GDBE (like LUKS on Linux). It also allows transparent filesystem journaling for ANY filesystem with GJOURNAL (yes also for FAT32 or exFAT) or allows one to export block devices over network with GEOM GATE devices (like NFS for block devices).

storage

FreeBSD also has its own FUSE implementation which allows all these FUSE based filesystems to work natively on FreeBSD. While lots of Linux folks know DRBD probably very few of them knew that FreeBSD comes with its own DRBD like solution called HAST – which does exactly the same thing. While ZFS has a lot features and possibilities FreeBSD still maintains and develops fast and small memory footprint UFS filesystem which today is used either with Soft Updates (SU) or Journaled Soft Updates (SUJ) depending on the use case. For example 10 TB data on UFS filesystem with Journaled Soft Updates (SUJ) takes about 1 minute under fsck(8). These storage solutions are available from FreeBSD Base System alone. The FreeBSD Ports offers much more with distributed filesystems solutions such as CEPH, LeoFS, LizardFS or Minio for Amazon S3 compatible storage.

More on the Storage topic:

Init System

FreeBSD offers really simple yet very powerful init system. It has system wide config under /etc/rc.conf file when you can enable/disable needed services with service_enable=YES and service_enable=NO stanzas. You do not even need to launch vi(1) to add them – just type sysrc service_enable=YES and they are added to the /etc/rc.conf file. There are also default values and services that are enabled and you will find them – along with many comments – in the /etc/defaults/rc.conf file. Each FreeBSD service file has PROVIDE/REQUIRE stanzas which are then used to automatically order the services to start. Services that can be run in parallel are started in parallel to save time. For example its pointless to start sshd(8) daemon without network. To start or stop the service you need to type service sshd start or service sshd stop command. But when a service is not enabled in the /etc/rc.conf file then you need to used add onestart and onestop instead. The Base System separation remains here as FreeBSD Base System services are located at /etc/rc.d directory and third party applications from ports/packages are kept under /usr/local prefix which means /usr/local/etc/rc.d dir.

When using systemd(1) you never know how the services gonna start because it will be different each time. Zero determinism. On FreeBSD you know exactly which services will start when because they are always ordered in the same state according to the PROVIDE/REQUIRE stanzas. FreeBSD also offers tools that will tell you the exact order – rcorder(8) – which can be used for all services, Base System services or third party services separately. There is also service -r command that will show you what was the order at the boot time.

FreeBSD # rcorder /etc/rc.d/* | head
/etc/rc.d/growfs
/etc/rc.d/sysctl
/etc/rc.d/hostid
/etc/rc.d/zvol
/etc/rc.d/dumpon
/etc/rc.d/ddb
/etc/rc.d/geli
/etc/rc.d/gbde
/etc/rc.d/ccd
/etc/rc.d/swap

FreeBSD # rcorder /usr/local/etc/rc.d/* | tail
/usr/local/etc/rc.d/hald
/usr/local/etc/rc.d/git_daemon
/usr/local/etc/rc.d/fscd
/usr/local/etc/rc.d/cupsd
/usr/local/etc/rc.d/cups_browsed
/usr/local/etc/rc.d/clamav-clamd
/usr/local/etc/rc.d/clamav-milter
/usr/local/etc/rc.d/clamav-freshclam
/usr/local/etc/rc.d/avahi-dnsconfd
/usr/local/etc/rc.d/aria2

FreeBSD # rcorder /etc/rc.d/* /usr/local/etc/rc.d/* 2> | grep -C 3 sshd
/etc/rc.d/ubthidhci
/etc/rc.d/syscons
/etc/rc.d/swaplate
/etc/rc.d/sshd
/etc/rc.d/cron
/etc/rc.d/jail
/etc/rc.d/localpkg

Adding new service to FreeBSD is also very easy as template for new service is very small and simple.

#!/bin/sh

. /etc/rc.subr

name=dummy
rcvar=dummy_enable

start_cmd="${name}_start"
stop_cmd=":"

load_rc_config $name
: ${dummy_enable:=no}
: ${dummy_msg="Nothing started."}

dummy_start()
{
	echo "$dummy_msg"
}

run_rc_command "$1"

If its not simple enough for you there is dedicated FreeBSD article about writing them – Practical rc.d Scripting in BSD – available here.

More on the Init System topic:

Linux Binary Compatibility

While Linux can not be FreeBSD – the FreeBSD can be Linux – and its not some slow emulation – its implementation of Linux system calls. There was time when enterprises used to work with Linux only applications (not available on FreeBSD by then) using the Linux Binary Compatibility on FreeBSD because it was faster then running them natively on Linux – FreeBSD Used to Generate Spectacular Special Effects – an official FreeBSD Press Release about FreeBSD being used to generate spacial effects to the one of the best movies of all time – The Matrix (1999).

matrix

Today the LINUX_COMPAT is also natively fast and allows one to run Linux applications – even Linux games in X11 with hardware acceleration for graphics. Think of it as WINE but for Linux applications. It lives under /compat/linux directory. It even implements Linux /proc virtual filesystem which can be mounted at the /compat/linux/proc dir but its not mandatory. For any software that does not come with source code and works on Linux the Linux Binary Compatibility saves the day. For example the f.lux project. Before I got to know Redshift I used f.lux Linux binary using LINUX_COMPAT to suppress blue spectrum light from my FreeBSD screen. The Linux Binary Compatibility subsystem can also be used to run Linux bases FreeBSD Jails – with Devuan for example.

More on the Linux Binary Compatibility topic:

Simplicity

FreeBSD is simple but not coarse/ornery. For example as Linux the FreeBSD system also supports the /proc virtual filesystem but on FreeBSD its optional and not used by default while Linux could not live without it. But while Linux has mandatory /proc it also has another virtual filesystem residing under /sys … but why Linux people need two different virtual filesystems with similar purposes? Why they could not create everything under /proc as it already existed. That is big enigma for my sanity.

But /sys is not the end of that madness. Its just a beginning.

What about these?

  • securityfs
  • devpts
  • cgroup
  • pstore
  • bpf
  • configfs
  • selinuxfs
  • systemd-1
  • mqueue
  • debugfs
  • hugetlbfs

Take a look at the FreeBSD mount(8) output after the default install on ZFS.

FreeBSD # mount
zroot/ROOT/12.1 on / (zfs, local, noatime, nfsv4acls)
devfs on /dev (devfs, local, multilabel)
zroot/tmp on /tmp (zfs, local, noatime, nosuid, nfsv4acls)
zroot/var/mail on /var/mail (zfs, local, nfsv4acls)
zroot/usr/home on /usr/home (zfs, local, noatime, nfsv4acls)
zroot/var/crash on /var/crash (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/var/log on /var/log (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/var/audit on /var/audit (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/var/tmp on /var/tmp (zfs, local, noatime, nosuid, nfsv4acls)
zroot/usr/src on /usr/src (zfs, local, noatime, nfsv4acls)
zroot/usr/ports on /usr/ports (zfs, local, noatime, nosuid, nfsv4acls)

Several ZFS datasets and one virtual devfs filesystem for /dev directory. With install on UFS it would be similar with several UFS partitions mounted instead of ZFS datasets.

Take a look at the CentOS 8.2 installation with just one physical root (/) XFS filesystem.

[root@centos8 ~]# mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime,seclabel)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
devtmpfs on /dev type devtmpfs (rw,nosuid,seclabel,size=919388k,nr_inodes=229847,mode=755)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,seclabel)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,seclabel,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,seclabel,mode=755)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,seclabel,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime,seclabel)
bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
configfs on /sys/kernel/config type configfs (rw,relatime)
/dev/sda1 on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
selinuxfs on /sys/fs/selinux type selinuxfs (rw,relatime)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=34,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=17309)
mqueue on /dev/mqueue type mqueue (rw,relatime,seclabel)
debugfs on /sys/kernel/debug type debugfs (rw,relatime,seclabel)
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime,seclabel,pagesize=2M)
tmpfs on /run/user/0 type tmpfs (rw,nosuid,nodev,relatime,seclabel,size=187088k,mode=700)

Fuck me. Its even really hard to just find any REAL filesystem there … fortunately we can ask for only XFS filesystems to display.

[root@centos7 ~]# mount -t xfs
/dev/sda1 on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

Lets get on the networking now. Lets assume that you want to make standard enterprise networking setup on a physical server with two interfaces aggregated together into highly available interface bond0 (lagg0 on FreeBSD) and then you want to put VLAN tag and IP address on that VLAN. The CentOS 7.x/8.x installer (Anaconda) will welcome you with this mess.

[root@centos7 ~]# ls -1 /etc/sysconfig/network-scripts/ifcfg-*
ifcfg-Bond_connection_1
ifcfg-eno49
ifcfg-eno49-1
ifcfg-eno50
ifcfg-eno50-1
ifcfg-VLAN_connection_1

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-Bond_connection_1
DEVICE=bond0
BONDING_OPTS="miimon=1 updelay=0 downdelay=0 mode=active-backup"
TYPE=Bond
BONDING_MASTER=yes
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_PRIVACY=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME="Bond connection 1"
UUID=ca85417f-8852-43bf-96ee-5bd3f0f83648
ONBOOT=yes

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno49
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=eno49
UUID=2f60f50b-38ad-492a-b90a-ba736acf6792
DEVICE=eno49
ONBOOT=no

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno49-1
HWADDR=xx:xx:xx:xx:xx:xx
TYPE=Ethernet
NAME=eno49
UUID=342b8494-126d-4f3a-b749-694c8c922aa1
DEVICE=eno49
ONBOOT=yes
MASTER=bond0
SLAVE=yes

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno50
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=eno50
UUID=4fd36e24-1c6d-4a65-a316-7a14e9a92965
DEVICE=eno50
ONBOOT=no

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno50-1
HWADDR=xx:xx:xx:xx:xx:xx
TYPE=Ethernet
NAME=eno50
UUID=a429b697-73c2-404d-9379-472cb3c35e06
DEVICE=eno50
ONBOOT=yes
MASTER=bond0
SLAVE=yes

[root@centos7 ~]# cat/etc/sysconfig/network-scripts/ifcfg-VLAN_connection_1
VLAN=yes
TYPE=Vlan
PHYSDEV=ca85417f-8852-43bf-96ee-5bd3f0f83648
VLAN_ID=601
REORDER_HDR=yes
GVRP=no
MVRP=no
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
IPADDR=10.20.30.40
PREFIX=24
GATEWAY=10.20.30.1
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_PRIVACY=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME="VLAN connection 1"
UUID=90f7a9bb-1443-4adf-a3eb-86a03b23ecfb
ONBOOT=yes

For the record – I have chosen ‘STATIC’ IPv4 address but installer made these interfaces to use DHCP and that STATIC address. That could be a bug but lets get to the point.

After manual fixing with vi(1) (and hour later) this is how it supposed to look.

[root@centos7 ~]# cat /etc/sysconfig/network
GATEWAY=10.20.30.1
NOZEROCONF=yes

[root@centos7 ~]# ls -1 /etc/sysconfig/network-scripts/ifcfg-*
ifcfg-bond0
ifcfg-bond0.601
ifcfg-eno49
ifcfg-eno50

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-bond0
DEVICE=bond0
BONDING_OPTS="miimon=1 updelay=0 downdelay=0 mode=active-backup"
TYPE=Bond
BONDING_MASTER=yes
BOOTPROTO=none
IPV4_FAILURE_FATAL=no
IPV6INIT=no
ONBOOT=yes

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-bond0.601
VLAN=yes
TYPE=Vlan
VLAN_ID=601
DEVICE=bond0.601
REORDER_HDR=yes
GVRP=no
MVRP=no
BOOTPROTO=none
IPADDR=10.20.30.40
PREFIX=24
IPV4_FAILURE_FATAL=no
IPV6INIT=no
ONBOOT=yes

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno49
BOOTPROTO=none
IPV4_FAILURE_FATAL=no
IPV6INIT=no
TYPE=Ethernet
NAME=eno49
DEVICE=eno49
ONBOOT=yes
MASTER=bond0
SLAVE=yes

[root@centos7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno50
BOOTPROTO=none
IPV4_FAILURE_FATAL=no
IPV6INIT=no
TYPE=Ethernet
NAME=eno50
DEVICE=eno50
ONBOOT=yes
MASTER=bond0
SLAVE=yes

Better … but still takes A LOT OF SPACE and several files to cover that quite simple setup. Not to mention its level of complication and making that very error prone way. The same configuration on FreeBSD would take just 7 lines within single /etc/rc.conf file as shown below.

FreeBSD # cat /etc/rc.conf
ifconfig_fxp0="up"
ifconfig_fxp1="up"
cloned_interfaces="lagg0"
ifconfig_lagg0="laggproto failover laggport fxp0 laggport fxp1"
vlans_lagg0="601"
ifconfig_lagg0_601="inet 10.20.30.40/24"
defaultrouter="10.20.30.1"

But that are not all the complications. In the past you could restart the network configuration on RHEL/CentOS/Rocky/… with simple service network restart command … not anymore. Now it will not work and you need to type two commands. They are nmcli networking off followed by nmcli networking on command … and to make sure that the second one will be executed and you will not loose the networking connection before executing it You need to type them one after another inside screen(1) or tmux(1) terminal multiplexer. Convenient as fuck 🙂

What about the boot process? FreeBSD boots from root on ZFS partition with just small 512 KB not mountable partition. No separate /boot device is needed. On the other side Linux always needs that separate /boot partition filled with GRUB modules. No matter if its ZFS or LVM. That is why implementation of ZFS Boot Environments is quite complicated on Linux because even if you have root on ZFS on a Linux system there is still unprotected /boot filesystem that can not be snapshoted with ZFS and has to be protected in old classic way which kill the idea of ZFS Boot Environments or Linux.

FreeBSD is really simple and well thought operating system. But also a very underestimated one.

Evolution Instead Rewriting

How many Linux tools or subsystems are abandoned or superseded by new ones? Why the ifconfig(8) command was not updated with new options and instead a new ip(8) command was introduced? Same with netstat(8) being replaced by ss(8). Same with arp(8)/iwconfig/route(8) and many more. What about whole init system? The Linux world has been taken over by systemd(1) whenever you like it or not. Even distributions that have grown their mature init systems like Ubuntu with its Upstart has moved to systemd(1) altogether. The distributions that do not use it are very few and considered a niche today.

evolution

In the FreeBSD land on the contrary such things happen only if there is no other way to implement new things. Its the last thing wanted in the FreeBSD. FreeBSD evolves and is developed with stability and backward compatibility in mind. Userland tools are grown and updated with new options instead of rewriting them over and over again. Not to mention how many new bugs are introduced by changing one tool to another.

More on the Evolution Instead Rewriting topic:

Documentation

Having system that can do almost anything but not knowing how to do that makes that system pretty useless (or at least pretty PITA to use). FreeBSD offers second to none documentation that is actively maintained and updated. Along with its legendary FreeBSD Handbook and FreeBSD FAQ the FreeBSD project also offers official FreeBSD Articles about various FreeBSD topics. The Man Pages are also very detailed and contain many examples. There is also FreeBSD Wiki page for work in progress documentation and ideas related to FreeBSD development and if you have any problems or questions related to FreeBSD there are official FreeBSD Forums and oldschool Mailing Lists available.

documentation

These were only the official project knowledge sources but there are also lots of FreeBSD books. You may also check my dedicated FreeBSD Books article for more in depth look about available FreeBSD books. Here are the best and up-to-date ones.

  • Absolute FreeBSD – Complete Guide to FreeBSD – 3nd Edition (2019)
  • Beginning Modern Unix (2018)
  • Book of PF – 3rd Edition (2015)
  • Design and Implementation of FreeBSD 11 Operating System – 2nd Edition (2015)
  • FreeBSD Device Drivers (2012)
  • FreeBSD Mastery – ZFS (2015)
  • FreeBSD Mastery – Advanced ZFS (2016)
  • FreeBSD Mastery – Storage Essentials (2014)
  • FreeBSD Mastery – Specialty Filesystems (2015)
  • FreeBSD Mastery – Jails (2019)

There are also two magazines that are dedicated to BSD and FreeBSD systems. Both are free and cover lots of interesting topics regarding FreeBSD.

With all this knowledge and support its really hard not to achieve what you need/want with FreeBSD system.

Community

Last but not least and I would say its even more important then good documentation (which FreeBSD has awesome). People that use FreeBSD do that consciously and are often experienced not only in FreeBSD land but also in topics related to other UNIX systems. Often they took long road of first using the Linux systems before finally setting on the FreeBSD land or they still do Linux administration for a living while resting using far more reasonable and sensible FreeBSD solution. I always find FreeBSD Community helpful and friendly. Always willingly helpful – especially towards newcomers. Even when you try to ‘force’ FreeBSD people to ‘fight’ in unjust/doubtful discussion they will reply with dignity and technical arguments instead of yelling at you.

The FreeBSD project even made several articles and Handbook chapters especially for Linux newcomers – or sometimes called systemd(1) refugees.

Closing Thoughts

I tried really hard to not make it a Linux rant but some may feel it that way – if so please remember that this was not my intention. FreeBSD like Linux and like any other operating system has its ups and downs. Hope that I showed you most interesting FreeBSD parts. I may add new sections here without a warning in the future 🙂

External Discussions

Discussions and comments from ‘external’ sources are available here:

EOF

RabbitMQ Cluster on FreeBSD Containers

I really like small and simple dedicated solutions that do one thing well and do it really good – maybe its because I like UNIX that much. Good example of such approach is Minio object storage which implements S3 protocol with distributed clustering, erasure code and builtin web interface along with many other features about which I wrote in the Distributed Object Storage with Minio on FreeBSD article.

The RabbitMQ is another such example – currently probably the most popular implementation of the AMQP protocol – it also comes with small and sleek web interface. The difference is power. Minio comes with very basic user oriented web interface while most administrative and configuration tasks needs to be done from the CLI. The Minio web interface allows one to create/delete buckets there and also to download/upload files. RabbitMQ have so sophisticated web interface that after you enable it you do not need command line anymore. Everything can be accomplished using just web interface.

rabbitmq-logo.png

Compared to other messaging solutions like ActiveMQ or Apache Kafka it is very popular when checked in the Google Trends query.

rabbitmq-trends.jpg

Today I would like to show you RabbitMQ messaging with quite redundant clustered setup with mirrored queues.

You will find Table of Contents below.

  • Jails Setup
  • RabbitMQ Installation
  • RabbitMQ Setup
  • RabbitMQ Plugins
  • RabbitMQ Administrative User
  • RabbitMQ Cluster Setup
  • RabbitMQ Highly Available Policy
  • Feed the Queue
  • Go Language Installation
  • Simple Benchmark
  • High Availability
  • UPDATE 1 – This Month in RabbitMQ
  • UPDATE 2 – Make RabbitMQ Use Less CPU

From all possible virtualization possibilities available on FreeBSD (VirtualBox/Bhyve/QEMU/Jails/Docker) I have chosen the lightweight FreeBSD Containers – Jails 🙂

The legend is the same as usual.

Command run on the host system as root user.

host # command

Command run on the host system as regular user.

host % command

Command run on the rabbitX Jail.

rabbitX # command

Jails Setup

First we will create the base Jails for our setup. Both the host system and Jails Containers use FreeBSD 11.2-RELEASE system.

host # mkdir -p /jail/BASE
host # fetch -o /jail/BASE/11.2-RELEASE.base.txz http://ftp.freebsd.org/pub/FreeBSD/releases/amd64/12.1-RELEASE/base.txz
host # for I in 1 2; do echo ${I}; mkdir -p /jail/rabbit${I}; tar --unlink -xpJf /jail/BASE/11.2-RELEASE.base.txz -C /jail/rabbit${I}; done
1
2
host #

We now have 2 empty clean Jails.

We will now add Jails configuration to the /etc/jail.conf file.

I have used my laptop for the Jail host thus Jails will configured to use the wireless wlan0 interface and 192.168.43.10X addresses. I also added 10.0.0.10X network addresses as this will make it more convenient for me for the purposes of writing this article.

host # for I in 1 2
do
  cat >> /etc/jail.conf << __EOF
rabbit${I} {
  host.hostname = rabbit${I}.local;
  ip4.addr += 192.168.43.10${I};
  ip4.addr += 10.0.0.10${I};
  interface = wlan0;
  path = /jail/rabbit${I};
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.clean;
  mount.devfs;
  allow.raw_sockets;
}

__EOF
done
host #

This is how the /etc/jail.conf file looks after its configured.

host # cat /etc/jail.conf
rabbit1 {
  host.hostname = rabbit1.local;
  ip4.addr += 192.168.43.101;
  ip4.addr += 10.0.0.101;
  interface = wlan0;
  path = /jail/rabbit1;
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.clean;
  mount.devfs;
  allow.raw_sockets;
}

rabbit2 {
  host.hostname = rabbit2.local;
  ip4.addr += 192.168.43.102;
  ip4.addr += 10.0.0.102;
  interface = wlan0;
  path = /jail/rabbit2;
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.clean;
  mount.devfs;
  allow.raw_sockets;
}

Now we can start our Jails.

host # for I in 1 2; do service jail onestart rabbit${I}; done
Starting jails: rabbit1.
Starting jails: rabbit2.

Jails are running properly.

# jls
   JID  IP Address      Hostname                      Path
     1  192.168.43.101  rabbit1.local                 /jail/rabbit1
     2  192.168.43.102  rabbit2.local                 /jail/rabbit2

Time to add DNS server to the Jails so they will have Internet connectivity.

host # for I in 1 2; do cat /jail/rabbit${I}/etc/resolv.conf; done
nameserver 1.1.1.1
nameserver 1.1.1.1

Now we will switch from 'quarterly' to 'latest' packages.

host # for I in 1 2; do sed -i '' s/quarterly/latest/g /jail/rabbit${I}/etc/pkg/FreeBSD.conf; done

host # for I in 1 2; do grep latest /jail/rabbit${I}/etc/pkg/FreeBSD.conf; done
  url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
  url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",

RabbitMQ Installation

We can now install RabbitMQ package.

host # for I in 1 2; do jexec rabbit${I} env ASSUME_ALWAYS_YES=yes pkg install -y rabbitmq; echo; done
Bootstrapping pkg from pkg+http://pkg.FreeBSD.org/FreeBSD:11:amd64/latest, please wait...
Verifying signature with trusted certificate pkg.freebsd.org.2013102301... done
[rabbit1.local] Installing pkg-1.10.5_5...
[rabbit1.local] Extracting pkg-1.10.5_5: 100%
Updating FreeBSD repository catalogue...
pkg: Repository FreeBSD load error: access repo file(/var/db/pkg/repo-FreeBSD.sqlite) failed: No such file or directory
[rabbit1.local] Fetching meta.txz: 100%    944 B   0.9kB/s    00:01    
[rabbit1.local] Fetching packagesite.txz: 100%    6 MiB 745.4kB/s    00:09    
Processing entries: 100%
FreeBSD repository update completed. 32114 packages processed.
All repositories are up to date.
Updating database digests format: 100%
The following 2 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        rabbitmq: 3.7.15
        erlang-runtime19: 21.3.8.2

Number of packages to be installed: 2

The process will require 104 MiB more space.
41 MiB to be downloaded.
[rabbit1.local] [1/2] Fetching rabbitmq-3.7.15.txz: 100%    9 MiB 762.2kB/s    00:12    
[rabbit1.local] [2/2] Fetching erlang-runtime19-21.3.8.2.txz: 100%   33 MiB 978.8kB/s    00:35    
Checking integrity... done (0 conflicting)
[rabbit1.local] [1/2] Installing erlang-runtime19-21.3.8.2...
[rabbit1.local] [1/2] Extracting erlang-runtime19-21.3.8.2: 100%
[rabbit1.local] [2/2] Installing rabbitmq-3.7.15...
===> Creating groups.
Creating group 'rabbitmq' with gid '135'.
===> Creating users
Creating user 'rabbitmq' with uid '135'.
[rabbit1.local] [2/2] Extracting rabbitmq-3.7.15: 100%
Message from erlang-runtime19-21.3.8.2:

===========================================================================

To use this runtime port for development or testing, just prepend
its binary path ("/usr/local/lib/erlang19/bin") to your PATH variable.

===========================================================================

(...)

// SAME MESSAGES FOR THE OTHER rabbit2 JAIL //

Lets verify that RabbitMQ package has installed successfully.

host # for I in 1 2; do jexec rabbit${I} which rabbitmqctl; done
/usr/local/sbin/rabbitmqctl
/usr/local/sbin/rabbitmqctl

RabbitMQ Setup

We will now configure /etc/hosts files on our Jails.

host # for I in 1 2; do cat >> /jail/rabbit${I}/etc/hosts << __EOF
192.168.43.101 rabbit1
192.168.43.102 rabbit2

__EOF
done

… and fast verification.

host # cat /jail/rabbit?/etc/hosts | grep 192.168.43 | sort -n | uniq -c
2 192.168.43.101 rabbit1
2 192.168.43.102 rabbit2

As we have RabbitMQ package installed we need to enable it and start it.

host # jexec rabbit1 /usr/local/etc/rc.d/rabbitmq rcvar
# rabbitmq
#
rabbitmq_enable="NO"
#   (default: "")

As we see we need to set rabbitmq_enable=YES value in /etc/rc.conf file within each of our Jails.

host # for I in 1 2; do jexec rabbit${I} sysrc rabbitmq_enable=YES; done
rabbitmq_enable:  -> YES
rabbitmq_enable:  -> YES

Now we can start the RabbitMQ in the Jails.

host # for I in 1 2; do jexec rabbit${I} service rabbitmq start; done
Starting rabbitmq.
Starting rabbitmq.

Now we have four RabbitMQ instances up and running.

This is the list of plugins enabled by default. None.

RabbitMQ Plugins

rabbit1 # rabbitmq-plugins list
 Configured: E = explicitly enabled; e = implicitly enabled
 | Status: * = running on rabbit@rabbit1
 |/
[  ] rabbitmq_amqp1_0                  3.7.15
[  ] rabbitmq_auth_backend_cache       3.7.15
[  ] rabbitmq_auth_backend_http        3.7.15
[  ] rabbitmq_auth_backend_ldap        3.7.15
[  ] rabbitmq_auth_mechanism_ssl       3.7.15
[  ] rabbitmq_consistent_hash_exchange 3.7.15
[  ] rabbitmq_event_exchange           3.7.15
[  ] rabbitmq_federation               3.7.15
[  ] rabbitmq_federation_management    3.7.15
[  ] rabbitmq_jms_topic_exchange       3.7.15
[  ] rabbitmq_management               3.7.15
[  ] rabbitmq_management_agent         3.7.15
[  ] rabbitmq_mqtt                     3.7.15
[  ] rabbitmq_peer_discovery_aws       3.7.15
[  ] rabbitmq_peer_discovery_common    3.7.15
[  ] rabbitmq_peer_discovery_consul    3.7.15
[  ] rabbitmq_peer_discovery_etcd      3.7.15
[  ] rabbitmq_peer_discovery_k8s       3.7.15
[  ] rabbitmq_random_exchange          3.7.15
[  ] rabbitmq_recent_history_exchange  3.7.15
[  ] rabbitmq_sharding                 3.7.15
[  ] rabbitmq_shovel                   3.7.15
[  ] rabbitmq_shovel_management        3.7.15
[  ] rabbitmq_stomp                    3.7.15
[  ] rabbitmq_top                      3.7.15
[  ] rabbitmq_tracing                  3.7.15
[  ] rabbitmq_trust_store              3.7.15
[  ] rabbitmq_web_dispatch             3.7.15
[  ] rabbitmq_web_mqtt                 3.7.15
[  ] rabbitmq_web_mqtt_examples        3.7.15
[  ] rabbitmq_web_stomp                3.7.15
[  ] rabbitmq_web_stomp_examples       3.7.15

Time to enable web interface plugin.

host # for I in 1 2; do jexec rabbit${I} rabbitmq-plugins enable rabbitmq_management; done
The following plugins have been configured:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@rabbit1...
The following plugins have been enabled:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch

started 3 plugins.

(...)

// SAME MESSAGES FOR THE OTHER rabbit2 JAIL //

Now we have web interface plugin enabled in each RabbitMQ FreeBSD Jail.

Big ‘E‘ letter means that this is the plugin that we enabled and small ‘e‘ letter means that this plugin is only enabled as ‘dependency’ for some other plugin we requested to be enabled.

rabbit1 # rabbitmq-plugins list
 Configured: E = explicitly enabled; e = implicitly enabled
 | Status: * = running on rabbit@rabbit1
 |/
[  ] rabbitmq_amqp1_0                  3.7.15
[  ] rabbitmq_auth_backend_cache       3.7.15
[  ] rabbitmq_auth_backend_http        3.7.15
[  ] rabbitmq_auth_backend_ldap        3.7.15
[  ] rabbitmq_auth_mechanism_ssl       3.7.15
[  ] rabbitmq_consistent_hash_exchange 3.7.15
[  ] rabbitmq_event_exchange           3.7.15
[  ] rabbitmq_federation               3.7.15
[  ] rabbitmq_federation_management    3.7.15
[  ] rabbitmq_jms_topic_exchange       3.7.15
[E*] rabbitmq_management               3.7.15
[e*] rabbitmq_management_agent         3.7.15
[  ] rabbitmq_mqtt                     3.7.15
[  ] rabbitmq_peer_discovery_aws       3.7.15
[  ] rabbitmq_peer_discovery_common    3.7.15
[  ] rabbitmq_peer_discovery_consul    3.7.15
[  ] rabbitmq_peer_discovery_etcd      3.7.15
[  ] rabbitmq_peer_discovery_k8s       3.7.15
[  ] rabbitmq_random_exchange          3.7.15
[  ] rabbitmq_recent_history_exchange  3.7.15
[  ] rabbitmq_sharding                 3.7.15
[  ] rabbitmq_shovel                   3.7.15
[  ] rabbitmq_shovel_management        3.7.15
[  ] rabbitmq_stomp                    3.7.15
[  ] rabbitmq_top                      3.7.15
[  ] rabbitmq_tracing                  3.7.15
[  ] rabbitmq_trust_store              3.7.15
[e*] rabbitmq_web_dispatch             3.7.15
[  ] rabbitmq_web_mqtt                 3.7.15
[  ] rabbitmq_web_mqtt_examples        3.7.15
[  ] rabbitmq_web_stomp                3.7.15
[  ] rabbitmq_web_stomp_examples       3.7.15

Now – in order to create a cluster – we need these RabbitMQ instances to share the same ERLANG cookie. The ERLANG cookie can be found at /var/db/rabbitmq/.erlang.cookie on FreeBSD system.

rabbot1 # cat /var/db/rabbitmq/.erlang.cookie; echo
NOEVQNXJDNLAJOSVWNIW
rabbot1 # 

We will need to stop RabbitMQ to change ERLANG cookie.

host # for I in 1 2; do jexec rabbit${I} service rabbitmq stop; done
Stopping rabbitmq.
Waiting for PIDS: 88684.
Stopping rabbitmq.
Waiting for PIDS: 20976.

Let’s set the same ERLANG cookie on each FreeBSD Jail then.

host # for I in 1 2; do cat > /jail/rabbit${I}/var/db/rabbitmq/.erlang.cookie << __EOF
RABBITMQFREEBSDJAILS
__EOF
done

… and now we need to start them again.

host # for I in 1 2; do jexec rabbit${I} service rabbitmq start; done
Starting rabbitmq.
Starting rabbitmq.

Fast verification.

host # for I in 1 2; do jexec rabbit${I} cat /var/db/rabbitmq/.erlang.cookie; done
RABBITMQFREEBSDJAILS
RABBITMQFREEBSDJAILS

RabbitMQ Administrative User

Now we will create administrative user called admin for the RabbitMQ instances.

host # for I in 1 2; do jexec rabbit${I} rabbitmqctl add_user admin ADMINPASSWORD; done
Adding user "admin" ...
Adding user "admin" ...

host # for I in 1 2; do jexec rabbit${I} rabbitmqctl set_user_tags admin administrator; done
Setting tags for user "admin" to [administrator] ...
Setting tags for user "admin" to [administrator] ...

host # for I in 1 2; do jexec rabbit${I} rabbitmqctl set_permissions -p / admin ".*" ".*" ".*" ; done
Setting permissions for user "admin" in vhost "/" ...
Setting permissions for user "admin" in vhost "/" ...

We should now be able to login to the http://192.168.43.101:15672/ (or http://10.0.0.101:15672/ also) RabbitMQ management page.

01-rabbitmq-login.png

After login a useful RabbitMQ dashboard will welcome you.

02-rabbitmq-dashboard.png

RabbitMQ Cluster Setup

We will now create RabbitMQ cluster.

rabbit1 # rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit1 ...
[{nodes,[{disc,[rabbit@rabbit1]}]},
 {running_nodes,[rabbit@rabbit1]},
 {cluster_name,},
 {partitions,[]},
 {alarms,[{rabbit@rabbit1,[]}]}]

rabbit2 # hostname
rabbit2.local

rabbit2 # rabbitmqctl join_cluster rabbit@rabbit1
Error: this command requires the 'rabbit' app to be stopped on the target node. Stop it with 'rabbitmqctl stop_app'.
Arguments given:
        join_cluster rabbit@rabbit1

Usage

rabbitmqctl [--node ] [--longnames] [--quiet] join_cluster [--disc|--ram] 

We first need to stop the RabbitMQ ‘application’ to join the cluster.

rabbit2 # rabbitmqctl stop_app
Stopping rabbit application on node rabbit@rabbit2 ...

rabbit2 # rabbitmqctl join_cluster rabbit@rabbit1
Clustering node rabbit@rabbit2 with rabbit@rabbit1

rabbit2 # rabbitmqctl start_app
Starting node rabbit@rabbit2 ...
 completed with 5 plugins.

rabbit2 # rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
 {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]},
 {cluster_name,},
 {partitions,[]},
 {alarms,[{rabbit@rabbit1,[]},{rabbit@rabbit2,[]}]}]

rabbit1 # rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit1 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
 {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]},
 {cluster_name,},
 {partitions,[]},
 {alarms,[{rabbit@rabbit2,[]},{rabbit@rabbit1,[]}]}]

Now we have formed two node RabbitMQ cluster. We will rename it to cluster then.

rabbit1 # rabbitmqctl set_cluster_name rabbit@cluster
Setting cluster name to rabbit@cluster ...

rabbit1 # rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit1 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
 {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]},
 {cluster_name,},
 {partitions,[]},
 {alarms,[{rabbit@rabbit2,[]},{rabbit@rabbit1,[]}]}]

Here is how our cluster looks in the web interface.

08-rabbitmq-cluster.png

RabbitMQ Highly Available Policy

To have Highly Available (Mirrored) Queues in RabbitMQ you need to create Policy. We will declare Policy named ha which will match queues whose names begin with ‘ha-‘ prefix so they will be configured with mirroring to all two nodes in the cluster.

This is the command you need to execute to create such Policy.

rabbit1 # rabbitmqctl set_policy ha "^ha-\.*" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
Setting policy "ha-mirror" for pattern "^ha-\." to "{"ha-mode":"all","ha-sync-mode":"automatic"}" with priority "0" for vhost "/" ...

… or alternatively you can use the web interface to create it.

No matter which method you have chosen you will end up with needed ha Policy as shown below.

03-rabbitmq-policy.png

Feed the Queue

We now have two node RabbitMQ cluster with HA for queues that name starts with ha- prefix. We will now test our RabbitMQ setup and will create and feed the queue with send.go script – as you probably guessed – written in Go. We will need to add Go language to our host system.

Go Language Installation

host # pkg install go
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
All repositories are up to date.
The following 1 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        go: 1.12.5,1

Number of packages to be installed: 1

The process will require 262 MiB more space.
75 MiB to be downloaded.

Proceed with this action? [y/N]: y
(...)

host % go version
go version go1.12.5 freebsd/amd64

This is the send.go script – we will use it to send 10 messages to the ha-default queue. Its based on the RabbitMQ Hello World tutorial.

host % cat send.go
package main

import (
  "log"
  "amqp"
)

func FAIL_ON_ERROR(err error, msg string) {
  if err != nil {
    log.Fatalf("%s: %s", msg, err)
  }
}

func main() {
  conn, err := amqp.Dial("amqp://admin:ADMINPASSWORD@10.0.0.101:5672/")
  FAIL_ON_ERROR(err, "ER: failed to connect to RabbitMQ")
  defer conn.Close()

  ch, err := conn.Channel()
  FAIL_ON_ERROR(err, "ER: failed to open channel")
  defer ch.Close()

  q, err := ch.QueueDeclare(
    "ha-default", // name
    false,        // durable
    false,        // delete when unused
    false,        // exclusive
    false,        // no-wait
    nil,          // arguments
  )
  FAIL_ON_ERROR(err, "ER: failed to declare queue")

  body := "Hello World!"

  for i := 1; i <= 10; i++ {
    err = ch.Publish(
      "",     // exchange
      q.Name, // routing key
      false,  // mandatory
      false,  // immediate
      amqp.Publishing{
        ContentType: "text/plain",
        Body:        []byte(body),
      })
    log.Printf("IN: sent message '%s' (%d)", body, i)
    FAIL_ON_ERROR(err, "ER: failed to publish message")
  }

}


We will now run it.

host % go run send.go
send.go:5:3: cannot find package "amqp" in any of:
        /usr/local/go/src/amqp (from $GOROOT)
        /home/vermaden/.gopkg/src/amqp (from $GOPATH)

We lack the amqp package for the Go language.

We will need to download it from its https://github.com/streadway/amqp page. We will get it by downloading everything in a ZIP package.

host % mkdir -p ~/.gopkg/src
host % cd !$
host % pwd
/home/vermaden/.gopkg/src
host % fetch https://github.com/streadway/amqp/archive/master.zip
host % unzip master.zip 
Archive:  /home/vermaden/.gopkg/src/master.zip
   creating: amqp-master/
 extracting: amqp-master/.gitignore
 extracting: amqp-master/.travis.yml
 (...)
 extracting: amqp-master/uri.go
 extracting: amqp-master/uri_test.go
 extracting: amqp-master/write.go
host % rm master.zip
host % mv amqp-master amqp
host % cd amqp
host % pwd
/home/vermaden/.gopkg/src/amqp
host % exa
_examples          confirms.go         delivery_test.go        LICENSE            spec091.go
spec               confirms_test.go    doc.go                  pre-commit         tls_test.go
allocator.go       connection.go       example_client_test.go  read.go            types.go
allocator_test.go  connection_test.go  examples_test.go        read_test.go       uri.go
auth.go            consumers.go        fuzz.go                 README.md          uri_test.go
certs.sh           consumers_test.go   gen.sh                  reconnect_test.go  write.go
channel.go         CONTRIBUTING.md     go.mod                  return.go          
client_test.go     delivery.go         integration_test.go     shared_test.go     

We also need to make sure that PATH and GOPATH are properly configured. To do so you need to put these in your interactive shell config.

# GO SHELL SETUP
mkdir -p ~/.gopkg
export GOPATH=~/.gopkg
export PATH="${PATH}:~/.gopkg"

Now we can get back to feeding our queue.

host % go run send.go
2019/06/05 13:53:59 IN: sent message 'Hello World!' (1)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (2)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (3)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (4)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (5)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (6)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (7)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (8)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (9)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (10)
% 

The ha-default queue has been created and feeded with 10 messages.

04-rabbitmq-queue

Now we need to ‘receive’ these messages from the queue. This is where receive.go script comes with help. It is also based on the RabbitMQ Hello World tutorial.

host % cat receive.go
package main

import (
  "log"
  "amqp"
)

func FAIL_ON_ERROR(err error, msg string) {
  if err != nil {
    log.Fatalf("%s: %s", msg, err)
  }
}

func main() {
  conn, err := amqp.Dial("amqp://admin:ADMINPASSWORD@10.0.0.102:5672/")
  FAIL_ON_ERROR(err, "ER: failed to connect to RabbitMQ")
  defer conn.Close()

  ch, err := conn.Channel()
  FAIL_ON_ERROR(err, "ER: failed to open channel")
  defer ch.Close()

  q, err := ch.QueueDeclare(
    "ha-default", // name
    false,        // durable
    false,        // delete when unused
    false,        // exclusive
    false,        // no-wait
    nil,          // arguments
  )
  FAIL_ON_ERROR(err, "ER: failed to declare queue")

  msgs, err := ch.Consume(
    q.Name, // queue
    "",     // consumer
    true,   // auto-ack
    false,  // exclusive
    false,  // no-local
    false,  // no-wait
    nil,    // args
  )
  FAIL_ON_ERROR(err, "ER: failed to register consumer")

  forever := make(chan bool)

  go func() {
    for d := range msgs {
      log.Printf("IN: received message: %s", d.Body)
    }
  }()

  log.Printf("IN: waiting for messages")
  log.Printf("IN: to exit press CTRL+C")
  <-forever
}

Here is its output after running. It will not stop running until you end it with CTRL-C sequence.

host % go run receive.go
2019/06/05 13:54:34 IN: waiting for messages
2019/06/05 13:54:34 IN: to exit press CTRL+C
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
2019/06/05 13:54:34 IN: received message: Hello World!
^C
%

If you checked the source code carefully then you probably noticed that I ‘sent’ messages to the rabbit1 node (10.0.0.101) while I ‘received’ the messages at the rabbit2 node (10.0.0.102).

Simple Benchmark

We will now make simple benchmark with receive.go script left running and modified send.go script with the for loop with 100000 messages.

host % go run receive.go
2019/06/05 13:52:34 IN: waiting for messages
2019/06/05 13:52:34 IN: to exit press CTRL+C

… and now the messages.

host % go run send.go
2019/06/05 13:53:59 IN: sent message 'Hello World!' (1)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (2)
2019/06/05 13:53:59 IN: sent message 'Hello World!' (3)
(...)
2019/06/05 13:56:26 IN: sent message 'Hello World!' (99998)
2019/06/05 13:56:26 IN: sent message 'Hello World!' (99999)
2019/06/05 13:56:26 IN: sent message 'Hello World!' (100000)
% 

The results of this simple benchmark are below.

05-rabbitmq-benchmark.png

About 4000-5000 messages per second are handled by this RabbitMQ clustered instance within two FreeBSD Jails.

High Availability

Now we will test the high availability of our RabbitMQ cluster.

Currently the ha-default qeue is at rabbit1 node. We will now kill the rabbit1 Jail and see how RabbitMQ web interface reacts.

host # jls
   JID  IP Address      Hostname                      Path
     1  192.168.43.101  rabbit1.local                 /jail/rabbit1
     2  192.168.43.102  rabbit2.local                 /jail/rabbit2

host # killall -9 -j 1

host # umount /jail/rabbit1/dev

Our ha-default queue in a matter of seconds switched to the rabbit2 node – HA works as desired.

06-rabbitmq-ha-node-fail.png

Let’s start rabbit1 Jail to get redundancy back.

host # service jail onestart rabbit1
Starting jails: rabbit1.
host # 

07-rabbitmq-ha-node-back.png

The ha-default queue got redundancy back with +1 mark but it remained on the rabbit2 node.

… and last but not least – little anniversary at the end – this is the 50th article (not counting Valuable News series) on my blog 🙂

UPDATE 1 – This Month in RabbitMQ

The RabbitMQ Cluster on FreeBSD Containers article was featured in the This Month in RabbitMQ – July 2019 episode.

Thanks for mentioning!

UPDATE 2 – Make RabbitMQ Use Less CPU

As reported by Felix Ehlers on Twitter – the RabbitMQ CPU usage will be reduced by setting RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="+sbwt none" variable.

EOF