systemd-nspawn - Containerization - Part 1

Nov 22, 2020

What is systemd-nspawn?

systemd is known as INIT system, which is used in many unix and linux distributions. systemd-nspawn is available as a part of default systemd package and if it is not available, install the following packages:

systemd-nspawn can be used to run raw images or docker images without installing additional software tools and it is controlled by systemd, with the help of namespaces all the networks and logs are separated in our host itself. This post is not about what docker can and cannot do, it’s more about how to use systemd-nspawn to achieve containerization without having to worry about extra tooling.

I have seen many people calling this as a replacement for chroot, which is true and it can be much more than that is my opinion. Before, proceeding further, please make backup of your system, incase of issues. Though this is purely a precaution, we don’t want to risk losing data.

Before we proceed:

Tools - we’ll be using:

Pick your favourite OS or Image:

I chose ubuntu, which is relevant to my usecase. Choose from Ubuntu Cloud Images .

Don’t rush to download it, we will be doing it using machinectl command. You can run the following commands as root (if you’re daring!)

$ machinectl pull-tar http://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-root.tar.gz xenial
$ machinectl pull-tar [LINK] [CONTAINER NAME]
......PROGRESS NOTIFICATION.........

Poof! where did my image go? Don’t worry, it’s safe in /var/lib/machines/xenial directory. By default, all images pulled using machinectl goto that directory.

Power on your first specialised container:

$ systemd-nspawn -D /var/lib/machines/alpine -U -M xenial
Spawning container xenial on /var/lib/machines/xenial.
Press ^] three times within 1s to kill container.
Selected user namespace base 706150400 and range 65536.
root@xenial:~#

Anything you do from now on is inside the container and make sure you’re not doing it on your actual machine.

Add a new user:

root@xenial:~# adduser test-user
......Normal User addition steps...........

Add instance name to hosts file:

root@xenial:~# echo "127.0.0.1 xenial" >> /etc/hosts
# If this is not done, then we'll keep getting an error `unable to resolve hostname: xenial` when executing commands.

Adding a default network interface inside the container (Optional)

If you don’t want to complicate your container setup by adding separate network for the container, then you can skip and proceed to the usage part. By default, the container talks to the host through a host0 network interface, we need to add it the interfaces to autostart, when the container boots.

When we say network couple of things come into picture,

Bridged Network with a Wired Connection in host

$ brctl addbr virxen
$ brctl show virxen
bridge name     bridge id               STP enabled     interfaces
virxen           8000.000000000000       no
$ brctl addif [host internet connected wired interface]
# systemd-nspawn -D /var/lib/machines/alpine -U -M xenial
Spawning container xenial on /var/lib/machines/xenial.
Press ^] three times within 1s to kill container.
Selected user namespace base 706150400 and range 65536.
root@xenial:~# echo 'auto host0' >> /etc/network/interfaces
root@xenial:~# echo 'iface host0 inet dhcp' >> /etc/network/interfaces
[Exec]
# Writable bind mounts don't with user namespacing
PrivateUsers=no
[Network]
VirtualEthernet=yes
Bridge=virxen

Bridged Network with a Wireless Connnection in Host using NAT:

$ sudo ip addr add 192.168.30.1/24 brd + dev [your brigde name]
# verify using the next command
$ ip a
...........Many other network interfaces................
9: virxen: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
        link/ether f6:33:3c:bd:a8:40 brd ff:ff:ff:ff:ff:ff
        inet 192.168.30.1/24 brd 192.168.30.255 scope global virxen
           valid_lft forever preferred_lft forever
$ sudo iptables -t nat -A POSTROUTING -s 192.168.30.0/24 ! -d 192.168.30.0/24 \
> -o [Your Internet Connected Wireles Interface Name] -j MASQUERADE
# Note: If you worry about security a lot, tweak the rule as per your requirement, as it a very generous rule
# verify using the next command
$ sudo iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination       
Chain INPUT (policy ACCEPT)
target     prot opt source               destination        
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  192.168.30.0/24     !192.168.30.0/24     
$ sudo iptables -A FORWARD -i virxen -o [your wireless interface name] -j ACCEPT
# verify using the next command
$ sudo iptables -L --verbose --line-numbers FORWARD
Chain FORWARD (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
.........................MANY OTHER NETWORK INTERFACES.................................
10      27  1608 ACCEPT     all  --  virxen [Your wirelsess interface name]  anywhere             anywhere            

Note:

These network rules that we have configured using iptables, will not persist across reboots, so you would need to find your way to keep them safely across reboots. Let’s cover the iptables rules persistence in the next section.


   til (4) , systemd (2) , nspawn (2) , systemd-nspawn (2) , linux (5) , unix (5) , script (3) , containers (2) , development (2)