Ansible 2.4: network automation для Cisco IOS - переходим на ios_config без expect
Ansible 2.4 закрепил network-модули для Cisco IOS/NX-OS. Разбираем ios_config и ios_command на реальном кейсе: конфигурируем VLAN и ACL без ручного CLI.
Ansible 2.4 закрепляет network-модули для Cisco IOS/NX-OS - ios_config и ios_command становятся полноценными инструментами управления сетевым оборудованием
Ansible 2.4 вышел осенью, и главное что нас там интересовало - не новые connection-плагины и не улучшенный include_tasks, а network-модули. Конкретно: ios_config, ios_command, ios_facts для Cisco IOS и первые модули под NX-OS. В 2.2 они появились как «вот это есть, попробуйте», в 2.4 стало понятно что команда всерьёз намерена делать из Ansible инструмент управления сетью, а не только серверами. Мы это восприняли с умеренным оптимизмом и пошли проверять на живой инфраструктуре.
Почему вообще это важно
У нас в нескольких клиентских средах есть Cisco - коммутаторы доступа и ядра, в одном месте NX-OS на агрегации. До Ansible 2.2 автоматизация сетевых устройств выглядела примерно так: либо Python с Paramiko и написанным вручную expect-автоматом, либо RANCID с шаблонами, либо просто руками через SSH. Все варианты были отдельным инструментом, отдельной базой знаний, отдельным местом где что-то могло пойти не так независимо от основного Ansible-пайплайна.
В 2017-м мы переписали обёртки под сетевое оборудование на ios_config - убрали три скрипта на expect, которые падали при каждом обновлении прошивки. Но тогда делали осторожно: сложные сценарии по-прежнему руками. 2.4 даёт достаточно уверенности чтобы двигаться дальше.
Что конкретно добавилось в 2.4
Ключевое изменение - network_cli как отдельный connection-плагин. Раньше модули ходили на устройства через local connection, то есть Ansible запускал задачи на control node, а модуль сам открывал SSH. Это создавало проблемы с параллельным запуском и с тем как работал become (вход в enable mode).
С network_cli устройство трактуется как обычный managed host, только вместо Python на другом конце - CLI коммутатора. Плагин знает как разбирать промпты IOS, как входить в enable, как обрабатывать --More-- в длинном выводе. Для инвентаря это означает:
[cisco_switches]
sw-core-01 ansible_host=192.168.1.1
sw-access-02 ansible_host=192.168.1.2
[cisco_switches:vars]
ansible_network_os=ios
ansible_connection=network_cli
ansible_become=yes
ansible_become_method=enable
Credentials - через ansible_user / ansible_password в vault, как обычно.
Реальный кейс: VLAN и ACL через playbook
Конкретная задача: на объекте добавили новый сегмент для IoT-устройств, нужно создать VLAN 150 на четырёх коммутаторах доступа, назначить его на несколько access-портов и добавить ACL на uplink. Раньше это делалось руками по SSH на каждый свич - двадцать минут суеты, высокий шанс опечататься в номере VLAN или забыть один из портов.
Playbook выглядит так:
- name: Configure IoT VLAN
hosts: cisco_switches
gather_facts: no
tasks:
- name: Create VLAN 150
ios_config:
lines:
- vlan 150
- name IoT-segment
- name: Assign access ports to VLAN 150
ios_config:
parents: "interface {{ item }}"
lines:
- switchport mode access
- switchport access vlan 150
- spanning-tree portfast
loop: "{{ iot_ports }}"
- name: Apply ACL on uplink
ios_config:
parents: "interface GigabitEthernet0/1"
lines:
- ip access-group IoT-UPLINK-IN in
Переменная iot_ports - список интерфейсов из host_vars для каждого коммутатора. У каждого свича своё.
ios_config сравнивает желаемое состояние с текущим конфигом через show running-config - если строка уже есть, changed не выставляется. Это и есть та идемпотентность которую раньше нужно было писать руками. Повторный прогон - никаких изменений, только подтверждение что всё на месте.
Где трётся
Идемпотентность работает, но не магически. Несколько мест где нужно знать заранее:
- Форматирование IOS.
show running-configиногда возвращает строки с отступами или в другом порядке чем то что прописано вlines:. Модуль сравнивает текстово, и бывают ложныеchanged. Лечится черезdiff_ignore_linesили черезmatch: strict/match: lineв зависимости от ситуации. Нужно проверять поведение на конкретном IOS-версии. - ACL - отдельная история. Добавить строку в ACL через
ios_configпросто, удалить сложнее - нет механизма «убрать строку если её нет в desired state». Для ACL лучше использоватьios_configсreplace: blockи передавать весь блок целиком. - NX-OS отличается. Мы попробовали перенести тот же подход на один NX-OS-свич - там свой синтаксис конфигурации, и плагин
nxos_configведёт себя немного иначе. Не критично, но один в один не перенесётся.
ios_command для диагностики
Параллельно с ios_config хорошо работает ios_command - выполняет произвольные show-команды и возвращает их вывод. Используем в двух сценариях.
Первый - сбор инвентаря перед изменениями:
- name: Show current VLAN database
ios_command:
commands:
- show vlan brief
- show interfaces trunk
register: vlan_state
Вывод пишем в файл как документацию состояния «до». Потом если что-то пошло не так - есть с чем сравнивать.
Второй - проверка после применения конфига. ios_command с wait_for умеет ждать пока в выводе не появится нужная строка:
- name: Verify VLAN 150 is active
ios_command:
commands:
- show vlan id 150
wait_for:
- result[0] contains "active"
Это удобнее чем ручная проверка через SSH после прогона.
Куда двигаемся
На managed-инфраструктуре сетевое оборудование у клиентов - это обычно несколько свичей доступа и один-два коммутатора уровня ядра. Не гигантская сеть, но достаточно чтобы ручные изменения регулярно давали мелкие расхождения между устройствами. Ansible с network_cli закрывает именно это: один playbook, все устройства, идемпотентный прогон, история в Git.
Пока не автоматизируем: изменения топологии (trunk-порты, etherchannel), работу с routing на IOS. Это сложнее и требует более тщательного тестирования на стенде прежде чем трогать продакшн. BGP-конфигурацию через Ansible мы видели в документации, но это отдельная история с отдельными рисками.
Сейчас примерно половина сетевых изменений на обслуживаемых объектах идёт через playbooks, половина - ещё руками. Хотим перевернуть это соотношение к середине года.