Modern Linux distributions have moved away from the traditional eth0 naming convention to predictable network interface names like ens33, enp0s3, or eno1. While this provides consistency, it creates challenges for automation when you need to:
- Identify physical interfaces dynamically
- Configure bridge interfaces properly
- Handle varying interface names across hosts
Using {{ ansible_default_ipv4.interface }} seems logical initially, but it presents problems:
# Problem scenario:
# After bridge creation, this will return br0 instead of the physical interface
bridge_ports: "{{ ansible_default_ipv4.interface }}" # Returns br0 after bridge creation
Here's how to properly extract physical interface names using Ansible's regex capabilities:
Solution 1: Using select and match filters
- name: Extract physical interfaces
set_fact:
physical_interfaces: "{{ ansible_interfaces | select('match','^en') | list }}"
- name: Get first physical interface
set_fact:
primary_interface: "{{ physical_interfaces[0] }}"
Solution 2: Jinja2 regex with list filtering
- name: Find matching interfaces
debug:
msg: "{{ item }}"
loop: "{{ ansible_interfaces }}"
when: item is regex('^en[a-z0-9]*$')
register: interface_results
- name: Extract successful matches
set_fact:
matched_interfaces: "{{ interface_results.results | selectattr('skipped', 'equalto', false) | map(attribute='item') | list }}"
Solution 3: Combining with network facts
- name: Gather specific interface facts
ansible.builtin.setup:
filter: "ansible_*"
gather_subset: "!all"
- name: Get interfaces with MAC addresses (physical interfaces)
set_fact:
physical_ifaces: "{{ ansible_facts | dict2items | selectattr('key', 'match', '^ansible_en') | map(attribute='key') | list | map('regex_replace', '^ansible_', '') }}"
Here's how to apply this in your /etc/network/interfaces template:
auto br0
iface br0 inet dhcp
bridge_ports {{ primary_interface | default('eth0') }}
bridge_stp off
bridge_fd 0
Consider these additional scenarios:
- name: Handle multiple matching interfaces
set_fact:
bridge_ports: "{{ physical_interfaces | join(' ') }}"
when: physical_interfaces | length > 1
- name: Fallback to eth0 if no matches found
set_fact:
primary_interface: "eth0"
when: physical_interfaces | length == 0
Here's a complete example to test interface detection:
- hosts: all
gather_facts: yes
tasks:
- name: Get physical interfaces
set_fact:
phys_ifaces: "{{ ansible_interfaces | select('match','^en') | list }}"
- name: Debug physical interfaces
debug:
var: phys_ifaces
- name: Configure bridge if physical interface exists
template:
src: interfaces.j2
dest: /etc/network/interfaces
when: phys_ifaces | length > 0
When automating bridge interface configuration in Ansible, one common challenge is reliably identifying the physical network interface that should be bridged. The traditional approach using ansible_default_ipv4.interface fails after bridge creation because it then returns the bridge interface itself.
Ansible provides ansible_interfaces, which contains a list of all network interfaces. For modern Linux systems with predictable network interface names, these typically follow patterns like ens*, enp*s*, or eth*.
ansible_interfaces: ['lo', 'ens33', 'br0']
The key to solving this is combining Jinja2 filters with proper regex matching. Here's the most robust solution:
- name: Find first matching physical interface
set_fact:
physical_interface: "{{ ansible_interfaces | select('match','^en') | first }}"
# '^en' matches ens*, enp*, eno*, etc.
Here's a full playbook example that handles the bridge configuration:
- hosts: all
tasks:
- name: Get first physical interface
set_fact:
bridge_port: "{{ ansible_interfaces | select('match','^en') | first }}"
- name: Configure bridge interface
template:
src: interfaces.j2
dest: /etc/network/interfaces
owner: root
group: root
mode: 0644
With the corresponding template (interfaces.j2):
auto br0
iface br0 inet dhcp
bridge_ports {{ bridge_port }}
For more complex environments, you might need additional filtering:
- name: Get physical interface excluding specific patterns
set_fact:
bridge_port: >-
{{
ansible_interfaces
| select('match','^en')
| reject('match','^enx') # exclude USB Ethernet
| reject('match','^enp.*f') # exclude virtual functions
| first
}}
Another method leverages the ansible_facts dictionary for more detailed interface information:
- name: Get physical interface with link status
set_fact:
bridge_port: >-
{{
ansible_facts
| dict2items
| selectattr('key','match','^en')
| selectattr('value.active','eq',true)
| map(attribute='key')
| first
}}