How to list internal network interfaces easily ?
I have more and more servers managed by Saltstack and they are increasingly different from one another. I use multiple distributions and a mix of different hardware and virtual machines.
At some point I was able to say 'eth0' is the internal network interface, 'eth1' is the external network (if any). It does not work anymore, at all. However, this is an information I need, to apply firewall rules for example (see my slightly modified iptables saltstack formula ), or assign a service to an interface.
Tried something with Jinja
And that was a total failure. It's an information we almost have, because we know what IP are assigned to which interface inside grains :
root@cs-salt-master:/srv/pillar# salt 'cs-prd-rbx-ovh-ded-mon-aaa.mon.ded.ovh.rbx.prd.cloud-solutions.fr' grains.get 'ip4_interfaces'
cs-prd-rbx-ovh-ded-mon-aaa.mon.ded.ovh.rbx.prd.cloud-solutions.fr:
----------
eth0:
eth1:
- 10.94.66.134
lo:
- 127.0.0.1
So I tried to define a pillar with Jinja who listed all internal network interfaces on a minion. However, it did not work, as I did not found a reliable way to test if a variable is defined or not. I tried is defined
, is not none
, simply if data['inet']
, if data['inet'][0]['address']
and I ended up with |default('')
. Nothing worked, so when an interface had no IP assigned, Jinja failed to render, with an error like data['inet'] is not defined
. The disaster :
hardware:
{%- set internalip = salt.network.ip_addrs(cidr='10.0.0.0/8')[0] %}
{%- set interfaces = salt.network.interfaces() %}
{%- for interface, data in interfaces.iteritems() %}
{%- if data['inet'][0]['address']|default('') == internalip %}
{%- set internalinterface = interface %}
internal-interface: {{ internalinterface }}
{%- endif %}
{%- endfor %}
I spend an hour trying to get this working, but at least I learned something important : don't use Jinja for anything else than simple templating !
A proper solution
It's an information that doesn't change often (even never in my case), so a custom grain is in fact much more appropriate. Ten minutes and a few python lines later in my favorite editor :
#!/usr/bin/env python
import salt.modules.network
def internal_interfaces():
grains = {}
internalips = salt.modules.network.ip_addrs(cidr='10.0.0.0/8')
interfaces = salt.modules.network.interfaces()
grains['internal_interfaces'] = []
for interface, data in interfaces.iteritems():
for internalip in internalips:
if 'inet' in data and data['inet'][0]['address'] and data['inet'][0]['address'] == internalip:
grains['internal_interfaces'].append(interface)
return grains
It works perfectly for my use case, with multiple interfaces or interfaces not connected or "new" interface names :
root@cs-salt-master:/srv/pillar# salt 'cs-prd-rbx-ovh-pcc-id-aaa.id.pcc.ovh.rbx.prd.cloud-solutions.fr' grains.get 'internal_interfaces'
cs-prd-rbx-ovh-pcc-id-aaa.id.pcc.ovh.rbx.prd.cloud-solutions.fr:
- ens192
However I do not check if interfaces have multiple IP, I only check the first one ( data['inet'][0]['address']
), so keep that in mind if you mix internal and external networks on a single interface.
Reactor
One last problem : custom grains are not synced at first highstate run. It's really important for me, as I use salt-cloud and reactor to do all the work, I need my minions to be 100% working after the first highstate run. There is a simple solution to this problem, that you can find in the official reactor documentation .
In short, it will sync custom grains at minion start, which is exactly what we want.
Enjoy !