Как убить вашу сеть с помощью Ansible

https://networklore.com/ansible-template-caveat/
  • Перевод
Прим. перев.: Эта статья, написанная сетевым инженером из Швеции, рассказывает о некоторых нюансах работы с шаблонами в Ansible, а главное — учит одному простому и очевидному правилу, помогающему не «выстрелить себе в ногу»… причём не только в ногу и даже не только свою, когда речь идёт об автоматизированном управлении большим множеством устройств/серверов. Описанный в ней пример будет полезен каждому системному администратору и DevOps-инженеру. (Выделения в тексте не являются авторскими — они сделаны при переводе для акцентирования внимания на нескольких моментах.)



Я не только пользуюсь Ansible, но и пишу о нём, и пытаюсь помочь другим понять, как он работает. Недавно отвечал на вопросы пользователей Ansible. Один из них не понимал, почему модуль ios_config некорректно применял его шаблон. Объяснив, что не так с этим шаблоном, и размышляя об этой проблеме дальше, я осознал, что подобная ошибка может быть по-настоящему опасной. Опасной настолько, что может привести вашу инфраструктуру к нерабочему состоянию.

Сценарий


Представьте, что вы сетевой администратор, которому только что поручили выкат новой IP-сети во все филиалы. Вы используете Ansible 2.4 (всё может работать иначе в более поздних версиях, когда они выйдут) [2.4.0.0 — текущая стабильная версия Ansible, выпущенная 19 сентября 2017 г. — прим. перев.]. Подробности о новых адресах уже зафиксированы в IPAM-системе, и скрипт (Dynamic Inventory) получит нужную информацию для вас. Теперь требуется обновить шаблон, добавив новые сети и выкатив это изменение. Кроме того, ответственный за управление сетями вписал «wormhole» в описание всех интерфейсов, указывающих на WAN (Wide Area Network). «Когда будешь добавлять эти сети, поменяй, пожалуйста, описание на „WAN“». Добавленная сеть будет использоваться новым решением для цифровой подписи.

Вы начинаете с просмотра текущего шаблона, который выглядит так:

interface FastEthernet0
 description Wormhole exit
 ip address {{ wan_ip }} {{ wan_mask }}

interface FastEthernet1
 description POS
 ip address {{ pos_ip }} {{ pos_mask }}

interface FastEthernet2
 description OFFICE
 ip address {{ office_ip }} {{ office_mask }}

Зайдя на роутер в Висконсине, вы видите, что получается следующая конфигурация:

interface FastEthernet0
 description Wormhole exit
 ip address 172.29.58.161 255.255.255.224

interface FastEthernet1
 description POS
 ip address 10.17.80.1 255.255.255.0

interface FastEthernet2
 description OFFICE
 ip address 10.17.81.1 255.255.255.0

interface FastEthernet3
 no ip address

Выглядит достаточно просто: новая сеть будет подключена к FastEthernet3. Запускаете свой редактор и обновляете шаблон. Новый файл получается следующим:

interface FastEthernet0
 description WAN
 ip address {{ wan_ip }} {{ wan_mask }}

interface FastEthernet1
 description POS
 ip address {{ pos_ip }} {{ pos_mask }}

interface FastEthernet2
 description OFFICE
 ip address {{ office_ip }} {{ office_mask }}

interface FastEthernet3
description SIGNAGE
ip address {{ signage_ip }} {{ signage_mask }}

Шаблон готов к использованию! Вы запускаете терминал и отправляете новый шаблон в свой Git-репозиторий:

ansible-playbook network-baseline.yml

Познакомьтесь с проблемой


Вы радуетесь проделанной работе и останавливаетесь, чтобы насладиться этой маленькой победой. Но удовольствие длится ровно до тех пор, пока кто-нибудь не спросит вас: «Почему Висконсин только что пропал из карты?»

Очень странно: ведь вы только добавили новую сеть, которая даже не связана с офисом Висконсина, так? И тут вас настигает неприятное чувство… Такой вопрос прозвучал, потому что произвели изменение в единственном офисе или только что были убиты вообще все филиалы?



Что произошло?


Перед тем, как детально в этом разбираться, необходимо понять, как вообще подобные случаи происходят. Вы можете делать ошибки, а в программном обеспечении могут быть баги. Поэтому действительно важно сначала проверять в безопасном окружении то, что вы делаете. В данном случае хорошей идеей будет использование проверочного режима в Ansible (-C) вместе с флагом verbose (-v). Так вы сможете увидеть, какая конфигурация будет отправлена на устройство, без реального применения изменений. Другой важный момент — не стоит запускать что-либо во всей сети, если вы не уверены, чем это закончится. Используйте опцию --limit и начните с нескольких устройств.

Итак, с помощью опции verbose мы можем понять, что пошло не так:



Похоже, актуальный конфиг, который был отправлен пострадавшему устройству, таков:

interface FastEthernet0
description WAN
description SIGNAGE
ip address 10.17.82.1 255.255.255.0

Прекрасно: playbook переконфигурировал интерфейс WAN, дав ему IP-адрес, который предназначался для FastEthernet3. В этот момент можете позвать своего терапевта, техподдержку Red Hat или, возможно, адвоката… Или продолжайте читать, почему же это произошло.

Как Ansible парсит шаблоны для сетевых устройств


Как и остальные компоненты Ansible, сетевые модули используют шаблонный движок Jinja2. Однако с сетями он работает несколько иначе, чем с шаблонами конфигураций для nginx и других сервисов. Ansible парсит действующую конфигурацию устройства и, основываясь на этих данных, решает, что нужно обновить на устройстве. Например, ничего не изменилось у FastEthernet1 и FastEthernet2 — тогда Ansible не предпримет попыток менять что-либо на этих интерфейсах.

