Modern Linux distributions have moved away from predictable interface naming (eth0, eth1) to more dynamic naming schemes like enp3s0 or ens160. While this improves consistency, it makes automation more challenging when you need to identify interfaces based on their order or position in the system.
Ansible gathers comprehensive network information during the fact gathering phase. The network interface data is stored in the ansible_facts
dictionary under the ansible_interfaces
key and detailed interface information is available in ansible_<interface>
facts.
- name: Display all network interfaces
debug:
var: ansible_interfaces
- name: Show details for a specific interface
debug:
var: ansible_ens160
To get a sorted list of interfaces that maintains consistent ordering across runs, we can process the facts with this playbook snippet:
- name: Create sorted interface list
set_fact:
sorted_interfaces: "{{ ansible_interfaces | sort }}"
- name: Get interface details in order
debug:
var: ansible_facts[item]
loop: "{{ sorted_interfaces }}"
For more precise control, we can filter and sort interfaces based on specific criteria:
- name: Filter and sort physical interfaces
set_fact:
physical_interfaces: "{{ ansible_interfaces |
select('match', '^en|^eth') |
sort }}"
- name: Get IPv4 addresses in order
debug:
msg: "Interface {{ item }} has IP {{ ansible_facts[item].ipv4.address }}"
loop: "{{ physical_interfaces }}"
when: ansible_facts[item].ipv4 is defined
Here's a complete example that assigns interface roles based on position:
- hosts: all
gather_facts: yes
tasks:
- name: Create ordered interface list
set_fact:
sorted_interfaces: "{{ ansible_interfaces |
select('match', '^en|^eth') |
sort }}"
- name: Assign roles based on interface order
set_fact:
lan_interface: "{{ sorted_interfaces | first }}"
wan_interface: "{{ sorted_interfaces | last }}"
- name: Display interface assignments
debug:
msg: >
LAN is {{ lan_interface }} ({{ ansible_facts[lan_interface].ipv4.address }}),
WAN is {{ wan_interface }} ({{ ansible_facts[wan_interface].ipv4.address | default('not configured') }})
When you need to reference interfaces dynamically in templates or configurations:
- name: Configure network settings in a template
template:
src: network.conf.j2
dest: /etc/network.conf
vars:
primary_interface: "{{ sorted_interfaces | first }}"
secondary_interface: "{{ sorted_interfaces | last }}"
Example template (network.conf.j2):
# Primary network interface
interface {{ primary_interface }} {
ip_address = {{ ansible_facts[primary_interface].ipv4.address }};
gateway = {{ ansible_facts[primary_interface].ipv4.gateway }};
}
# Secondary interface
{% if ansible_facts[secondary_interface].ipv4 is defined %}
interface {{ secondary_interface }} {
ip_address = {{ ansible_facts[secondary_interface].ipv4.address }};
}
{% endif %}
When working with modern Linux distributions, traditional interface naming conventions (eth0, eth1) have been replaced by unpredictable naming schemes like enp3s0 or ens160. This creates challenges when you need to programmatically identify interfaces based on their order or position in the system.
Ansible automatically gathers network interface information during playbook execution. The facts are stored in the ansible_facts
dictionary under ansible_interfaces
and ansible_<interface>
structures. Here's how to access them:
- name: Display all network interfaces
debug:
var: ansible_interfaces
- name: Display details for a specific interface
debug:
var: ansible_ens160
To sort interfaces by their numerical index (like eth0, eth1 would be), we need to extract and sort the interface names:
- name: Get sorted list of network interfaces
set_fact:
sorted_interfaces: "{{ ansible_interfaces | select('match','^[a-z]+[0-9]+') | sort }}"
- name: Get first interface IP
debug:
msg: "LAN IP: {{ hostvars[inventory_hostname]['ansible_' + sorted_interfaces[0]]['ipv4']['address'] }}"
For systems using predictable network interface names (systemd's naming scheme), we can use this more robust approach:
- name: Create sorted interface list with IP information
set_fact:
network_interfaces: |
{% set interfaces = [] %}
{% for iface in ansible_interfaces %}
{% if hostvars[inventory_hostname]['ansible_' + iface]['ipv4'] is defined %}
{% set _ = interfaces.append({
'name': iface,
'ip': hostvars[inventory_hostname]['ansible_' + iface]['ipv4']['address'],
'index': hostvars[inventory_hostname]['ansible_' + iface]['device_os_index'] | default(999)
}) %}
{% endif %}
{% endfor %}
{{ interfaces | sort(attribute='index') }}
- name: Use the sorted interfaces
debug:
msg: "LAN interface is {{ network_interfaces[0].name }} with IP {{ network_interfaces[0].ip }}"
Here's a complete playbook example that handles both traditional and modern interface naming:
- hosts: all
gather_facts: yes
tasks:
- name: Build interface information structure
set_fact:
network_info: |
{% set result = [] %}
{% for iface in ansible_interfaces %}
{% set iface_data = hostvars[inventory_hostname]['ansible_' + iface] %}
{% if iface_data.ipv4 is defined %}
{% set _ = result.append({
'name': iface,
'ipv4': iface_data.ipv4.address,
'mac': iface_data.macaddress,
'index': iface_data.device_os_index | default(999)
}) %}
{% endif %}
{% endfor %}
{{ result | sort(attribute='index') }}
- name: Configure LAN settings (first interface)
template:
src: lan_config.j2
dest: /etc/network/lan.conf
vars:
lan_interface: "{{ network_info[0].name }}"
lan_ip: "{{ network_info[0].ipv4 }}"
- name: Configure WAN settings (last interface)
template:
src: wan_config.j2
dest: /etc/network/wan.conf
vars:
wan_interface: "{{ network_info[-1].name }}"
wan_ip: "{{ network_info[-1].ipv4 }}"
This approach gives you a reliable way to work with network interfaces regardless of their naming scheme, while maintaining the order-based functionality you need for your network topology.