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
.network
config
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-veth
in/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
Name
in[Match]
must match the container interface displayed to you byip link
(remove the@if2
suffix):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:
Address
specifies the static IP address of the container, where I chose192.100.100.10
(*.10
is the container IP,*.1
is the host/gateway IP). Set a customDNS
, such as8.8.8.8
(Google) or1.1.1.1
(Cloudflare). Note thatGateway
MUST 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
internet0
is 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