Получив шаблон, Ansible применит только конфигурацию, которой ещё нет на устройстве. Однако Ansible в действительности не понимает конфигурацию и что она делает. Вместо этого он пытается парсить конфигурацию по набору предопределённых правил. Если мы начнём с описания интерфейса WAN, очевидно, его надо поменять. Однако мы не можем дать отдельную команду добавления описания — требуется его сконфигурировать в рамках интерфейса. Поскольку строка с описанием имеет отступ и находится после строки interface FastEthernet0, Ansible рассматривает эту строку с интерфейсом как родительскую для последующей секции. Итак, сначала Ansible отправляет команду с интерфейсом, а затем — команду с описанием. Вот почему обновления, отправляемые Ansible, начинаются с:

interface FastEthernet0
 description WAN

А вот как будет выглядеть заключительная часть шаблона:

interface FastEthernet3
description SIGNAGE
ip address 10.17.82.1 255.255.255.0

Поскольку отступа нет, Ansible не поймет, что interface FastEthernet3 — родительская команда для description и ip address. Вместо этого он просто воспримет команды как сниппеты глобального конфига и, поскольку не бывает строк для описания или IP в глобальном конфиге, включит их в список команд, отправляемых устройству.

Если мы напрямую вводим описание и IP-адрес в глобальном конфиге, то получаем ошибку:

WIS-RTR-01(config)#description SIGNAGE
                   ^
% Invalid input detected at '^' marker.

WIS-RTR-01(config)#ip address 10.17.82.1 255.255.255.0
                           ^
% Invalid input detected at '^' marker.

WIS-RTR-01(config)#

Однако в нашем случае мы также поменяли описание FastEthernet0, поэтому сессия всё ещё находится в контексте config-if. Поскольку мы не отправляем команду выхода для возвращения к глобальному конфигу (т.е. для перехода с (config-if)# к (config)#), неправильный IP-адрес будет применён к интерфейсу FastEthernet0. А финальная конфигурация получится следующей:

interface FastEthernet0
 description SIGNAGE
 ip address 10.17.82.1 255.255.255.0

interface FastEthernet1
 description POS
 ip address 10.17.80.1 255.255.255.0

interface FastEthernet2
 description OFFICE
 ip address 10.17.81.1 255.255.255.0

interface FastEthernet3
 no ip address

Упс…

Что необходимо помнить


Думаю, можно с уверенностью сказать, что для того, чтобы описанный выше сценарий случился, должна присутствовать определённая доля неудачи. В данном случае одновременно не хватало двух пробелов и производилось изменение описания, которое не относилось к этой правке. Но даже если такой катастрофы не произойдёт, всё может завершиться применением конфигурации там, где вы бы этого не хотели.

Повторю ещё раз. Убедитесь, что вы тестируете и валидируете то, что хотите сделать! Используйте пробные запуски (dry run) и смотрите на результат, чтобы знать, что происходит.

Другой подход


Если приведённый выше пример звучит страшновато для вас, помните, что вам вовсе не обязательно использовать шаблоны таким способом. Вам также доступны параметры lines и parents в ios_config. А ещё вы можете взглянуть на библиотеку NAPALM, в особенности на модуль napalm_install_config для Ansible. Как упоминалось выше, базовые сетевые модули Ansible парсят работающую конфигурацию и пытаются понять, какой конфигурации не хватает устройству, чтобы решить, какие команды отправить. NAPALM не смотрит на конфигурации устройств и оставляет на усмотрение устройству решение о том, что применять. В случае IOS-устройства NAPALM скопирует весь сгенерированный шаблон в файловую систему устройства, оценит, нужны ли изменения, и после этого объединит их с текущей конфигурацией (или заменит всю конфигурацию, если вы хотите).

Вывод


Надеюсь, эта статья поможет вам понять, как Ansible работает с шаблонами, применяя их к сетевым устройствам, и почему важно использовать корректные отступы. А наиболее важный вывод из неё — убедитесь, что всегда проверяете все изменения конфигов, которые вы выкатываете.

Наконец, надеюсь, что ни одна сеть в Висконсине и где-либо ещё не пострадает от ваших рук.
Флант 327,12
Специалисты по DevOps и высоким нагрузкам в вебе
Поделиться публикацией
Комментарии 6
  • +5
    Я не DevOps. А с точки зрения программиста решение этой проблемы выглядит по другому: коммиты должны быть атомарными. Не надо делать две разные вещи в одном коммите. Если бы сначала поменяли настройки FastEthernet0, а потом — добавили FastEthernet3, то ничего бы не сломалось.
    • +1
      Это в духе ансибля.

      Определить в cisco-конфиге, что измененные строчки приходятся на int F0 они могут, а подать команду exit после отправки всех измененных строчек, чтобы вернуться в основной контекст они уже не могут.
      • 0
        Потому что нужно было это запустить на паре хостов для начала.
        • 0
          Да зачем на паре хостов, сейчас лаборатории (например, eve-ng) позволяют все попробовать без воздействия на продуктив.
        • 0

          Не понял, ансибль что, вообще игнорировал указанный в шаблоне "interface FastEthernet3"? Т.е. "interface FastEthernet0" для входа в контекст используется, а с другим интерфейсом это не работает? Такое ощущение, что автор написал о том что не проверял на практике.


          Или я что-то упустил (трудности перевода?)?

          • 0
            Это одно из слабых мест подхода Configuration-as-Code.
            В руках человека оказывается очень мощный инструмент, при неправильном использовании которого возможны самые печальные последствия.

            Ошибка в пробелах.

            В числе возможных обходных решений, генерировать конфигурацию, а не руками набивать.

            Это помимо code review, и тестовых контуров.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое