Note - this article assumes you have reasonable familiarity with systemd, networkd and nspawn/machinectl
There seem to be a lot of references on the internet for nspawn networking with a bridge - but not on simply using the stock veth networking with static addresses. The default behaviour of nspawn (as of 24/03/20 and systemd:245 on Arch) is to:
- Make a virtual ethernet port on the host (defined by
/lib/systemd/network/80-container-ve.network) - Make a virtual ethernet port in the container (defined by
/lib/systemd/network/80-container-host0.network) - Based on those configurations, serve the host port a random IP from the local network ranges with a 28 bit subnet, and a DHCP server for the host port serves a random IP in the subnet to the container
And with that - internet "just works". Ain't that nice
Essentially exactly what the default behaviour was but with two key changes
- The host port should have a statically set IP - so that applications on the host expecting output from the container can bind to a specific IP range via their own configs
- Set the container port statically as well - so a forwarding application (like NGINX) knows where to send traffic
No bridges - just virtual ethernet ports. If this sounds like you - read on.
First - systemd-networkd will evaluate matches for assinging configs to ports based on alphabetical order. So - we need to inject config before we hit 80-.
To keep things simple - make sure your container is poweroff'd with machinectl.
On the host - copy the /lib/systemd/network/80-container-ve.network into /etc/systemd/network/50-mycontainer.network. Crack it open - it should look like
[Match]
Name=ve-*
Driver=veth
[Network]
# Default to using a /28 prefix, giving up to 13 addresses per container.
Address=0.0.0.0/28
LinkLocalAddressing=yes
DHCPServer=yes
IPMasquerade=yes
LLDP=yes
EmitLLDP=customer-bridge
We need to change the Name in [Match] to be the interface we expect. This is either defined by container name in machinectl to fit in the 16 character limit - or defined by the container config itself with VirtualEthernetExtra to give it a special name.
We also change the Address to be a static IP - I picked something in the 10.0.0.0/8 space, then restricted it to a 28 bit subnet (ie: 10.34.60.49/28 -> 10.34.60.48 - 10.34.60.51). This will sort the host side out.
For the container - we will need to either do
- Very restricted DHCP
- Another static
.networkconfig
Neither are amazingly hard
For DHCP - add to the 50-mycontainer.network config
[DHCPServer]
PoolOffset=2
PoolSize=1
This gives the DHCP server 1 address to serve, at 10.34.60.48 + 2 => 10.34.60.50
Doing it this way means that you have all networking controlled by the host - the container is fully stock systemd-networkd.
However - we can also do what we did for the host in the container. Start your container, log into it as root and copy /lib/systemd/network/80-container-host0.network to somewhere sane. It should look like
[Match]
Virtualization=container
Name=host0
[Network]
DHCP=yes
LinkLocalAddressing=yes
[DHCP]
UseTimezone=yes
We need to remove DHCP and instead define an Address=10.34.60.50/28, plus a Gateway=10.34.60.49 if you want this to talk to the net/wider network. I still haven't figured out how to get systemd-resolved happy with this configuration however - workaround could be just to point it directly at an internet DNS.
This should give you a predictable pair of IPs for things to talk over! systemd as always has great documentation - so if the internet is still turning up nothing its best to sit with a good cup of tea and just read, reread and reread it again.
So I've been fighting with this quite a while, but finally figured it out. So when you're running a nspawn container in private networking mode (the service is started with
--network-vethin/etc/systemd/system/[email protected], which is configured by default), then two things must be configured:NOTE: This assigns a static IP to an individual container.
On the host
Configure the host interface which will be exposed to the container.
Modify the new file as desired:
Note that
Namein[Match]must match the container interface displayed to you byip link(remove the@if2suffix):Then specify your desired, static IP address in
[Network], which represents the GATEWAY exposed to the container, NOT the container itself. I chose192.100.100.1/24. Some other fields could probably be removed, but I'm leaving it as is.Finally, restart the networking service:
In the container
Set the static IP address of the container, and specify the Host/Gateway IP.
Modify the new file as desired:
Addressspecifies the static IP address of the container, where I chose192.100.100.10(*.10is the container IP,*.1is the host/gateway IP). Set a customDNS, such as8.8.8.8(Google) or1.1.1.1(Cloudflare). Note thatGatewayMUST match the IP address as specified in the host, as described above. Now, exit the container and restart it (from the host):The container should now have a static IP address. For convenience, add it to
/etc/hosts(which simplifies SSH access, for example):Note about firewalls:
If your host has a (passive) firewall enabled, an additional rule is required (compatible with
ufw). Make sure you persist that rule:Note that
internet0is the interface to the internet, from the host. In my case, it'senp82s0.More info: https://wiki.archlinux.org/index.php/Systemd-nspawn#Use_a_virtual_Ethernet_link