Screw netplan, screw ifupdown
Let’s to systemd it!
I got tired of fighting different ‘network configuration suites’ for ip alias purposes. I gave up on them completely and start to use simple but robust systemd dependency system.
Why? Who? What’s going on?
If your code need to add an additional IP to the server’s network interface in a persistent manner, the abyss is unearthed. I’m talking about the case when you don’t control the whole network configuration, and all you want is just to stick one (or more) IPs onto interfaces.
Table of content for abyss survival attempts:
- lineinfile
- … to find that you can have multiple files
- …. in unexpected locations
- ….. and some of them are ‘dhcp’ and does not support address assignment
- … and may be, you already have this IP assigned.
now multiple this (use Cartesian product) by differences in ifupdown and netplan.
And don’t forget that interfaces may not be present instantly (hello, overlays, VPNs and SDNs).
Now you are 1% deep into abyss. There are 99% more awaiting you with impatience.
So, don’t go there. There is a better way.
Precise problem definition
We need to add an additional IP address onto given interface for use by a specific service/services. It should work if:
- This address is already assigned to the interface.
- The interface may appear after normal network configuration was done but before we are starting our target service(es).
- Interface may not be present at all and we want to have some grace failure which does not render our server unreachable on it’s ssh (management) interface.
Please note, it’s not a generic ‘network configuration’ task and we don’t want to mess with all other parameters: DHCP leases, associated routing entries into the kernel routing table, MTU value, up/down state, etc, etc. All we want is to sneak few IP aliases onto existing and otherwise configured interface.
The solution is to use systemd units with Before= dependency
Well, basically, that’s it. We need to write a systemd service unit for each IP alias we want to configure. We can use ‘Before=
’ stanza by fixing execution order for ‘ip alias’ and ‘dependent service’, and we use normal WantedBy=multi-user.target
for installation (enablement) purposes.
Here is my snippet from a template for Anisble role for this:
[Unit]
Description=Foo IP alias
After=network.target
Before=foo-service.target
Before=foobar-service.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/ip address replace {{ item }} dev {{ interface }}
[Install]
WantedBy=multi-user.target
As you can see, it does everything I’ve talked about.
The single rarely known thing here is ‘replace’ stanza for ip. If you try to ‘ip address add’ of address onto interface with this address already assigned, you get an error:
RTNETLINK answers: File exists
replace
, on another hand, will add the address if it’s not present, and silently ‘ok’ if the address is already present.
That’s it. My role generates few of those for services, and on each boot each service has all aliases it needs.
Amazingly, this trick is gonna work on practically everything modern: Centos, old Ubuntu (16.04), new Ubuntu (18.04, 20.04), Debian, etc. All you need is some systemd lurking about.
If you have a really flaky interface, you may want to add a dependency for device unit, but for my purposes it wasn’t necessary.