Вопрос или проблема
Я хотел бы получить упорядоченный список сетевых интерфейсов на машине, используя Ansible. Современные системы Linux не используют eth0, eth1 и т.д. Поэтому названия непредсказуемы. В нашей сети мы подключаем интерфейс с наименьшим номером к LAN, а интерфейс с наибольшим номером – к WAN, так что я могу использовать позицию интерфейса в упорядоченном списке, чтобы определить его функцию.
Я ищу канонический способ сделать это в Ansible. Чтобы я мог использовать что-то вроде {{ansible_eth0.ipv4.address}}. (Где eth0 – это какое-то другое имя).
Даже если я вручную установлю переменную с именем интерфейса, похоже, нет способа получить IP этого интерфейса (используя содержимое переменной).
Я хотел бы обработать факты Ansible, чтобы получить то, что мне нужно, а не запускать shell-скрипт на удаленной системе.
Факт ansible_interfaces
перечисляет все существующие сетевые интерфейсы.
Некоторые подсказки, чтобы получить интерфейс, когда мы знаем некоторую информацию:
var:
allNetworkInterfaces: "{{ ansible_facts | dict2items | selectattr('value.ipv4', 'defined') | map(attribute='value') | list }}"
allNetworkInterfaces_variant2: "{{ ansible_facts.interfaces | map('extract', ansible_facts) | list }}"
interfaceWithKnownIp: "{{ ansible_facts | dict2items | selectattr('value.ipv4', 'defined') | selectattr('value.ipv4.address', 'equalto', myKnowIpV4) | first }}"
interfaceWithKnownIp_fromVar: "{{ allNetworkInterfaces | selectattr('ipv4.address', 'equalto', myKnowIpV4) | first }}"
interfacesWithPartialKnowMac: "{{ allNetworkInterfaces | selectattr('macaddress', 'match', knownMacPrefix ~ '.*') | list }}"
interfacesWitKnowType: "{{ allNetworkInterfaces | selectattr('type', 'equalto', knownType) | sort(attribute='device') | list }}"
# расширено 2020-10-28
queryForKnownIpv6: "[*].{device: device, ipv4: ipv4, ipv6: ipv6[? address == 'fe80::a00:27ff:fe38:ad36']}[?ipv6])" # строка должна быть в ' # извините, только частичная информация об интерфейсе, не удалось узнать, как вернуть всю информацию напрямую
interfacesWithKnownIpv6: '{{ allNetworkInterfaces | json_query(queryForKnownIpv6) | first }}'
queryForKnownIpv4_linux: "[?ipv4.address == '{{ myKnownIpV4 }}']}[?ipv4])" # строка должна быть в '
interfacesWithKnownIp_variantJsonQuery: '{{ allNetworkInterfaces | json_query(queryForKnownIpv4_linux) | first }}'
Некоторые короткие объяснения:
- dict2item, потому что selectattr ожидает массив
- map(attribute=’…’) для распаковки этого массива
- map(‘extract’, …) для извлечения интерфейсов из
ansible_facts
ansible_facts.interfaces
это то же самое, чтоansible_interfaces
- json_query() для гибкого выбора и распаковки, альтернативный вариант с `map()? приветствуется
Я понимаю, откуда исходит Neik, и после небольшого эксперимента, я думаю, что нашел задачу, которая сработает.
Как уже упоминал Майкл Гамильтон, факт ansible_interfaces
содержит список всех сетевых интерфейсов, и они, похоже, упорядочены (т.е. первый Ethernet интерфейс был бы назван eth0, второй eth1 и т.д.). Так что после небольшого волшебства set_fact
и множества экспериментов:
- name: определить традиционные факты Ethernet
set_fact:
ansible_eth: "{% set ansible_eth = ansible_eth|default([]) + [hostvars[inventory_hostname]['ansible_' + item]] %}{{ ansible_eth|list }}"
when: hostvars[inventory_hostname]['ansible_' + item]['type'] == 'ether'
with_items:
- "{{ hostvars[inventory_hostname]['ansible_interfaces'] }}"
Это проходит по всем записям ansible_interfaces
для текущей машины и создает список записей hostvars[inventory_hostname]['ansible_' + item]
, у которых type
равно “ether”.
Таким образом, теперь ansible_eth.0
и ansible_eth.1
должны быть примерно эквивалентны старым ansible_eth0
и ansible_eth1
соответственно.
Я не тестировал это тщательно, чтобы убедиться, что порядок всегда работает так, как ожидалось, но оно, похоже, срабатывает.
Большое спасибо за этот ответ на StackOverflow, который показал мне, как построить список, используя with_items.
Прошло некоторое время с тех пор, как я прикасался к ansible, но без дополнительных деталей я бы ожидал, что что-то вроде:
ip link show | grep mode | sed 's/://g' | awk '{print $1,$2}'
будет работать…
1 lo
2 eth0
Очень похоже на ответ Даниэля Видрика, но избегаю использования старого with_items
в пользу loop
. Также я хотел найти только Wi-Fi интерфейс, который все еще wlan0
для Raspberry. Это также найдет много других адаптеров, так как большинство из них начинаются с wl
(но не все)
- name: Получить адаптер Wi-Fi
set_fact:
wifi_adapter: '{{ item }}'
loop: '{{ ansible_facts.interfaces }}'
when: 'item.startswith("wl")'
Еще один подход к этому, с изюминкой. Это должно создать список “реальных” (Ethernet и bonding) интерфейсов, который соответствует списку сетей в telegraf_setup.bind.nets
(в нотации CIDR). Очень удобно для правил брандмауэра.
- set_fact:
telegraf_if: "{% set telegraf_if = telegraf_if|default([]) %}{% if hostvars[inventory_hostname]['ansible_' + item.0]['ipv4']['address']|ipaddr(item.1) %}{% set telegraf_if = telegraf_if + [ item.0 ] %}{% endif %}{{ telegraf_if|list }}"
when: hostvars[inventory_hostname]['ansible_' + item.0]['ipv4']['network'] is defined and
(hostvars[inventory_hostname]['ansible_' + item.0]['type'] == "boding" or hostvars[inventory_hostname]['ansible_' + item.0]['type'] == "ether")
with_items:
- "{{ hostvars[inventory_hostname]['ansible_interfaces']|map('replace','-','_')|list|product(telegraf_setup.bind.nets)|list }}" # например, br-2139083489
Обычно я объявляю telegram_if
как пустой список ранее вместе с некоторыми другими переменными, пропуская часть {% set telegraf_if = telegraf_if|default([]) %}
.
Вот пример настройки правила брандмауэра на основе этого рецепта. Я использовал route: "yes"
, потому что Telegraf работает в контейнере Docker:
- name: telegraf | Открыть брандмауэр для Telegraf
ufw:
rule: "allow"
route: "yes"
src: "{{ item.1 }}"
dest: "{{ telegraf_ip }}"
port: "{{ telegraf_setup.bind.port }}"
proto: "tcp"
direction: "in"
interface: "{{ item.0 }}"
comment: "telegraf-in"
delete: "false"
when: "hostvars[inventory_hostname]['ansible_' + item.0]['ipv4']['address']|ipaddr(item.1)"
with_items:
- "{{ telegraf_if|product(telegraf_setup.bind.nets)|list }}"
Ответ или решение
Как перечислить сетевые интерфейсы в Ansible
Введение
Аутентификация сетевых интерфейсов на машинах с использованием Ansible может быть непростой задачей, особенно с учетом того, что современные операционные системы Linux не придерживаются стандартного наименования интерфейсов (таких как eth0, eth1 и т. д.). В данной статье мы рассмотрим, как правильно получить упорядоченный список сетевых интерфейсов и воспользоваться ими в вашем Ansible проекте.
Использование фактов Ansible
Ansible автоматически собирает множество фактов о целевой системе, которые можно использовать для получения информации о сетевых интерфейсах. Два ключевых источника фактов – это ansible_interfaces
и ansible_facts
.
ansible_interfaces
– это список всех сетевых интерфейсов, доступных на целевой машине.ansible_facts
предоставляет более детальную информацию о каждом интерфейсе, включая его настройки IP.
Шаги по перечислению сетевых интерфейсов
1. Сбор фактов
На каждом этапе вам нужно будет собрать факты с помощью модуля setup
. Вы можете это сделать следующим образом:
- name: Собрать факты о системе
setup:
Эта задача собирает все доступные факты о системе, включая информацию о сетевых интерфейсах.
2. Перечисление интерфейсов
После сбора фактов вы можете использовать ansible_interfaces
для получения списка интерфейсов. Рассмотрим пример:
- name: Перечислить сетевые интерфейсы
debug:
msg: "{{ ansible_interfaces }}"
Это даст вам простой список всех интерфейсов.
3. Упорядочивание интерфейсов
Чтобы упорядочить интерфейсы по определенному критерию (например, по порядку их номеров), вы можете воспользоваться фильтрами Jinja. К примеру, чтобы получить интерфейсы, тип которых "ether", вы можете использовать следующий подход:
- name: Определить традиционные ethernet факты
set_fact:
ansible_eth: "{% set ansible_eth = ansible_eth|default([]) + [hostvars[inventory_hostname]['ansible_' + item]] %}{{ ansible_eth|list }}"
loop: "{{ ansible_facts.interfaces }}"
when: hostvars[inventory_hostname]['ansible_' + item]['type'] == 'ether'
Это создаст список ansible_eth
, который будет содержать все ethernet интерфейсы, упорядоченные по их появлению в ansible_interfaces
.
Получение IP адреса интерфейса
Теперь, когда у вас есть упорядоченный список интерфейсов, вы можете получить IP адреса, используя переменные динамически. Например, чтобы получить IP адрес первого интерфейса, вы можете сделать так:
- name: Получить IP адрес первого Ethernet интерфейса
debug:
msg: "{{ ansible_eth[0].ipv4.address }}"
Пример использования
Вы можете использовать все эти приемы для создания более сложных задач. Вот пример:
- name: Получить адреса всех Ethernet интерфейсов
debug:
msg: "Интерфейс: {{ item }} - IP: {{ hostvars[inventory_hostname]['ansible_' + item]['ipv4']['address'] }}"
loop: "{{ ansible_eth|map(attribute='name') }}"
Заключение
Использование Ansible для перечисления сетевых интерфейсов на современных Linux системах – это значительно более удобный и мощный способ управления ресурсами по сравнению с запуском произвольных скриптов. С помощью представленных выше методов вы сможете получить четкую и упорядоченную информацию о ваших интерфейсах и медленно переходить к более сложным задачам управления. Важно понимать, что правильная структуризация ваших переменных и фактов в Ansible позволяет вам максимально использовать возможности этого инструмента.