blog: Jails to nspawn

In my efforts to migrate my server to a new machine and a different OS, I found myself in need of replacing FreeBSD Jails with an analogous way of containerization. My distaste for Docker outweighed my distaste for systemd, so I tried systemd-nspawn. With Jails and ezjail it was fairly easy to set up a container with a separate, static IP address to which I could redirect particular traffic, either by port or (in case of HTTP) name. I’m not completely sure about my solution with nspawn yet, but it seems ok. I switched the host network configuration from networking.service to systemd-networkd and I’m using the trick described here to keep other parts of the network setup simple. It uses the default virtual ethernet interfaces and runs a DHCP server on the host with a pool of one - thus effectively forcing a static IP. I’m using it for the same purpose as described in the link - to proxy by name via nginx. You don’t necessarily need that for redirection by port, you can just use “port mapping” there.

It makes sense to configure some resource limits for CPU and the like, depending on the application. But other than that, below is my script to create a new nspawn machine for now. It takes a machine name, installs Debian stable and sets up networking.

#!/bin/bash

set -eu

if [ "$#" -ne 1 ]; then
	echo "Need machine name"
	exit 1
fi

name="$1"
path="/var/lib/machines/$name"

if [[ -e "$path" ]]; then
	echo "Machine already exists"
	exit 2
fi

echo "Bootstrapping debian stable to $path"
debootstrap --include=systemd,dbus stable "$path"

echo "Enable networkd in container"
systemd-nspawn -M "$name" --as-pid2 rm /etc/hostname
systemd-nspawn -M "$name" --as-pid2 systemctl enable systemd-networkd

echo "Set root password"
systemd-nspawn -M "$name" --as-pid2 passwd root

# find out which virtual device name systemd uses
machinectl start "$name"
ip a
read -p "Virtual ethernet name? " veth

# set a "static" ip 192.168.x.y with
# host y = 1 and machine y = 2
# by running a DHCP server with pool size 1
numberOfMachines=`ls /var/lib/machines/ | wc -l`
machinectl stop "$name"
networkdpath="/etc/systemd/network/50-$name.network"

cat <<EOF >"$networkdpath"
[Match]
Name=$veth
Driver=veth

[Network]
Address=192.168.$numberOfMachines.1
LinkLocalAddressing=ipv4
DHCPServer=yes
IPMasquerade=ipv4
LLDP=yes
EmitLLDP=customer-bridge
IPv6SendRA=no

[DHCPServer]
PoolOffset=2
PoolSize=1
EOF

echo "Restarting networkd and starting machine"
systemctl restart systemd-networkd
sleep 2
machinectl start "$name"
machinectl enable "$name"

# list all machines with IP address
machinectl
Posted in note to self
2023-08-21 19:37 UTC