jmtd → log → Puppet and persistent network interface names
On Linux, network interface device naming has been somewhat chaotic: depending
on a number of factors, eth0
today might not be eth0
tomorrow. For me, this
has never been a problem in practice. At work, our physical servers have a bank
of on-mainboard network ports, all managed by the same driver and so are
assigned names predictably. For our virtual machines, the same is true: 99% of
the time they have one network interface, but when they have more than one,
they are of the same type and so are assigned predictably. At home, even on
desktops and laptops with multiple network devices being added and removed at a
time, I've never actually hit a problem where one was mis-identified.
However, it must have been a problem for some people because it has been solved three times now. Sadly, these incompatible solutions cause headaches in all of my use cases.
At work we have a puppet snippet that relies on knowing the interface name for
a host's "primary" interface. In our case, "primary" means something like "the
one the default route is over". We used to hardcode eth0
into this recipe. I
don't feel too bad about that, as the puppet folks do the exact same thing in
many of their scripts.
This breaks for newer physical hosts where one of the three schemes are in play
and the interfaces are named/numbered em1
and counting. We worked around it
by parameterizing the interface name in our recipe and defining it in our
nodes.pp
for the troublesome hosts, but that just moves the problem around.
My more permanent solution is to define a facter fact primary_interface
that
tells puppet what the correct interface should be. Here's my first attempt:
Facter.add("primary_interface") do
setcode do
ifs = Dir.new("/sys/class/net").each.to_a - \
Dir.new("/sys/devices/virtual/net").each.to_a
# filter out ones with no link
ifs.reject! do |i|
begin
File.read("/sys/class/net/#{i}/carrier") != "1\n"
rescue Errno::EINVAL
true
end
end
ifs[0]
end
end
Of course, there is plenty of room for improvement with this solution: if ifs
has more than one element at the end, a lexographic sort may prove more
accurate; if there is an existing network default route up, that might be a
strong clue too. It's also very Linux specific. My question though is whether
this is a sensible approach in the first place?
Comments
Your method is clever to generate a list of link active network devices. But if there are more than one then there isn't a way to know which one is primary. But it all depends upon what you need to know that information for and what you will do with it.
Perhaps just looking at what the default route is using is sufficient.
Of course if you are looking to determine the primary interface so that you can configure an appropriate default route then it is a circular dependency and something else is needed.
Hi Bob, thanks for your comment.
In my case, situations where there are more than one link-active interface are very rare, and in all cases the "natural" order would provide the correct answer (so if I have
eth0
andeth1
wired in, by convention I'm usingeth0
as the "primary" interface andeth1
for something else). The persistent naming schemes all try to preserve "natural" order, even when they use different names: soem1
will always be the first on-board NIC port, beforeem2
; likewise forsystemd
's forthcomingen1
notation, etc.I think for most people the exceptions would be rare enough to special-case (i.e., don't use
$primary_interface
in your puppet recipes for those situations, or override it).Indeed there is a circular issue. The default route might be over a bridge in which case the "primary interface" is something else. There may be more than one default route in the routing table (but in that situation I think "ip route show" prints them in order so the top-most is the relevant one. Sad that this data is not exposed in sysfs). You may not have a default route at all if the recipe you are trying to run is one that fixes up the networking.
I think I'll insert a lexographic sort into the mix to make it a bit more robust.
For the sake of completion, here's my latest version. Internally I distribute this as a puppet module, although it's farcically simple, just this one snippet. However I will probably put that up somewhere public shortly.