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
}}