Говоря о типовых сценариях автоматизации сети, никак не обойтись без набора модулей command. Благодаря этим модулям, Ansible позволяет запускать команды на сетевом оборудовании так, как будто вы вводите их прямо с консоли. При этом вывод команд не просто проскакивает в окне терминала, чтобы кануть в лету, а может быть сохранен и использован в дальнейшем. Его можно записать в переменные, парсить для использования в последующих задачах или же сохранить на будущее в переменных хоста.
Цель этого поста – показать, что любую повторяющуюся задачу по управлению сетью можно автоматизировать, и что Ansible не просто позволяет управлять конфигурациями, а помогает избавиться от рутины и сэкономить время.
Разберем базовые способы использования сетевых модулей command, включая сохранение вывода команд с помощью параметра register. Также рассмотрим, как выполнять масштабирование на несколько сетевых устройств с помощью hostvars и как организовать условное выполнение с помощью параметра wait_for и еще трех связанных параметров: interval, retries и match.
Для различных сетевых платформ есть свои модули command, причем все они поддерживаются на уровне расширения Red Hat Ansible Engine Networking Add-on:
Сетевые платформы | Модули *os_command |
Arista EOS | eos_command |
Cisco IOS / IOS-XE | ios_command |
Cisco IOS-XR | iosxr_command |
Cisco NX-OS | nxos_command |
Juniper Junos | junos_command |
VyOS | vyos_command |
Основы работы с модулями command
Рассмотрим плейбук, который просто запускает команду show version с помощью модуля eos_command:
---
- name: COMMAND MODULE PLAYBOOK
hosts: eos
connection: network_cli
tasks:
- name: EXECUTE ARISTA EOS COMMAND
eos_command:
commands: show version
register: output
- name: PRINT OUT THE OUTPUT VARIABLE
debug:
var: output
Здесь у нас две задачи и первая использует модуль eos_command с единственным параметром commands. Поскольку мы запускаем только одну команду – show version – ее можно указать в той же строке, что и сам параметр commands. Если команд две и больше, то каждую их них надо размещать на отдельной строке после commands:. В этом примере мы используем ключевое слово register, чтобы сохранить вывод команды show version. Параметр register (его можно использовать в любой задаче Ansible) задает переменную, куда будет сохранен вывод нашей задачи, чтобы им можно было воспользоваться позже. В нашем примере эта переменная называется output.
Вторая задача в нашем примере использует модуль debug, чтобы вывести на экран содержимое только что созданной переменой output. То есть, это те же данные, что вы увидели бы в интерфейсе командной строки на устройстве EOS, если бы ввели там “show version”. Отличие в том, что наш плейбук покажет их в окне терминала, на котором вы его запускаете. Как видите, модуль debug позволяет легко проверить переменные Ansible.
Вот как выглядит вывод нашего плейбука:
PLAY [eos] *************************************************************************
TASK [execute Arista eos command] **************************************************
ok: [eos]
TASK [print out the output variable] ***********************************************
ok: [eos] => {
"output": {
"changed": false,
"failed": false,
"stdout": [
"Arista vEOSnHardware version: nSerial number: nSystem MAC address: 0800.27ec.005ennSoftware image version: 4.20.1FnArchitecture: i386nInternal build version: 4.20.1F-6820520.4201FnInternal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91nnUptime: 1 day, 3 hours and 23 minutesnTotal memory: 2017324 kBnFree memory: 1111848 kB"
],
"stdout_lines": [
[
"Arista vEOS",
"Hardware version: ",
"Serial number: ",
"System MAC address: 0800.27ec.005e",
"",
"Software image version: 4.20.1F",
"Architecture: i386",
"Internal build version: 4.20.1F-6820520.4201F",
"Internal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91",
"",
"Uptime: 1 day, 3 hours and 23 minutes",
"Total memory: 2017324 kB",
"Free memory: 1111848 kB"
]
]
}
}
PLAY RECAP *************************************************************************
eos : ok=2 changed=0 unreachable=0 failed=0
Как видно из скриншота, обе наши задачи отработали успешно. Поскольку в первой задаче используется уровень детализации сообщений по умолчанию, она просто говорит, что хост eos выполнил задачу с результатом ok, подчеркивая успешность выполнения зеленым цветом. Вторая задача, с модулем debug, возвращает вывод выполненной команды, отображая одну и ту же информацию в двух форматах:
- stdout
- stdout_lines
В секции stdout показано то же самое, что вы увидели бы в интерфейсе командной строки на устройстве, но в виде одной длинной строки. А секция stdout_lines разбивает этот вывод на строки, чтобы его было удобно читать. Каждый элемент в этом списке представляет собой отдельную строку в выводе команды.
Сравним вывод команды на устройстве и в Ansible:
Вывод команды в Arista EOS | stdout_lines в Ansible |
eos>show vers Arista vEOS Hardware version: Serial number: System MAC address: 0800.27ec.005e Software image version: 4.20.1F Uptime: 1 day, 3 hours and 56 minutes |
«stdout_lines»: [ [ «Arista vEOS», «Hardware version: », «Serial number: », «System MAC address: 0800.27ec.005e», "", «Software image version: 4.20.1F», «Architecture: i386», «Internal build version: 4.20.1F-6820520.4201F», «Internal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91», "", «Uptime: 1 day, 3 hours and 23 minutes», «Total memory: 2017324 kB», «Free memory: 1111848 kB» ] |
Если вы знакомы с JSON и YAML, то наверное уже обратили вниманием на одну странность: stdout_lines начинается с двух открывающих скобок:
"stdout_lines": [
[
Две открывающие скобки указывают на то, что stdout_lines на самом деле возвращает перечень списков строк. Если слегка изменить нашу debug-задачу, то эту фишку можно использовать для выборочного просмотра результатов выполнения команды. Поскольку в нашем перечне есть только один список строк, этот список называется нулевым (вообще-то он первый, но отсчет идет с нуля). Теперь посмотрим, как извлечь из него отдельную строку, допустим, System MAC Address. В выводе команды эта строка идет четвертой по счету, но поскольку считаем с нуля, нам, в итоге, нужна строка 3 из списка 0, иначе говоря: output.stdout_lines[0][3].
- name: print out a single line of the output variable
debug:
var: output.stdout_lines[0][3]
В ответ debug-задача возвращает именно её:
TASK [print out a single line of the output variable] ******************************
ok: [eos] => {
"output.stdout_lines[0][3]": "System MAC address: 0800.27ec.005e"
}
Какой смысл в нумерации списков и зачем она вообще нужна? Дело в том, что в рамках одной задачи можно запускать несколько команд, например, вот так (здесь у нас три команды):
---
- hosts: eos
connection: network_cli
tasks:
- name: execute Arista eos command
eos_command:
commands:
- show version
- show ip int br
- show int status
register: output
- name: print out command
debug:
var: output.stdout_lines
Вот как выглядит вывод:
"output.stdout_lines": [
[
"Arista vEOS",
"Hardware version: ",
"Serial number: ",
"System MAC address: 0800.27ec.005e",
"",
"Software image version: 4.20.1F",
"Architecture: i386",
"Internal build version: 4.20.1F-6820520.4201F",
"Internal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91",
"",
"Uptime: 1 day, 4 hours and 20 minutes",
"Total memory: 2017324 kB",
"Free memory: 1111104 kB"
],
[
"Interface IP Address Status Protocol MTU",
"Ethernet1 172.16.1.1/24 up up 1500",
"Management1 192.168.2.10/24 up up 1500"
],
[
"Port Name Status Vlan Duplex Speed Type Flags",
"Et1 connected routed full unconf EbraTestPhyPort ",
"Et2 connected 1 full unconf EbraTestPhyPort ",
"Et3 connected 1 full unconf EbraTestPhyPort ",
"Ma1 connected routed a-full a-1G 10/100/1000"
]
]
Здесь список номер ноль – это вывод команды show version, список номер один – вывод show ip int br, список номер два – вывод show int status. То есть номер списка определяется порядком выполнения команд.
Команды Arista EOS | Соответствующие списки вывода |
show version | output.stdout_lines[0] |
show ip int br | output.stdout_lines[1] |
show int status | output.stdout_lines[2] |
Масштабирование модуля command: переменные хоста
А что будет, если запустить плейбук на нескольких устройствах одновременно?
Чтобы сохранить однозначность, переменная output сохраняется как переменная хоста для каждого хоста в inventory. Если у нас есть три коммутатора, и мы прогоним на них наш плейбук, то получим переменную output для каждого уникального хоста. Допустим, нам нужен IP-адрес из команды show ip int br для порта Ethernet1 на коммутаторе switch03. Поскольку show ip int br – это вторая по счету команда, которая запускается в рамках задачи, а данные по интерфейсу Ethernet1 содержатся во второй строке ее вывода, то нам надо будет написать stdout_lines[1][1]. Чтобы обращаться к переменным конкретного хоста, мы используем ключевое слово hostvars и выполняем поиск нужного нам хоста по имени.
Вот как это делается:
- name: debug hostvar
debug:
var: hostvars["switch03"].output.stdout_lines[1][1]
В результате output содержит именно то, что нам нужно:
TASK [debug hostvar] ***************************************************************
ok: [switch03] => {
"hostvars["switch03"].output.stdout_lines[1][1]": "Ethernet1 172.16.1.3/24 up up 1500"
}
По умолчанию задача использует переменные текущего хоста, но hostvars позволяет напрямую обратиться и к переменным другого хоста.
Условия в задачах с модулями command: параметр wait_for
Параметр wait_for позволяет реализовать проверку условий сразу после выполнения команды. Например, сделать так, что задача будет считаться выполненной успешно, только если вывод команды проверки статуса содержит определенный текст. По умолчанию параметр wait_for не используется, поэтому задача запускается только один раз, как в примерах выше. Но если задать его в явном виде, задача будет повторно запускаться до тех пор, пока не выполнится условие либо не кончится лимит попыток (по умолчанию их 10). Если включить журналирование команд, то можно увидеть, что в приведенном ниже плейбуке (который специально написан так, чтобы условие никогда не выполнилось) все происходит именно так.
---
- hosts: eos
connection: network_cli
tasks:
- name: execute Arista eos command
eos_command:
commands:
- show int status
wait_for:
- result[0] contains DURHAM
Этот плейбук будет 10 раз запускать команду show int status, поскольку в ее выводе никогда не будет строки DURHAM.
В этом можно убедиться с помощью команды show logging:
Mar 24 20:33:52 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=17 start_time=1521923632.5 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:33:53 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=18 start_time=1521923633.71 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:33:54 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=19 start_time=1521923634.81 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:33:55 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=20 start_time=1521923635.92 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:33:56 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=21 start_time=1521923636.99 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:33:58 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=22 start_time=1521923638.07 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:33:59 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=23 start_time=1521923639.22 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:34:00 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=24 start_time=1521923640.32 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:34:01 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=25 start_time=1521923641.4 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Mar 24 20:34:02 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=26 start_time=1521923642.47 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status
Теперь рассмотрим пример реального плейбука, в котором все настроено для установления OSPF-соседства (adjacency) с другим устройством, кроме команды ip ospf area. Мы применим эту команду и затем воспользуемся параметром wait_for, чтобы проверить наличие в выводе слова FULL: если оно там есть, то соседство успешно установлено. Если за 10 попыток FULL так и не появится, то задача завершится с ошибкой.
---
- hosts: eos
connection: network_cli
tasks:
- name: turn on OSPF for interface Ethernet1
eos_config:
lines:
- ip ospf area 0.0.0.0
parents: interface Ethernet1
- name: execute Arista eos command
eos_command:
commands:
- show ip ospf neigh
wait_for:
- result[0] contains FULL
Выполним этот плейбук с помощью команды ansible-playbook:
➜ ansible-playbook ospf.yml
PLAY [eos] *********************************************************************************************
TASK [turn on OSPF for interface Ethernet1] *******************************************************
changed: [eos]
TASK [execute Arista eos command] ****************************************************************
ok: [eos]
PLAY RECAP ******************************************************************************************
eos : ok=2 changed=1 unreachable=0 failed=0
Смотрим командную строку и видим, что плейбук выполнен успешно:
eos#show ip ospf neigh
Neighbor ID VRF Pri State Dead Time Address Interface
2.2.2.2 default 1 FULL/DR 00:00:33 172.16.1.2 Ethernet1
Помимо contains можно использовать следующие операторы сравнения:
- eq: – равно
- neq: – не равно
- gt: – больше
- ge: – больше или равно
- lt: – меньше
- le: – меньше или равно
Кроме того, вместе с wait_for можно использовать три дополнительных параметра, (подробно описывается в документации на модули):
Параметр | Описание |
interval | Время между повторами команды. |
retries | Макс. количество повторов, прежде чем задача завершится с ошибкой, либо будет выполнено условие. |
match | Совпадение всех условия или хотя бы одного. |
Остановимся чуть подробнее на параметре match:
- name: execute Arista eos command
eos_command:
commands:
- show ip ospf neigh
match: any
wait_for:
- result[0] contains FULL
- result[0] contains 172.16.1.2
Когда задано match: any, задача считается успешной, если результат содержит FULL или 172.16.1.2. Если же задано match: all, то результат должен содержать и FULL, и 172.16.1.2. По умолчанию используется match: all, поскольку если вы прописываете несколько условий, то, скорее всего, хотите, чтобы они выполнялись все, а не хотя бы одно.
Когда может пригодиться match: any? Допустим, надо проверить, что дата-центр имеет двустороннюю связь с интернетом. А дата-центр подключен к пяти разным интернет-провайдерам, для каждого из которых есть свое BGP-соединение. Плейбук может проверить все ‘эти пять соединений, и если работает хотя бы одно из них, а не все пять, сообщить, что все в порядке. Просто запомните, что any – это логическое ИЛИ, а all – логическое И.
Параметр | Описание |
match: any | Логическое «ИЛИ» Требуется выполнение хотя бы одного условия |
match: all | Логическое «И» Требуется выполнение всех условий |
Негативные условия: строим обратную логику
Иногда важно не то, что есть в выводе, а то, чего там нет. Здесь конечно всегда заманчиво использовать оператор сравнения neq, но для некоторых сценариев с негативными условиями есть варианты получше. Например, если надо инвертировать оператор contains (типа, «вывод команды не должен содержать то-то и то-то»), можно использовать ключевое слово register, чтобы сохранить вывод, и затем обработать его в следующей задаче с помощью выражения when. Или, например, когда надо остановить плейбук при невыполнении условий, просто используйте модули fail или assert, чтобы специально выйти с ошибкой. Что касается оператора сравнения neq, то он полезен лишь тогда, когда из вывода можно вытащить точное значение (например, из пары ключ-значение или из JSON), а не просто строку или список строк. Иначе будет выполняться посимвольное сравнение строк.
Что дальше
Ознакомитесь с документацией по работе с выводом команд в сетевых модулях. Там приводятся полезные примеры использования ge, le и других условий при работе с выводом в формате JSON на конкретных сетевых платформах.
Автор: redhatrussia