Автоматизация для самых маленьких¶
Часть 0. Планирование¶
СДСМ закончился, а бесконтрольное желание писать - осталось.
АДСМ я попробую вести в формате, немного отличном от СДСМ. По-прежнему будут появляться большие обстоятельные номерные статьи, а между ними я буду публиковать небольшие заметки из повседневного опыта. Постараюсь тут бороться с перфекционизмом и не вылизывать каждую из них.
Как это забавно, что во второй раз приходится проходить один и тот же путь.Сначала пришлось писать самому статьи про сети из-за того, что их не было в рунете.Теперь я не смог найти всесторонний документ, который систематизировал бы подходы к автоматизации и на простых практических примерах разбирал вышеперечисленные технологии.Возможно, я ошибаюсь, поэтому, кидайте ссылки на годные ресурсы. Впрочем это не изменит моей решимости писать, потому что, основная цель - это всё-таки научиться чему-то самому, а облегчить жизнь ближнему - это приятный бонус, который ласкает ген распространения опыта.
В описываемых тут идеях и инструментах я буду не оригинален. У Дмитрия Фиголя есть отличный канал со стримами на эту тему.
Статьи во многих аспектах будут с ними пересекаться.
Цели¶
Сейчас мы поставим максимально абстрактные цели:
- Сеть - как единый организм
- Тестирование конфигурации
- Версионирование состояния сети
- Мониторинг и самовосстановление сервисов
Позже в этой статье разберём какие будем использовать средства, а в следующих и цели и средства в подробностях.
Сеть - как единый организм¶
То есть, например, если мы решили, что с этого момента стоечные коммутаторы в Казани должны анонсировать две сети вместо одной, мы
- Сначала документируем изменения в системах
- Генерируем целевую конфигурацию всех устройств сети
- Запускаем программу обновления конфигурации сети, которая вычисляет, что нужно удалить на каждом узле, что добавить, и приводит узлы к нужному состоянию.
При этом руками мы вносим изменения только на первом шаге.
Тестирование конфигурации¶
Автоматика нам позволит совершать меньше ошибок, но в большем масштабе. Так можно окирпичить не одно устройство, а всю сеть разом.
В одной из частей мы рассмотрим как реализовать это с помощью системы контроля версий, вероятно, гитхаба.
Как только вы свыкнитесь с мыслью о сетевом CI/CD, в одночасье метод проверки конфигурации путём её применения на рабочую сеть покажется вам раннесредневековым невежеством. Примерно как стучать молотком по боеголовке.
Органическим продолжением идей о системе управления сетью и CI/CD становится полноценное версионирование конфигурации.
Версионирование¶
Вот давайте эти состояния и будем называть версиями?
При этом разные ДЦ могут иметь разные версии - сеть развивается, ставится новое оборудование, где-то добавляются новые уровни спайнов, где-то - нет, итд.
Про семантическое версионирование мы поговорим в отдельной статье.
Повторюсь - любое изменение (кроме отладочных команд) - это обновление версии. О любых отклонениях от актуальной версии должны оповещаться администраторы.
То же самое касается отката изменений - это не отмена последних команд, это не rollback силами операционной системы устройства - это приведение всей сети к новой (старой) версии.
Мониторинг и самовосстановление сервисов¶
И здесь мы тоже мониторим не только отдельные устройства, но и здоровье сети целиком, причём как вайтбокс, что сравнительно понятно, так и блэкбокс, что уже сложнее.
Что нам понадобится для реализации таких амбициозных планов?
- Иметь список всех устройств в сети, их расположение, роли, модели, версии ПО. (kazan-leaf-1.lmu.net, Kazan, leaf, Juniper QFX 5120, R18.3)
- Иметь систему описания сетевых сервисов. (IGP, BGP, L2/3VPN, Policy, ACL, NTP, SSH)
- Уметь инициализировать устройство. (Hostname, Mgmt IP, Mgmt Route, Users, RSA-Keys, LLDP, NETCONF)
- Настраивать устройство и приводить конфигурацию к нужной (в том числе старой) версии.
- Тестировать конфигурацию
- Периодически проверять состояние всех устройств на предмет отхождения от актуального и сообщать кому следует. (Ночью кто-то тихонько добавил правило в ACL)
- Следить за работоспособностью.
Средства¶
Звучит достаточно сложно для того, чтобы начать декомпозировать проект на компоненты.
И будет их десять:
Инвентарная система
Система управления IP-пространством
Система описания сетевых сервисов
Механизм инициализации устройств
Вендор-агностик конфигурационная модель
Вендор-интерфейс специфичный драйвер
Механизм доставки конфигурации на устройство
CI/CD
Механизм резервного копирования и поиска отклонений
Система мониторинга
Это, кстати, пример того, как менялся взгляд на цели цикла - в черновике компонентов было 4.
Компонент 1. Инвентарная система¶
Для наших задач в ней мы будем хранить следующую информацию про устройство:
- Инвентарный номер
- Название/описание
- Модель (Huawei CE12800, Juniper QFX5120 итд)
- Характерные параметры (платы, интерфейсы итд)
- Роль (Leaf, Spine, Border Router итд)
- Локацию (регион, город, дата-центр, стойка, юнит)
- Интерконнекты между устройствами
- Топологию сети
В ней же мы будем хранить виртуальные сетевые устройства, например виртуальные маршрутизаторы или рут-рефлекторы. Можем добавить DNS-сервера, NTP, Syslog и вообще всё, что так или иначе относится к сети.
Компонент 2. Система управления IP-пространством¶
Для наших задач в ней мы будем хранить следующую информацию:
- VLAN
- VRF
- Сети/Подсети
- IP-адреса
- Привязка адресов к устройствам, сетей к локациям и номерам VLAN
Опять же понятно, что мы хотим быть уверены, что, выделяя новый IP-адрес для лупбэка ToR’а, мы не споткнёмся о то, что он уже был кому-то назначен. Или что один и тот же префикс мы использовали дважды в разных концах сети.
Но как это поможет в автоматизации?
Легко.
Компонент 3. Система описания сетевых сервисов¶
Если первые две системы хранят переменные, которые ещё нужно как-то использовать, то третья описывает для каждой роли устройства, как оно должно быть настроено.
Стоит выделить два разных типа сетевых сервисов:
- Инфраструктурные
- Клиентские
- физические и логические интерфейсы (тег/антег, mtu)
- IP-адреса и VRF (IP, IPv6, VRF)
- ACL и политики обработки трафика
- Протоколы (IGP, BGP, MPLS)
- Политики маршрутизации (префикс-листы, коммьюнити, ASN-фильтры).
- Служебные сервисы (SSH, NTP, LLDP, Syslog…)
- Итд.
Как именно мы это будем делать, я пока ума не приложу. Разберёмся в отдельной статье.
Если чуть ближе к жизни, то мы могли бы описать, что Leaf-коммутатор должен иметь BGP-сессии со всем подключенными Spine-коммутаторами, импортировать в процесс подключенные сети, принимать от Spine-коммутаторов только сети из определённого префикса. Ограничивать CoPP IPv6 ND до 10 pps итд. В свою очередь спайны держат сессии со всеми подключенными лифами, выступая в качестве рут-рефлекторов, и принимают от них только маршруты определённой длины и с определённым коммунити.
Компонент 4. Механизм инициализации устройства¶
Под этим заголовком я объединяю множество действий, которые должны произойти, чтобы устройство появилось на радарах и на него можно было попасть удалённо.
- Завести устройство в инвентарной системе.
- Выделить IP-адрес управления.
- Настроить базовый доступ на него: Hostname, IP-адрес управления, маршрут в сеть управления, пользователи, SSH-ключи, протоколы - telnet/SSH/NETCONF
Тут существует три подхода:
- Полностью всё вручную. Устройство привозят на стенд, где обычный органический человек, заведёт его в системы, подключится консолью и настроит. Может сработать на небольших статических сетях.
- ZTP - Zero Touch Provisioning. Железо приехало, встало, по DHCP получило себе адрес, сходило на специальный сервер, самонастроилось.
- Инфраструктура консольных серверов, где первичная настройка происходит через консольный порт в автоматическом режиме.
Про все три поговорим в отдельной статье.
Компонент 5. Вендор-агностик конфигурационная модель¶
До сих пор все системы были разрозненными лоскутами, дающими переменные и декларативное описание того, что мы хотели бы видеть на сети. Но рано или поздно, придётся иметь дело с конкретикой.
На этом этапе для каждого конкретного устройства примитивы, сервисы и переменные комбинируются в конфигурационную модель, фактически описывающую полную конфигурацию конкретного устройства, только в вендоронезависимой манере.
Что даёт этот шаг? Почему бы сразу не формировать конфигурацию устройства, которую можно просто залить?
На самом деле это позволяет решить три задачи:
- Не подстраиваться под конкретный интерфейс взаимодействия с устройством. Будь то CLI, NETCONF, RESTCONF, SNMP - модель будет одинаковой.
- Не держать количество шаблонов/скриптов по числу вендоров в сети, и в случае изменения дизайна, менять одно и то же в нескольких местах.
- Загружать конфигурацию с устройства (бэкапа), раскладывать её в точно такую же модель и непосредственно сравнивать между собой целевую конфигурацию и имеющуюся для вычисления дельты и подготовки конфигурационного патча, который изменит только те части, которые необходимо или для выявления отклонений.
В результате этого этапа мы получаем вендоронезависимую конфигурацию.
Компонент 6. Вендор-интерфейс специфичный драйвер¶
Не стоит тешить себя надеждами на то, что когда-то настраивать циску можно будет точно так же, как джунипер, просто отправив на них абсолютно одинаковые вызовы. Несмотря на набирающие популярность whitebox’ы и на появление поддержки NETCONF, RESTCONF, OpenConfig, конкретный контент, который этими протоколами доставляется, отличается от вендора к вендору, и это одно из их конкурентных отличий, которое они так просто не сдадут.
Это примерно точно так же, как OpenContrail и OpenStack, имеющие RestAPI в качестве своего NorthBound-интерфейса, ожидают совершенно разные вызовы.
Итак, на пятом шаге вендоронезависимая модель должна принять ту форму, в которой она поедет на железо.
И здесь все средства хороши (нет): CLI, NETCONF, RESTCONF, SNMP простихоспаде.
Поэтому нам понадобится драйвер, который результат предыдущего шага переложит в нужный формат конкретного вендора: набор CLI команд, структуру XML.
Компонент 7. Механизм доставки конфигурации на устройство¶
Конфигурацию-то мы сгенерировали, но её ещё нужно доставить на устройства - и, очевидно, не руками.
Во-первых, перед нами тут встаёт вопрос, какой транспорт будем использовать? А выбор на сегодняшний день уже не маленький:
- CLI (telnet, ssh)
SNMP- NETCONF
- RESTCONF
- REST API
- OpenFlow (хотя он из списка и выбивается, поскольку это способ доставить FIB, а не настройки)
Давайте тут расставим точки над ё. CLI - это легаси. SNMP… кхе-кхе.
RESTCONF - ещё пока неведомая зверушка, REST API поддерживается почти никем. Поэтому мы в цикле сосредоточимся на NETCONF.
На самом деле, как уже понял читатель, с интерфейсом мы к этому моменту уже определились - результат предыдущего шага уже представлен в формате того интерфейса, который был выбран.
Во-вторых, а какими инструментами мы будем это делать?
Тут выбор тоже большой:
- Самописный скрипт или платформа. Вооружимся ncclient и asyncIO и сами всё сделаем. Что нам стоит, систему деплоймента с нуля построить?
- Ansible с его богатой библиотекой сетевых модулей.
- Salt с его скудной работой с сетью и связкой с Napalm.
- Собственно Napalm, который знает пару вендоров и всё, до свиданья.
- Nornir - ещё один зверёк, которого мы препарируем в будущем.
Здесь ещё фаворит не выбран - будем шупать.
Что здесь ещё важно? Последствия применения конфигурации.
Успешно или нет. Остался доступ на железку или нет.
Кажется, тут поможет commit с подтверждением и валидацией того, что в устройство сгрузили. Это в совокупности с правильной реализацией NETCONF значительно сужает круг подходящих устройств - нормальные коммиты поддерживают не так много производителей. Но это просто одно из обязательных условий в RFP. В конце концов никто не переживает, что ни один российский вендор не пройдёт под условие 32*100GE интерфейса. Или переживает?
Компонент 8. CI/CD¶
К этому моменту у нас уже готова конфигурация на все устройства сети.
Я пишу «на все», потому что мы говорим о версионировании состояния сети. И даже если нужно поменять настройки всего лишь одного свитча, просчитываются изменения для всей сети. Очевидно, они могут быть при этом нулевыми для большинства узлов.
Но, как уже было сказано, выше, мы же не варвары какие-то, чтобы катить всё сразу в прод.
Сгенерированная конфигурация должна сначала пройти через Pipeline CI/CD.
CI/CD означает Continuous Integration, Continuous Deployment. Это подход, при котором команда не раз в полгода выкладывает новый мажорный релиз, полностью заменяя старый, а регулярно инкрементально внедряет (Deployment) новую функциональность небольшими порциями, каждую из которых всесторонне тестирует на совместимость, безопасность и работоспособность (Integration).
Для этого у нас есть система контроля версий, следящая за изменениями конфигурации, лаборатория, на которой проверяется не ломается ли клиентский сервис, система мониторинга, проверяющая этот факт, и последний шаг - выкатка изменений в рабочую сеть.
За исключением отладочных команд, абсолютно все изменения на сети должны пройти через CI/CD Pipeline - это наш залог спокойной жизни и длинной счастливой карьеры.
Компонент 9. Система резервного копирования и поиска отклонений¶
Ну про бэкапы лишний раз говорить не приходится.
Будем просто их по крону или по факту изменения конфигурации в гит складывать.
А вот вторая часть поинтереснее - за этими бэкапами кто-то должен приглядывать. И в одних случаях этот кто-то должен пойти и вертать всё как было, а в других, мяукнуть кому-нибудь, о том, что непорядок.
Например, если появился какой-то новый пользователь, который не прописан в переменных, нужно от хака подальше его удалить. А если новое файрвольное правило - лучше не трогать, возможно кто-то просто отладку включил, а может новый сервис, растяпа, не по регламенту прописал, а в него уже люди пошли.
От некой небольшой дельты в масштабах всей сети мы всё равно не уйдём, несмотря на любые системы автоматизации и стальную руку руководства. Для отладки проблем всё равно никто конфигурацию не будет вносить в системы. Тем более, что их может даже не предусматривать модель конфигурации.
Например, файрвольное правило для подсчёта числа пакетов на определённый IP, для локализации проблемы - вполне рядовая временная конфигурация.
Компонент 10. Система мониторинга¶
Сначала я не собирался освещать тему мониторинга - всё же объёмная, спорная и сложная тема. Но по ходу дела оказалось, что это неотъемлемая часть автоматизации. И обойти её стороной хотя бы даже без практики нельзя.
Развивая мысль - это органическая часть процесса CI/CD. После выкатки конфигурации на сеть, нам нужно уметь определить, а всё ли с ней теперь в порядке.
И речь не только и не столько о графиках использования интерфейсов или доступности узлов, сколько о более тонких вещах - наличии нужных маршрутов, атрибутов на них, количестве BGP-сессий, OSPF-соседей, End-to-End работоспособности вышележащих сервисов.
А не перестали ли складываться сислоги на внешний сервер, а не сломался ли SFlow-агент, а не начали ли расти дропы в очередях, а не нарушилась ли связность между какой-нибудь парой префиксов?
В отдельной статье мы поразмышляем и над этим.
Заключение¶
Усложним себе жизнь использованием только Open Source инструментов и мультивендорной сетью - поэтому кроме джунипер по ходу дела выберу ещё одного счастливчика.
И да, я не обещаю изящно закончить этот цикл готовым решением. :)
Полезные ссылки¶
- Перед тем, как углубляться в серию, стоит почитать книгу Наташи Самойленко Python для сетевых инженеров. А, возможно, и пройти курс.
- Полезным будет также почитать RFC про дизайн датацентровых фабрик от Фейсбука за авторством Петра Лапухова.
- О том, как работает Overlay’ный SDN вам даст представление документация по архитектуре Tungsten Fabric.
Спасибы¶
- Роман Горге. За комментарии и правки.
- Артём Чернобай. За КДПВ.
Часть 1. Виртуализация¶
Эту часть мы разбили на две:
- Виртуализация сети, которая рассказывает, как и зачем она появилась в виде SDN и NFV. И почему вообще мы рассматриваем её в серии статей про автоматизацию.
- Основы виртуализации, которая охватывает более общие вопросы о виртуальных хранилищах, процессорах, памяти и сети. Автор этой части - Роман Горге - бывший ведущий подкаста linkmeup.
Виртуализация сети¶
В предыдущем выпуске я описал фреймворк сетевой автоматизации. По отзывам у некоторых людей даже этот первый подход к проблеме уже разложил некоторые вопросы по полочкам. И это очень меня радует, потому что наша цель в цикле - не обмазать питоновскими скриптами анзибль, а выстроить систему.
Этот же фреймворк задаёт порядок, в котором мы будем разбираться с вопросом. И виртуализация сети, которой посвящён этот выпуск, не особо укладывается в тематику АДСМ, где мы разбираем автоматику.
Но давайте взглянем на неё под другим углом. Уже давно одной сетью пользуются многие сервисы. В случае оператора связи это 2G, 3G, LTE, ШПД и B2B, например. В случае ДЦ: связность для разных клиентов, Интернет, блочное хранилище, объектное хранилище. И все сервисы требуют изоляции друг от друга. Так появились оверлейные сети. И все сервисы не хотят ждать, когда человек настроит их вручную. Так появились оркестраторы и SDN.
Первый подход к систематической автоматизации сети, точнее её части, давно предпринят и много где внедрён в жизнь: VMWare, OpenStack, Google Compute Cloud, AWS, Facebook.
Вот с ним сегодня и поразбираемся.
Причины¶
И раз уж мы об этом заговорили, то стоит упомянуть предпосылки к виртуализации сети. На самом деле этот процесс начался не вчера.
Наверно, вы не раз слышали, что сеть всегда была самой инертной частью любой системы. И это правда во всех смыслах. Сеть - это базис, на который опирается всё, и производить изменения на ней довольно сложно - сервисы не терпят, когда сеть лежит. Зачастую вывод из эксплуатации одного узла может сложить большую часть приложений и повлиять на много клиентов. Отчасти поэтому сетевая команда может сопротивляться любым изменениям - потому что сейчас оно как-то работает (мы, возможно, даже не знаем как), а тут надо что-то новое настроить, и неизвестно как оно повлияет на сеть.
Чтобы не ждать, когда сетевики прокинут VLAN и любые сервисы не прописывать на каждом узле сети, люди придумали использовать оверлеи - наложенные сети - коих великое многообразие: GRE, IPinIP, MPLS, MPLS L2/L3VPN, VXLAN, GENEVE, MPLSoverUDP, MPLSoverGRE итд.
Их привлекательность заключается в двух простых вещах:
- Настраиваются только конечные узлы - транзитные трогать не приходится. Это значительно ускоряет процесс, а иногда вообще позволяет исключить отдел сетевой инфраструктуры из процесса ввода новых сервисов.
- Нагрузка сокрыта глубоко внутри заголовков - транзитным узлам не нужно ничего знать о ней, об адресации на хостах, маршрутах наложенной сети. А это значит, нужно хранить меньше информации в таблицах, значит взять попроще/подешевле устройство.
В этом не совсем полноценном выпуске я не планирую разбирать все возможные технологии, а скорее описать фреймворк работы оверлейных сетей в ДЦ.
Вся серия будет описывать датацентр, состоящий из рядов однотипных стоек, в которых установлено одинаковое серверное оборудование. На этом оборудовании запускаются виртуальные машины/контейнеры/серверлесс, реализующие сервисы.
Терминология¶
Физическая машина - x86-компьютер, установленный в стойке. Наиболее часто употребим термин хост. Так и будем называть её «машина» или хост.
Гипервизор - приложение, запущенное на физической машине, эмулирующее физические ресурсы, на которых запускаются Виртуальные Машины. Иногда в литературе и сети слово «гипервизор» используют как синоним «хост».
Виртуальная машина - операционная система, запущенная на физической машине поверх гипервизора. Для нас в рамках данного цикла не так уж важно, на самом ли деле это виртуальная машина или просто контейнер. Будем называть это «ВМ»
Тенант - широкое понятие, которое я в этой статье определю как отдельный сервис или отдельный клиент.
Мульти-тенантность или мультиарендность - использование одного и того же приложения разными клиентами/сервисами. При этом изоляция клиентов друг от друга достигается благодаря архитектуре приложения, а не отдельно-запущенным экземплярам.
ToR - Top of the Rack switch - коммутатор, установленный в стойке, к которому подключены все физические машины.
Кроме топологии ToR, разные провайдеры практикуют End of Row (EoR) или Middle of Row (хотя последнее - пренебрежительная редкость и аббревиатуры MoR я не встречал).
Underlay network или подлежащая сеть или андэрлей - физическая сетевая инфраструктура: коммутаторы, маршрутизаторы, кабели.
Overlay network или наложенная сеть или оверлей - виртуальная сеть туннелей, работающая поверх физической.
L3-фабрика или IP-фабрика - потрясающее изобретение человечества, позволяющее к собеседованиям не повторять STP и не учить TRILL. Концепция, в которой вся сеть вплоть до уровня доступа исключительно L3, без VLAN и соответственно огромных растянутых широковещательных доменов. Откуда тут слово «фабрика» разберёмся в следующей части.
SDN - Software Defined Network. Едва ли нуждается в представлении. Подход к управлению сетью, когда изменения на сети выполняются не человеком, а программой. Обычно означает вынесение Control Plane за пределы конечных сетевых устройств на контроллер.
NFV - Network Function Virtualization - виртуализация сетевых устройств, предполагающая, что часть функций сети можно запускать в виде виртуальных машин или контейнеров для ускорения внедрения новых сервисов, организации Service Chaining и более простой горизонтальной масштабируемости.
VNF - Virtual Network Function. Конкретное виртуальное устройство: маршрутизатор, коммутатор, файрвол, NAT, IPS/IDS итд. Хотя понятие функция более широкое, чем устройство. Так некоторые функции (например, v`IMS <https://ru.wikipedia.org/wiki/IMS_(электросвязь)>`_ - это система виртуальных машин/контейнеров/ПО, реализующая определённую функцию.
Это верно, как для случая ДЦ (который мы разберём в этой статьей), так и для ISP (который мы разбирать не будем, потому что уже было в СДСМ. С энтерпрайзными сетями, конечно, ситуация несколько иная.
Картинка с фокусом на сеть:
Underlay¶
Underlay - это физическая сеть: аппаратные коммутаторы и кабели. Устройства в андерлее знают, как добраться до физических машин.
Опирается он на стандартные протоколы и технологии. Не в последнюю очередь потому, что аппаратные устройства по сей день работают на проприетарном ПО, не допускающем ни программирование чипа, ни реализацию своих протоколов, соответственно, нужна совместимость с другими вендорами и стандартизация.
А вот кто-нибудь вроде Гугла может себе позволить разработку собственных коммутаторов и отказ от общепринятых протоколов. Но LAN_DC не Гугл.
Underlay сравнительно редко меняется, потому что его задача - базовая IP-связность между физическими машинами. Underlay ничего не знает о запущенных поверх него сервисах, клиентах, тенантах - ему нужно только доставить пакет от одной машины до другой. Underlay может быть например таким:
- IPv4+OSPF
- IPv6+ISIS+BGP+L3VPN
- IP+EBGP
- L2+TRILL
- L2+STP
Настраивается Underlay’ная сеть классическим образом: CLI/GUI/NETCONF. Вручную, скриптами, проприетарными утилитами.
Более подробно андерлею будет посвящена следующая статья цикла.
Overlay¶
Так ВМ одного клиента (одного сервиса) могут общаться друг с другом через Overlay, даже не подозревая какой на самом деле путь проходит пакет. Overlay может быть например таким, как уже я упоминал выше:
- GRE-туннель
- VXLAN
- EVPN
- L3VPN
- GENEVE
Overlay’ная сеть обычно настраивается и поддерживается через центральный контроллер. С него конфигурация, Control Plane и Data Plane доставляются на устройства, которые занимаются маршрутизацией и инкапсуляцией клиентского трафика. Чуть ниже разберём это на примерах. Да, это SDN в чистом виде.
Существует два принципиально различающихся подхода к организации Overlay-сети:
- Overlay с ToR’a
- Overlay с хоста
Overlay с ToR’a¶
Замечу, что VXLAN - это только метод инкапсуляции и терминация туннелей может происходить не на ToR’е, а на хосте, как это происходит в случае OpenStack’а, например.Однако, VXLAN-фабрика, где overlay начинается на ToR’е является одним из устоявшихся дизайнов оверлейной сети.
Overlay с хоста¶
В серии АДСМ я выбираю подход оверлея с хоста - далее мы говорим только о нём и возвращаться к VXLAN-фабрике мы уже не будем.
Проще всего рассмотреть на примерах. И в качестве подопытного мы возьмём OpenSource’ную SDN платформу OpenContrail, ныне известную как Tungsten Fabric.
В конце статьи я приведу некоторые размышления на тему аналогии с OpenFlow и OpenvSwitch.
На примере Tungsten Fabric¶
TAP - Terminal Access Point - виртуальный интерфейс в ядре linux, которые позволяет осуществлять сетевое взаимодействие.
Сделаю тут оговорку, что не всё так просто, и отправлю любознательного читателя в конец статьи.
Чтобы vRouter’ы могли общаться друг с другом, а соответственно и ВМ, находящиеся за ними, они обмениваются маршрутной информацией через SDN-контроллер.
Чтобы выбраться во внешний мир, существует точка выхода из матрицы - шлюз виртуальной сети VNGW - Virtual Network GateWay (термин мой).
Теперь рассмотрим примеры коммуникаций - и будет ясность.
Коммуникация внутри одной физической машины¶
VM0 хочет отправить пакет на VM2. Предположим пока, что это ВМ одного клиента.
У VM-0 есть маршрут по умолчанию в его интерфейс eth0. Пакет отправляется туда. Этот интерфейс eth0 на самом деле виртуально соединён с виртуальным маршрутизатором vRouter через TAP-интерфейс tap0.
vRouter анализирует на какой интерфейс пришёл пакет, то есть к какому клиенту (VRF) он относится, сверяет адрес получателя с таблицей маршрутизации этого клиента.
Обнаружив, что получатель на этой же машине за другим портом, vRouter просто отправляет пакет в него без каких-либо дополнительных заголовков - на этот случай на vRouter’е уже есть ARP-запись.
Пакет в этом случае не попадает в физическую сеть - он смаршрутизировался внутри vRouter’а.
Гипервизор при запуске виртуальной машины сообщает ей:
- Её собственный IP-адрес.
- Маршрут по умолчанию - через IP-адрес vRouter’а в этой сети.
vRouter’у через специальный API гипервизор сообщает:
Что нужно создать виртуальный интерфейс.
Какой ей (ВМ) нужно создать Virtual Network.
К какому VRF его (VN) привязать.
Статическую ARP-запись для этой VM - за каким интерфейсом находится её IP-адрес и к какому MAC-адресу он привязан.
Таким образом все ВМ одного клиента на данной машине vRouter видит как непосредственно подключенные сети и может сам между ними маршрутизировать.
В противной же ситуации возможно пересечение адресных пространств - нужно ходить через NAT-сервер, чтобы получить публичный адрес - это похоже на выход во внешние сети, о которых ниже.
Коммуникация между ВМ, расположенными на разных физических машинах¶
Начало точно такое же: VM-0 посылает пакет с адресатом VM-7 (172.17.3.2) по своему дефолту.
vRouter его получает и на этот раз видит, что адресат находится на другой машине и доступен через туннель Tunnel0.
Сначала он вешает метку MPLS, идентифицирующую удалённый интерфейс, чтобы на обратной стороне vRouter мог определить куда этот пакет поместить причём без дополнительных лукапов.
- У Tunnel0 источник 10.0.0.2, получатель: 10.0.1.2.vRouter добавляет заголовки GRE (или UDP) и новый IP к исходному пакету.
В таблице маршрутизации vRouter есть маршрут по умолчанию через адрес ToR1 10.0.0.1. Туда и отправляет.
- ToR1 как участник Underlay сети знает (например, по OSPF), как добраться до 10.0.1.2, и отправляет пакет по маршруту. Обратите внимание, что здесь включается ECMP. На иллюстрации два некстхопа, и разные потоки будут раскладываться в них по хэшу. В случае настоящей фабрики тут будет скорее 4 некстхопа.При этом знать, что находится под внешним заголовком IP ему не нужно. То есть фактически под IP может быть бутерброд из IPv6 over MPLS over Ethernet over MPLS over GRE over over over GREка.
Соответственно на принимающей стороне vRouter снимает GRE и по MPLS-метке понимает, в какой интерфейс этот пакет надо передать, раздевает его и отправляет в первоначальном виде получателю.
При запуске машины происходит всё то же, что было описано выше. И плюс ещё следующее:
- Для каждого клиента vRouter выделяет MPLS-метку. Это сервисная метка L3VPN, по которой клиенты будут разделяться в пределах одной физической машины.На самом деле MPLS-метка выделяется vRouter’ом безусловно всегда - ведь неизвестно заранее, что машина будет взаимодействовать только с другими машинам за тем же vRouter’ом и это скорее всего даже не так.
vRouter устанавливает соединение с SDN-контроллером по протоколу BGP (или похожему на него - в случае TF -это XMPP 0_o).
Через эту сессию vRouter сообщает SDN-контроллеру маршруты до подключенных сетей:
- Адрес сети
- Метод инкапсуляции (MPLSoGRE, MPLSoUDP, VXLAN)
- MPLS-метку клиента
- Свой IP-адрес в качестве nexthop
SDN-контроллер получает такие маршруты ото всех подключенных vRouter’ов, и отражает их другим. То есть он выступает Route Reflector’ом.
То же самое происходит и в обратную сторону.
При этом не меняется никоим образом конфигурация Underlay-сети, которую кстати, автоматизировать на порядок сложнее, а сломать неловким движением проще.
Выход во внешний мир¶
Где-то симуляция должна закончиться, и из виртуального мира нужно выйти в реальный. И нужен таксофон^W шлюз.
Практикуют два подхода:
Ставится аппаратный маршрутизатор.
Запускается какой-либо appliance, реализующий функции маршрутизатора (да-да, вслед за SDN мы и с VNF столкнулись). Назовём его виртуальный шлюз.
Преимущество второго подхода в дешёвой горизонтальной масштабируемости - не хватает мощности - запустили ещё одну виртуалку со шлюзом. На любой физической машине, без необходимости искать свободные стойки, юниты, вывода питания, покупать саму железку, везти её, устанавливать, коммутировать, настраивать, а потом ещё и менять в ней сбойные компоненты.Минусы же у виртуального шлюза в том, что единица физического маршрутизатора всё же на порядки мощнее многоядерной виртуалки, а его софт, подогнанный под его же аппаратную основу, работает значительно стабильнее (нет). Сложно отрицать и тот факт, что программно-аппаратный комплекс просто работает, требуя только настройки, в то время как запуск и обслуживание виртуального шлюза - занятие для сильных инженеров.
Одной своей ногой шлюз смотрит в виртуальную сеть Overlay, как обычная Виртуальная Машина, и может взаимодействовать со всеми другими ВМ. При этом она может терминировать на себе сети всех клиентов и, соответственно, осуществлять и маршрутизацию между ними.
Другой ногой шлюз смотрит уже в магистральную сеть и знает о том, как выбраться в Интернет.
То есть процесс выглядит так:
VM-0, имея дефолт всё в тот же vRouter, отправляет пакет с адресатом во внешнем мире (185.147.83.177) в интерфейс eth0.
- vRouter получает этот пакет и делает лукап адреса назначения в таблице маршрутизации - находит маршрут по умолчанию через шлюз VNGW1 через Tunnel 1.Также он видит, что это туннель GRE с SIP 10.0.0.2 и DIP 10.0.255.2, а ещё нужно сначала повесить MPLS-метку данного клиента, которую ожидает VNGW1.
vRouter упаковывает первоначальный пакет в заголовки MPLS, GRE и новый IP и отправляет на адрес ToR1 10.0.0.1 по дефолту.
Андерлейная сеть доставляет пакет до шлюза VNGW1.
Шлюз VNGW1 снимает туннелирующие заголовки GRE и MPLS, видит адрес назначения, консультируется со своей таблицей маршрутизации и понимает, что он направлен в Интернет - значит через Full View или Default. При необходимости производит NAT-трансляцию.
- От VNGW до бордера может быть обычная IP-сеть, что вряд ли.Может быть классическая MPLS сеть (IGP+LDP/RSVP TE), может быть обратно фабрика с BGP LU или GRE-туннель от VNGW до бордера через IP-сеть.Как бы то ни было VNGW1 совершает необходимые инкапсуляции и отправляет первоначальный пакет в сторону бордера.Трафик в обратную сторону проходит те же шаги в противоположном порядке.
Бордер добрасывает пакет до VNGW1
Тот его раздевает, смотрит на адрес получателя и видит, что тот доступен через туннель Tunnel1 (MPLSoGRE или MPLSoUDP).
- Соответственно, вешает метку MPLS, заголовок GRE/UDP и новый IP и отправляет на свой ToR3 10.0.255.1.Адрес назначения туннеля - IP-адрес vRouter’а, за которым стоит целевая ВМ - 10.0.0.2.
Андерлейная сеть доставляет пакет до нужного vRouter’а.
Целевой vRouter снимает GRE/UDP, по MPLS-метке определяет интерфейс и шлёт голый IP-пакет в свой TAP-интерфейс, связанный с eth0 ВМ.
VNGW1 устанавливает BGP-соседство с SDN-контроллером, от которого он получает всю маршрутную информацию о клиентах: за каким IP-адресом (vRouter’ом) находится какой клиент, и какой MPLS-меткой он идентифицируется. Аналогично он сам SDN-контроллеру сообщает дефолтный маршрут с меткой этого клиента, указывая себя в качестве nexthop’а. А дальше этот дефолт приезжает на vRouter’ы.
FAQ¶
Зачем ты всё время делаешь ремарку GRE/UDP?
До некоторого времени маршрутизаторы, если и умели в динамические туннели, то только в MPLSoGRE, и только совсем недавно, научились в MPLSoUDP. Поэтому приходится делать всегда ремарку о возможности двух разных инкапсуляций.
Справедливости ради, стоит отметить, что TF вполне поддерживает и L2-связность с помощью VXLAN.
Ты обещал провести параллели с OpenFlow.
А можно чуть больше про vRouter?
vRouter Agent спускает настройки на vRouter Forwarder.
Что за Virtual Network?
Заключение¶
Это весьма поверхностное описание работы виртуальной сети с оверлеем с хоста и SDN-контроллером. Но какую бы платформу виртуализации вы сегодня ни взяли, работать она будет похожим образом, будь то VMWare, ACI, OpenStack, CloudStack, Tungsten Fabric или Juniper Contrail. Они будут отличаться видами инкапсуляций и заголовков, протоколами доставки информации на конечные сетевые устройства, но принцип программно настраиваемой оверлейной сети, работающей поверх сравнительно простой и статичной андерлейной сети останется прежним. Можно сказать, что области создания приватного облака на сегодняшний день SDN на основе оверлейной сети победил. Впрочем это не значит, что Openflow нет места в современном мире - он используется в OpenStacke и в той же VMWare NSX, его, насколько мне известно, использует Google для настройки андерлейной сети.
Чуть ниже я привёл ссылки на более подробные материалы, если хочется изучить вопрос глубже.
А что там наш Underlay? А в общем-то ничего. Он всю дорогу не менялся. Всё, что ему нужно делать в случае оверлея с хоста - это обновлять маршруты и ARP’ы по мере появления и исчезновения vRouter/VNGW и таскать пакеты между ними.
Давайте сформулируем список требований к Underlay-сети.
- Уметь в какой-то протокол маршрутизации, в нашей ситуации - BGP.
- Иметь широкую полосу, желательно без переподписки, чтобы не терялись пакеты из-за перегрузок.
- Поддерживать ECMP - неотъемлемая часть фабрики.
- Уметь обеспечить QoS, в том числе хитрые штуки, вроде ECN.
- Поддерживать NETCONF - задел на будущее.
Работе самой Underlay-сети я посвятил здесь совсем мало времени. Это потому, что далее в серии я именно на ней и сосредоточусь, а Overlay мы будем затрагивать только вскользь.
Очевидно, я сильно ограничиваю нас всех, используя в качестве примера сеть ДЦ, построенную на фабрике Клоза с чистой IP-маршрутизацией и оверлеем с хоста. Однако я уверен, что любую сеть, имеющую дизайн, можно описать в формальных терминах и автоматизировать. Просто я здесь преследую целью разобраться в подходах к автоматизации, а не запутать всех вообще, решая задачу в общем виде.
В рамках АДСМ мы с Романом Горге планируем опубликовать отдельный выпуск про виртуализацию вычислительных мощностей и её взаимодействие с виртуализацией сети. Оставайтесь на связи.
Полезные ссылки¶
- Tungsten Fabric Archvitecture.
- 6 часов про Яндекс.Облако, где в том числе затрагивается виртуальная сеть на TF.
- What Is Open vSwitch?
- Введение в VxLAN.
- RFC 7348. Virtual eXtensible Local Area Network (VXLAN): A Framework for Overlaying Virtualized Layer 2 Networks over Layer 3 Networks.
- Scaleway approach to VXLAN EVPN Fabric. Тут рассказывается про всю сеть ДЦ, включая Underlay, Overlay, подходы к мульти-хоумингу и управлению.
Спасибы¶
- Роману Горге - бывшему ведущему подкаста linkmeup, а ныне эксперту в области облачных платформ. За комментарии и правки. Ну и ждём в скором будущем его более глубокой статьи о виртуализации.
- Александру Шалимову - моему коллеге и эксперту в области разработки виртуальных сетей. За комментарии и правки.
- Валентину Синицыну- моему коллеге и эксперту в области Tungsten Fabric . За комментарии и правки.
- Артёму Чернобаю - иллюстратору linkmeup. За КДПВ.
- Александру Лимонову. За мем «automato».
Основы виртуализации¶
Автор этой статьи - Роман Горге - бывший ведущий linkmeup.
Предыдущая статья рассматривала архитектуру виртуализированной сети, underlay-overlay, путь пакета между VM и прочее. В данной статье мы затронем (или попытаемся затронуть) вопросы а как собственно происходит виртуализация сетевых функций, как реализован backend основных продуктов, обеспечивающих запуск и управление VM, а также как работает виртуальный свитчинг (OVS и Linux bridge).
Тема виртуализации широка и глубока, объяснить все детали работы гипервизора невозможно (да и не нужно). Мы ограничимся минимальным набором знаний необходимым для понимания работы любого виртуализированного решения, не обязательно Telco.
Введение и краткая история виртуализации¶
История современных технологий виртуализации берет свое начало в 1999 году, когда молодая компания VMWare выпустила продукт под названием VMWare Workstation. Это был продукт обеспечивающий виртуализацию desktop/client приложений. Виртуализация серверной части пришла несколько позднее в виде продукта ESX Server, который в дальнейшем эволюционировал в ESXi (i означает integrated) - это тот самый продукт, который используется повсеместно, как в IT так и в Telco, как гипервизор серверных приложений.
На стороне Opensource два основных проекта принесли виртуализацию в Linux:
KVM (Kernel-based Virtual Machine) - модуль ядра Linux, который позволяет kernel работать как гипервизор (создает необходимую инфраструктуру для запуска и управления VM). Был добавлен в версии ядра 2.6.20 в 2007 году.
QEMU (Quick EMUlator - отсюда и страус на лого) - непосредственно эмулирует железо для виртуальной машины (CPU, Disk, RAM, что угодно, включая USB порт) и используется совместно с KVM для достижения почти «native» производительности.
На самом деле на сегодняшний момент вся функциональность KVM доступна в QEMU, но это не принципиально, так как бо́льшая часть пользователей виртуализации на Linux не использует напрямую KVM/QEMU, а обращается к ним как минимум через один уровень абстракции, но об этом позже.
Сегодня VMWare ESXi и Linux QEMU/KVM это два основных гипервизора, которые доминируют на рынке. Они же являются представителями двух разных типов гипервизоров:
- Type 1 - гипервизор запускается непосредственно на железе (bare-metal). Таковым является VMWare ESXi.
- Type 2 - гипервизор запускается внутри Host OS (операционной системы). Таковым является Linux KVM.
Обсуждение что лучше, а что хуже выходит за рамки данной статьи.
Производители железа также должны были сделать свою часть работы, дабы обеспечить приемлемую производительность.
Пожалуй, наиболее важной и самой широко используемой является технология Intel VT (Virtualization Technology) - набор расширений, разработанных Intel для своих x86 процессоров, которые используются для эффективной работы гипервизора (а в некоторых случаях необходимы, так, например, KVM не заработает без включенного VT-x и без него гипервизор вынужден заниматься чисто софтверной эмуляцией, без аппаратного ускорения). Наиболее известны два из этих расширений - VT-x и VT-d. Первое важно для улучшения производительности CPU при виртуализации, так как обеспечивает аппаратную поддержку некоторых ее функций (с VT-x 99.9% Gust OS кода выполняется прямо на физическом процессоре, делая выходы для эмуляции только в самых необходимых случаях) ), второе для подключения физических устройств напрямую в виртуальную машину (для проброса виртуальных функций (VF) SRIOV, например, VT-d должен быть включен).
- Front-end driver - то что находится в виртуальной машине
- Back-end driver - то что находится в гипервизоре
- Transport driver - то что связывает backend и frontend
Если вы хоть раз писали вопрос в RFP или отвечали на вопрос в RFP «Поддерживается ли в вашем продукте virtio?» Это как раз было о поддержке front-end virtio драйвера.
Типы виртуальных ресурсов - compute, storage, network¶
Из чего же состоит виртуальная машина? Выделяют три основных вида виртуальных ресурсов:
- compute - процессор и оперативная память
- storage - системный диск виртуальной машины и блочные хранилища
- network - сетевые карты и устройства ввода/вывода
Compute¶
CPU¶
Теоретически QEMU способен эмулировать любой тип процессора и соотвествующие ему флаги и функциональность, на практике используют либо host-model и точечно выключают флаги перед передачей в Guest OS либо берут named-model и точечно включаютвыключают флаги.
По умолчанию QEMU будет эмулировать процессор, который будет распознан Guest OS как QEMU Virtual CPU. Это не самый оптимальный тип процессора, особенно если приложение, работающее в виртуальной машине, использует CPU-флаги для своей работы. Подробнее о разных моделях CPU в QEMU.
QEMU/KVM также позволяет контролировать топологию процессора, количество тредов, размер кэша, привязывать vCPU к физическому ядру и много чего еще.
Нужно ли это для виртуальной машины или нет, зависит от типа приложения, работающего в Guest OS. Например, известный факт, что для приложений, выполняющих обработку пакетов с высоким PPS, важно делать CPU pinning, то есть не позволять передавать физический процессор другим виртуальным машинам.
Memory¶
Далее на очереди оперативная память - RAM. С точки зрения Host OS запущенная с помощью QEMU/KVM виртуальная машина ничем не отличается от любого другого процесса, работающего в user-space операционной системы. Соотвественно и процесс выделения памяти виртуальной машине выполняется теми же вызовами в kernel Host OS, как если бы вы запустили, например, Chrome браузер.
Перед тем как продолжить повествование об оперативной памяти в виртуальных машинах, необходимо сделать отступление и объяснить термин `NUMA <https://ru.wikipedia.org/wiki/Non-Uniform_Memory_Access>` - Non-Uniform Memory Access.
Архитектура современных физических серверов предполагает наличие двух или более процессоров (CPU) и ассоциированной с ней оперативной памятью (RAM). Такая связка процессор + память называется узел или нода (node). Связь между различными NUMA nodes осуществляется посредством специальной шины - QPI (QuickPath Interconnect)
Выделяют локальную NUMA node - когда процесс, запущенный в операционной системе, использует процессор и оперативную память, находящуюся в одной NUMA node, и удаленную NUMA node - когда процесс, запущенный в операционной системе, использует процессор и оперативную память, находящиеся в разных NUMA nodes, то есть для взаимодействия процессора и памяти требуется передача данных через QPI шину.
С точки зрения виртуальной машины память ей уже выделена на момент ее запуска, однако в реальности это не так, и kernel Host OS выделяет процессу QEMU/KVM новые участки памяти по мере того как приложение в Guest OS запрашивает дополнительную память (хотя тут тоже может быть исключение, если прямо указать QEMU/KVM выделить всю память виртуальной машине непосредственно при запуске).
Память выделяется не байт за байтом, а определенным размером - page. Размер page конфигурируем и теоретически может быть любым, но на практике используется размер 4kB (по умолчанию), 2MB и 1GB. Два последних размера называются HugePages и часто используются для выделения памяти для memory intensive виртуальных машин. Причина использования HugePages в процессе поиска соответствия между виртуальным адресом page и физической памятью в Translation Lookaside Buffer (`TLB <https://en.wikipedia.org/wiki/Translation_lookaside_buffer>`_), который в свою очередь ограничен и хранит информацию только о последних использованных pages. Если информации о нужной page в TLB нет, происходит процесс, называемый TLB miss, и требуется задействовать процессор Host OS для поиска ячейки физической памяти, соответствующей нужной page.
Данный процесс неэффективен и медлителен, поэтому и используется меньшее количество pages бо́льшего размера. QEMU/KVM также позволяет эмулировать различные NUMA-топологии для Guest OS, брать память для виртуальной машины только из определенной NUMA node Host OS и так далее. Наиболее распространенная практика - брать память для виртуальной машины из NUMA node локальной по отношению к процессорам, выделенным для виртуальной машины. Причина - желание избежать лишней нагрузки на QPI шину, соединяющую CPU sockets физического сервера (само собой, это логично если в вашем сервере 2 и более sockets).
Storage¶
Как известно, оперативная память потому и называется оперативной, что ее содержимое исчезает при отключении питания или перезагрузке операционной системы. Чтобы хранить информацию, требуется постоянное запоминающее устройство (ПЗУ) или persistent storage. Существует два основных вида persistent storage:
- Block storage (блоковое хранилище) - блок дискового пространства, который может быть использован для установки файловой системы и создания партиций. Если грубо, то можно воспринимать это как обычный диск.
- Object storage (объектное хранилище) - информация может быть сохранена только в виде объекта (файла), доступного по HTTP/HTTPS. Типичными примерами объектного хранилища являются AWS S3 или Dropbox.
Виртуальная машина нуждается в persistent storage, однако, как это сделать, если виртуальная машина «живет» в оперативной памяти Host OS? (кстати, именно поэтому невозможно запустить виртуальную машину с оперативной памятью меньше, чем размер ее qcow2 образа). Если вкратце, то любое обращение Guest OS к контроллеру виртуального диска перехватывается QEMU/KVM и трансформируется в запись на физический диск Host OS. Этот метод неэффективен, и поэтому здесь так же как и для сетевых устройств используется virtio-драйвер вместо полной эмуляции IDE или iSCSI-устройства. Подробнее об этом можно почитать здесь. Таким образом виртуальная машина обращается к своему виртуальному диску через virtio-драйвер, а далее QEMU/KVM делает так, чтобы переданная информация записалась на физический диск. Важно понимать, что в Host OS дисковый backend может быть реализован в виде CEPH, NFS или iSCSI-полки.
Наиболее простым способом эмулировать persistent storage является использование файла в какой-либо директории Host OS как дискового пространства виртуальной машины. QEMU/KVM поддерживает множество различных форматов такого рода файлов - raw, vdi, vmdk и прочие. Однако наибольшее распространение получил формат qcow2 (QEMU copy-on-write version 2). В общем случае, qcow2 представляет собой определенным образом структурированный файл без какой-либо операционной системы. Большое количество виртуальных машин распространяется именно в виде qcow2-образов (images) и являются копией системного диска виртуальной машины, упакованной в qcow2-формат. Это имеет ряд преимуществ - qcow2-кодирование занимает гораздо меньше места, чем raw копия диска байт в байт, QEMU/KVM умеет изменять размер qcow2-файла (resizing), а значит имеется возможность изменить размер системного диска виртуальной машины, также поддерживается AES шифрование qcow2 (это имеет смысл, так как образ виртуальной машины может содержать интеллектуальную собственность).
Далее, когда происходит запуск виртуальной машины, QEMU/KVM использует qcow2-файл как системный диск (процесс загрузки виртуальной машины я опускаю здесь, хотя это тоже является интересной задачей), а виртуальная машина имеет возможность считать/записать данные в qcow2-файл через virtio-драйвер. Таким образом и работает процесс снятия образов виртуальных машин, поскольку в любой момент времени qcow2-файл содержит полную копию системного диска виртуальной машины, и образ может быть использован для резервного копирования, переноса на другой хост и прочее.
В общем случае этот qcow2-файл будет определяться в Guest OS как /dev/vda-устройство, и Guest OS произведет разбиение дискового пространства на партиции и установку файловой системы. Аналогично, следующие qcow2-файлы, подключенные QEMU/KVM как /dev/vdX устройства, могут быть использованы как block storage в виртуальной машине для хранения информации (именно так и работает компонент Openstack Cinder).
Network¶
Последним в нашем списке виртуальных ресурсов идут сетевые карты и устройства ввода/вывода. Виртуальная машина, как и физический хост, нуждается в PCI/PCIe-шине для подключения устройств ввода/вывода. QEMU/KVM способен эмулировать разные типы чипсетов - q35 или i440fx (первый поддерживает - PCIe, второй - legacy PCI ), а также различные PCI-топологии, например, создавать отдельные PCI-шины (PCI expander bus) для NUMA nodes Guest OS.
После создания PCI/PCIe шины необходимо подключить к ней устройство ввода/вывода. В общем случае это может быть что угодно - от сетевой карты до физического GPU. И, конечно же, сетевая карта, как полностью виртуализированная (полностью виртуализированный интерфейс e1000, например), так и пара-виртуализированная (virtio, например) или физическая NIC. Последняя опция используется для data-plane виртуальных машин, где требуется получить line-rate скорости передачи пакетов - маршрутизаторов, файрволов и тд.
Здесь существует два основных подхода - PCI passthrough и SR-IOV. Основное отличие между ними - для PCI-PT используется драйвер только внутри Guest OS, а для SRIOV используется драйвер Host OS (для создания VF - Virtual Functions) и драйвер Guest OS для управления SR-IOV VF. Более подробно об PCI-PT и SRIOV отлично написал Juniper.
Для уточнения стоит отметить что, PCI passthrough и SR-IOV это дополняющие друг друга технологии. SR-IOV это нарезка физической функции на виртуальные функции. Это выполняется на уровне Host OS. При этом Host OS видит виртуальные функции как еще одно PCI/PCIe устройство. Что он дальше с ними делает - не важно.
А PCI-PT это механизм проброса любого Host OS PCI устройства в Guest OS, в том числе виртуальной функции, созданной SR-IOV устройством
Таким образом мы рассмотрели основные виды виртуальных ресурсов и следующим шагом необходимо понять как виртуальная машина общается с внешним миром через сеть.
Виртуальная коммутация¶
Если есть виртуальная машина, а в ней есть виртуальный интерфейс, то, очевидно, возникает задача передачи пакета из одной VM в другую. В Linux-based гипервизорах (KVM, например) эта задача может решаться с помощью Linux bridge, однако, большое распространение получил проект `Open vSwitch <https://www.openvswitch.org>`_(OVS). Есть несколько основных функциональностей, которые позволили OVS широко распространиться и стать de-facto основным методом коммутации пакетов, который используется во многих платформах облачных вычислений(например, Openstack) и виртуализированных решениях.
- Передача сетевого состояния - при миграции VM между гипервизорами возникает задача передачи ACL, QoSs, L2/L3 forwarding-таблиц и прочего. И OVS умеет это.
- Реализация механизма передачи пакетов (datapath) как в kernel, так и в user-space
- CUPS (Control/User-plane separation) архитектура - позволяет перенести функциональность обработки пакетов на специализированный chipset (Broadcom и Marvell chipset, например, могут такое), управляя им через control-plane OVS.
- Поддержка методов удаленного управления трафиком - протокол OpenFlow (привет, SDN).
Архитектура OVS на первый взгляд выглядит довольно страшно, но это только на первый взгляд.
Для работы с OVS нужно понимать следующее:
- Datapath - тут обрабатываются пакеты. Аналогия - switch-fabric железного коммутатора. Datapath включает в себя приём пакетов, обработку заголовков, поиск соответствий по таблице flow, который в Datapath уже запрограммирован. Если OVS работает в kernel, то выполнен в виде модуля ядра. Если OVS работает в user-space, то это процесс в user-space Linux.
- vswitchd и ovsdb - демоны в user-space, то что реализует непосредственно сам функциональность коммутатора, хранит конфигурацию, устанавливает flow в datapath и программирует его.
- Набор инструментов для настройки и траблшутинга OVS - ovs-vsctl, ovs-dpctl, ovs-ofctl, ovs-appctl. Все то, что нужно, чтобы прописать в ovsdb конфигурацию портов, прописать какой flow куда должен коммутироваться, собрать статистику и прочее. Добрые люди написали статью по этому поводу.
Каким же образом сетевое устройство виртуальной машины оказывается в OVS?
Для решения данной задачи нам необходимо каким-то образом связать между собой виртуальный интерфейс, находящийся в user-space операционной системы с datapath OVS, находящимся в kernel.
В операционной системе Linux передача пакетов между kernel и user-space-процессами осуществляется посредством двух специальных интерфейсов. Оба интерфейса использует запись/чтение пакета в/из специальный файл для передачи пакетов из user-space-процесса в kernel и обратно - file descriptor (FD) (это одна из причин низкой производительности виртуальной коммутации, если datapath OVS находится в kernel - каждый пакет требуется записать/прочесть через FD)
- TUN (tunnel) - устройство, работающее в L3 режиме и позволяющее записывать/считывать только IP пакеты в/из FD.
- TAP (network tap) - то же самое, что и tun интерфейс + умеет производить операции с Ethernet-фреймами, т.е. работать в режиме L2.
</ul>
Именно поэтому при запущенной виртуальной машине в Host OS можно увидеть созданные TAP-интерфейсы командой ip link или ifconfig - это «ответная» часть virtio, которая «видна» в kernel Host OS. Также стоит обратить внимание, что TAP-интерфейс имеет тот же MAC-адрес что и virtio-интерфейс в виртуальной машине.
TAP-интерфейс может быть добавлен в OVS с помощью команд ovs-vsctl - тогда любой пакет, скоммутированный OVS в TAP-интерфейс, будет передан в виртуальную машину через file descriptor.
Реальный порядок действий при создании виртуальной машины может быть разным, т.е. сначала можно создать OVS bridge, потом указать виртуальной машине создать интерфейс, соединенный с этим OVS, а можно и наоборот.
Теперь, если нам необходимо получить возможность передачи пакетов между двумя и более виртуальными машинами, которые запущены на одном гипервизоре, нам потребуется лишь создать OVS bridge и добавить в него TAP-интерфейсы с помощью команд ovs-vsctl. Какие именно команды для этого нужны легко гуглится.
На гипервизоре может быть несколько OVS bridges, например, так работает Openstack Neutron, или же виртуальные машины могут находиться в разных namespace для реализации multi-tenancy.
А если виртуальные машины находятся в разных OVS bridges?
Для решения данной задачи существует другой инструмент - veth pair. Veth pair может быть представлен как пара сетевых интерфейсов, соединенных кабелем - все то, что «влетает» в один интерфейс, «вылетает» из другого. Veth pair используется для соединения между собой нескольких OVS bridges или Linux bridges. Другой важный момент что части veth pair могут находиться в разных namespace Linux OS, то есть veth pair может быть также использован для связи namespace между собой на сетевом уровне.
Инструменты виртуализации - libvirt, virsh и прочее¶
В предыдущих главах мы рассматривали теоретические основы виртуализации, в этой главе мы поговорим об инструментах, которые доступны пользователю непосредственно для запуска и изменения виртуальных машин на KVM-гипервизоре. Остановимся на трех основных компонентах, которые покрывают 90 процентов всевозможных операций с виртуальными машинами:
libvirt
virsh CLI
virt-install
Конечно, существует множество других утилит и CLI-команд, которые позволяют управлять гипервизором, например, можно напрямую пользоваться командами qemu_system_x86_64 или графическим интерфейсом virt manager, но это скорее исключение. К тому же существующие сегодня Cloud-платформы, Openstack, например, используют как раз libvirt.
libvirt¶
libvirt - это масштабный open-source проект, который занимается разработкой набора инструментов и драйверов для управления гипервизорами. Он поддерживает не только QEMU/KVM, но и ESXi, LXC и много чего еще. Основная причина его популярности - структурированный и понятный интерфейс взаимодействия через набор XML-файлов плюс возможность автоматизации через API. Стоит оговориться что libvirt не описывает все возможные функции гипервизора, он лишь предоставляет удобный интерфейс использования полезных, с точки зрения участников проекта, функции гипервизора.
И да, libvirt это де-факто стандарт в мире виртуализации сегодня. Только взгляните на список приложений, которые используют libvirt.
Хорошая новость про libvirt - все нужные пакеты уже предустановлены во всех наиболее часто используемых Host OS - Ubuntu, CentOS и RHEL, поэтому, скорее всего, собирать руками нужные пакеты и компилировать libvirt вам не придется. В худшем случае придется воспользоваться соответствующим пакетным инсталлятором (apt, yum и им подобные).
При первоначальной установке и запуске libvirt по умолчанию создает Linux bridge virbr0 и его минимальную конфигурацию.
Именно поэтому при установке Ubuntu Server, например, вы увидите в выводе команды ifconfig Linux bridge virbr0 - это результат запуска демона libvirtd
Этот Linux bridge не будет подключен ни к одному физическому интерфейсу, однако, может быть использован для связи виртуальных машин внутри одного гипервизора. Libvirt безусловно может быть использован вместе с OVS, однако, для этого пользователь должен самостоятельно создать OVS bridges с помощью соответствующих OVS-команд.
Любой виртуальный ресурс, необходимый для создания виртуальной машины (compute, network, storage) представлен в виде объекта в libvirt. За процесс описания и создания этих объектов отвечает набор различных XML-файлов.
Детально описывать процесс создания виртуальных сетей и виртуальных хранилищ не имеет особого смысла, так как эта прикладная задача хорошо описана в документации libvirt:
Сама виртуальная машина со всеми подключенными PCI-устройствами в терминологии libvirt называется domain. Это тоже объект внутри libvirt, который описывается отдельным XML-файлом.
Этот XML-файл и является, строго говоря, виртуальной машиной со всеми виртуальными ресурсами - оперативная память, процессор, сетевые устройства, диски и прочее. Часто данный XML-файл называют libvirt XML или dump XML. Вряд ли найдется человек, который понимает все параметры libvirt XML, однако, это и не требуется, когда есть документация.
В общем случае, libvirt XML для Ubuntu Desktop Guest OS будет довольно прост - 40-50 строчек. Поскольку вся оптимизация производительности описывается также в libvirt XML (NUMA-топология, CPU-топологии, CPU pinning и прочее), для сетевых функций libvirt XML может быть очень сложен и содержать несколько сот строк. Любой производитель сетевых устройств, который поставляет свое ПО в виде виртуальных машин, имеет рекомендованные примеры libvirt XML.
virsh CLI¶
Утилита virsh - «родная» командная строка для управления libvirt. Основное ее предназначение - это управление объектами libvirt, описанными в виде XML-файлов. Типичными примерами являются операции start, stop, define, destroy и так далее. То есть жизненный цикл объектов - life-cycle management.
Описание всех команд и флагов virsh также доступно в документации libvirt.
virt-install¶
Еще одна утилита, которая используется для взаимодействия с libvirt. Одно из основных преимуществ - можно не разбираться с XML-форматом, а обойтись лишь флагами, доступными в virsh-install. Второй важный момент - море примеров и информации в Сети.
Таким образом какой бы утилитой вы ни пользовались, управлять гипервизором в конечном счете будет именно libvirt, поэтому важно понимать архитектуру и принципы его работы.
Заключение¶
В данной статье мы рассмотрели минимальный набор теоретических знаний, который необходим для работы с виртуальными машинами. Я намеренно не приводил практических примеров и выводов команд, поскольку таких примеров можно найти сколько угодно в Сети, и я не ставил перед собой задачу написать «step-by-step guide». Если вас заинтересовала какая-то конкретная тема или технология, оставляйте свои комментарии и пишите вопросы.
Полезные ссылки¶
Спасибы¶
- Александру Шалимову - моему коллеге и эксперту в области разработки виртуальных сетей. За комментарии и правки.
- Евгению Яковлеву - моему коллеге и эксперту в области виртуализации за комментарии и правки.
Часть 2. Дизайн сети¶
В первых двух статьях я поднял вопрос автоматизации и набросал её фреймворк, во второй сделал отступление в виртуализацию сети, как первый подход к автоматизации настройки сервисов. А теперь пришло время нарисовать схему физической сети.
Если вы не на короткой ноге с устройством сетей датацентров, то я настоятельно рекомендую начать со статьи о них.
Описанные в этой серии практики должны быть применимы к сети любого типа, любого масштаба с любым многообразием вендоров (нет). Однако нельзя описать универсальный пример применения этих подходов. Поэтому я остановлюсь на современной архитектуре сети ДЦ: Фабрике Клоза. DCI сделаем на MPLS L3VPN. Поверх физической сети работает Overlay-сеть с хоста (это может быть VXLAN OpenStack’а или Tungsten Fabric или что угодно другое, что требует от сети только базовой IP-связности).
В этом случае получится сравнительно простой сценарий для автоматизации, потому что имеем много оборудования, настраивающегося одинаковым образом. Мы выберем сферический ДЦ в вакууме:
- Одна версия дизайна везде
- Два вендора, образующих две плоскости сети
- Один ДЦ похож на другой как две капли воды
Пусть наш Сервис-Провайдер LAN_DC будет, например, хостить обучающие видео о выживании в застрявших лифтах. В мегаполисах это пользуется бешенной популярностью, поэтому физических машин надо много.
Сначала я опишу сеть приблизительно такой, какой бы её хотелось видеть. А потом упрощу для лабы.
Физическая Топология¶
Локации¶
У LAN_DC будет 6 ДЦ:
- Россия (RU):
- Москва (msk)
- Казань (kzn)
- Испания (SP):
- Барселона (bcn)
- Малага (mlg)
- Китай (CN):
- Шанхай (sha)
- Сиань (sia)
Внутри ДЦ (Intra-DC)¶
Во всех ДЦ идентичные сети внутренней связности, основанные на топологии Клоза. Что за сети Клоза и почему именно они - в отдельной статье.
В каждом ДЦ по 10 стоек с машинами, они будут нумероваться как A, B, C итд. В каждой стойке по 30 машин. Они нас интересовать не будут. Также в каждой стойке стоит коммутатор, к которому подключены все машины - это Top of the Rack switch - ToR или иначе в терминах фабрики Клоза будем называть его Leaf.
Общая схема фабрики
Именовать их будем XXX-leafY, где XXX - трёхбуквенное сокращение ДЦ, а Y - порядковый номер. Например, kzn-leaf11.
Я в статьях позволю себе достаточно фривольно обращаться терминами Leaf и ToR, как синонимами. Однако нужно помнить, что это не так. ToR - это коммутатор, установленный в стойке, к которому подключаются машины. Leaf - это роль устройства в физической сети или свитч первого уровня в терминах топологии Клоза. То есть Leaf != ToR. Так Leaf’ом может быть End Of Raw-коммутатор, например. Однако в рамках этой статьи будем всё же обращаться ими как синонимами.
Каждый ToR-коммутатор в свою очередь соединён с четырьмя вышестоящими агрегирующими коммутаторами - Spine. Под Spine’ы выделено по одной стойке в ДЦ. Именовать будем аналогично: XXX-spineY.
В этой же стойке будет стоять сетевое оборудование для связности между ДЦ - 2 маршрутизатора с MPLS на борту. Но по большому счёту - это те же самые ToR’ы. То есть с точки зрения Spine-коммутаторов не имеет никакого значения обычный там ToR с подключенными машинами или маршрутизатор для DCI - один чёрт форвардить. Такие специальные ToR’ы называются Edge-leaf. Мы их будем именовать XXX-edgeY.
Выглядеть это будет так.
На схеме выше edge и leaf я действительно разместил на одном уровне. Классические трёхуровневые сети приучили нас рассматривать, аплинк (собственно отсюда и термин), как линки вверх. А тут получается «аплинк» DCI идёт обратно вниз, что некоторым немного ломает привычную логику. В случае крупных сетей, когда датацентры делятся ещё на более мелкие единицы - POD’ы (Point Of Delivery), выделяют отдельные Edge-POD’ы для DCI и выхода во внешние сети.
Для удобства восприятия в дальнейшем я буду всё же рисовать Edge над Spine, при этом мы будем держать в уме, что никакого интеллекта на Spine и отличий при работе с обычными Leaf и с Edge-leaf нет (хотя тут могут быть нюансы, но в целом это так).
Схема фабрики с Edge-leaf’ами.
Троица Leaf, Spine и Edge образуют Underlay-сеть или фабрику.
Задача сетевой фабрики (читай Underlay), как мы уже определились в прошлом выпуске, очень и очень простая - обеспечить IP-связность между машинами как в пределах одного ДЦ, так и между. Оттого-то сеть и называется фабрикой, так же, например, как фабрика коммутации внутри модульных сетевых коробок, о чём подробнее можно почитать в СДСМ14.
Фабрика полностью L3. Никаких VLAN, никаких Broadcast - вот такие у нас в LAN_DC замечательные программисты, умеют писать приложения, живущие в парадигме L3, а виртуальные машины не требуют Live Migration c сохранением IP-адреса.
И ещё раз: ответ на вопрос почему фабрика и почему L3 - в отдельной статье.
DCI - Data Center Interconnect (Inter-DC)¶
DCI будет организован с помощью Edge-Leaf, то есть они - наша точка выхода в магистраль. Для простоты предположим, что ДЦ связаны между собой прямыми линками. Исключим из рассмотрения внешнюю связность.
Я отдаю себе отчёт в том, что каждый раз, как я убираю какой-либо компонент, я значительно упрощаю сеть. И при автоматизации нашей абстрактной сети всё будет хорошо, а на реальной возникнут костыли. Это так. И всё же задача этой серии - подумать и поработать над подходами, а не героически решать выдуманные проблемы.
На Edge-Leaf’ах underlay помещается в VPN и передаётся через MPLS-магистраль (тот самый прямой линк).
Вот такая верхнеуровневая схема получается.
Маршрутизация¶
Для маршрутизации внутри ДЦ будем использовать BGP. На MPLS-магистрали OSPF+LDP. Для DCI, то есть организации связности в андерлее - BGP L3VPN over MPLS.
Общая схема маршрутизации
На фабрике никаких OSPF и ISIS (запрещённый в Российской Федерации протокол маршрутизации). А это значит, что не будет Auto-discovery и вычисления кратчайших путей - только ручная (на самом деле автоматическая - мы же здесь про автоматизацию) настройка протокола, соседства и политик.
Схема маршрутизации BGP внутри ДЦ
Почему BGP? На эту тему есть целый RFC имени Facebook’a и Arista, где рассказывается, как строить очень крупные сети датацентров, используя BGP. Читается почти как художественный, очень рекомендую для томного вечера.
А ещё целый раздел в моей статье посвящён этому. Куда я вас и отсылаю.
Но всё же если коротко, то никакие IGP не подходят для сетей крупных датацентров, где счёт сетевым устройствами идёт на тысячи. Кроме того использование BGP везде позволит не распыляться на поддержку нескольких разных протоколов и синхронизацию между ними.
Положа руку на сердце, на нашей фабрике, которая с большой долей вероятности не будет стремительно расти, за глаза хватило бы и OSPF. Это на самом деле проблемы мегаскейлеров и клауд-титанов. Но пофантазируем всего лишь на несколько выпусков, что нам это нужно, и будем использовать BGP, как завещал Пётр Лапухов.
Политики маршрутизации¶
На Leaf-коммутаторах мы импортируем в BGP префиксы с Underlay’ных интерфейсов с сетями. У нас будет BGP-сессия между каждой парой Leaf-Spine, в которой эти Underlay’ные префиксы будут анонсироваться по сети тудыть-сюдыть.
Внутри одного датацентра мы будем распространять специфики, которые импортировали на ТоРе. На Edge-Leaf’ах будем их агрегировать и анонсировать в удалённые ДЦ и спускать до ТоРов. То есть каждый ТоР будет знать точно, как добраться до другого ТоРа в этом же ДЦ и где точка входа, чтобы добраться до ТоРа в другом ДЦ. В DCI маршруты будут передаваться, как VPNv4. Для этого на Edge-Leaf интерфейс в сторону фабрики будет помещаться в VRF, назовём его UNDERLAY, и соседство со Spine на Edge-Leaf будет подниматься внутри VRF, а между Edge-Leaf’ами в VPNv4-family.
А ещё мы запретим реанонсировать маршруты полученные от спайнов, обратно на них же.
На Leaf и Spine мы не будем импортировать Loopback’и. Они нам понадобятся только для того, чтобы определить Router ID. А вот на Edge-Leaf’ах импортируем его в Global BGP. Между Loopback-адресами Edge-Leaf’ы будут устанавливать BGP-сессию в IPv4 VPN-family друг с другом.
Между EDGE-устройствами у нас будет растянута магистраль на OSPF+LDP. Всё в одной зоне. Предельно простая конфигурация.
Вот такая картина с маршрутизацией.
BGP ASN¶
Edge-Leaf ASN¶
На Edge-Leaf’ах будет один ASN во всех ДЦ. Это важно, чтобы между Edge-Leaf’ами был iBGP, и мы не накололись на нюансы eBGP. Пусть это будет 65535. В реальности это мог бы быть номер публичной AS.
Spine ASN¶
На Spine у нас будет один ASN на ДЦ. Начнём здесь с самого первого номера из диапазона приватных AS - 64512, 64513 итд. Почему ASN на ДЦ? Декомпозируем этот вопрос на два:
- Почему одинаковые ASN на всех спайнах одного ДЦ?
- Почему разные в разных ДЦ?
Почему одинаковые ASN на всех спайнах одного ДЦ Вот как будет выглядеть AS-Path Андерлейного маршрута на Edge-Leaf:
[leafX_ASN, spine_ASN, edge_ASN]
При попытке заанонсировать его обратно на Спайн, тот его отбросит потому что его AS (Spine_AS) уже есть в списке.
Однако в пределах ДЦ нас совершенно устраивает, что маршруты Underlay, поднявшиеся до Edge не смогут спуститься вниз. Вся коммуникация между хостами внутри ДЦ должна происходить в пределах уровня спайнов.
При этом агрегированные маршруты других ДЦ в любом случае беспрепятственно будут доходить до ТоРов - в их AS-Path будет только ASN 65535 - номер AS Edge-Leaf’ов, потому что именно на них они были созданы.
Почему разные в разных ДЦ Теоретически нам может потребоваться протащить Loopback’и каких-нибудь сервисных виртуальных машин между ДЦ. Например, на хосте у нас запустится Route Reflector или тот самый VNGW (Virtual Network Gateway), который по BGP запирится с ТоРом и проанонсирует свой лупбэк, который должен быть доступен из всех ДЦ. Так вот как будет выглядеть его AS-Path:
[VNF_ASN, leafX_DC1_ASN, spine_DC1_ASN, edge_ASN, spine_DC2_ASN, leafY_DC2_ASN]
И здесь нигде не должно быть повторяющихся ASN.
- То есть Spine_DC1 и Spine_DC2 должны быть разными, ровно как и leafX_DC1 и leafY_DC2, к чему мы как раз и подходим.
- Как вы, наверно, знаете, существуют хаки, позволяющие принимать маршруты с повторяющимися ASN вопреки механизму предотвращения петель (allowas-in на Cisco). И у этого есть даже вполне законные применения. Но это потенциальная брешь в устойчивости сети. И я лично в неё пару раз проваливался. И если у нас есть возможность не использовать опасные вещи, мы ей воспользуемся.
Leaf ASN¶
У нас будет индивидуальный ASN на каждом Leaf-коммутаторе в пределах всей сети. Делаем мы так из соображений, приведённых выше: AS-Path без петель, конфигурация BGP без закладок. Чтобы маршруты между Leaf’ами беспрепятственно проходили, AS-Path должен выглядеть так:
[leafX_ASN, spine_ASN, leafY_ASN]
где leafX_ASN и leafY_ASN хорошо бы, чтобы отличались. Требуется это и для ситуации с анонсом лупбэка VNF между ДЦ:
[VNF_ASN, leafX_DC1_ASN, spine_DC1_ASN, edge_ASN, spine_DC2_ASN, leafY_DC2_ASN]
Будем использовать 4-байтовый ASN и генерировать его на основе ASN Spine’а и номера Leaf-коммутатора, а именно, вот так: Spine_ASN.0000X.
Вот такая картина с ASN.
IP-план¶
Принципиально, нам нужно выделить адреса для следующих подключений:
Адреса сети Underlay между ToR и машиной. Они должны быть уникальны в пределах всей сети, чтобы любая машина могла связаться с любой другой. Отлично подходит 10/8. На каждую стойку по /26 с запасом. Будем выделять по /19 на ДЦ и /17 на регион.
- Линковые адреса между Leaf/Tor и Spine.
- Их хотелось бы назначать алгоритмически, то есть вычислять из имён устройств, которые нужно подключить.Пусть это будет… 169.254.0.0/16.А именно 169.254.00X.Y/31, где X - номер Spine, Y - P2P-сеть /31.Это позволит запускать до 128 стоек, и до 10 Spine в ДЦ. Линковые адреса могут (и будут) повторяться из ДЦ в ДЦ.
Cтык Spine - Edge-Leaf организуем на подсетях 169.254.10X.Y/31, где точно так же X - номер Spine, Y - P2P-сеть /31.
Линковые адреса из Edge-Leaf в MPLS-магистраль. Здесь ситуация несколько иная - место соединения всех кусков в один пирог, поэтому переиспользовать те же самые адреса не получится - нужно выбирать следующую свободную подсеть. Поэтому за основу возьмём 192.168.0.0/16 и будем из неё выгребать свободные.
- Адреса Loopback. Отдадим под них весь диапазон 172.16.0.0/12.
- Leaf - по /25 на ДЦ - те же 128 стоек. Выделим по /23 на регион.
- Spine - по /28 на ДЦ - до 16 Spine. Выделим по /26 на регион.
- Edge-Leaf - по /29 на ДЦ - до 8 коробок. Выделим по /27 на регион.
Если в ДЦ нам не будет хватать выделенных диапазонов (а их не будет - мы же претендуем на гиперскейлероство), просто выделяем следующий блок.
Вот такая картина с IP-адресацией.
- Loopback’и:
+------------------+-------+--------+-----+ | Префикс | Роль | Регион | ДЦ | +==================+=======+========+=====+ | 172.16.0.0/23 | | | | +------------------+ +--------+-----+ | 172.16.0.0/27 | | | ru | | 172.16.0.0/29 | | ru | msk | | 172.16.0.8/29 | | | kzn | +------------------+ +--------+-----+ | 172.16.0.32/27 | edge | | sp | | 172.16.0.32/29 | | sp | bcn | | 172.16.0.40/29 | | | mlg | +------------------+ +--------+-----+ | 172.16.0.64/27 | | | cn | | 172.16.0.64/29 | | cn | sha | | 172.16.0.72/29 | | | sia | +------------------+-------+--------+-----+ | 172.16.2.0/23 | | | | +------------------+ +--------+-----+ | 172.16.2.0/26 | | | | | 172.16.2.0/28 | | ru | msk | | 172.16.2.16/28 | | | kzn | +------------------+ +--------+-----+ | 172.16.2.64/26 | spine | | | | 172.16.2.64/28 | | sp | bcn | | 172.16.2.80/28 | | | kzn | +------------------+ +--------+-----+ | 172.16.2.128/26 | | | | | 172.16.2.128/28 | | cn | sha | | 172.16.2.144/28 | | | sia | +------------------+-------+--------+-----+ | 172.16.8.0/21 | | | | +------------------+ +--------+-----+ | 172.16.8.0/23 | | | | | 172.16.8.0/25 | | ru | msk | | 172.16.8.128/25 | | | kzn | +------------------+ +--------+-----+ | 172.16.10.0/23 | leaf | | | | 172.16.10.0/25 | | sp | bcn | | 172.16.10.128/25 | | | mlg | +------------------+ +--------+-----+ | 172.16.12.0/23 | | | | | 172.16.12.0/25 | | cn | sha | | 172.16.12.128/25 | | | sia | +------------------+-------+--------+-----+
- Underlay:
+------------------+--------+-----+ | Префикс | Регион | ДЦ | +==================+========+=====+ | 10.0.0.0/17 | | | | 10.0.0.0/19 | ru | msk | | 10.0.32.0/19 | | kzn | +------------------+--------+-----+ | 10.0.128.0/17 | | | | 10.0.128.0/19 | sp | bcn | | 10.0.160.0/19 | | mlg | +------------------+--------+-----+ | 10.1.0.0/17 | | | | 10.1.0.0/19 | cn | sha | | 10.1.32.0/19 | | sia | +------------------+--------+-----+
Лаба¶
Два вендора. Одна сеть. АДСМ.
Juniper + Arista. Ubuntu. Старая добрая Ева.
Количество ресурсов на нашей виртуалочке в Миране всё же ограничено, поэтому для практики мы будем использовать вот такую упрощённую до предела сеть.
- Два датацентра: Казань и Барселона.
- По два спайна в каждом: Juniper и Arista.
- По одному тору (Leaf’у) в каждом - Juniper и Arista, с одним подключенным хостом (возьмём легковесный Cisco IOL для этого).
- По одной ноде Edge-Leaf (пока только Juniper).
- One Cisco switch to rule them all.
- Помимо сетевых коробок запущена виртуальная машина-управляка. Под управлением Ubuntu.
- Она имеет доступ ко всем устройствам, на ней будут крутиться IPAM/DCIM-системы, букет питоновских скриптов, анзибль и что угодно ещё, что нам может понадобиться.
Полная конфигурация всех сетевых устройств, которую мы будем пробовать воспроизвести с помощью автоматики.
Заключение¶
В следующей статье разберёмся с Netbox - системой инвентаризации и управления IP-пространством в ДЦ.
Спасибы¶
- Андрею Глазкову aka @glazgoo за вычитку и правки
- Александру Клименко aka @v00lk за вычитку и правки
- Артёму Чернобаю за КДПВ
Часть 3. IPAM/DCIM-системы¶
Сегодня рынок предлагает около дюжины инструментов, реализующих эту задачу: как платных, так и Open Source.
Для задач этой серии статей я выбрал NetBox по следующим причинам:
Это бесплатно
Он содержит в себе обе необходимые части - инвентаризацию и управление IP-пространством.
У него есть RESTful API-интерфейс.
Его разработал Digital Ocean (а конкретнее, любимый всеми Jeremy Stretch) для себя, то есть для дата-центров. Поэтому тут есть почти всё, что нужно, и почти ничего лишнего.
Он активно поддерживается (Slack, Github, Google-рассылки) и обновляется.
Это Open Source
Для нужд АДСМ я развернул NetBox в виртуалочке на нашем сервере (спасибо Антону Клочкову и Мирану): http://netbox.linkmeup.ru:45127Кроме того я заполнил почти все необходимые нам в дальнейшем данные.Поэтому вы можете попробовать почти все примеры и изучать схему данных в режиме чтения, пока не развернули свою инсталляцию.
Немного полезного перед началом:
- Сам NetBox на github
- Контейнерная версия
- Полная инструкция по установке и вся документация по продукту
- SDK для работы с NetBox в Python
- Документация по API
- Mailing list
- Канал в Slack NetworkToCode
- Что такое REST
- Инсталляция NetBox для нужд АДСМ
Архитектура системы¶
NetBox написан на Python3. Что хорошо, потому что ряд других решений написан на php и изменять их при необходимости не так уж просто.
Фреймворк для самого сайта - Django.
В качестве БД используется PostgreSQL.
WEB-frontend (HTTP-сревис) - NGINX - он проксирует запросы в Gunicron.
WSGI - Gunicorn - интерфейс между Nginx и самим приложением.
Фреймворк для документации по API - Swagger.
Чтобы демонизировать NetBox - Systemd.
NetBox - проект молодой и быстро развивающийся. Например, в 2.7 отказались от supervisord и тянущегося за ним Python 2.7 в пользу systemd. Не так давно там не было ни кэширования, ни Webhooks. Поэтому меняется всё быстро и информация в статье может устареть к моменту чтения.
Иными словами все компоненты зрелые и проверенные.
По словам автора NetBox отражает не реальное состояние сети, а целевое. Поэтому ничего не подгружается в NetBox из сети - это сеть настраивается в соответствие с содержимым NetBox. Таким образом NetBox выступает единственным источником истины (калька с single source of truth). И изменения на сети должны быть инициированы изменениями в NetBox. А это очень неплохо ложится на идеологию, которую я исповедую в этой серии статей - хочешь сделать изменения на сети - сначала внеси их в системы.
Схема данных NetBox¶
Две главные задачи, которые решает NetBox: управление адресным пространством и инвентаризация. NetBox едва ли станет единственной системой инвентаризации в компании, скорее, это будет специфическая дополнительная система для инвентаризации именно сети, забирающая данные из основной. Очевидно, в нашем случае для целей АДСМ будет только NetBox.
К данному моменту бо́льшая часть начальных данных в NetBox уже внесена. На этих данных я буду демонстрировать различные примеры работы через API. Вы можете просто полазить и посмотреть: http://netbox.linkmeup.ru:45127 И эти же данные понадобятся в дальнейшем, когда мы перейдём к автоматизации.
- В общих чертах схему данных можно увидеть по схеме БД в Postgres’е
+--------+------------------------------------+-------+--------+ | | List of relations | | | | Schema | Name | Type | Owner | +========+====================================+=======+========+ | public | auth_group | table | netbox | | public | auth_group_permissions | table | netbox | | public | auth_permission | table | netbox | | public | auth_user | table | netbox | | public | auth_user_groups | table | netbox | | public | auth_user_user_permissions | table | netbox | | public | circuits_circuit | table | netbox | | public | circuits_circuittermination | table | netbox | | public | circuits_circuittype | table | netbox | | public | circuits_provider | table | netbox | | public | dcim_cable | table | netbox | | public | dcim_consoleport | table | netbox | | public | dcim_consoleporttemplate | table | netbox | | public | dcim_consoleserverport | table | netbox | | public | dcim_consoleserverporttemplate | table | netbox | | public | dcim_device | table | netbox | | public | dcim_devicebay | table | netbox | | public | dcim_devicebaytemplate | table | netbox | | public | dcim_devicerole | table | netbox | | public | dcim_devicetype | table | netbox | | public | dcim_frontport | table | netbox | | public | dcim_frontporttemplate | table | netbox | | public | dcim_interface | table | netbox | | public | dcim_interface_tagged_vlans | table | netbox | | public | dcim_interfacetemplate | table | netbox | | public | dcim_inventoryitem | table | netbox | | public | dcim_manufacturer | table | netbox | | public | dcim_platform | table | netbox | | public | dcim_powerfeed | table | netbox | | public | dcim_poweroutlet | table | netbox | | public | dcim_poweroutlettemplate | table | netbox | | public | dcim_powerpanel | table | netbox | | public | dcim_powerport | table | netbox | | public | dcim_powerporttemplate | table | netbox | | public | dcim_rack | table | netbox | | public | dcim_rackgroup | table | netbox | | public | dcim_rackreservation | table | netbox | | public | dcim_rackrole | table | netbox | | public | dcim_rearport | table | netbox | | public | dcim_rearporttemplate | table | netbox | | public | dcim_region | table | netbox | | public | dcim_site | table | netbox | | public | dcim_virtualchassis | table | netbox | | public | django_admin_log | table | netbox | | public | django_content_type | table | netbox | | public | django_migrations | table | netbox | | public | django_session | table | netbox | | public | extras_configcontext | table | netbox | | public | extras_configcontext_platforms | table | netbox | | public | extras_configcontext_regions | table | netbox | | public | extras_configcontext_roles | table | netbox | | public | extras_configcontext_sites | table | netbox | | public | extras_configcontext_tags | table | netbox | | public | extras_configcontext_tenant_groups | table | netbox | | public | extras_configcontext_tenants | table | netbox | | public | extras_customfield | table | netbox | | public | extras_customfield_obj_type | table | netbox | | public | extras_customfieldchoice | table | netbox | | public | extras_customfieldvalue | table | netbox | | public | extras_customlink | table | netbox | | public | extras_exporttemplate | table | netbox | | public | extras_graph | table | netbox | | public | extras_imageattachment | table | netbox | | public | extras_objectchange | table | netbox | | public | extras_reportresult | table | netbox | | public | extras_tag | table | netbox | | public | extras_taggeditem | table | netbox | | public | extras_webhook | table | netbox | | public | extras_webhook_obj_type | table | netbox | | public | ipam_aggregate | table | netbox | | public | ipam_ipaddress | table | netbox | | public | ipam_prefix | table | netbox | | public | ipam_rir | table | netbox | | public | ipam_role | table | netbox | | public | ipam_service | table | netbox | | public | ipam_service_ipaddresses | table | netbox | | public | ipam_vlan | table | netbox | | public | ipam_vlangroup | table | netbox | | public | ipam_vrf | table | netbox | | public | secrets_secret | table | netbox | | public | secrets_secretrole | table | netbox | | public | secrets_secretrole_groups | table | netbox | | public | secrets_secretrole_users | table | netbox | | public | secrets_sessionkey | table | netbox | | public | secrets_userkey | table | netbox | | public | taggit_tag | table | netbox | | public | taggit_taggeditem | table | netbox | | public | tenancy_tenant | table | netbox | | public | tenancy_tenantgroup | table | netbox | | public | users_token | table | netbox | | public | virtualization_cluster | table | netbox | | public | virtualization_clustergroup | table | netbox | | public | virtualization_clustertype | table | netbox | | public | virtualization_virtualmachine | table | netbox | +--------+------------------------------------+-------+--------+
- IP address management (IPAM) - IP-префиксы, адреса, VRF’ы и VLAN’ы
- Equipment racks - Стойки для оборудования, организованные по сайтам, группам и ролям
- Devices - Устройства, их модели, роли, комплектующие и расопложение
- Connections - Сетевые, консольные и силовые соединения между устройствами
- Virtualization - Виртуальные машины и вычислительные кластера
- Data circuits - Подключения к провайдерам
- Secrets - Зашифрованное хранилище учётных данных пользователей
В этой статье я коснусь следующих вещей: DCIM - Data Center Infrastructure Management, IPAM - IP Address Management, Виртуализация, дополнительные приятные вещи. Обо всём по порядку.
DCIM¶
Самая важная часть - это, несомненно, какое оборудование у нас стоит и как оно друг к другу подключено. Но начинается всё с того, где оно стоит.
Регионы и сайты (regions/sites)¶
В парадигме NetBox устройство устанавливается на сайт, сайт принадлежит региону, регионы могут быть вложены. При этом устройство не может быть установлено просто в регионе. Если такая необходимость есть, должен быть заведён отдельный сайт.
Для нашего случая это может (и будет) выглядеть так:
Напоминаю где и как мы планировали нашу сеть: АДСМ2. Дизайн сети
Давайте посмотрим, что позволяет API. Вот так можно вывести список всех регионов:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/regions/" -H "Accept: application/json; indent=4"nb.dcim.regions.all()Здесь и далее я буду приводить примеры curl и pynetbox без вывода результата. Не забудьте слэш в конце URL - без него не заработает. Как использовать pynetbox я рассказывал в статье про RESTful API.
Получить список сайтов:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/sites/" -H "Accept: application/json; indent=4"nb.dcim.sites.all()
Список сайтов конкретного региона:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/sites/?region=ru" -H "Accept: application/json; indent=4"nb.dcim.sites.filter(region="ru")Обратите внимание, что поиск идёт не по полному имени, а по так называемому slug. Slug - это идентификатор, содержащий только безопасные символы: [0-9A-Za-z-_], который можно использовать в URL. Задаётся он при создании объекта, например, «bcn» вместо «Барселона».
Устройства¶
Само устройство обладает какой-то ролью, например, leaf, spine, edge, border. Оно, очевидно, является какой-то моделью какого-то вендора. Например, Arista. Таким образом, сначала создаётся вендор, далее внутри него модели. Модель характеризуется именем, набором сервисных интерфейсов, интерфейсом удалённого управления, консольным портом и набором модулей питания.
Помимо коммутаторов, маршрутизаторов и хостов, обладающих Ethernet-интерфейсами, можно создавать консольные сервера.
Получить список всех устройств:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/" -H "Accept: application/json; indent=4"nb.dcim.devices.all()
Всех устройств конкретного сайта:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?site=mlg" -H "Accept: application/json; indent=4"nb.dcim.devices.filter(site="mlg")
Всех устройств определённой модели
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?model=veos" -H "Accept: application/json; indent=4"nb.dcim.devices.filter(device_type_id=2)
Всех устройств определённой роли:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?role=leaf" -H "Accept: application/json; indent=4"nb.dcim.devices.filter(role="leaf")
Устройство может быть в разных статусах: Active, Offline, Planned итд. Все активные устройства:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?status=active" -H "Accept: application/json; indent=4"nb.dcim.devices.filter(status="active")
Интерфейсы¶
NetBox поддерживает множество типов физических интерфейсов и LAG, однако все виртуальные, такие как Vlan/IRB и loopback объединены под одним типом - Virtual. Каждый интерфейс привязан к какому-либо устройству.
Интерфейсы устройств могут быть подключены друг к другу. Это будет отображаться как в интерфейсе, так и в ответах API (атрибут connected_endpoint).
Интерфейс может быть в различных режимах: Tagged или Access. Соответственно, в него могут быть спущены с тегом или без VLAN’ы - данного сайта или глобальные.
Получить список всех интерфейсов устройства:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/interfaces/?device=mlg-leaf-0" -H "Accept: application/json; indent=4"nb.dcim.interfaces.filter(device="mlg-leaf-0")
Получить список VLAN’ов конкретного интерфейса.
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/interfaces/?device=mlg-leaf-0&name=Ethernet7" -H "Accept: application/json; indent=4"nb.dcim.interfaces.get(device="mlg-leaf-0", name="Ethernet7").untagged_vlan.vidОбратите внимание, что тут я уже использую метод get вместо filter. Filter возвращает список, даже если результат - один единственный объект. Get - возвращает один объект или падает с ошибкой, если результатом запроса является список объектов. Поэтому get следует использовать только тогда, когда вы абсолютно уверены, что результат будет в единственном экземпляре. Ещё здесь же прямо после запроса я обращаюсь к атрибутам объекта. Строго говоря, это неправильно: если по запросу ничего не найдено, то pynetbox вернёт None, а у него нет атрибута «untagged_vlan». И ещё обратите внимание, что не везде pynetbox ожидает slug, где-то и name.
Выяснить к какому интерфейсу какого устройства подключен определённый интерфейс:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/interfaces/?device=mlg-leaf-0&name=Ethernet1" -H "Accept: application/json; indent=4"iface = nb.dcim.interfaces.get(device="mlg-leaf-0", name="Ethernet1") iface.connected_endpoint.device iface.connected_endpoint.name
Узнать имя интерфейса управления:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/interfaces/?device=mlg-leaf-0&mgmt_only=true" -H "Accept: application/json; indent=4"nb.dcim.interfaces.get(device="mlg-leaf-0", mgmt_only=True)
Консольные порты¶
Консольные порты не являются интерфейсами, поэтому вынесены как отдельные эндпоинты. Порты устройства можно связать с портами консольного сервера.
Выяснить к какому порту какого консольного сервера подключено конкретное устройство.
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/console-ports/?device=mlg-leaf-0" -H "Accept: application/json; indent=4"nb.dcim.console_ports.get(device="mlg-leaf-0").serialize()Метод serialize в pynetbox позволяет преобразовать атрибуты экземпляра класса в словарь.
IPAM¶
VLAN и VRF¶
Могут быть привязаны к локации - полезно для VLAN. При создании VRF можно указать, допускается ли пересечение адресного пространства с другими VRF.
Получить список всех VLAN:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/vlans/" -H "Accept: application/json; indent=4"nb.ipam.vlans.all()
Получить список всех VRF:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/vrfs/" -H "Accept: application/json; indent=4"nb.ipam.vrfs.all()
IP-префиксы¶
Имеют иерархическую структуру. Может принадлежать какому-либо VRF (если не принадлежит - то Global).
В NetBox очень удобное визуальное представление свободных префиксов:
Выделить можно просто кликом на зелёную строчку.
Может быть привязан к локации. Можно через API выделить следующий свободный под-префикс нужного размера или следующий свободный IP-адрес. Галочка/параметр «Is a pool» определяет, будет ли при автоматическом выделении выделяться 0-й адрес из этого префикса, или начнётся с 1-го.
Получить список IP-префиксов сайта Малага c ролью Underlay и длиной 19:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/prefixes/?site=mlg&role=underlay&mask_length=19" -H "Accept: application/json; indent=4"prefix = nb.ipam.prefixes.get(site="mlg", role="underlay", mask_length="19")
Получить список свободных префиксов в регионе Россия c ролью Underlay:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/prefixes/40/available-prefixes/" -H "Accept: application/json; indent=4"prefix.available_prefixes.list()
Выделить следующий свободный префикс длиной в 24:
curl -X POST "http://netbox.linkmeup.ru:45127/api/ipam/prefixes/40/available-prefixes/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f" \ -d "{\"prefix_length\": 24}"prefix.available_prefixes.create({"prefix_length":24})Когда внутри одного объекта нам нужно выделить какой-то дочерний, используется метод POST и нужно указать ID родительского объекта - в данном случае - 40. Его мы выяснили вызовом из предыдущего примера. В случае pynetbox мы сначала (в предыдущем примере) сохранили результат в переменную prefix, а далее обратились к его атрибуту available_prefixes и методу create. Этот пример у вас не сработает, поскольку токен с правом записи уже недействителен.
IP-адреса¶
Если есть включающий этот адрес префикс, то будут его частью. Могут быть и сами по себе. Могут принадлежать какому-либо VRF или быть в Global. Могут быть привязаны к интерфейсу, а могут висеть в воздухе. Можно выделить следующий свободный IP-адрес в префиксе.
Чтобы сделать это, просто нужно кликнуть по зелёной строчке.
Получить список IP-адресов конкретного интерфейса:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/ip-addresses/?interface_id=8" -H "Accept: application/json; indent=4"nb.ipam.ip_addresses.filter(interface_id=8)
Или:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/ip-addresses/?device=mlg-leaf-0&interface=Ethernet1" -H "Accept: application/json; indent=4"nb.ipam.ip_addresses.filter(device="mlg-leaf-0", interface="Ethernet1")
Получить список всех IP-адресов устройства:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/ip-addresses/?device=mlg-leaf-0" -H "Accept: application/json; indent=4"nb.ipam.ip_addresses.filter(device="mlg-leaf-0")
Получить список доступных IP-адресов префикса:
curl -X GET "http://netbox.linkmeup.ru:45127/api/ipam/prefixes/28/available-ips/" -H "Accept: application/json; indent=4"prefix = nb.ipam.prefixes.get(site="mlg", role="leaf-loopbacks") prefix.available_ips.list()Здесь снова нужно в URL указать ID префикса, из которого выделяем адрес - на сей раз это 28.
Выделить следующий свободный IP-адрес в префиксе:
curl -X POST "http://netbox.linkmeup.ru:45127/api/ipam/prefixes/28/available-ips/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"prefix.available_ips.create()
Виртуализация¶
Мы же всё-таки боремся за звание современного ДЦ. Куда же без виртуализации. NetBox не выглядит и не является местом, где стоит хранить информацию о виртуальных машинах (даже о необходимости хранения в нём физических машин можно порассуждать). Однако нам это может оказаться полезным, например, можно занести информация о Route Reflector’ах, о служебных машинах, таких как NTP, Syslog, S-Flow-серверах, о машинах-управляках. ВМ обладает своим списком интерфейсов - они отличны от интерфейсов физических устройств и имеют свой отдельный Endpoint.
Так можно вывести список всех виртуальных машин:
curl -X GET "http://netbox.linkmeup.ru:45127/api/virtualization/virtual-machines/" -H "Accept: application/json; indent=4"nb.virtualization.virtual_machines.all()
Так - всех интерфейсов всех ВМ:
curl -X GET "http://netbox.linkmeup.ru:45127/api/virtualization/interfaces/" -H "Accept: application/json; indent=4"nb.virtualization.interfaces.all()
Для ВМ нельзя указать конкретный гипервизор/физическую машину, на котором она запущена, но можно указать кластер. Хотя не всё так безнадёжно. Читаем дальше.
Дополнительные приятные вещи¶
Основная функциональность NetBox закрывает большинство задач многих пользователей, но не все. Всё-таки изначально продукт написан для решения задач конкретной компании. Однако он активно развивается и новые релизы выходят довольно часто. Соответственно появляются и новые функции. Так, например, с моей первой установки NetBox пару лет назад в нём появились теги, config contexts, webhooks, кэширование, supervisord сменился на systemd, внешние хранилища для файлов.
Custom fields¶
Иногда хочется к какой-либо сущности добавить поле, в которое можно было бы поместить произвольные данные. Например, указать номер договора поставки, по которому был приобретён коммутатор или имя физической машины, на которой запущена ВМ. Тут на помощь и приходит custom fields - как раз такое поле с текстовым значением, которое можно добавить почти к любой сущности в NetBox.
Создаётся Custom fields в админской панели
Вот так это выглядит при редактировании устройства, для которого был создан custom field:
Запросить список устройств по значению custom_field
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?cf_contract_number=0123456789" -H "Accept: application/json; indent=4"nb.dcim.devices.filter(cf_contract_number="0123456789")
Config Context¶
Иногда хочется чего-то большего, чем неструктурированный текст. Тогда на помощь приходит Config Context. Это возможность ввести набор структурированных данных в формате JSON, который больше некуда поместить. Это может быть, например, набор BGP communities или список Syslog-серверов. Config Context может быть локальным - настроенным для конкретного объекта - или глобальным, когда он настраивается однажды, а затем распространяется на все объекты, удовлетворяющие определённым условиям (например, расположенные на одном сайте, или запущенные на одной платформе).
Config Context автоматически добавляется к результатам запроса. При этом локальные и глобальные контексты сливаются в один.
Например, для устройства just a simple russian girl, для которого есть локальный контекст, в выводе будет ключ «config_context»:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?q=russian" -H "Accept: application/json; indent=4""config_context": { "syslog_servers": [ { "ip": "1.1.1.1" }, { "ip": "2.2.2.2" } ], "ntp_servers": [ { "ip": "3.3.3.3" } ] }
Теги¶
Про теги сложно сказать что-то новое. Они есть. Они удобны для добавления какого-либо признака. К примеру, можно пометить тегом «бяда» коммутаторы из партии, в которой сбоит память.
Webhooks¶
Незаменимая вещь, когда нужно, чтобы об изменениях в NetBox’е узнавали другие сервисы. Например, при заведении нового коммутатора отправляется хука в систему автоматизации, которая запускает процесс настройки устройства и ввода в эксплуатацию.
Некоторые нюансы установки NetBox¶
Я не буду описывать процесс инсталляции в деталях - он более чем классно описан в официальной документации.
Посмотреть на процесс запуска docker-образа NetBox и работу в GUI можно в видео Димы Фиголя (раз и два) и Эмиля Гарипова.
- В файле configuration.py должен быть заполнен параметр ALLOWED_HOSTS:
ALLOWED_HOSTS = ['netbox.linkmeup.ru', 'localhost']
Тут нужно указать все возможные имена NetBox, к которым вы будете обращаться, например, может быть внешний IP-адрес или 127.0.0.1 или DNS-alias.Если этого не будет сделано, сайт NetBox не откроется и будет показывать 400. В этом же файле должен быть указан SECRET_KEY, который можно выдумать самому или сгенерировать скриптом.
Главная страница будет показывать 502 Bad Gateway, если что-то не так с настройкой базы PostgreSQL: проверьте хост(если ставили на другую машину), порт, имя базы, имя пользователя, пароль.
- С некоторых пор NetBox по умолчанию не даёт никаких прав на чтение без авторизации.Изменяется это всё в том же configuration.py:
EXEMPT_VIEW_PERMISSIONS = ['*']
- А ещё API запросы будут возвращать 200 и не работать, если в API URL не будет слэша в конце.
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices" -H "Accept: application/json; indent=4"
Немного о PostgreSQL¶
Для подключения к серверу:
psql -U *username* -h *hostname* *db_name*
Например:
psql -U netbox -h localhost netbox
Для вывода всех таблиц:
/dt
Для выхода:
/q
Для дампа БД:
pg_dump -U *username* -h *hostname* *db_name* > netbox.sql
Если не хочется каждый раз вводить пароль:
echo *:*:*:*username*:*password* > ~/.pgpass chmod 600 ~/.pgpass
Если у вас есть своя инсталляция и не хочется вносить всё руками, можно просто сделать так, взяв дамп текущей БД NetBox тут:
psql -U *username* -h *hostname* *db_name* < netbox_initial_db.sql
Если предварительно нужно дропнуть все таблицы (а сделать это придётся), то можно подготовить заранее файл:
psql -U *username* -h *hostname* *db_name* \o drop_all_tables.sql select 'drop table ' || tablename || ' cascade;' from pg_tables; \q psql -U *username* -h *hostname* *db_name* -f drop_all_tables.sql
RESTful API¶
Эта статья - одна из обещанных коротких заметок по ходу АДСМ. Поскольку основным способом взаимодействия с NetBox будет RESTful API, я решил рассказать о нём отдельно.
Воздаю хвалы архитекторам современного мира - у нас есть стандартизированные интерфейсы. Да их много - это минус, но они есть - это плюс.
Эти интерфейсы взаимодействия обрели имя API - Application Programming Interface.
Одним из таких интерфейсов является RESTful API, который мы будем использовать для работы с нашей IPAM/DCIM-системой в будущем.
Если очень просто, то API даёт клиенту набор инструментов, через которые тот может управлять сервером. А клиентом может выступать по сути что угодно: веб-браузер, командная консоль, разработанное производителем приложение, или вообще любое другое приложение, у которого есть доступ к API.
Например, в случае NetBox, добавить новое устройство в него можно следующими способами: через веб-браузер, отправив curl’ом запрос в консоли, использовать Postman, обратиться к библиотеке requests в питоне, воспользоваться SDK pynetbox или перейти в Swagger.
Таким образом, один раз написав единый интерфейс, производитель навсегда освобождает себя от необходимости с каждым новым клиентом договариваться как его подключать (хотя, это самую малость лукавство).
REST, RESTful, API¶
Ниже я дам очень упрощённое описание того, что такое REST.
Начнём с того, что RESTful API - это именно интерфейс взаимодействия, основанный на REST, в то время как сам REST (REpresentational State Transfer) - это набор ограничений, используемых для создания WEB-сервисов. тот самый VNGW
О каких именно ограничениях идёт речь, можно почитать в главе 5 диссертации Роя Филдинга Architectural Styles and the Design of Network-based Software Architectures. Мне же позвольте привести только три наиболее значимых (с моей точки зрения) из них:
- В REST-архитектуре используется модель взаимодействия Клиент-Сервер.
- Каждый REST-запрос содержит всю информацию, необходимую для его выполнения. То есть сервер не должен помнить ничего о предыдущих запросах клиента, что, как известно, характеризуется словом Stateless - не храним информацию о состоянии.
- Единый интерфейс. Реализация приложения отделена от сервиса, который оно предоставляет. То есть пользователь знает, что оно делает и как с ним взаимодействовать, но как именно оно это делает не имеет значения. При изменении приложения, интерфейс остаётся прежним, и клиентам не нужно подстраиваться.
Для этой серии статей на linkmeup развёрнута read-only (для вас, дорогие, читатели) инсталляция NetBox: http://netbox.linkmeup.ru:45127.На чтение права не требуются, но если хочется попробовать читать с токеном, то можно воспользоваться этим: API Token: 90a22967d0bc4bdcd8ca47ec490bbf0b0cb2d9c8.
Давайте интереса ради сделаем один запрос:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/1/" -H "Accept: application/json; indent=4"
То есть утилитой curl мы делаем GET объекта по адресу http://netbox.linkmeup.ru:45127/api/dcim/devices/1/ с ответом в формате JSON и отступом в 4 пробела. Или чуть более академически: GET вовзращает типизированный объект devices, являющийся параметром объекта DCIM.
Этот запрос можете выполнить и вы - просто скопируйте себе в терминал.
URL, к которому мы обращаемся в запросе, называется Endpoint. В некотором смысле это конечный объект, с которым мы будем взаимодействовать. Например, в случае netbox’а список всех endpoint’ов можно получить тут. И ещё обратите внимание на знак / в конце URL - он обязателен.
Вот что мы получим в ответ:
{ "id": 1, "name": "mlg-host-0", "display_name": "mlg-host-0", "device_type": { "id": 4, "url": "http://netbox.linkmeup.ru/api/dcim/device-types/4/", "manufacturer": { "id": 4, "url": "http://netbox.linkmeup.ru/api/dcim/manufacturers/4/", "name": "Hypermacro", "slug": "hypermacro" }, "model": "Server", "slug": "server", "display_name": "Hypermacro Server" }, "device_role": { "id": 1, "url": "http://netbox.linkmeup.ru/api/dcim/device-roles/1/", "name": "Server", "slug": "server" }, "tenant": null, "platform": null, "serial": "", "asset_tag": null, "site": { "id": 6, "url": "http://netbox.linkmeup.ru/api/dcim/sites/6/", "name": "Малага", "slug": "mlg" }, "rack": { "id": 1, "url": "http://netbox.linkmeup.ru/api/dcim/racks/1/", "name": "A", "display_name": "A" }, "position": 41, "face": { "value": "front", "label": "Front", "id": 0 }, "parent_device": null, "status": { "value": "active", "label": "Active", "id": 1 }, "primary_ip": null, "primary_ip4": null, "primary_ip6": null, "cluster": null, "virtual_chassis": null, "vc_position": null, "vc_priority": null, "comments": "", "local_context_data": null, "tags": [], "custom_fields": {}, "config_context": {}, "created": "2020-01-14", "last_updated": "2020-01-14T18:39:01.288081Z" }
Это JSON (как мы и просили), описывающий device с ID 1: как называется, с какой ролью, какой модели, где стоит итд.
Так будет выглядеть HTTP-запрос:
GET /api/dcim/devices/1/ HTTP/1.1 Host: netbox.linkmeup.ru:45127 User-Agent: curl/7.54.0 Accept: application/json; indent=4
Так будет выглядеть ответ:
HTTP/1.1 200 OK Server: nginx/1.14.0 (Ubuntu) Date: Tue, 21 Jan 2020 15:14:22 GMT Content-Type: application/json Content-Length: 1638 Connection: keep-alive Data
А теперь разберёмся, что же мы натворили.
Структура сообщений HTTP¶
HTTP-сообщение состоит из трёх частей, только первая из которых является обязательной.
- Стартовая строка
- Заголовки
- Тело сообщения
Стартовая строка¶
Стартовые строки HTTP-запроса и ответа выглядят по-разному.
HTTP-Запрос¶
METHOD URI HTTP_VERSIONМетод определяет, какое действие клиент хочет совершить: получить данные, создать объект, обновить его, удалить. URI - идентификатор ресурса, куда клиент обращается или иными словами путь к ресурсу/документу. HTTP_VERSION - соответственно версия HTTP. На сегодняшний день для REST это всегда 1.1.
- Пример:
GET /api/dcim/devices/1/ HTTP/1.1
HTTP-Ответ¶
HTTP_VERSION STATUS_CODE REASON_PHRASEHTTP_VERSION - версия HTTP (1.1).STATUS_CODE - три цифры кода состояния (200, 404, 502 итд)REASON_PHRASE - Пояснение (OK, NOT FOUND, BAD GATEWAY итд)
- Пример:
HTTP/1.1 200 OK
Заголовки¶
В заголовках передаются параметры данной HTTP-транзакции.
Например, в примере выше в HTTP-запросе это были:
Host: netbox.linkmeup.ru:45127 User-Agent: curl/7.54.0 Accept: application/json; indent=4
В них указано, что
- Обращаемся к хосту netbox.linkmeup.ru:45127,
- Запрос был сгенерирован в curl,
- А принимаем данные в формате JSON с отступом 4.
А вот какие заголовки были в HTTP-ответе:
Server: nginx/1.14.0 (Ubuntu) Date: Tue, 21 Jan 2020 15:14:22 GMT Content-Type: application/json Content-Length: 1638 Connection: keep-alive
В них указано, что
- Тип сервера: nginx на Ubuntu,
- Время формирования ответа,
- Формат данных в ответе: JSON
- Длина данных в ответе: 1638 байтов
- Соединение не нужно закрывать - ещё будут данные.
Заголовки, как вы уже заметили, выглядят как пары имя:значение, разделённые знаком «:».
Тело HTTP-сообщения¶
Тело используется для передачи собственно данных. В HTTP-ответе это может быть HTML-страничка, или в нашем случае JSON-объект.
Между заголовками и телом должна быть как минимум одна пустая строка.
При использовании метода GET в HTTP-запросе обычно никакого тела нет, потому что передавать нечего. Но тело есть в HTTP-ответе. А вот например, при POST уже и в запросе будет тело. Давайте о методах и поговорим теперь.
Методы¶
Как вы уже поняли, для работы с WEB-сервисами HTTP использует методы. То же самое касается и RESTful API. Возможные сценарии описываются термином CRUD - Create, Read, Update, Delete. Вот список наиболее популярных методов HTTP, реализующих CRUD:
- HTTP GET
- HTTP POST
- HTTP PUT
- HTTP DELETE
- HTTP PATCH
Методы также называются глаголами, поскольку указывают на то, какое действие производится.
Давайте на примере NetBox разберёмся с каждым из них.
HTTP GET¶
Это метод для получения информации.
Так, например, мы забираем список устройств:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/" -H "Accept: application/json; indent=4"
Метод GET безопасный (safe), поскольку не меняет данные, а только запрашивает. Он идемпотентный с той точки зрения, что один и тот же запрос всегда возвращает одинаковый результат (до тех пор, пока сами данные не поменялись).
Давайте ещё пару примеров. Теперь мы запросим информацию по конкретному устройству по его имени.
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?name=mlg-leaf-0" -H "Accept: application/json; indent=4"
Здесь, чтобы задать условия поиска в URI я ещё указал атрибут объекта (параметр name и его значение mlg-leaf-0). Как вы можете видеть, перед условием и после слэша идёт знак «?», а имя и значение разделяются знаком «=».
Так выглядит запрос.
GET /api/dcim/devices/?name=mlg-leaf-0 HTTP/1.1 Host: netbox.linkmeup.ru:45127 User-Agent: curl/7.54.0 Accept: application/json; indent=4
Если нужно задать пару условий, то запрос будет выглядеть так:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?role=leaf&site=mlg" -H "Accept: application/json; indent=4"
Здесь мы запросили все устройства с ролью leaf, расположенные на сайте mlg. То есть два условия отделяются друг от друга знаком «&».
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?name=mlg-leaf-0&name=mlg-spine-0" -H "Accept: application/json; indent=4"
Попробуем обратиться к несуществующему URL.
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/IDGAF/" -H "Accept: application/json; indent=4"
HTTP POST¶
Выберем тот же Endpoint и с помощью POST создадим новое устройство.
curl -X POST "http://netbox.linkmeup.ru:45127/api/dcim/devices/" \ -H "accept: application/json"\ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f" \ -d "{ \"name\": \"just a simple russian girl\", \"device_type\": 1, \"device_role\": 1, \"site\": 3, \"rack\": 3, \"position\": 5, \"face\": \"front\"}"
Здесь уже появляется заголовок Authorization, содержащий токен, который авторизует запрос на запись, а после директивы -d расположен JSON с параметрами создаваемого устройства:
{ "name": "just a simple russian girl", "device_type": 1, "device_role": 1, "site": 3, "rack": 3, "position": 5, "face": "front"}Запрос у вас не сработает, потому что токен уже не валиден - не пытайтесь записать в мой NetBox.
В ответ приходит HTTP-ответ с кодом 201 (CREATED) и JSON’ом в теле сообщения, где сервер возвращает все параметры о созданном устройстве.
HTTP/1.1 201 Created Server: nginx/1.14.0 (Ubuntu) Date: Sat, 18 Jan 2020 11:00:22 GMT Content-Type: application/json Content-Length: 1123 Connection: keep-alive JSON
Теперь новым запросом с методом GET можно его увидеть в выдаче:
curl -X GET "http://netbox.linkmeup.ru:45127/api/dcim/devices/?q=russian" -H "Accept: application/json; indent=4"«q» в NetBox’е позволяет найти все объекты, содержащие в своём названии строку, идущую дальше.
POST, очевидно, не является ни безопасным, ни идемпотентным - он наверняка меняет данные, и дважды выполненный запрос приведёт или к созданию второго такого же объекта, или к ошибке.
HTTP PUT¶
curl -X PUT "http://netbox.linkmeup.ru:45127/api/dcim/devices/18/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f" \ -d "{ \"asset_tag\": \"12345678\"}"
Вот такую:
{"device_type":["This field is required."],"device_role":["This field is required."],"site":["This field is required."]}
Но если добавить недостающие поля, то всё сработает:
curl -X PUT "http://netbox.linkmeup.ru:45127/api/dcim/devices/18/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f" \ -d "{ \"name\": \"just a simple russian girl\", \"device_type\": 1, \"device_role\": 1, \"site\": 3, \"rack\": 3, \"position\": 5, \"face\": \"front\", \"asset_tag\": \"12345678\"}"
Обратите внимание на URL здесь - теперь он включает ID устройства, которое мы хотим менять (18).
HTTP PATCH¶
PUT - изначально существовавший в стандарте метод, предполагающий полную замену изменяемого объекта. Соответственно в методе PUT, как я и писал выше, придётся указать даже те атрибуты объекта, которые не меняются.
А PATCH был добавлен позже и позволяет указать только те атрибуты, которые действительно меняются.
Например:
curl -X PATCH "http://netbox.linkmeup.ru:45127/api/dcim/devices/18/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f" \ -d "{ \"serial\": \"BREAKINGBAD\"}"
Здесь также в URL указан ID устройства, но для изменения только один атрибут serial.
HTTP DELETE¶
Очевидно, удаляет объект.
Пример.
curl -X DELETE "http://netbox.linkmeup.ru:45127/api/dcim/devices/21/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"
Метод DELETE идемпотентен с той точки зрения, что повторно выполненный запрос уже ничего не меняет в списке ресурсов (но вернёт код 404 (NOT FOUND).
curl -X DELETE "http://netbox.linkmeup.ru:45127/api/dcim/devices/21/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"{"detail":"Not found."}
Способы работы с RESTful API¶
Curl - это, конечно, очень удобно для доблестных воинов CLI, но есть инструменты получше:
- Графическая утилита Postman
- Библиотека requests в Python
- Python SDK для NetBox Pynetbox
- API-фреймворк Swagger
Postman¶
Так мы можем сделать GET:
Здесь указан Token в GET только для примера.
А так POST:
Postman служит только для работы с RESTful API.
Например, не пытайтесь через него отправить NETCONF XML, как это делал я на заре своей автоматизационной карьеры.
Далее, всё, что только можно, вы найдёте в коллекциях.
Python+requests¶
import requests HEADERS = {'Content-Type': 'application/json', 'Accept': 'application/json'} NB_URL = "http://netbox.linkmeup.ru:45127" request_url = f"{NB_URL}/api/dcim/devices/" devices = requests.get(request_url, headers = HEADERS) print(devices.json())
Снова добавим новое устройство:
import requests API_TOKEN = "a9aae70d65c928a554f9a038b9d4703a1583594f" HEADERS = {'Authorization': f'Token {API_TOKEN}', 'Content-Type': 'application/json', 'Accept': 'application/json'} NB_URL = "http://netbox.linkmeup.ru:45127" request_url = f"{NB_URL}/api/dcim/devices/" device_parameters = { "name": "just a simple REQUESTS girl", "device_type": 1, "device_role": 1, "site": 3, } new_device = requests.post(request_url, headers = HEADERS, json=device_parameters) print(new_device.json())
Python+NetBox SDK¶
В случае NetBox есть также Python SDK - Pynetbox, который представляет все Endpoint’ы NetBox в виде объекта и его атрибутов, делая за вас всю грязную работу по формированию URI и парсингу ответа, хотя и не бесплатно, конечно.
Например, сделаем то же, что и выше, использую pynetbox. Список всех устройств:
import pynetbox NB_URL = "http://netbox.linkmeup.ru:45127" nb = pynetbox.api(NB_URL) devices = nb.dcim.devices.all() print(devices)
Добавить новое устройство:
import pynetbox API_TOKEN = "a9aae70d65c928a554f9a038b9d4703a1583594f" NB_URL = "http://netbox.linkmeup.ru:45127" nb = pynetbox.api(NB_URL, token = API_TOKEN) device_parameters = { "name": "just a simple PYNETBOX girl", "device_type": 1, "device_role": 1, "site": 3, } new_device = nb.dcim.devices.create(**device_parameters) print(new_device)
SWAGGER¶
За что ещё стоит поблагодарить ушедшее десятилетие, так это за спецификации API. Если вы перейдёте по этому пути, то попадёте в Swagger UI - документацию по API Netbox.
На этой странице перечислены все Endpoint’ы, методы работы с ними, возможные параметры и атрибуты и указано, какие из них обязательны. Кроме того описаны ожидаемые ответы.
На этой же странице можно выполнять интерактивные запросы, кликнув на Try it out.
По какой-от причине swagger в качестве Base URL берёт имя сервера без порта, поэтому функция Try it out не работает в моих примерах со Swagger’ом. Но вы можете попробовать это на собственной инсталляции.
При нажатии на Execute Swagger UI сформирует строку curl, с помощью которой можно аналогичный запрос сделать из командной строки.
В Swagger UI можно даже создать объект:
Для этого достаточно быть авторизованным пользователем, обладающим нужными правами.
То, что мы видим на этой странице - это Swagger UI - документация, сгенерированная на основе спецификации API.
Заключение¶
Не всё в том мире 2000-го года так уже радужно. REST по праву часто критикуют. Не являясь экспертом, не берусь предметно раскрывать вопрос, но дам ссылку на небесспорную статью на хабре.
Альтернативным интерфейсом взаимодействия компонентов системы сегодня является gRPC. Ему же пророчат большое будущее на ниве новых подходов к работе с сетевым оборудованием. Но о нём мы поговорим когда-то в будущем, когда придёт его черёд.
Можно также взглянуть на многообещающий GraphQL, но нам опять же нет нужды с ним работать пока, поэтому остаётся на самостоятельное изучение.
Важно Токен a9aae70d65c928a554f9a038b9d4703a1583594f был использован только в демонстрационных целях и больше не работает. Прямое указание токенов в коде программы недопустимо и сделано здесь мной только в интересах упрощения примеров.
Полезные ссылки¶
Спасибы¶
- Андрею Панфилову за вычитку и правки
- Александру Фатину за вычитку и правки
Заключение¶
В этой статье я не преследовал цель рассмотреть все возможности NetBox, поэтому всё остальное отдаю вам на откуп. Разбирайтесь, пробуйте.
Далее в рамках построения системы автоматизации я буду касаться только тех частей, которые нам действительно нужны.
Итак, выше я коротко рассказал о том, что из себя представляет NetBox, и как в нём хранятся данные. Повторюсь, что почти все необходимые данные я туда уже внёс, и вы можете утащить себе дамп БД
Всё готово к следующему этапу автоматизации: написанию системы (ахаха, просто скриптов) инициализации устройств и управления конфигурацией.
Полезные ссылки¶
- Сам NetBox на github
- Контейнерная версия
- Полная инструкция по установке и вся документация по продукту
- Documenting your network infrastructure in NetBox, integrating with Ansible over REST API
- IPAM NetBox and its API, Docker, Postman
- IPAM NetBox and its API, configuration templates with Python
- SDK для работы с NetBox в Python
- Документация по API
- Mailing list
- Канал в Slack NetworkToCode
- Что такое REST
- Инсталляция NetBox для нужд АДСМ
Часть 4. Архитектура системы автоматизации¶
Общий взгляд на жизненный цикл оборудования¶
Вот мы купили сетевую железку. Что теперь? Проследим её жизнь с первого и до последнего дня.
- Day 0 - железка только появилась в наших руках. Сейчас самое важное - базовая настройка:
- Добавить IP-адрес управления и маршрут
- Включить SSH
- Создать пользователя с правами настройки
Иными словами задача Day0 конфигурации - организовать доступ на устройство. - Day 1 - Железка уже встала на позицию, определена его роль в сети и сервисы, которые она обслуживает.Теперь нужно настроить уже целевую конфигурацию, с которой устройство встанет в сеть под нагрузку.
Day N - Изменения конфигурации в процессе эксплуатации. | Бывает мы добавляем новый сервис, пересматриваем дизайн или на худой конец нужно ACL поправить. | Такие изменения нужно тоже уметь довозить до устройства.
Обслуживание - Помимо нормальной работы есть периоды, когда устройство нужно аккуратно вывести из под нагрузки, чтобы, например, поменять в нём плату, провести обновление ПО.
Отслеживание изменений - со всей сети следует собирать информацию о том, где и во сколько применялась новая конфигурация. Это позволит как скоррелировать жалобы клиентов с изменениями, так и знать, когда применялась новая конфигурация в обход процедуры.
Проверка соответствия эталонной конфигурации** - В течение всей жизни устройства нужно проверять, что его конфигурация не разошлась с целевой из-за сбоев в автоматике, обновлений ПО или прямого вмешательства чьих-то рук.
Бэкапы - Даже если мы в любой момент можем сгенерировать эталонную конфигурацию, чтобы применить её на устройство, бэкапы необходимы.
The Last Day - снятие нагрузки, удаление из всех систем, ритуальное сжигание. Под сжиганием я понимаю безопасную затирку конфигурации, чтобы хэши паролей (или, упаси Лейбниц, клиртекст), префикс-листы и ACL’и не оказались достоянием общественности.
Я намеренно обхожу вниманием в этой статье вопрос мониторингов операционного состояния и реакции на них, поскольку её лейтмотив - это всё же конфигурация.
Далее обсудим Day0 - DayN более детально.
Day0¶
Итак, поставщик привёз на склад новый свитч. Его нужно установить, настроить, проверить, запустить трафик, добавить во все системы: инвентарные, мониторинги, бэкапы, скрипты автоматизации всякой рутины.
Задачи Day 0 можно грубо разделить на две части:
- Завести устройство в системах
- Настроить базовый доступ
Говорить про них в отрыве друг от друга сложно, и делать мы так не будем.
Какие же есть способы?
- Бумер - вручную завести устройство в инвентарной системе и выделить свободный IP-адрес. Пусть это будет даже экселька.Подключить свитч к компьютеру и через консольный порт настроить IP-адрес, маршрут, включить SSH, создать пользователя.Отвезти свитч на позицию.+ Просто, не требует почти никакой инфраструктуры.- Склонно к ошибками, масштабируется человеко-часами.
- Бумер+ - автоматизируем заведение устройства в DCIM/IPAM. Мы только нажимаем кнопочку, а в системе появляется железка на правильной локации со всеми нужными портами, ей выделяется автоматически имя и следующий свободный IP-адрес. В итоге генерируется базовый конфиг в виде текстового файлика.Администратор подключает свитч к компьютеру и через консольный порт копипастит содержимое этого файлика в терминал.+ Ниже вероятность ошибок, значительно меньше ручной работы- Требуется уже какая-никакая инфраструктура: IPAM/DCIM с API, скрипт, всё ещё ручная работа, всё ещё настраивать на стенде и потом везти устройство на позицию.
- Миллениал - ZTP - Zero Touch Provisioning - подход, которому 100 лет в обед, но он почему-то всё ещё есть не везде. Идея в том, что устройство сразу же ставится на позицию и подключается в сеть управления, после чего по DHCP оно само получает свою конфигурацию.Для этого устройство должно быть уже заведено в IPAM/DCIM и предгенерирована конфигурация, которая и передаётся устройству.+ Устройство можно сразу везти на позицию, минимум ручного труда- Нужна уже продуманная связная инфраструктура: IPAM/DCIM, DHCP, (T)FTP, автогенерация конфигов. Классическую вендорскую реализацию сложно применить для распределённых сетей, вроде ритейла.
- Зумеры - SD-WAN. Кстати, как раз подходит для ритейлов, хотя в свою очередь не очень для датацентров. Подход разделяет идею ZTP - мы устройство включаем, а оно само настраивается.+ Меньше вероятность ошибок. На первый взгляд меньше работы- Однако SD-WAN - это преимущественно проприетарные решения вендоров, требующие мощной инфраструктуры, причём иногда только в облаке вендора. У нас, кстати, был целый подкаст про SD-WAN: telecom №91. SD-WAN.
- Пост-хипстеры - есть компании, где помимо Out of Band сети управления, есть ещё консольное соединение до абсолютно каждой железки. Для этого есть соответственно сеть консольных серверов внутри датацентров и точек присутствия.Каждое новое устройство после установки подключается отдельно в OOB-свитч по Ethernet и в консольный сервер консольным линком.Это позволяет реализовать схему, подобную описанной ниже:
- Устройство добавляется в IPAM/DCIM
- Устройство устанавливается и подключается по управлению
- Инженер в ДЦ создаёт задачу на сервер наливки: настроить свитч за консольными сервером №7, порт 3
- Сервер наливки подключается на указанный порт, забирает серийный номер, с которым идёт в IPAM, генерирует базовый конфиг и обратно через тот же консольный порт применяет данную конфигурацию
+ Всегда есть консольный доступ на устройство, какие бы шторма ни гуляли в сети трафика и управления. Нет проблем с вендорскими особенностями - консольный протокол у всех реализован одинаково (с поправкой на параметры порта)- Совсем непросто и в абсолютных цифрах недёшево реализовывать ещё одну сеть управления. Не подходит для географически распределённых сетей. Требуется серьёзная инфраструктура даже в минимальном варианте без использования сервера наливки.
Так или иначе эта часть автоматизирована у многих, потому что подходы понятны, инструменты в ассортименте.
Day 1¶
- Формализованный дизайн
- Заполненные данные в IPAM/DCIM
- Набор генераторов
- Ручной копипаст из файлика в терминал
- Применение команд последовательно через SSH из кода, используя тот же netmiko
- Копирование файла на флэшку устройства, установка его в качестве конфигурационного и ребут железки
- А-ля config replace
- Пульнуть через NETCONF весь конфиг в XML
- gNMI
Об этом тоже ещё поговорим.
С автоматизацией этой задачи большинство тоже справляются - один раз настроить железку без нагрузки - дело нехитрое.
Замечу, что если есть процесс и инструменты Configuration Management и версионирования конфигурации, то Day1 - это лишь частный случай DayN.
Day N¶
А вот применить этот конфиг на железку ещё и под продуктивной нагрузкой - цель для инженеров со стальными нервами.
Тут целый ком проблем, как очевидных, так и неявных.
Во-вторых, инструмент доставки: netmiko, ncclient, ansible (какой модуль), SaltStack?
В-третьих, как заливать вслепую? Отправляя полную конфигурацию, мы не знаем, как она изменит состояние устройства. Даже если мы видим дифф между файлами или в ветке в гите, это не говорит о том, какие команды фактически применятся на железке.
В-четвёртых, даже если мы видим будущие изменения (кандидат-конфиг на самом устройстве, к примеру), то это не говорит о том, что мы ничего не разломаем по своей неосмотрительности. Тут уже напрашивается сетевой CI/CD.
В-пятых, весь ворох вопросов мультивендорной взрослой сети: разный синтаксис, семантика даже между версиями софта, где-то есть коммиты, где-то нет, где-то можно увидеть кандидат, где-то нет.
Это область компромиссов.
А мы ведь всё же хотим
- Полную автоматизацию
- Универсальное решение
- Минимизацию рутины
- Безопасные выкатки конфигурации
- Формализованный дизайн
- Версионирование
- Транзакционность, а если быть точнее, то соответствие требованиям ACID
Поэтому давайте составим схему системы автоматизации, которая позволит нам решить все задачи. Но прежде расширим понятие «Инфраструктура как код» на сетевую инфраструктуру.
IaC¶
Если вы не слышали о IaC - Infrastructure as Code, у меня для вас плохие новости. Очень плохие. Ладно, не напрягайтесь, сейчас всё расскажу.
https://dteslya.engineer/network_automaiton_101/
IaC - и ничегошеньки не настраиваем руками.
Система автоматизации с высоты птичьего полёта¶
Один из принципов, который нужно заложить в систему - это Zero Touch Prod, то есть свести к минимуму прямое хождение инженера на устройства. Любые изменения конфигурации только через платформу, только через интерфейс.
Итого, какой список задач решаем?
- Нужен интерфейс, через который можно создавать задачи. Он абстрагирует работу с сетевыми устройствами.Графический - для инженеров, API - для внешних сервисов.
Ввод новых устройств
Актуализация данных в инвентарной системе (LLDP, список интерфейсов, IP-адресов, версия ПО)
Генерация целевых конфигураций
Применение целевых конфигураций и временных патчей (тшут, костыль)
Сличение целевых и реальных конфигов
Снятие и возврат нагрузки
Обновление ПО
Сбор бэкапов, коммитов,
Диспетчеризация задач, выполняющихся на железе.
Соответственно схематично я бы изобразил это так:
IPAM/DCIM - система, являющаяся Source of Truth для всей системы автоматизации. В нашем случае - Netbox.
- NetAPI - служба одного окна. Что бы ни вздумалось сделать с сетью - идём в него.Например, захотелось добавить новый свитч - идём в ручку NetAPI с нужным набором параметров (серийник, имя, локация) и создаём задачу на добавление свитча. А-ля: https://netapi.linkmeup.ru/api/adddevice.Захотелось собрать LLDP с устройств - идём в другую ручку со списком устройств. А-ля: https://netapi.linkmeup.ru/api/lldp.Исключительно как вариант: это может быть приложение на Django, FastAPI, Flask, запущенное как systemd-сервис.
- Набор приложений, которые реализуют функционал ручек NetAPI. Например, клиент хочет получить список MAC-адресов со свитча - он идёт в ручку, а ручка дёргает модуль сбора MAC’ов, модуль идёт на свитч по SSH и собирает необходимую информацию (через CLI или NETCONF).Это может быть как интегральная часть NetAPI, так и отдельные сервисы, с которыми NetAPI взаимодействует по ещё одному API (REST, GRPC).
- Сервис NetGet, выполняющий регулярные и разовые задачи на сбор данных с сетевых устройств, таких как бэкапы, коммиты, версии ПО итд.Это может быть systemd-сервис или просто набор скриптов, запускающихся по cron’у или триггеру.
- ConfMan - Configuration Manager - это набор сервис и компонентов, выполняющий всю работу по управлению конфигурацией.Его составными частями являются:
- HLD - формализованный дизайн сети (High Level Design). Это могут быть объекты того языка программирования, на котором написана система автоматизации, может быть набор YAML-файлов или что-то своё собственное.
- Хранилище переменных, необходимых для конфигурации, которые по тем или иным причинам не получается хранить в IPAM/DCIM (например, префикс-листы или syslog-сервера).
- Специфические компоненты, такие как система управления доступами - для ACL. Или система планирования нагрузки для генерирования конфигов QoS-очередей. Возможно, оркестраторы/контроллеры для инжиниринга трафика, тоже стоит рассматривать как часть ConfMan.
- Набор генераторов конфигурации - то самое, что возьмёт HLD, обогатит его данными из IPAM/DCIM, хранилища, других систем и сформирует конечный вид конфигурации устройства.
- Возможно, часть, которая вычисляет фактическую дельту конфига и формирует патч, то еcть список команд для достижения целевого состояния. Возможно - потому что вместо применения только изменений, можно целиком конфигурацию заменять.
- Модуль, отвечающий за сличение целевого и реального конфига.
Отдельные компоненты ConfMan взаимодействуют друг с другом через тот или иной API. - Carrier - доставщик изменений на сеть. Например, ConfMan сгенерировал пачку конфигов и передал Carrier’у на применение.В зависимости от используемого интерфейса взаимодействия с сетевым устройством он выполняет разные функции.Так, для CLI он знает специфику взаимодействия с консолью конкретного вендора - интерактивные ответы, ошибки, информационные сообщения.Для NETCONF’а он умеет определять успешность или неуспешность применения конфигурации.
Можно было бы назвать его worker’ом, но Carrier - это функциональный компонент, тогда как Worker - это его экземпляр. То есть может быть несколько worker’ов, выполняющих задачу Carrier, настраивая одновременно две разные железки.
- Над всем этим царит Dispatcher - этакий диспетчер задач, бригадир, который распределяет работу.Он ведёт учёт всех поступивших задач, отслеживает их статусы, составляет расписание на исполнение.Например, если стоит задача обновить 300 свитчей, то он знает, что нельзя это делать одновременно, поэтому он составит расписание. Так же он не выведет из эксплуатации больше двух спайнов одновременно, и не проведёт работы на двух бордерах.Если на конкретную железку уже есть задача или на ней CPU под сотку, это значит, что применение изменений нужно отложить.В общем вот таким составлением расписания и занимается Dispatcher.Все задачи связанные с доступом на сетевое устройство, проходят через него.
Вот такая получается система. Не очень простая, но не очень и сложная.
Давайте сразу отметим несколько важных характеристик этой системы.
Характеристики системы¶
Единый интерфейс¶
Асинхронность¶
Соответственно должна существовать отдельная ручка (-и), в которую (-ые) можно прийти и узнать статус запроса по ID.
ACID¶
- A - Atomicity. Никакая конфигурация не должна примениться частично. Как в пределах устройства, так и в периметре сервиса - на наборе устройств. Применяется либо вся конфигурация, либо никакая. Соответственно, если на ряде устройств конфигурация применилась, она должна быть откачена. Либо средствами встроенного rollback-механизма, либо набором отменяющих изменения команд.
- C - Consistency. Именно в том виде, как понятие консистентность применяется к БД, к сети, пожалуй, не применима, но мы будем иметь в виду, что все сетевые сервисы после применения новой конфигурации остаются работоспособными.Факт консистенстности проверяется набором тестов, запускающимся после выкатки конфигурации. В зависимости от типа изменений могут быть разные наборы тестов. Иногда достаточно проверить CPU на паре коробок, в другой раз запустить пинги и проверить статусы BGP-сессий, а в третьем - всесторонние тесты всего, что настроено на сети.
- I - Isolation. Вполне понятный принцип применительно к сети - с того момента, как мы запланировали выкатку новой версии и до её применения, статус сети должен быть зафиксирован - никто не должен её менять. И уж тем более никто не должен настраивать что-то одновременно с запланированной выкаткой.Но это качество проще обозначить, чем обеспечить. Допустим, все таски внутри системы управляются Диспетчером, и он выстроит все задачи в правильном порядке. Однако как быть с тем, что кто-то может руками наадхочить на железке? Есть только один способ с этим справиться - люди не ходят на оборудование напрямую - Zero Touch Prod, помним. То есть на железе остаётся служебная учётка нашей системы автоматизации и аварийная для инженеров, которую используют только в ситуациях, когда система сложилась и надо срочно попасть на железо.Увы, это не отвечает на два вопроса: «А для тшута мы что делаем?» и «Что мешает инженеру пользоваться аварийной учёткой?». Вообще-то и на тот и на другой вопрос можно подобрать ответы, но не будем тут зацикливаться.
D - Durability. Ну тут всё просто - что бы ни случилось на сети, после восстановления конфигурация должна быть прежней. Решается это сохранением конфигурации при каждом коммите (или изменении конфиги, если коммита нет). Но есть нюанс - идентичная конфигурация не говорит об идентичном поведении - дело может быть в консистентности FIB. Но это тоже уже за рамками данной статьи.
Взаимодействие компонент через API¶
Однако, сосредоточимся на важнейшей задаче - снизить нагрузку на инженера, а для этого надо исключить хождение инженеров на железо напрямую - нужно теперь разобрать сценарии, когда это требуется в обычной жизни.
Сценарии¶
В реальной жизни их, конечно, будет много. Я же опишу самые необходимые:
- 0. Проверка сети
- 1. Ввод нового оборудования
- 2. Переконфигурация из-за изменений переменных в инвентарной системе
- 3. Переконфигурация из-за изменения дизайна
- 4. Сбор информации с устройств
- 5. Снятие и возврат нагрузки
- 6. Обновление ПО
- 7. Удаление устройства
- 8. Замена устройства
Распишем каждый из них детально.
0. Проверка сети¶
- Человек или сервис приходит в ручку NetAPI с запросом, в теле которого указаны параметры теста. Например, ICMP, устройство-источник, адрес назначения, VRF, число проб, размер пакета.
- NetAPI формирует запрос в NetGet, чтобы тот собрал данные с сети/устройства. И тот собирает.
- Результаты теста возвращаются клиенту.
1. Ввод нового оборудования¶
В зависимости от скорости роста, возможно, самый важный сценарий - быстро запускать новые узлы (стойки, филиалы, офисы), поскольку обычно занимает больше всего времени.
- Человек или часть системы, реализующей нечто а-ля ZTP, приходит в NetAPI для инициализации устройства.Устройство идентифицируется по своему серийнику или инвентарному номеру, и ему должна быть задана роль, чтобы было понятно, с какой конфигурацией его наливать.
Ручка дёргает конкретное приложение, отвечающее за этот шаг
Приложение создаёт устройство в NetBox и прописывает его
- Имя
- Серийник
- Локацию
- Вендор/модель
- Роль в сети
- Присущие ему свойства: список интерфейсов, консольных портов, комментарии.
Приложение определяет и при необходимости создаёт MGMT-интерфейс
Приложение выделяет MGMT IP.
На данном шаге устройство в минимальном виде заведено в инвентарной системе, и заполнены необходимые для первичной настройки параметры. - Далее другая часть процесса, а-ля ZTP, приходит в ручку NetAPI в поисках первоначального конфига
- Ручка дёргает конкретное приложение
- Приложение собирает данные из NetBox и, возможно, внешних систем
- Приложение рендерит конфиг, возвращает его клиенту и заодно складывает его в git-репозиторий.
- Клиент каким-то образом доставляет конфигурацию до устройства - это может быть ZTP или пропихивание конфига через консольный порт. Идентификатором устройства тут выступает серийник.
После этого шага появляется удалённый SSH-доступ на устройство.Теперь по какому-то триггеру запускается конвейер ввода устройства в эксплуатацию.Триггером может быть:- Чьё-то ручное действие - например, нажатие кнопки в интерфейсе - и сигнал в NetAPI.
- Обращение к ручке ввода в NetAPI от системы ZTP после завершения.
- Факт появления доступа по SSH на устройство - например, кроняка пытается доступиться до железки, которая помечена как «для ввода».
Заполняются данные в NetBox, которые в дальнейшем будут служить переменными для генерации конфигурации.
Система посылает в NetGet запрос на сбор данных о LLDP с данного свитча.
Информация о соседях вносится в NetBox, порты связываются друг с другом.
При необходимости создаются сабинтерфейсы или интерфейсы добавляются в LAG.
- Вычисляются (или выделяются) P2P IP-адреса.Необходимые изменения выполняются и на соседнем устройстве.Этот шаг позволяет, во-первых, подготовить данные для настройки IP-адресов, во-вторых, визуализировать топологию при необходимости, в-третьих, собрать в будущем информацию о BGP-соседях, если на узле используется BGP.
Система создаёт набор виртуальных интерфейсов и выделяет IP-адреса. Например, loopback’и и VLAN-интерфейсы.
Заполняет другие необходимые данные. Например, ASN, IS-IS Network Entity, настройки l2-интерфейсов.
- Обновление данных в NetBox инициирует запрос в NetAPI на запуск конвейера для вычисления и деплоя новой конфигурации. Это может быть, например, Web-hook, отправленный самим Netbox’ом.Речь здесь идёт обо всех устройствах, конфигурация которых меняется в результате ввода новых устройств. Добавляется новый Leaf - поменяется конфигурация Spine.
- NetAPI через Диспетчера адресует задачу на ConfMan, который вычисляет вендор-агностик конфигурацию.Для этого система берёт формализованную модель конфигурации данных (питоновские объекты, yaml итд) и подставляет в неё данные из NetBox.Результатом может быть словарь, тот же yaml или питоновский объект.
Система генерит конфиг для списка устройств. Результатом может быть текст, содержащий последовательность CLI-команд, NETCONF XML, набор объектов для YANG, Protobuf для gNMI.
- Выполняются лабораторные тесты CI/CD. Они могут быть в симуляторе, вроде Batfish, виртуальном стенде или всамделишной небольшой железной лабе, мимикрирующей под настоящую сеть.Даже для типовой операции, вроде описываемого ввода новых, серверов разумно их делать, ведь данные в SoT изменились - и выкатка может разломать сеть.Проходят ручные проверки и подтверждения.Это немного сколькзий момент. С одной стороны я всё же не верю, что в обозримом будущем на сеть новый конфиг можно катить без человеческого подтверждения, как это давно происходит в мире WEB-приложений.С другой - когда изменения катятся на тысячу устройств, пойди глазами всё просмотри. Поэтому всё же CI/CD и канареечные деплои - это то, к чему мы будем стремиться.Опционально этот шаг может выполняться в git-репозитории. Хотя заставлять человека переходить во внешний относительно основной системы автоматизации сервис - негуманно. Впрочем как первые шаги разработки такой системы - вполне нормально.
Я всё же не верю, что в обозримом будущем на сеть новый конфиг можно катить без человеческого подтверждения, как это давно происходит в мире WEB-приложений.
По факту сгенерированного конфига или полученных апрувов формируется задача в Dispatcher для Carrier’а на доставку и применение конфигурации на сеть.
- Диспетчер диспетчеризирует и следит за выполнением каждой конкретной задачи и всей транзакции целиком.Он несёт полную ответственность за то, когда выполняется задача и с каким статусом она завершается.
- В случае успешной транзакции Диспетчер обращается в ручки NetAPI, чтобы провести ряд тестов, проверяющих две вещи:Запускаются какие-то пинги. Проверяется маршрутная информация на сети - сравнивается с бейзлайном (например, состояние, как было до деплоя). Последнее предполагает, что мы либо собрали состояние перед обновлением, либо есть некая база данных с временными рядами (TSDB - Time Series Data Base), содержащая срезы исторических данных.Есть тесты, падение которых вызовет аварию, но операция будет считаться завершённой. А есть те, после которых произойдёт автоматический откат всей транзакции. Лучше не сделать ничего, чем сделать хорошо, но наполовину.
В случае успешных тестов в NetBox и/или иных системах проставляются индикаторы успешного ввода, новое устройство заводится в мониторинги и другие системы.
С результатами Диспетчер идёт в ручку NetAPI и сообщает, что ввод завершён успешно, либо нет.
2. Переконфигурация из-за изменений переменных в инвентарной системе¶
Триггером может быть Web-hook от SoT или опять же кроняка, которая следит за изменениями в этом SoT.
Не забываем про версионирование - изменения переменных в SoT фактически ведёт к изменению версии конфигурации сети. Мажорное, минорное или патч - это предмет жарких дискуссий, судьёй которому будет semver.
3. Переконфигурация из-за изменения дизайна¶
Впрочем, тут возможна специфика: изменения дизайна несут риски, поэтому неплохо бы добавить шаг проведения тестов в лабе с помощью CI/CD.
С точки зрения версионирования - инженер, меняющий дизайн и коммитящий изменения в гит, сам определяет насколько это важное обновление.
4. Сбор информации с устройств¶
Из любопытных идей для оптимизации: NetGet видится очень активноиспользуемым компонентом - вплоть до того, что мониторинг будет ходить в него, чтобы собрать счётчики и состояние сети - и, возможно, ему стоило бы держать открытыми и прогретыми сессии со всем флотом сетевых устройств. С использованием asyncio данные будут собираться просто в мгновение ока. А шардирование сетевых элементов по разным worker’ам позволит не упираться в лимиты.
5. Снятие и возврат нагрузки¶
Этот сценарий не является самостоятельным, если мы говорим про окончательное решение вопроса автоматизации - это, скорее, ручка, к которой мы будем обращаться из других сценариев.
Но для сравнительно простых устройств, каковыми являются торы, спайны и суперспайны или один из маршрутизаторов в ISP на резервированном канале, сделать это выглядит несложным.
Это может быть реализовано как две ручки: для снятия нагрузки и для возврата - так и как одна: выполняющая полный цикл.
Клиент приходит в ручку NetAPI. А тот запускает конвейер увода нагрузки
Приложение определяет список сервисов, которые нужно погасить (L2/L3VPN, базовая маршрутизация, MPLS итд)
- Приложение формирует список действий, которые нужно совершить.Например:
- Плавно увести трафик с помощью BGP gshut community или ISIS overload bit (или ещё чего-то
- Убедиться в отсутствии трафика на интерфейсах
- Выключить BGP-сессии в нужном порядке (сначала сервисные, потом транспортные
- Выключить интерфейсы
- Убедиться в отсутствии активных аварий по сервисам
Зафиксировать статус задачи.
Клиент может начинать выполнять запланированные работы. Клиентом может быть другой конвейер.
6. Обновление ПО¶
Клиент приходит в ручку NetAPI
Запускается конвейер снятия нагрузки
Запускается конвейер обновления ПО:
- Залить файлы ПО
- Проверить контрольную сумму
- Обновить прошивку, указать загрузочные файлы, перезагрузить устройство и провести иные мероприятия
- После обновления проверить версию ПО
Запустить конвейер возврата нагрузки.
7. Удаление устройства¶
Это весьма частый сценарий. Особенно если рассматривать переезд старого устройства в новую роль или локацию, как удаление и создание нового.
Клиент приходит в NetAPI. Тот дёргает приложение, отвечающее за удаление устройства.
Приложение проверяет, что нагрузка на устройстве ниже определённого порога.
Приложение обращается в NetAPI в ручку снятия нагрузки.
Приложение ищет все зависящие от этого устройства объекты в SoT. Как пример:
- Интерфейсы
- IP-адреса
- Подсети
- Интерфейсы соседних устройств
- P2P-адреса соседних устройств
- Итд.
Приложение удаляет их все.
- Изменения в SoT триггерят запуск уже известного нам конвейера. Как вы видите он весьма и весьма универсален.Как результат - настройки соседних устройств, относящиеся к удаляемому, так же удаляются в процессе деплоя новой конфигурации.Само же устройство затирается к заводским настройкам. Кроме того оно удаляется из всех мониторингов и других систем.
Устройство удаляется из БД или помечается каким-то образом, если нужно сохранить о нём информацию.
8. Замена устройства¶
- Удаление текущего устройства
- Добавление нового
Но нам важны несколько вещей:
- Имя нового устройства должно быть таким же, как и у прежнего
- Сохранить MGMT IP
- Сохранить и другие атрибуты: лупбэки, вланы, ASN, итд
- Скорее всего, и конфигурацию
Не факт, что это всё необходимо, но, скорее всего, так.
Поэтому я бы всё же рассматривал замену устройства на сети как
- Удаление старого устройства
- Добавление нового с определённым набором атрибутов, значение которых хотим зафиксировать, и которые в противном случае определялись/выделялись бы автоматически.
Опять же мы тут опускаем вопросы подавления аварийных сообщений, коммита изменений в репы и подобные.
Полезные ссылки¶
Заключение¶
Итак, скромной задачей этой статьи было спуститься на один уровень абстракции ниже по сравнению с нулевой публикацией и попытаться декомпозировать жизненный цикл оборудования на понятные блоки.
Спасибы¶
Роману Горге - бывшему ведущему подкаста linkmeup, а ныне эксперту в области облачных платформ - за комментарии про подход IaC и концепцию ACID применительно к сети.
Михаилу Арефьеву - руководителю проектов по сетевой автоматизации в Яндексе - за анализ и критику архитектуры решения и сценариев.
Дмитрию Фиголю - Network Automation Architect at Cisco Global Demo Engineering - за острые замечания и дискуссию.
Никите Асташенко - моему другу и классному разработчику - за поездку на Алтай и долгие разговоры у костра, без которых эта идея не вызрела бы.
Особо благодарных просим задержаться и пройти на Патреон.
Часть 5. История сетевой автоматизации¶
С NETCONF? Точно нет, CLI ещё мой дед парсил. А уж сколько expect’ов там поработало…
SNMP - вот что приходит на ум в качестве первого подхода - он родом из 90-х.
Однако как насчёт перехода от коммутации каналов к коммутации пакетов? Нельзя ли назвать динамическую сеть, не требующую мгновенного ручного вмешательства при обрывах, разновидностью автоматизации?
А первый декадно-шаговый искатель, разработанный Строуджером в 19-м веке и раз и навсегда избавивший мир от ручного труда телефонисток?
Да и в целом даже сам факт появления телефонных станций взамен почты, курьеров и гонцов?
Источник: доклад на Cisco Live
В этой статье посмотрим, сколько всего в эти 30 лет уместилось. А уместилось немало.
One CLI to rule them all¶
С начала времён и до сего дня.
Командный интерфейс (CLI) реализует императивный интерфейс. А мы во всех сферах стремимся к декларативности.
Команды затем транслируются в вызовы какого-то внутреннего API, который может быть понятен и даже в каком-то смысле документирован (привет, Juniper) или представлять из себя чёрный ящик (йо, хуавэй, как жизнь?).
Но самая главная проблема CLI - это фундаментальные различия в синтаксисе и семантике у различных вендоров. А порой даже в различных версиях.
Не существует такого способа, который позволил бы декларативно объявить - хочу вот именно такую конфигурацию BGP с такими пирами и таким набором политик, и не хочу вычислять, что мне нужно для этого добавить, а что удалить.
И кроме всего прочего долгое время не существовало надёжного инструмента доставки и применения конфигурации - каждый изгалялся в меру своей фантазии.
Спасибо Майклу Дехану, вендорам и сообществу, попытка создать такой инструмент была предпринята - Ansible. Да, к нему много (у кого-то очень много) вопросов. Да, он не решает большей части проблем, озвученных выше. Но по факту это лучший опенсорсный инструмент для применения конфигурации на сетевое железо (ну, кроме Nornir), и Ansible - спасение для многих сетевых инженеров. Сложить весь прод одним нажатием Enter никогда прежде не было так просто.
Ещё одна особенность CLI - отсутствие контроля состояния. CLI делает то, что ему сказали. Если ты не побеспокоился о том, чтобы удалить лишнего пира специальной командой - он и не почешется. Контроль состояния - ответственность инженера.
С другой стороны CLI - это на сегодняшний день (и долго ещё так будет) единственный вариант, который на 100% современных железок позволит настроить 100% предоставляемой ими функциональности.
Учитывая, что компаний, которые сейчас строят сеть с нуля и могут выбрать только то оборудование, которое поддерживает полноценный NETCONF/gNMI, очень и очень немного, всем остальным нужно уметь поддерживать зоопарк оборудования разного возраста и уровня.
Собственно в Яндексе именно тем, что CLI работает в буквальном смысле на всём, и воспользовались. Аннушка настраивает сетёвку именно через CLI.
Однако факт того, что все способы автоматизации чего-либо через CLI - это попытка написать скрипт, который будет прикидываться человеком и предугадать все возможные исключительные ситуации и варианты ответа операционной системы, заставляет писать очень изощрённые программы и постоянно их адаптировать под изменяющееся от версии к версии поведение и синтаксис новых вендоров.
А сколько радости представляет как крафтинг, так и парсинг текста, вы можете представить.
Желание унифицировать подходы и сделать стандартизированным интерфейс взаимодействия появилось не вчера. О нём думали уже в 80-е, что и породило очень удачное решение - SNMP.
SNMP - и не simple, и не management, и не short term¶
Бунтарские 80-е - лихие 90-е.
И вот умники в IAB (Internet Activities Board) крепко призадумались и 21-го марта 1988 года, собравшись в тогдашнем зуме (без шуток, не офлайн), постановили много важного стратегического про будущее систем управления интернетом. Ох они тогда напридумывали!
Результаты встречи они сели, записали и превратили в RFC1052 .
Они уже тогда действительно проектировали штуки, которые должны были не дать превратиться системам управления в то, во что они всё же превратились.
Как иронично теперь читать это послание из 80-х:
(i) Future Internet development is a joint interest of the R&D community, the vendor community and the user community. (ii) We still don't have a common understanding of what [Inter]Network Management really is. (iii) We will learn what [Inter]Network Management is by doing it. (v) Define the Management Information Base for TCP/IP suite NOW! (vi) Seek a seat for IETF on ANSI, ISO and/or CCITT Удачи вам там, пацаны, в будущем…
Но работа закипела. RFC выходил за RFC. А количество рабочих групп не оставляло шансов для провала.
Что любопытно, так это то, что SNMP по их задумке был временным протоколом, решающим насущные нужды вендоров и операторов в перспективе нескольких лет. А в дальнейшем все должны были перейти на ISO CMIP/CMIS (RFC1095, RFC1189). Общими для них оставались MIB (RFC1066) - спецификации, описывающие формат данных.
Уверен, что уже тогда не всем эта идея пришлась по душе.В те дни человечество ещё верило в ISO.
SNMP именно потому и был Simple, что на горизонте маячил Common. И его планировали держать «Simple», пока не откажутся. А вовсе не потому что он сам по себе был прост. Кажется, нам, как цивилизации, ещё повезло, что на смену SNMP не пришло что-то не столь Simple.
Уже тогда, в 90-е, все хотели сделать что-то универсальное и отвязаться от вендорской специфики, но настойчивых попыток не предпринимали, а вендоры в погоне за time to market были ещё меньше заинтересованы вкладываться в стандартизацию того места, где им не придётся стыковаться друг с другом. Поэтому единой модели тогда не появилось.
Однако теперь следите за руками: к концу 90-х у нас уже были:
- Протокол - SNMP
- Спецификации - MIB
- Язык их моделирования - SMI
- Возможность стримить данные с железки на NMS - Trap’ы (ну серединка на половинку, конечно, но всё же)
- Целая пачка инструментов, утилит и NMS, работающих с MIB и SNMP - snmpwalk, MIB browser
- Желание вендоров поддерживать это и выпускать MIB’ы для каждой новой версии вовремя
По всей видимости мы были просто в шаге от дивного мира с единым фреймворком для сетевой автоматизации.
Но добавляя ещё один пункт:
- Никто из вендоров при этом так и не поддержал полноценное конфигурирование через SNMP
мы получаем ситуацию, в которой мы находимся прямо сейчас. Та-дам!
Но даже без этого в силу сложности (S for Slozhnost), вопросов к архитектуре, безопасности, транзакционности, нечитаемости спецификаций, непрогнозируемости результатов, невозможности проиграть изменения повторно, UDP в качестве транспорта и многим другим, SNMP нашёл применение лишь в задачах сбора данных с сетевых устройств и в крайне вырожденных случаях для настройки точечно тех или иных вещей.
Впрочем сегодня даже в вопросах мониторинга SNMP скромно уступает место NETCONF и gNMI.
Смахнули скупую слезу и забыли! И про SNMP и про CMIP/CMIS. Не забываем только про SMI.
Переходим к современности.
API¶
И под одним этим зонтичным термином скрываются совершенно разные виды:
REST API
GraphQL
XML RPC
Linux Kernel API
SOAP
CORBA
PCI шины
JSON RPC
Android API
И сотни других
RPC - Remote Procedure Call¶
Например, мы могли бы по HTTP/FTP скачать несколько гигов данных sFlow с сервера и проанализировать их локально, а можем отправить сигнал на сервер, чтобы сложную статистику вычислил он сам и вернул результаты в ответе. Так вот второе - это удалённое исполнение кода.
Так в случае RPC, из-под винды в питоне, например, вы можете исполнить удалённую программу, написанную на Go, запущенную на линуксе. И никто вам не сможет помешать! (Кроме сетевиков)
Только представьте, как было бы восхитительно, если бы для вызова этого кода, не нужно было заходить на железку по SSH и вбивать команду?!
Наевшись с CLI и SNMP, сетевики придумали два протокола, которые используют под капотом RPC и при этом позволяют управлять сетевым железом:
- NETCONF
- gNMI (как фреймворк над gRPC)
NETCONF¶
Сытые 0-е и по ныне
Если вам по какой-то причине кажется, что стандарты рождаются где-то в недрах институтов, оторванных от жизни, то послушайте вот эту историю. Скорбно при этом помним про ISO.
В 1996 выходец из Ксерокса Прадип Синдху и Скот Кринс из StrataCom, купленной Циской, основали Juniper Networks. Идея создания мощного пакетного маршрутизатора пришла в голову Синдху, и он стал CTO компании, а второго наняли на роль CEO.
Juniper M40. Источник
Так вот, их CLI и способ взаимодействия его с системой оказался настолько естественным и удачным, что его и положили в основу стандарта NETCONF в 2006-м году. Не без участия Juniper Networks, конечно же, появился RFC4741. Будем честны, один только джунипер там и постарался в практической части. И то тут, то там будут проскакивать его куски, начиная с set и заканчивая candidate config.
Вот как он был определён в нулевых:
Abstract The Network Configuration Protocol (NETCONF) defined in this document provides mechanisms to install, manipulate, and delete the configuration of network devices. It uses an Extensible Markup Language (XML)-based data encoding for the configuration data as well as the protocol messages. The NETCONF protocol operations are realized on top of a simple Remote Procedure Call (RPC) layer.
И определение с тех пор не менялось - вся суть NETCONF в этом параграфе.
Но как так получилось, с чего началось? Да с того, что в начале 2000-х IAB проснулся в одно недоброе утро и осознал, что все планы по CMIP мир провалил, SNMP прорастил свои корни глубоко и перестал быть Simple, никто из вендоров так и не реализовал на 100% его поддержку, в самой аббревиатуре SNMP «M» вместо «Management» стала обозначать «Monitoring», и к тому же единой модели данных конфигурации не получилось. Хуже того - появился этот выскочка Juniper, который везде суёт свой нос.
В общем умники из IAB крепко призадумались. И собрались снова - на этот раз в офлайне 4-го июня 2002-го года в Рестоне (это в Штатах - любопытный городок - почитайте), чтобы «продолжить важный диалог, начатый между операторами и протокол-девелоперами».
Сели, похаяли SNMP, покекали с аббревиатур COPS, SPPI, PIB, CIM, MOF и записали это всё в –тик-ток– RFC3535.
Выхлопом этой встречи стали 33 наблюдения и 8 рекомендаций. Среди них есть действительно важные, определившие наше настоящее.
1. Программные интерфейсы должны предоставлять полное покрытие, иначе они не будут использоваться операторами, поскольку они будут вынуждены использовать CLI. 5. Необходимо строгое разделение между конфигурационными и операционными данными 8. Необходимо иметь возможность выгрузить и загрузить конфигурацию в текстовом формате в единообразной манере между всеми вендорами и типами устройства 9. Желательно иметь механизм доставки конфигурации в условиях транзакционных ограничений. 14. Необходимо, чтобы устройства поддерживали как программный, так и пользовательский интерфейс 15. Внутренние операции на устройстве должны быть одинаковы как для программного, так и для пользовательского интерфейсов. 26. Должна быть возможность произвести операцию над указанной секцией конфигурации. 27. Должна быть возможность выяснить возможности устройства 28. Необходимы безопасный транспорт, механизмы аутентификации и авторизации, поддерживаемые текущей инфраструктурой. 30. Полная конфигурация устройства должна быть применима через один протокол.
Часть из них мы воспринимаем сегодня как самоочевидное, мол, а как вы ещё иначе могли бы такое сделать? Но это не воспринималось так тогда. Просто вспомним как устроен SNMP :)
А ещё были явно полезные рекомендации:
- Рабочее совещание рекомендует прекратить форсить рабочие группы предоставлять конфигурационные MIB’ы
- Рабочее совещание рекомендует не тратить время на CIM, COPS-PR, SPPI PIB
В общем-то какие претензии к SNMP и его компании заставили уважаемых людей собраться на три дня?
- Проблемы масштабирования. Забирать большие объёмы данных с большого количества устройств он не был рассчитан.
- Транзакционность изменений на устройстве, и тем более на сети, должна была поддерживаться не протоколом и устройством, а системой инструментов.
- Откат также лежал на инструментах.
- Writable MIB не покрывали большей части задач по настройке устройства.
- Весь этот куст OID’ов был крайне сложночитаем для человека. Понять, что произойдёт после работы скрипта было очень сложно. Сколькие из вас отчаялись, пытаясь его понять?
- Не было никакого инструмента, который позволял бы повторно выполнить те же действия идемпотентно на этом же устройстве или на другом.
- Контроль состояния тоже отсутствовал.
Вот так в итоге скромно упомянут джунипер в этом RFC:
In the late 1990's, some vendors started to use the Extensible Markup Language (XML) [XML] for describing device configurations and for protocols that can be used to retrieve and manipulate XML formatted configurations.
А через 5 лет, в 2011, исправленное и дополненное издание вышло под номером RFC6241. Там уже потрудились несколько университетов и компаний. Одной из них стала восходящая звезда сетевой автоматизации Tail-f, купленная и погубленная в 2014-м году циской. Нет, формально она, конечно, осталась внутри как отдельный Business Unit, но в большой мир они отсвечиваюь теперь только Cisco NSO, хотя могли бы приносить большую пользу. Впрочем, может, я зря наговариваю? Надо будет потрогать его.
И вот в операторские сети на белом коне въезжает NETCONF.
- Работает по SSH (и не только),
- Представляет данные в структурированном виде,
- Разделяет конфигурационные и операционные данные,
- Имеет несколько операций над данными: create, merge, replace, delete, remove,
- Может обеспечить контроль целевого состояния конфигурации,
- Поддерживает концепцию нескольких версий конфигурации (datastores),
- Может поддерживать commit конфигурации. Обеспечивает транзакционность,
- И вообще красавчик.
running
, candidate
, saved
и, возможно, другие. Они позволяют не менять на лету работающую конфигурацию.replace
не поддерживается или поддерживается, но работает через delete/create - ух, неспасибо за это.Но своё место NETCONF уже прочно занял и будет дальше только расширять и углублять. Несколько вендоров действительно его поддерживают в полной мере. А на других точечные операции всё равно многократно удобнее через программный интерфейс со структурированными данными выполнять. Плюс своё давление оказывают крупные заказчики, требующие его поддержки.
RESTCONF¶
Буйные 10-е и забыли
The workshop recommends, with strong consensus from the operators and rough consensus from the protocol developers, that the IETF/IRTF should not spend resources on developing HTML-based or HTTP-based methods for configuration management.
Драфт был опубликован в 2014м, а в 2017 мир увидел RFC8040.
running
, candidate
, saved
) от пользователя скрыта в случае NETCONF.С другой стороны отсутствие в выдаче поисковиков хоть сколько-то серьёзных работ по автоматизации с помощью RESTCONF и даже популистских статей от больших игроков говорит о том, что это всё не более чем баловство. И я намеренно не пишу слово «пока». Лично я в него не верю.
При этом CRUD не очень гладко ложится на RPC-подход, да и в идее держать открытым на сетевом железе HTTP есть что-то противоестественное, согласитесь? Нет? Ну ладно.
Просто жаль сил, вложенных в этот протокол. Потому что на пятки ему наступает gRPC/gNMI.
Модели и языки¶
Однако вернёмся к NETCONF: в чём его фундаментальная проблема? Да в том, что он вышел в мир один одинёшенек. Не было предложено никаких схем, языка, стандартов для семантики. И всё пошло вразнос.
Модели были нужны, но языка для их описания не было. До 2010 (на самом деле больше) каждый вендор писал их кто во что горазд.
YANG, который (по-)меняет мир¶
Кстати, будьте аккуратнее, когда ищете «yang models» в интернетах, серьёзно вам говорю.
Виды моделей¶
Вендоры очень быстро сориентировались в ситуации на самом деле - и довольно скоро насоздавали YANG-модели для своих устройств.
Проприетарные, они же Native¶
<get-schema>
, но не все вендоры это поддерживают.Сделать по отдельности у каждого вендора Configuration State Management - одноразовая, решаемая (а много где и решённая) задача. А вот договориться между всеми производителями, как должна выглядеть универсальная модель - так же сложно, как и любая другая задача, где людям нужно договориться.
Но ни один из зарождавшихся и выживших стандартов или не ставил целью унификацию вообще, или пытался поднять этот вопрос, но был выброшен в окно штаб-квартиры вендора.
Хотя вру. IETF предприняли отчасти успешную попытку написать универсальную модель.
IETF-модели¶
- Слышь, а зачем вам эти полумеры? Давай из вашей модели сразу же в коробку перекладывать? Мы вам поможем агента написать- Да, но у нас циски, проприетарная ось.- Да это фигня. О, Джон, здоров. Давай парням линукс на свитчи вкорячим?- Так давай, изян. Через сколько месяцев надо?- Подождите, подождите, там типа чип, SDK, памяти маловато- Хей, Рони, алло! Нам нужен свитч, на который мы можем свою операционку поставить- Без базы, ща, в R&D запустим.
Ну как-то так я себе представляю рождение OpenConfig.
OpenConfig - мечта, становящаяся явью¶
Возможно, впервые за шестидесятилетнюю историю телекоммуникаций у нас появился шанс изобрести свой USB Type C. Представьте мир, в котором Cisco, Juniper, Nokia и Mikrotik настраиваются одними и теми же командами и это к тому же приводит к одинаковому результату?
Я не могу.
OpenConfig - это открытая YANG-модель, которая предполагается единой для всех вендоров. Одна стандартизированная модель для сбора операционных данных с устройств, управления конфигурацией и анализа телеметрии.
Итак, OpenConfig появился в Google, как они сами сказали на наноге в 2015, как ответ на следующие вызовы:
- 20+ ролей сетевых устройств
- Больше полудюжины вендоров
- Множество платформ
- 4M строк в конфигурационных файлах
- 30K изменений конфигураций в месяц
- Больше 8M OIDs опрашиваются каждые 5 минут
- Больше 20к CLI-команд выполняется каждые 5 минут
- Множество инструментов и поколений софта, куча скриптов
- Отсутствие абстракций и проприетарные CLI
- SNMP не был рассчитан на столь большое количество устройств и на столько большие объёмы данных (RIB)
Это всё настолько знакомые ежедневные трудности, что любой может приписать их себе, просто уменьшив цифры.
Вскоре после этого в том же 2015м был сделан первый коммит в публичную репу openconfig/public.
Никаким стандартом он не стал, в RFC не превратился, но вендоры его подхватили. Ещё бы они его не подхватили - очень быстро к гуглу подтянулись и другие гиганты - за OC теперь топят десятки компаний.
OpenConfig сегодня даёт возможность настройки стандартных сервисов, таких как интерфейсы, IP-адреса, NTP, OSPF и прочее. Безусловно, речь не идёт про вещи, завязанные на аппаратные особенности: QoS, управление буферами и ресурcами чипа, сплиты портов, работа с трансиверами. И в каком-то хоть сколько-то обозримом будущем этого ждать не стоит.
И что же делать, если брать 5 разных несвязанных Native-моделей не хочется, а OC-модель не покрывает всех необходимых функций?
Этот способ, конечно, не покрывает все потребности.
Другой - совмещать OC и Native. В целом рекомендуют (даже сами вендоры), использовать OC там, где это возможно, а где нет - прибегать к Native. Главное - не настраивать одно и то же с помощью разных моделей.
Источник: доклад на Cisco Live
Если вам всё ещё кажется, что так можно жить, то пришло время сказать, что разные вендоры, оборудование и даже версии ПО могут использовать разные версии OC-модели и быть не полностью совместимыми. Вам всё ещё придётся думать о том, что и куда вы деплоите.
gRPC/gNMI¶
Сверхлихие 20-е
За последние лет семь gRPC уже всем уши прожужжали. И только самые ловкие разработчики могли избежать реализации взаимодействия с какой-нибудь системой по gRPC.
«g» в gRPC, кстати, означает вовсе не «google».
gRPC¶
Вообще-то RPC вроде бы как начал давным давно уходить в тень, уступая место REST и ему подобным. Но в недрах гугла он цвёл, эффективно связывая между собой микросервисы, и назывался Stubby. Ровно до тех пор пока, в 2015 они не решили его переписать и заопенсорсить, чтобы нанести непоправимую пользу миру.
- Строгий IDL (Interface Definition Language), диктующий то, как именно описывать спецификации - protocol buffers или protobufs.
- Готовый формат данных и механизм их маршалинга и демаршалинга - тоже protocol buffers (protobufs).
- Библиотеки для разных языков программирования, которые на основе спецификации генерируют объекты языка (классы, методы итд) - разработчику остаётся только использовать их. Как для сервера, так и для клиента.
То есть. Поставил себе пакет grpc: перед тобой сразу язык спецификации, генераторы кода, интерфейсы, форматы данных, транспорт. Красота-тра-та-та!
Вообще для обывателей всё началось 24 сентября 2015, когда OpenConfig consortium выпустил OpenConfig в мир. Весь FANG (кроме Amazon) поучаствовал в этом консорциуме. Но начал всю заварушку и продолжает её паровозить гугл. Естественно, среди них и крупные телекомы, вроде Level3, AT&T, Verizon, Bell.
И пока OpenConfig прокладывал себе дорогу, раскидывая в сторону вендорские и IETF модели, гугл сделал следующий шаг - как раз таки реализовал gNMI.
Итак, в 2016-м мир увидел плод труда инженеров гугл - протокол gNMI, реализующий весь стек технологий для программного взаимодействия с железом.
И… Ничего не изменилось.
В 2018 они, видимо, поняли, что их не услышали и на IETF 101 снова пришли с рассказом про gNMI, и уже более явно сообщали, что он пришёл на замену этим вашим x-CONF’ам. Слышите вы, старпёры? Ало?! gNMI пришёл!
И тут завертелось! Сообщество сетевых автоматизаторов из вендоров, телекомов и просто одиноких пассионариев понесло благую весть в народ.
Однако свою дорогу в мир прокладывает. Медленно, но, похоже, что верно.
А ещё, что немаловажно, gNMI приводит с собой стриминг телеметрии. Впервые в истории хоть кто-то наконец подумал о том, что push-модель на сетевом устройстве может быть эффективнее pull, как делали системы мониторинга на основе CLI, SNMP и NETCONF. Можно подписаться на рассылку и хоть несколько раз в секунду получать метрики и даже анализировать утилизацию буфера на чипе. И для всех этих данных есть модели, позволяющие удобно с ними работать.
В этой статье я не копаю глубоко в каждый протокол и фреймворк, не разбираюсь, как они устроены, а даю только взгляд на историю развития автоматизации. За деталями приглашаю во шестую часть.
Настоящее сетевой автоматизации¶
Ну и пришло время подводить итоги?
В целом описать всё многообразие проявлений автоматизаторской фантазии сегодня просто невозможно. Мы сейчас в мире, в котором чёрный параллелепипед ещё не был признан стандартом в области форм-факторов смартфонов.
Видимое будущее¶
И это очень-очень-очень хорошо.
А это означает, что обслуживать его можно как обычный Linux-сервер.
Можно поднять nginx и совершенно любое REST-приложение за ним. Ну или gRPC-сервер. Поставить телеграф-агент - и сливать метрики в коллекторы.
А есть сервисы - BGP, VPN, VxLAN, который на физической машине запущен. И вот они могут управляться через REST, gRPC, ну или хотя бы путём подсовывания конфигурационных файлов.
Сюда же можно подтащить версионирование работающих приложений, в которое можно включить и конфигурацию, запуск двух версий side-by-side и где-то не за горизонтом даже полноценный Continious Integation маячит.
А все заморочки с ансиблом, питон-скриптами, притворяющимися человеком с руками и какой-никакой головой, поддержанием стейта конфигурационной портянки текста - они просто испаряются в предрассветном тумане.
И попробуйте после этого взглянуть на существующую модель работы с сетевым железом - какой кривой и инертной она выглядит.
И всё же этот мир прекрасен.
Шаг за горизонт¶
А что если я вам теперь скажу, что мы делаем всё неправильно?
BGP вот, кстати, стал козлом отпущения, сам того не желая (как будто это бывает иначе). В его NLRI насовали всего, что только могли - BGP всё стерпит. Отсюда же у нас и в некоторых случаях отсутствие обязательного атрибута NextHop.
Ещё сетям свойственно перегружаться и ронять пакеты на пол. С такими потерями имеют дело дюжина механизмов Congestion Avoidance и ещё столько же Congestion Management. Tail-drop, RED, WRED, RR, WRR, PQ, CBWFQ, ECN, CWR. Мы знаем, что такое HoLB, VoQ, Pause Frames, PFС, CAM, TCAM, SerDes, Traffic Manager - и как они все поступают с пакетами. И вообще мы эксперты по аббревиатурам - найдите, в какой профессии их больше - устроим батл!
И давайте не будем трогать мультикаст?
А сколько радости доставляет каждому сетевику и разработчику вендора интероп разных вендоров?
Расскажите сетевикам про проклятое легаси и обратную совместимость?
И какой-нибудь протокол, как Segment Routing v6, может тут сыграть свою достойную роль. В пределах своего домена используем контроллер и SRv6 для транспорта трафика, на конечных узлах с помощью P4 программируем обработку инкапсуляций на мощном и гибком ASIC’е, а Control Plane сгружаем на обычные сервера. И больше не нужны гигантские вендорские мангалы, не нужно платить за 80% функциональности, которой так никогда и не воспользуешься, этот проприетарный софт, и бесконечные баги.
А потом вы начинаете задавать себе и другим вопросы, вроде - а нужен ли Ethernet?
Полезные ссылки¶
- Репозиторий с опубликованными YANG-моделями
- Слайды NANOG 2015
- Слайды IETF 98
- Слайды IETF 101
- SDK Broadcom на github
- Концепция RPC: Remote Procedure Call (RPC)
- Сайт OpenConfig: openconfig.net
- Опубликованные модели OpenConfig
- Хорошая вводная в NETCONF и немного YANG: YANG Data Modelling and NETCONF
- Продолжение про YANG и немного про NETCONF: The Road to Model Driven Programmability
- На русском про NETCONF. Начало
- Если жить не можете, хочется на русском про YANG, и вы воспринимаете художественную речь: YANG — это имя для вождя
- Спецификация gNMI в его же репозитории
- RESTCONF Tutorial - Everything you need to know about RESTCONF in 2020
- Серия статей про RESTCONF, но рекомендую я её из-за того, что там хорошо разобраны примеры с YANG-моделями: RESTCONF, NETCONF and YANG
- Документация по gRPC
- Блог Романа Додина
- Блог Антона Карнелюка
- Блог Михаила Кашина
- Яндекс Nextop. Про использование CLI для Configuration Management
Заключение¶
И, надеюсь, этой статьёй мне удалось немного раскрыть длинную историю сетевой автоматизации, показать, что мы живём в эпоху Кембрийского взрыва инструментов и интерфейсов. И перед нами сейчас открыты разные пути, каждый из которых сулит как минимум интересное развитие событий.
Рекомендую к прочтению шестую часть АДСМ.
Благодарности¶
- Роману Додину за дельные комментарии как по теоретической, так и по практической частям. А так же за полезный блог и инструменты. GitHub.
- Кириллу Плетнёву за наведение порядка с NETCONF и YANG - язык, модели, спецификации, форматы данных. И за уместные и остроумные замечания по языкам и библиотекам. GitHub, fb.
- Александру Лимонову за несколько идеологических замечаний и исправлений фактических ошибок.
Часть 6. Интерфейсы взаимодействия с сетевым устройством¶
CLI - Command Line Interface¶
И ещё долго мы не останемся без дела - выпускают всё новые версии софта, ещё более другие модели железа, постоянно меняется CLI, и там, где вчера был string, завтра будет integer. И там, где вчера было no some shitty service enable
, завтра будет some shitty service disable
. И там, где вчера на вопрос интерфейса надо было ответить yes
, завтра вылезет ошибка.
- Модели конфигурации не формализованы
- Модель и поведение не зафиксированы
- CLI интерактивен
- Формат данных не структурированный
- Нет явного признака успешности операции
- Сложно вычислять разницу между целевой и текущей конфигурацией
- Сложно считать конфигурационный патч
- Транзакционность не всегда доступна
- Поддержание целевого состояния – задача инженера
9 грехов CLI¶
1. Модели конфигурации не формализованы¶
2. Модель и поведение не зафиксированы¶
Всё, что мы изучили на предыдущем этапе, может поменяться в новой версии - и мы сначала переобучаем себя, потом переписываем код.
3. CLI интерактивен¶
expect("Вы точно хотите выключить bgp-сессию, mpls на всей коробке [Y/n]?"] Yes!
expect("Вы точно хотите выключить электричество в серверной [Y/n]?"] No!
4. Формат данных не структурированный¶
Строго говоря, будь-то json или выводshow version
, в итоге это всё равно поток байтов и по сути текст. Только в одном случае в нём есть структура, а в другом - это просто набор символов.
5. Нет явного признака успешности операции¶
6. Сложно вычислять разницу между целевой и текущей конфигурацией¶
7. Сложно считать конфигурационный патч¶
8. Транзакционность не всегда доступна¶
В целом мы уже избалованы коммитами - многие вендоры его поддерживают. Но многие ещё нет. А те, кто поддерживает, может это делать тоже собственным уникальным способом, как например валидация ввода только при коммите, или коммит заключается в последовательном применении всех команд без вычисления дельты.
9. Поддержание целевого состояния – задача инженера¶
Но тут стоит быть чуть более честным - не всегда CLI настолько плох. Некоторые вендоры генерируют CLI-интерфейс из YANG-модели, что гарантирует чёткое соответствие между тем, что и как конфигурируется через CLI или любые другие интерфейсы.Например, в Nokia SR Linux интерфейс командной строки, а так же gNMI, JSON-RPC и внутренние приложения работают с единым API - mgmt_srv - поэтому не только формализованы из одной и той же YANG-модели, но и имеют одинаковые возможности по чтению/записи конфигурации.Дифы, коммиты, датасторы и прочее, тоже могут быть сделаны с умом - как у той же Nokia или у Juniper.Но это всё, конечно, не отменяет факта работы с неструктурированным текстом.
Этого всего, как мне кажется, достаточно для того, чтобы даже не приступать к написанию полноценной системы автоматизации, основанной на CLI.
- Представление данных в структурированном виде,
- Разделение конфигурационных и операционных данных,
- Читаемость для человека исходных данных и самой конфигурации,
- Воспроизводимость - задачу на исходных данных можно запустить повторно - проиграть,
- Механизм основан на формальных моделях,
- Транзакционность изменений и их откат,
- Поддержание целевого состояния.
Не все они появились сразу. Не все они появились. Но это понятная и приятная цель.
Концепция RPC - Russian Pravoslavnaya Church¶
Но только представьте, как было бы восхитительно, если бы для вызова этого кода, не нужно было заходить на железку по SSH и вбивать команду?!
Постойте! Да ведь именно об этом мы и говорим в данном разделе. | Большую оставшуюся часть статьи мы посвятим именно RPC.
Пример¶
- Наша убер-платформа автоматизации вызывает некую функцию
add_bgp_peer_stub(ip="10.1.1.1", as="12345"
). - Функция
add_bgp_peer_stub
открывает спецификацию для протокола, реализующего RPC, и согласно ей упаковывает полученные параметры, которые станут payload’ом для сообщения. Такая упаковка называется маршалинг. - Далее формирует пакет и передаёт его вниз по стеку и - в сеть.
- На другой стороне - на устройстве - приложение получает пакета.
- Функция, принявшая сообщение, вытаскивает из него параметры процедуры, согласно той же самой спецификации и формирует список параметров. Это называется демаршалинг.
- Приложение выполняет функцию - настраивает BGP-соседа 10.1.1.1 с AS 12345. Проверяет успешность выполнения.
- Далее функция формирует на основе всё той же спецификации сообщение-ответ и передаёт его в ответном пакете.
- Наша локальная сторона, с которой мы инициировали выполнение RPC, получает ответ, словно бы его вернула локальная функция.
- Воаля
Мы дальше разберём два протокола, которые используются под капотом RPC и при этом позволяют управлять сетевым железом.
- NETCONF
- gNMI (использующий gRPC)
NETCONF¶
Если вам по какой-то причине кажется, что стандарты рождаются где-то в недрах институтов, оторванных от жизни, то вот вам контр-пример.
| display xml
, то увидите ответ в формате XMLeucariot@kzn-spine-0> show system uptime | display xml <rpc-reply xmlns:junos="http://xml.juniper.net/junos/18.3R3/junos"> <multi-routing-engine-results> <multi-routing-engine-item> <re-name>localre</re-name> <system-uptime-information xmlns="http://xml.juniper.net/junos/18.3R3/junos"> <current-time> <date-time junos:seconds="1641211199">2022-01-03 14:59:59 MSK</date-time> </current-time> <time-source> LOCAL CLOCK </time-source> <system-booted-time> <date-time junos:seconds="1614866046">2021-03-04 16:54:06 MSK</date-time> <time-length junos:seconds="26345153">43w3d 22:05</time-length> </system-booted-time> <protocols-started-time> <date-time junos:seconds="1614866101">2021-03-04 16:55:01 MSK</date-time> <time-length junos:seconds="26345098">43w3d 22:04</time-length> </protocols-started-time> <last-configured-time> <date-time junos:seconds="1638893962">2021-12-07 19:19:22 MSK</date-time> <time-length junos:seconds="2317237">3w5d 19:40</time-length> <user>scamp</user> </last-configured-time> <uptime-information> <date-time junos:seconds="1641211200">3:00PM</date-time> <up-time junos:seconds="26345160">304 days, 22:06</up-time> <active-user-count junos:format="1 users">1</active-user-count> <load-average-1>0.20</load-average-1> <load-average-5>0.17</load-average-5> <load-average-15>0.20</load-average-15> <user-table></user-table> </uptime-information> </system-uptime-information> </multi-routing-engine-item> </multi-routing-engine-results> <cli> <banner>{master:0}</banner> </cli> </rpc-reply>
В корне вы можете видеть <rpc-reply>
, что означает, что был какой-то <rpc>
-request. И вот так вы можете увидеть, каким RPC-запросом можно получить такие данные:
eucariot@kzn-spine-0> show version | display xml rpc <rpc-reply xmlns:junos="http://xml.juniper.net/junos/18.3R3/junos"> <rpc> <get-software-information> </get-software-information> </rpc> <cli> <banner>{master:0}</banner> </cli> </rpc-reply> *Внимание, работает только для Juniper!*
Так вот, их CLI и способ взаимодействия его с системой оказался настолько естественным и удачным, что его и положили в основу стандарта. Не без участия Juniper Networks, конечно же, появился RFC4741. Будем честны, один только джунипер там и постарался. И то тут, то там будут проскакивать его куски, начиная с commit confirmed
и заканчивая candidate config
.
Вот как NETCONF был определён в 2006-м году:
Abstract The Network Configuration Protocol (NETCONF) defined in this document provides mechanisms to install, manipulate, and delete the configuration of network devices. It uses an Extensible Markup Language (XML)-based data encoding for the configuration data as well as the protocol messages. The NETCONF protocol operations are realized on top of a simple Remote Procedure Call (RPC) layer.
И определение с тех пор не менялось - вся суть NETCONF в этом параграфе.
А теперь давайте разбираться с очень непростым NETCONF и его составными частями.
NETCONF и его команды¶
Если совсем коротко, NETCONF - это четырёхуровневый стек, согласно которому через SSH передаётся RPC, где указана операция и конкретный набор действий (контент).
Стек NETCONF¶
Итак, в качестве транспорта NETCONF использует SSH. На самом деле, там есть и другие протоколы: SSH, SOAP, BEEP, TLS - но мы их опустим - SSH стал де-факто стандартом.
Каждый NETCONF запрос содержит элемент (или сообщение):
<rpc>
- это собственно запрос на вызов процедуры с необходимыми параметрами.<rpc-reply>
- ответ на RPC.<rpc-error>
- очевидно, ответная ошибка, когда RPC некорректен.<ok>
- rpc корректен и отработал.
<notification>
- сообщение о событии, инициированное сетевой коробкой - аналог трапа в snmp. (из RFC6241)
Это всё сообщения, внутри которых определённым образом сформированные XML.
<get>
- retrieve running configuration and device state information<get-config>
- retrieve all or part of a specified configuration datastore<edit-config>
- edit a configuration datastore by creating, deleting, merging or replacing content<copy-config>
- copy an entire configuration datastore to another configuration datastore<delete-config>
- delete a configuration datastore<lock>
- lock an entire configuration datastore of a device<unlock>
- release a configuration datastore lock previously obtained with the <lock> operation<close-session>
- request graceful termination of a netconf session<kill-session>
- force the termination of a netconf session
Каждый вендор может расширять список операций хоть до бесконечности. Так, у кого-то, например, есть <copy-config>
.
Установка сессии и Capabilities¶
Так, сначала включаем SSH NETCONF. На примере джунипер.
set system services netconfЭто значит, что SSH будет использоваться как транспорт для указанной подсистемы.Для netconf IANA установила специальный порт 830, хотя часто используется и обычный для SSH 22.
ssh kazan-spine-0.juniper -s netconf <!-- No zombies were killed during the creation of this user interface --> <!-- user eucariot, class j-super-user --> <hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <capabilities> <capability>urn:ietf:params:netconf:base:1.0</capability> <capability>urn:ietf:params:netconf:capability:candidate:1.0</capability> <capability>urn:ietf:params:netconf:capability:confirmed-commit:1.0</capability> <capability>urn:ietf:params:netconf:capability:validate:1.0</capability> <capability>urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file</capability> <capability>urn:ietf:params:xml:ns:netconf:base:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:candidate:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:validate:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:url:1.0?protocol=http,ftp,file</capability> <capability>http://xml.juniper.net/netconf/junos/1.0</capability> <capability>http://xml.juniper.net/dmi/system/1.0</capability> </capabilities> <session-id>15420</session-id> </hello> ]]>]]>
При этом базовые могут расширяться другими стандартизированными capability и даже проприетарными. Давайте рассмотрим сначала стандартные, а потом самые интересные расширенные. Ну и будем называть их «способностями», а то капабилитя - это почти как капибара.
NETCONF Standard Capabilities (стандартные способности)¶
- Candidate configuration
- Эта способность говорит о том. Что коробка поддерживает отдельный кандидат-конфиг, содержащий полную конфигурацию, с которой можно работать без влияния на фактически применённую конфигурацию. Аналоги candidate-config на Juniper.
- Confirmed commit
- Опять же аналог джуниперовоского commit confirmed - откат изменений после коммита, если не было подтверждения коммита.
- Validate
- Способность проверить желаемую конфигурацию до её применения.
- Rollback-on-error
- Способность отмены изменений при ошибке. Работает, если поддерживается способность candidate configuration.
- Writable-running
- Такая способность говорит о том, что устройство позволяет писать непосредственно в running-конфигурацию, в обхода candidate.
- Distinct startup
- Способность задавать startup конфигурацию отличную от running и candidate.
- Notification
- Аналог SNMP-trap. Коробка может слать аварии и события клиенту.
NETCONF Extended Capabilities (сверх-способности)¶
Их тьма. Из самых интересных:
- YANG push
- Способность отсылать данные с коробки на клиент - периодически или по событию.
- YANG-library
- Способность сервера сообщить клиенту о поддерживаемых параметрах относительно YANG: версия, модель, нейспейсы итд.
- Commit-description
- Самоговорящее название.
urn:ietf:params:netconf:capability:{name}:1.0
.urn:ietf:params:netconf:base:1.1
- это имя базовой капабилити для версии 1.1.В ответ на <hello>
сервера клиент в свою очередь должен послать свои capability:
<hello> <capabilities> <capability>urn:ietf:params:xml:ns:netconf:base:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:candidate:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:validate:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:url:1.0?protocol=http,ftp,file</capability> <capability>xml.juniper.net/netconf/junos/1.0</capability> <capability>xml.juniper.net/dmi/system/1.0</capability> </capabilities> </hello> ]]>]]>
Чего почти нигде не пишут, но что очень важно: если вы пробуете взаимодействовать с коробкой по нетконф руками, то нужно обязательно вручную отослать такую последовательность ]]>]]>
, сообщающую, что ввод закончен. Она называется Framing Marker или Message Separator Sequence.
Есть важный нюанс, описанный в RFC6242,]]>]]>
- это старый End-of-Message Framing Marker, который был выбран из соображений, что такая последовательность не должна встречаться в well-formed XML. Однако жизнь показала, что она встречается. Поэтому в NETCONF 1.1 придумали новый механизм, который делит данные на блоки - чанки - и нумерует их. Так он и называется: Chunked Framing Mechanism.Каждый чанк данных начинается с##X
, гдеX
- это число октетов в нём.Это одно из фундаментальных отличий между 1.0 и 1.1 :). Другие менее значительны.
Сейчас NETCONF-сессия установлена и можно заслать какой-то RPC.
Посылаем свой первый RPC
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <running/> </source> <filter type="subtree"> <configuration> <system> <host-name/> </system> </configuration> </filter> </get-config> </rpc> ]]>]]><rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1644510087" junos:commit-localtime="2022-02-10 16:21:27 UTC" junos:commit-user="eucariot"> <system> <host-name>kzn-spine-0</host-name> </system> </configuration> </data> </rpc-reply>
Мы отправили элемент <rpc>
, в котором запросили <running>
-конфигурацию с помощью операцию <get-config>
. И ещё на сервере отфильтровали по интересной ветке.
А в ответ пришёл <rpc-reply>
с ответом. И в запросе, и в ответе можете найти message-id
- по ним можно отслеживать на что именно ответ - ведь режим работы NETCONF асинхронный и можно засылать следующее сообщение, пока предыдущее ещё не было обработано.
Так за что же так с нами?
<XML>¶
В общем выбор в те дни был предопределён - XML был сверхсовременным и суперудобным,
Сложность читаемости XML компенсируется простотой его программной обработки. Чёткая иерархическая структура, понятные начало, конец и значение. В том же питоне xmltodict изящно любой валидный XML разворачивает в словарь. А вообще вот годная статья про то, как предполагается работать с XML средствами стандартной библиотеки.
Давайте сначала на отвлечённом примере поразбираемся?
<?xml version="1.0" encoding="UTF-8"?> <bookstore> <book> <title>Everyday Italian</title> <author>Giada De Laurentiis</author> <year>2005</year> <instock> </instock> </book> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <instock /> </book> </bookstore>
Тут у нас XML, описывающий книжный магазин и имеющиеся в нём книги. У каждой книги есть свой набор атрибутов - название, автор, год выпуска, наличие в магазине.
Всё начинается с
XML Prolog¶
<?xml version="1.0" encoding="UTF-8"?>
Дерево элементов¶
root
, все последующие - его дети.<bookstore>
. Элемент представляет из себя открывающий и закрывающий теги и содержимое.<bookstore>
и <Bookstore>
- это разные теги.siblings
).<book>
. Разные элементы <book>
друг для друга являются собратьями.<book>
есть дочерние элементы. Их состав совсем не обязательно должен быть одинаковым - XML этого не требует, однако этого может (и скорее всего будет) требовать приложение.Главное правило XML - каждый открывшийся тег должен быть закрыт: сказал <a>
- говори и </a>
. Элемент может быть пустым, просто выражая факт своего существования, тогда запись <instock></instock>
можно заменить на просто <instock/>
.
Атрибуты¶
Взглянем на другой пример:
<bookstore> <book category="cooking"> <title lang="en">Everyday Italian</title> <author>Giada De Laurentiis</author> <year>2005</year> </book> <book category="children"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> </book> </bookstore>
category="cooking"
. Она описывает дополнительные данные об элементе. Своего рода метаданные.<book category="cooking"> <title lang="en">Everyday Italian</title> <author>Giada De Laurentiis</author> <year>2005</year> </book>
и
<book> <category>cooking</category> <title>Everyday Italian <lang>en</lang> </title> <author>Giada De Laurentiis</author> <year>2005</year> </book>
Чтобы далеко не уходить, вот пример из netconf:
<rpc message-id=”101″> <get-config> <source> <running/> </source> </get-config> </rpc>
message-id
- это атрибут элемента RPC, который не имеет непосредственного отношения к передаваемым далее данным, но позволяет отследить по message-id
ответ сервера (он вставит его в <rpc-reply>
).<interfaces operation="replace">
. Атрибут operation="replace"
не является частью конфигурации интерфейса, он лишь говорит, что то, что существует сейчас на коробке в ветке <interfaces>
, нужно заменить на то, что описано в данном XML.Namespaces¶
<name>
может быть как у интерфейса, так и у пользователя и у влана итд. Их можно разнести в разные NS, хотя это не обязательно, потому что они находятся под разными родителями.<root> <address> <city> <name>Moscow</name> <street>Novocheremushkinskaya, 50</street> </city> </address> <address> <ipv6>2a01:ba80:e:20::32</ipv6> <ipv4>185.127.149.137</ipv4> </address> </root>
Прямо объявляем неймспейсы с префиксами:
<root> <postal:address xmlns:postal="https://www.linkmeup.ru/postal_address/"> <postal:city> <postal:name>Moscow</postal:name> <postal:street>Novocheremushkinskaya, 50</postal:street> </postal:city> </postal:address> <ip:address xmlns:ip="https://www.linkmeup.ru/ip/"> <ip:ipv6>2a01:ba80:e:20::32</ip:ipv6> <ip:ipv4>185.127.149.137</ip:ipv4> </ip:address> </root>
Теперь это полное, fully qualified, имя безо всяких ограничений. Обращаемся из приложений, соответственно, по полному имени.
postal
иip
- это короткие префиксы. Само имя namespace - это произвольная строка. Но негласная договорённость, что все используют URI. Он может вести на страницу с описанием этого неймспейса, а может и не вести. Но указание префикса в каждом теге может показаться не очень удобным, тогда есть второй способ.Определяем
default namespace
<root> <address xmlns="https://www.linkmeup.ru/postal_address/"> <city> <name>Moscow</name> <street>Novocheremushkinskaya, 50</street> </city> </address> <address xmlns="https://www.linkmeup.ru/ip/"> <ipv6>2a01:ba80:e:20::32</ipv6> <ipv4>185.127.149.137</ipv4> </address> </root>
Область действия дефолтного неймспейса - сам элемент и все его потомки, если он нигде не переопределяется.
Концепция namespace с одной стороны проста, с другой стороны и там есть место тёмным пятнам. Если хочется подетальнее изучить, то есть пара полезных FAQ про них.
Xpath - XML Path¶
XPath
- это способ выбрать ноды или множество нод из XML документа./
».Например, в XML из примера выше путь к элементу <title>
будет записан в виде /bookstore/book/title
sum
, count
, avg
, min
, starts-with
, contains
, concat
, true
, false
- над разными типами данных: числа, строки, булевы./bookstore/book[price>35]/title
XPath оперирует нодами, которыми являются элементы, атрибуты, текст, неймспейсы и другое.
- Вернуть BGP-группу, в которой есть peer 10.1.1.1
- Вернуть интерфейс, на котором число ошибок больше 100
- Вернуть список интерфейсов, на которых native-vlan 127
- Вернуть количество интерфейсов, в имени которых есть «Ethernet».
В контексте NETCONF вы можете его встретить, но это не самая популярная capability. В общем, знать про него полезно, но глубоко копать не будем. Если хочется поподробнее почитать, то это можно сделать например, тут.
Схема¶
<interface>
, а читать его пытаются из элемента <unit>
?YYYY-MM-DD
, а читать её пытаются в MM-DD-YYYY
(больные ублюдки).- двум сторонам использовать один и тот же способ хранения и распаковки данных.
- описывать содержимое документа
- определять ограничения на данные
- проверять корректность XML
Называется это хозяйство XML Schema Definition - или коротко XSD.
Поскольку это тот же самый XML, он должен как-то обозначать себя, что является схемой. Для этого есть ключевой элемент <schema>
. Вот так будет выглядеть XSD для кусочка XML выше:
<xs:schemaxmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:elementname="address"> <xs:complexType> <xs:sequence> <xs:elementname="country_name" type="xs:string"/> <xs:elementname="population" type="xs:decimal"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
При этом в самом XML можно дать ссылку на XSD
<note xmlns="https://www.linkmeup.ru" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.linkmeup.ru/404.xsd">
Самостоятельное продолжение изучения XSD.
Лучшая сторона XSD - это то, что на его основе можно автоматически генерировать объекты в языках программирования. То есть XSD описывает, какие именно объекты и структуры должны быть созданы, а конкретный XML - наполняет экземпляр, пользоваться которым значительно удобнее, чем крафтить XML. Со схемами и моделями мы будем разбираться дальше.
Надеюсь получилось, не утопая в деталях, дать понимание, что из себя представляет XML. Далее для нас это будет важным.
</XML>
NETCONF Again¶
<get>
<get-config>
<edit-config>
<copy-config>
<delete-config>
<lock>
<unlock>
<close-session>
<kill-session>
Но зачастую вендоры определяют свои собственные операции.
Действия, операции¶
<get>¶
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get/> </rpc> ]]>]]>
<rpc-reply>
. В случае ошибки внутри <rpc-reply>
сервер вернёт <rpc-error>
с текстом ошибки.</get>
:<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <rpc-error> <error-type>protocol</error-type> <error-tag>operation-failed</error-tag> <error-severity>error</error-severity> <error-message>syntax error, expecting <filter> or </get></error-message> <error-info> <bad-element>interfaces</bad-element> </error-info> </rpc-error> </rpc-reply>
Или запросить несуществующую ветку:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get> <interfaces/> </get> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <rpc-error> <error-type>protocol</error-type> <error-tag>operation-failed</error-tag> <error-severity>error</error-severity> <error-message>syntax error, expecting <filter> or </get></error-message> <error-info> <bad-element>interfaces</bad-element> </error-info> </rpc-error> </rpc-reply>
<get>
будет содержаться либо вообще всё, что вам может дать устройство - полный конфиг и вся информацию по состоянию, либо какую-то часть.<get-interface-information>
:<rpc> <get-interface-information/> </rpc>
Вот такой будет ответ: https://pastebin.com/2xTpuSi3.
Этому, кстати, сложно найти объяснение. Довольно неудобно для каждой ветки операционных данных иметь собственный RPC. И более того, непонятно как это вообще описывается в моделях данных.
Очевидно, это не всегда (никогда) удобно. Хотелось бы пофильтровать данные. NETCONF позволяет не просто отфильтровать результат, а указать NETCONF-серверу, какую именно часть клиент желает запросить. Для этого используется элемент <filter>
.
<filter>¶
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get/> </rpc> ]]>]]>
Вот такой будет ответ: https://pastebin.com/MMWXM2eT.
С пустым фильтром не вернётся никаких данных.
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get> <filter type="subtree"> </filter> </get> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <database-status-information> <database-status> <user>eucariot</user> <terminal></terminal> <pid>31101</pid> <start-time junos:seconds="1644636396">2022-02-12 03:26:36 UTC</start-time> <edit-path></edit-path> </database-status> </database-status-information> </data> </rpc-reply> ]]>]]>
Вот таким запросом можно вытащить конфигурационные данные по всем интерфейсам
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get> <filter type="subtree"> <configuration> <interfaces/> </configuration> </filter> </get> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:changed-seconds="1644510087" junos:changed-localtime="2022-02-10 16:21:27 UTC"> <interfaces> <interface> <name>ge-0/0/0</name> <unit> <name>0</name> <family> <inet> <address> <name>169.254.0.1/31</name> </address> </inet> </family> </unit> </interface> <interface> <name>ge-0/0/2</name> <unit> <name>0</name> <family> <inet> <address> <name>169.254.100.1/31</name> </address> </inet> </family> </unit> </interface> <interface> <name>em0</name> <unit> <name>0</name> <family> <inet> <address> <name>192.168.1.2/24</name> </address> </inet> </family> </unit> </interface> </interfaces> </configuration> <database-status-information> <database-status> <user>eucariot</user> <terminal></terminal> <pid>31101</pid> <start-time junos:seconds="1644636721">2022-02-12 03:32:01 UTC</start-time> <edit-path></edit-path> </database-status> </database-status-information> </data> </rpc-reply> ]]>]]>
Если вы хотите выбрать не все элементы дерева, а только интересующую вас часть, то можно указать, какие именно нужны:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get> <filter type="subtree"> <configuration> <interfaces> <interface> <name/> <description/> </interface> </interfaces> </configuration> </filter> </get> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:changed-seconds="1644637011" junos:changed-localtime="2022-02-12 03:36:51 UTC"> <interfaces> <interface> <name>ge-0/0/0</name> <description>kzn-leaf-0</description> </interface> <interface> <name>ge-0/0/2</name> <description>kzn-edge-0</description> </interface> <interface> <name>em0</name> <description>mgmt-switch</description> </interface> </interfaces> </configuration> <database-status-information> <database-status> <user>eucariot</user> <terminal></terminal> <pid>31316</pid> <start-time junos:seconds="1644637103">2022-02-12 03:38:23 UTC</start-time> <edit-path></edit-path> </database-status> </database-status-information> </data> </rpc-reply> ]]>]]>
При этом если хочется забрать данные только по конкретному интерфейсу:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get> <filter type="subtree"> <configuration> <interfaces> <interface> <name>ge-0/0/0</name> </interface> </interfaces> </configuration> </filter> </get> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:changed-seconds="1644637011" junos:changed-localtime="2022-02-12 03:36:51 UTC"> <interfaces> <interface> <name>ge-0/0/0</name> <description>kzn-leaf-0</description> <unit> <name>0</name> <family> <inet> <address> <name>169.254.0.1/31</name> </address> </inet> </family> </unit> </interface> </interfaces> </configuration> <database-status-information> <database-status> <user>eucariot</user> <terminal></terminal> <pid>31316</pid> <start-time junos:seconds="1644637321">2022-02-12 03:42:01 UTC</start-time> <edit-path></edit-path> </database-status> </database-status-information> </data> </rpc-reply> ]]>]]>
Соответственно можно совместить запрос конкретного интерфейса и только тех его полей, которые интересны.
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get> <filter type="subtree"> <configuration> <interfaces> <interface> <name>ge-0/0/0</name> <description/> </interface> </interfaces> </configuration> </filter> </get> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:changed-seconds="1644637011" junos:changed-localtime="2022-02-12 03:36:51 UTC"> <interfaces> <interface> <name>ge-0/0/0</name> <description>kzn-leaf-0</description> </interface> </interfaces> </configuration> <database-status-information> <database-status> <user>eucariot</user> <terminal></terminal> <pid>31316</pid> <start-time junos:seconds="1644637396">2022-02-12 03:43:16 UTC</start-time> <edit-path></edit-path> </database-status> </database-status-information> </data> </rpc-reply> ]]>]]>
Ещё немного про subtree filtering.
<get>
ничем практически не отличается от <get-config>
. Для того, чтобы забрать операционные данные, нужно воспользоваться другими операциями - специфическими под каждую задачу.show version | display xml rpc
<get>
удобно забирать операционные данные с устройства. Например, для мониторинга. Или для отладки. Можно выбрать всех BGP-соседей в состоянии Idle, или все интерфейсы с ошибками, данные по маршрутам.<get-config>¶
<get-config>
- это поддерево <get>
, но это всё-таки не так.С помощью <get-config>
можно указать из какого источника мы хотим получить конфигу - running
, candidate
, startup
итд.
Забираем текущий конфиг:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <running/> </source> </get-config> </rpc> ]]>]]>
<get-config>
так же, как и <get>
позволяет использовать элемент <filter>
. Например:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <running/> </source> <filter type="subtree"> <configuration> <system> <host-name/> </system> </configuration> </filter> </get-config> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1644637011" junos:commit-localtime="2022-02-12 03:36:51 UTC" junos:commit-user="eucariot"> <system> <host-name>kzn-spine-0</host-name> </system> </configuration> </data> </rpc-reply> ]]>]]>
В запросе самые внимательные обратили внимание на элемент <source>
.
Configuration Datastores¶
<running>
- это текущая актуальная конфигурация.<candidate>
, <startup>
и какие-то другие.Соответственно запросить конфигурацию можно из разных Datastores при их наличии, указывая соответствующий элемент внутри <source>
.
<target>
.<running>
, а кто-то только <candidate>
с последующим <commit>
.<edit-config>¶
ЕЙ богу, самая интересная штука во всём NETCONF! Операция, с помощью которой можно привести конфигурацию к нужному состоянию. Серебряная пуля, панацея, окончательное решение конфигурационного вопроса. Ага, щаз! Идея в теории прекрасна: мы отправляем на устройство желаемую конфигурацию в виде XML, а оно само шуршит и считает, что нужно применить, а что удалить. Давайте идеальный случай и разберём сначала.
<edit-config>
позволяет загрузить полную конфигурацию устройства или его часть в указанный datastore. При этом устройство сравнивает актуальную конфигурацию в datastore и передаваемую с клиента и предпринимает указанные действия.operation
в любом из элементов поддерева <configuration>
. Operation может встречаться несколько раз в XML и быть при этом разным. Атрибут может принимать следующие значения:- Merge - новая конфига вливается в старую - что необходимо заменить - заменяется, новое - добавляется, ничего не удаляется.
- Replace - заменяет старую конфигурацию новой.
- Create - создаёт блок конфигурации. Однако, если он уже существует, вернётся
<rpc-error>
- Delete - удаляет блок конфигурации. Однако, если его не существует, вернётся
<rpc-error>
- Remove - удаляет блок конфигурации. Однако, если его не существует, проигнорирует. Определён в RFC6241.
Если тип операции не задан, то новая конфигурация будет вмёржена в старую. Задать операцию по умолчанию можно с помощью параметра <default-operation>
: merge
, replace
, none
.
В дереве <configuration>
задаётся собственно целевая конфигурация в виде XML.
Безусловно, самая интересная операция внутри <edit-config>
- это replace. Ведь она предполагает, что устройство возьмёт конфигурацию из RPC и заменит ею ту, что находится в datastore. А где-то там под капотом и крышкой блока цилиндров система сама просчитает дельту, которую нужно отправить на чипы.
Практика edit-config¶
Давайте сначала что-то простое: поменяет hostname:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <edit-config> <target> <candidate/> </target> <config> <configuration> <system> <host-name>just-for-lulz</host-name> </system> </configuration> </config> </edit-config> </rpc> ]]>]]>
Проверяем, что в кандидат-конфиге эти изменения есть, а в текущем - нет
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <candidate/> </source> <filter type="subtree"> <configuration> <system> <host-name/> </system> </configuration> </filter> </get-config> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:changed-seconds="1644719855" junos:changed-localtime="2022-02-13 02:37:35 UTC"> <system> <host-name>just-for-lulz</host-name> </system> </configuration> </data> </rpc-reply> ]]>]]>
Проверяем running:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <running/> </source> <filter type="subtree"> <configuration> <system> <host-name/> </system> </configuration> </filter> </get-config> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1644637011" junos:commit-localtime="2022-02-12 03:36:51 UTC" junos:commit-user="eucariot"> <system> <host-name>kzn-spine-0</host-name> </system> </configuration> </data> </rpc-reply>
Значит, надо закоммитить изменения.
<rpc> <commit/> </rpc> ]]>]]> <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos"> <ok/> </rpc-reply>
Проверяем running:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <running/> </source> <filter type="subtree"> <configuration> <system> <host-name/> </system> </configuration> </filter> </get-config> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1644720065" junos:commit-localtime="2022-02-13 02:41:05 UTC" junos:commit-user="eucariot"> <system> <host-name>just-for-lulz</host-name> </system> </configuration> </data> </rpc-reply>
На Juniper доступны в NETCONF те же функции коммитов, что и в CLI. Например, commit confirmed
и confirmed-timeout
.
А теперь что-то посложнее и с операцией replace
: заменим список BGP-пиров:
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <edit-config> <target> <candidate/> </target> <config> <configuration> <protocols> <bgp operation="replace"> <group> <name>LEAFS</name> <type>external</type> <import>ALLOW</import> <family> <inet> <unicast> </unicast> </inet> </family> <export>EXPORT</export> <neighbor> <name>169.254.0.0</name> <peer-as>64513.00000</peer-as> </neighbor> </group> <group> <name>EDGES</name> <type>external</type> <import>ALLOW</import> <family> <inet> <unicast> </unicast> </inet> </family> <export>EXPORT</export> <neighbor> <name>222.222.222.0</name> <peer-as>65535</peer-as> </neighbor> </group> </bgp> </protocols> </configuration> </config> </edit-config> </rpc> ]]>]]>
Коммит
<rpc> <commit/> </rpc> ]]>]]>
Проверяем running
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <running/> </source> <filter type="subtree"> <configuration> <protocols> <bgp> <group> <neighbor/> </group> </bgp> </protocols> </configuration> </filter> </get-config> </rpc> ]]>]]> <rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params: xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1644720678" junos:commit-localtime="2022-02-13 02:51:18 UTC" junos:commit-user="eucariot"> <protocols> <bgp> <group> <name>LEAFS</name> <neighbor> <name>169.254.0.0</name> <peer-as>64513.00000</peer-as> </neighbor> </group> <group> <name>EDGES</name> <neighbor> <name>222.222.222.0</name> <peer-as>65535</peer-as> </neighbor> </group> </bgp> </protocols> </configuration> </data> </rpc-reply>
Всё сработало)
А теперь попробуем операцию merge при добавлении нового пира.
<rpc message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <edit-config> <target> <candidate/> </target> <config> <configuration> <protocols> <bgp operation="merge"> <group> <name>LEAFS</name> <type>external</type> <import>ALLOW</import> <family> <inet> <unicast> </unicast> </inet> </family> <export>EXPORT</export> <neighbor> <name>169.254.0.0</name> <peer-as>64513.00000</peer-as> </neighbor> </group> <group> <name>EDGES</name> <type>external</type> <import>ALLOW</import> <family> <inet> <unicast> </unicast> </inet> </family> <export>EXPORT</export> <neighbor> <name>222.222.222.0</name> <peer-as>65535</peer-as> </neighbor> <neighbor> <name>169.254.100.0</name> <peer-as>65535</peer-as> </neighbor> </group> </bgp> </protocols> </configuration> </config> </edit-config> </rpc> ]]>]]>
Коммит
<rpc> <commit/> </rpc> ]]>]]>
Проверка:
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/14.1R1/junos" message-id="100" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1644721481" junos:commit-localtime="2022-02-13 03:04:41 UTC" junos:commit-user="eucariot"> <protocols> <bgp> <group> <name>LEAFS</name> <neighbor> <name>169.254.0.0</name> <peer-as>64513.00000</peer-as> </neighbor> </group> <group> <name>EDGES</name> <neighbor> <name>222.222.222.0</name> <peer-as>65535</peer-as> </neighbor> <neighbor> <name>169.254.100.0</name> <peer-as>65535</peer-as> </neighbor> </group> </bgp> </protocols> </configuration> </data> </rpc-reply> ]]>]]>
replace
и merge
.Operation replace¶
replace
следует иметь в виду некоторые нюансы. Например, что нужно передавать полную конфигурацию того или иного сервиса или функциональности - не просто новые параметры - ведь железка натурально заменит то, что было, тем, что прилетело. Едва ли вы хотите создав один интерфейс в OSPF Area, удалить остальные?Использовать replace можно как на уровне отдельных частей конфигурации, так и на верхнем уровне, требуя заменить всё поддерево.
Однако ещё один нюанс заключается в том, что в зависимости от реализации вычисление дельты может занять много ресурсов CPU. Поэтому, если собираетесь кинуть диф на 13 000 строк политик BGP, то дважды подумайте и трижды оттестируйте, что после этого происходит с коробкой.
<commit>¶
<commit>
не замещает running на candidate, как это делает <copy-config>
, а выполняет именно применение конфигурационной дельты, как это происходит в CLI.Как и в CLI у commit
может быть параметр confirmed
, заставляющий откатить изменения, если commit не был подтверждён. За это отвечает отдельная capability: confirmed-commit
.
<commit>
не входит в число базовых операций, поскольку как раз зависит от поддерживаемых возможностей сервера.
<copy-config>¶
Операция заменяет одну конфигурацию другой. Имеет два параметра: source
- откуда - и target
- куда.
Может использоваться как для применения новой конфигурации на коробку, так и для бэкапа активной.
Если коробка поддерживает capability :url
, то в качестве source
и/или target
может быть указан URL.
<delete-config>¶
Очевидно, удаляет конфигурацию из target datastore. Без хитростей.
<lock/unlock>¶
Аналогично Juniper CLI ставит блок на target datastore от совместного редактирования, чтобы не было конфликта. Причём блок должен работать как на NETCONF, так и на другие способы изменения конфигурации - SNMP, CLI, gRPC итд.
<close-session>¶
Аккуратно закрывает существующую NETCONF-сессию, снимает локи, высвобождает ресурсы.
<kill-session>¶
Грубо разрывает сессиию, но снимает локи. Если сервер получил такую операцию в тот момент, когда он дожидается confirmed commit, он должен отменить его и откатить изменения к состоянию, как было до установки сессии.
Инструменты разработчика для NETCONF¶
И я думаю, к этому моменту вам уже очевидно, что отправка XML через SSH с ручным проставлением Framing Marker (]]>]]>
) - не самый удобный способ. Давайте посмотрим на существующие библиотеки.
netconf-console¶
Прежде чем писать какой-то код, обычно стоит проверить всё руками. Но вот руками крафтить XML и проставлять framing marker’ы тоскливо. Тут отца русской автоматизации спасёт netconf-console
- главный и, возможно, единственный CLI-инструмент для работы с NETCONF.
Может работать в режиме команды:
netconf-console --host 192.168.1.2 --port 22 -u eucariot -p password --get-config
А может в интерактивном:
netconf-console2 --host 192.168.1.2 --port 22 -u eucariot -p password -i netconf> hello
NCclient¶
from ncclient import manager if __name__ == "__main__": with manager.connect( host="kzn-spine-0.juniper", ssh_config=True, hostkey_verify=False, device_params={'name': 'junos'} ) as m: c = m.get_config(source='running').data_xml print(c)Дабы уберечь читателя от многочасовых мук с отладкой аунтентификации, небольшая подсказка тут. Текущая версия
paramiko
на момент написания статьи (>=2.9.0), которую подтягиваетncclient
, в ряде случае не может работать с OpenSSH-ключами и падает с ошибкой «Authentication failed». Рекомендую в этом случае устанавливать 2.8.0. На гитхабе открыта куча issue на эту тему. И, кажется, его даже починили, но я не проверял. И вроде бы даже есть решение, но и это я не проверял.
Так же работают filter:
from ncclient import manager rpc = """ <filter> <configuration> <system> <host-name/> </system> </configuration> </filter> """ if __name__ == "__main__": with manager.connect( host="kzn-spine-0.juniper", ssh_config=True, hostkey_verify=False, device_params={"name": "junos"} ) as m: c = m.get_config("running", rpc).data_xml print(c)
С таким вот результатом:
<?xml version="1.0" encoding="UTF-8"?> <rpc-reply message-id="urn:uuid:864dd143-7a86-40ca-8992-5a35f2322ea0"> <data> <configuration commit-seconds="1644732354" commit-localtime="2022-02-13 06:05:54 UTC" commit-user="eucariot"> <system> <host-name> kzn-spine-0 </host-name> </system> </configuration> </data> </rpc-reply>
На текстовый XML смотреть не надо - парсим библиотечкой xmltodict:
from ncclient import manager import xmltodict rpc = """ <filter> <configuration> <system> <host-name/> </system> </configuration> </filter> """ if __name__ == "__main__": with manager.connect( host="kzn-spine-0.juniper", ssh_config=True, hostkey_verify=False, device_params={"name": "junos"} ) as m: result = m.get_config("running", rpc).data_xml result_dict = xmltodict.parse(result) print(f'hostname is {result_dict["rpc-reply"]["data"]["configuration"]["system"]["host-name"]}')
С уже таким результатом:
hostname is kzn-spine-0При работе с сетевыми коробками по NETCONF xmltodict, пожалуй, самая практичная библиотека, преобразующая XML-данные в объект Python. Она использует C-шный парсер pyexpat, так что недостатков у такого подхода фактически нет.
Точно так же можно обновить конфигурацию в два действия: <edit-config>
в <candidate>
и <commit>
:
from ncclient import manager import xmltodict rpc = """ <config> <configuration> <interfaces> <interface> <name>ge-0/0/0</name> <description>Mit der Dummheit kämpfen Götter selbst vergebens.</description> </interface> </interfaces> </configuration> </config> """ if __name__ == "__main__": with manager.connect( host="kzn-spine-0.juniper", ssh_config=True, hostkey_verify=False, device_params={"name": "junos"} ) as m: result = m.edit_config(target="candidate", config=rpc).data_xml m.commit() result_dict = xmltodict.parse(result) print(result_dict) OrderedDict([('rpc-reply', OrderedDict([('@message-id', 'urn:uuid:93bde991-81f9-42d6-a343-b4fc267646c2'), ('ok', None)]))])
Дальше пока копать не будем. Тем более, бытует мнение «без всяких сомнений, самый ублюдочно написанный Python код, что я видел в opensource»
scrapli-netconf¶
Давайте взглянем на пару примеров работы.
from scrapli_netconf.driver import NetconfDriver rpc = """ <filter> <configuration> <system> <host-name/> </system> </configuration> </filter> """ device = { "host": "kzn-spine-0.juniper", "auth_strict_key": False, "port": 22 } if __name__ == "__main__": with NetconfDriver(**device) as conn: response = conn.get_config("running", rpc) print(response.result)
Как это использовать¶
Мониторинг¶
NETCONF предоставляет возможность собирать операционные данные:
- Состояния протоколов (OPSF, BGP-пиринги)
- Статистику интерфейсов
- Утилизацию ресурсов CPU
- Таблицы маршрутизации
- Другое
- Используем безопасный SSH, не используем SNMP
- Не несём дополнительные протоколы в сеть
- Полная свобода того, какие данные мы собираем, без необходимости разбираться в OID’ах и MIB’ах
- При этом есть возможность собирать данные в соответствии с YANG-моделью
- Гипотетическая возможность оформить подписку на события в системе
Выполнение отдельных операций¶
Configuration Management¶
Да, это тоже возможно, если
Оборудование поддерживает 100% конфигурации через NETCONF. Увы, я на своём веку повидал ситуаций, когда некоторые секции просто-напросто отсутствовали в NETCONF и никакого способа настроить нужную функцию нет.
- Оборудование честно поддерживает операцию «replace», без этого вычисление конфигурационной дельты ложится вновь на сетевиков.Однако, в том виде, в котором мы познакомились с темой на данный момент, дальше начинается Jinja-программирование. Каждому, кто этим занимался, обычно неловко, и он стыдливо избегает разговора на эту тему.Задача решается примерно следующим образом:
- Пишем циклопические развесистые jinja-шаблоны с ифами и форами, внутри которых XML. Шаблоны под каждого вендора, конечно, свои собственные, поскольку и схемы данных у них разные. Но при этом они универсальные в плане ролей устройств - не нужно для свитчей доступа и маршрутизаторов ядра писать разные шаблоны - просто в зависимости от роли будут активироваться те или иные их части.Здесь в нужных местах сразу описаны типы операций - где merge, где replace.
Каким-то образом формируем под каждое устройство файлы переменных, в которых указаны хостнеймы, IP-адреса, ASN, пиры и прочие специфические вещи. Эти файлы переменных в свою очередь, напротив, вендор-нейтральны, но будут отличаться от роли к роли.
Рендерим конфигурацию в формате XML, накладывая переменные на шаблоны. Получаем целевую конфигурацию в виде дерева XML, где в нужных местах проставлена операция
replace
.Этот XML с помощью ncclient, ansible, scrapli-netconf или чего-то ещё подпихиваем на коробку.
NETCONF-сервер на коробке получает RPC и вычисляет конфигурационный патч, который фактически применит. То есть он находит разницу между целевой конфигурацией в RPC и текущей в
<running>
. Применяет эту конфигурацию.
Как бы это могло выглядеть я уже показывал в предыдущем выпуске АДСМ.
Ручная правка файлов переменных - это очень неудобно, конечно же. Просто мрак, если мы говорим про какие-то типовые вещи, как например датацентровые регулярные топологии. Новая пачка стоек - сотни и тысячи строк для копипащения и ручного изменения. Но на самом деле их можно создавать автоматически на основе данных из централизованной базы данных - DCIM/IPAM.
Что тут хорошо:
- Изменения в Jinja-шаблонах версионируются через git и проходят проверку другими инженерами перед применением. Это систематические изменения, влияющие на большое количество устройств.
- Изменения в переменных - точно так же. Это точечное изменение конкретного устройства.
- Только после согласования изменений в пунктах выше, можно сгенерировать новую конфигурацию и далее уже её отправить на проверку в git.
- Если соблюдать процесс, то отсутствует конфигурационный дрейф.
Что тут плохо?
- Ну, очевидно, Jinja-программирование
- Работа с текстом, вместо объектов языка.
- Отсутствие возможности взглянуть на конфигурационный диф до его применения.
На этом на самом деле заканчивается первая большая часть этой статьи, которая позволяет просто уже взять и получать пользу от NETCONF в задачах автоматизации.
Я вот прям серьёзно сейчас, ей богу! Не туманные абстракции - берём NETCONF - и на многих вендорах уже можно с ним работать выстраивая автоматизацию того или иного объёма.
Хух. Давайте просто не будем об этом сейчас? Просто не сейчас? Попозже. После RESTCONF и gRPC?
RESTCONF¶
Описан в RFC8040.
Что нужно настроить, чтобы заработал restconf:
Выпускаем самоподписанный сертификат
security pki certificate generate self-signed restconf.crt key restconf.key generate rsa 2048 parameters common-name restconf certificate:restconf.crt generated
Разрешаем доступ на устройство по порту 6020 - правим control-plane acl
Смотрим то, что разрешено сейчас - это readonly acl.
show ip access-lists default-control-plane-acl
Копируем правила и создаём копию ACL. Добавляем правило, разрешающее доступ по порту 6020
ip access-list control-plane-acl-with-restconf 9 permit tcp any any eq 6020 30 permit udp any any eq bfd ttl eq 255 40 permit udp any any eq bfd-echo ttl eq 254 50 permit udp any any eq multihop-bfd 60 permit udp any any eq micro-bfd 70 permit ospf any any 80 permit tcp any any eq ssh telnet www snmp bgp https msdp ldp 90 permit udp any any eq bootps bootpc snmp rip ntp ldp 100 permit tcp any any eq mlag ttl eq 255 110 permit udp any any eq mlag ttl eq 255 120 permit vrrp any any 130 permit ahp any any 140 permit pim any any 150 permit igmp any any 160 permit tcp any any range 5900 5910 170 permit tcp any any range 50000 50100 180 permit udp any any range 51000 51100 190 permit tcp any any eq 3333 200 permit tcp any any eq nat ttl eq 255 210 permit tcp any eq bgp any 220 permit rsvp any any
Применяем ACL на Control-Plane
control-plane ip access-group control-plane-acl-with-restconf in
Включаем сервис RESTCONF
management api restconf transport https test ssl profile restconf
Настраиваем SSL
management security ssl profile restconf certificate restconf.crt key restconf.key
Вы божественны
Теперь проверяем, что порт открыт
nc -zv bcn-spine-1.arista 6020 Connection to bcn-spine-1.arista 6020 port [tcp/*] succeeded!
И собственно курлим:
curl -k -s GET 'https://bcn-spine-1.arista:6020/restconf/data/openconfig-interfaces:interfaces/interface=Management1' \ --header 'Accept: application/yang-data+json' \ -u eucariot:password
Так мы извлекли информацию про интерфейс Management1
.
А вот так можно получить данные по BGP:
curl -k -s GET 'https://bcn-spine-1.arista:6020/restconf/data/network-instances/network-instance/config/protocols' \ --header 'Accept: application/yang-data+json' \ -u eucariot:password | jq
Строка URL формируется следующим образом:
https://<ADDRESS>/<ROOT>/data/<[YANG-MODULE]:CONTAINER>/<LEAF>/[?<OPTIONS>]
- <ADDRESS> - адрес RESTCONF-сервера.
- <ROOT> - Точка входа для запросов RESTCONF. Можно найти тут:
https://<ADDRESS>/.well-known/
- data - прям так и остаётся
- <[YANG MODULE:]CONTAINER> - Базовый контейнер YANG. Наличие YANG Module - не обязательно.
- <LEAF> - Отдельный элемент в контейнере
- <OPTIONS> - Опциональные параметры, влияющие на результат.
Пробуем выяснить <ROOT>
:
curl -k https://bcn-spine-1.arista:6020/.well-known/host-meta <XRD xmlns=’http://docs.oasis-open.org/ns/xri/xrd-1.0’> <Link rel=’restconf’ href=’/restconf’/> </XRD>
curl -k -X PUT https://bcn-spine-1.arista:6020/restconf/data/system/config \ -H 'Content-Type: application/json' -u eucariot:password \ -d '{"openconfig-system:hostname":"vika-kristina-0"}' {"openconfig-system:hostname":"vika-kristina-0"}
Проверим?
curl -k -X GET https://bcn-spine-1.arista:6020/restconf/data/system/config \ --header 'Accept: application/yang-data+json' \ -u eucariot:password {"openconfig-system:hostname":"bcn-spine-1","openconfig-system:login-banner":"","openconfig-system:motd-banner":""}
Что? Не поменялось?! И оно действительно не поменялось. Я не смог заставить это работать.
Просто жаль сил, вложенных в этот протокол. Потому что на пятки ему наступает gRPC/gNMI.
На самостоятельное изучение: RESTCONF intro with Postman - Part 1.
Call-Home¶
Это, что называется, звонок домой - способ инициировать соединение с NETCONF/RESTCONF-сервера к клиенту, то есть с сетевой коробки на систему управления.
На устройстве настраивается IP-адрес NETCONF/RESTCONF-клиента, куда оно отсылает периодически данные по своему состоянию. Либо обращается для того, чтобы зарегистрироваться в системе и забрать свою конфигурацию.
Применимо для сценариев, когда
- Новое устройство должно сообщить о себе в систему управления
- Устройство находится за NAT или фаерволом
- Администратор считает, что безопаснее иметь закрытые порты на сетевых элементах и открывать только well-known порт на системе управления
Подробно тут останавливаться не будем.
gRPC/gNMI¶
За последние лет семь gRPC уже всем уши прожужжали. И только самые ловкие разработчики могли избежать реализации взаимодействия с какой-нибудь системой по gRPC.
g в gRPC, кстати, означает вовсе не «google».
В любом случае я не я, если перед gNMI я не разберу gRPC. Поэтому простите за отступление, но без него статья превратится в бесполезное поверхностное хауту.
gRPC¶
Есть, правда, и более последовательная инструкция.
Хотя точно стоит сказать о том, что gRPC использует Protocol Buffers (или коротко protobuf). Термин этот довольно нагруженный:
- это и спецификация, в которой описано, как данные должны выглядеть. Ещё это называется прото-спека.
- это и IDL (Interface Definition Language), позволяющим разным системам друг с другом на одном языке общаться
- это и формат сериализованных данных, в котором информация передаётся между системами
- То есть всего лишь один proto-файл (или их набор), определяет сразу все эти три вещи
- То есть когда вы пишете gRPC-приложение, формирование protobuf - это важнейший шаг.
Пишем ping!¶
Спецификация
Описываем protobuf:
service Ping { rpc SendPingReply (PingRequest) returns (PingReply) {} }
Ping
. А в нём есть метод - SendPingReply
- это собственно и есть RPC - та самая процедура, которую мы дёрнем удалённо - процедура отправить Ping Reply
.PingReply
.А что такое эти PingRequest
и PingReply
??
message PingRequest { string payload = 1; }
PingRequest
- это одно из пересылаемых сообщений между клиентом и сервером.payload
типа string
.payload
- это произвольное имя, которое мы можем выбрать, как хотим.string
- определение типа.1
- позиция поля в сообщении - для нас не имеет значения.message PingReply { string message = 1; }
Всё точно то же самое. Именем поля может быть даже слово message.
Вот так будет выглядеть полный proto-файл:
syntax = "proto3"; option go_package = "go-server/ping"; package ping; // The ping service definition. service Ping { // Sends a ping reply rpc SendPingReply (PingRequest) returns (PingReply) {} } // The request message containing the ping payload. message PingRequest { string payload = 1; } // The response message containing the ping replay message PingReply { string message = 1; }
То есть именно вот так и выглядит спецификация, описывающая схему данных на обеих сторонах. И сервер и клиент будут использовать один и тот же proto-файл и всегда знать, как разобрать то, что отправила другая сторона. Даже если они написаны на разных языках.
protos/ping.proto
- он будет один для всех.А теперь мы напишем пинг-клиент на Python, а пинг-сервер на Go.
gRPC Client¶
Сгенерированный Код
python-client
.grpcio-tools
.pip install grpcio-tools
И используя его уже нагенерить нужные классы:
python3 -m grpc_tools.protoc \ -I protos \ --python_out=python-client \ --grpc_python_out=python-client \ protos/ping.proto
ping_pb2.py
и ping_pb2_grpc.py
- это сгенерированный код.PingServicer
) и для клиента (PingStub
). Там же у класса Ping
есть и метод SendPingReply
. И куча других штуковин.Пока структура выглядит так:
. ├── ping_client.py ├── ping_pb2.py └── ping_pb2_grpc.py
Давайте писать gRPC-клиент.
Клиент будет совсем бесхитростным.
В цикле он будет пытаться выполнить RPC SendPingReply
на удалённом хосте 84.201.157.17:12345
. В качестве аргумента передаём payload, который считали из аргументов запуска скрипта.
В функции run
мы устанавливаем соединение к серверу, подключаем stub
и выполняем RPC SendPingReply
, которому передаём сообщение PingRequest
с тем самым payload
.
import sys import time from datetime import datetime import grpc import ping_pb2 import ping_pb2_grpc server = "84.201.157.17:12345" def run(payload) -> None: with grpc.insecure_channel(server) as channel: stub = ping_pb2_grpc.PingStub(channel) start_time = datetime.now() response = stub.SendPingReply(ping_pb2.PingRequest(payload=payload)) rtt = round((datetime.now() - start_time).total_seconds()*1000, 2) print(f"Ping response received: {response.message} time={rtt}ms") if __name__ == "__main__": payload = sys.argv[1] while True: run(payload) time.sleep(1)
Если запустить его сейчас, клиент вернёт StatusCode.UNAVAILABLE
- сервера пока нет, порт 12345 никто не слушает.
Давайте теперь писать
gRPC-сервер¶
на Go. Я его развернул на облачной виртуалочке, поэтому какое-то время он будет доступен и читателям.
Всё, что делает сервер - получает какую-то строку в payload
, добавляет к нему «-pong» и возвращает это клиенту.
Сгенерированный Код
ping
- для хранения спецификации и кода интерфейса.protoc --go_out=. --go-grpc_out=. protos/ping.proto
И получается так:
. ├── go.mod ├── go.sum └── ping ├── ping_grpc.pb.go ├── ping.pb.go └── ping.proto
package main import ( "context" "flag" "fmt" "log" "net" "google.golang.org/grpc" pb "ping-server/ping" ) var ( port = flag.Int("port", 12345, "The server port") ) type server struct { pb.UnimplementedPingServer } func (s *server) SendPingReply(ctx context.Context, in *pb.PingRequest) (*pb.PingReply, error) { log.Printf("Received: %v", in.GetPayload()) return &pb.PingReply{Message: in.GetPayload() + "-pong"}, nil } func main() { flag.Parse() lis, err := net.Listen("tcp", fmt.Sprintf("10.128.0.6:%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterPingServer(s, &server{}) log.Printf("server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
Вся бизнес логика описана в функции SendPingReply
, ожидающей PingRequest
, а возвращающей PingReply
, в котором мы отправляем сообщение message
: payload
+ «-pong» (GetPayload
). Естественно, там может быть более изощрённая логика.
main
мы запускаем сервер на адресе 10.128.0.6
.84.201.157.17
, на который стучится клиент? Тут без хитростей - это внутренний адрес ВМ, на который натируются все запросы к публичному адресу.Я положу его в
. └── ping-server └── main.go$ go run ping-server/main.go 2022/01/30 04:26:11 server listening at 10.128.0.6:12345
Всё, сервер готов слушать.
Пример сервера на питоне, если хочется попробовать.
Используем сразу asyncio, это же сервер, нельзя тут блочиться.
Для того, чтобы запустить сервер, нужно доставить пакет grpcio
python -m pip install grpcio
Запускаем?
❯ python ping_client.py piu Ping response received: piu-pong time=208.13ms Ping response received: piu-pong time=165.62ms Ping response received: piu-pong time=162.89ms
У-хууу, ё-моё, grpc-заработал!!!!
А давайте теперь попробуем подампать трафик? Я запустил сервер удалённо и снял трафик.
Вот тут уже видно почти все наши объекты, которые передаются между клиентом и сервером. pcap-файл.
Кайф!!
Давайте ещё раз проговорим, что мы сделали.
- Описали спецификацию сервиса - какие методы доступны, какими сообщениями с какими полями они обмениваются.
- Сгенерировали из этой спецификации код как для сервера на Go, так и для клиента на Python.
- Написали логику сервера и клиента
- Клиент сделал вызов удалённого метода на сервере. Список доступных методов мы знаем из proto-файла.
- Сервер вернул результат работы процедуры клиенту.
Итак, разобрались с gRPC. Ну, будем так считать, по крайней мере.
Внутри гугла gRPC удалось адаптировать даже к задачам сети. То есть gRPC стал единым интерфейсом взаимодействия между разными компонентами во всей компании (или одним из - мы не знаем).
gNMI¶
gNMI довольно новый протокол. Про него нет страницы на вики, довольно мало материалов и мало кто рассказывает о том, как его использует в своём проде.
Он не является стандартом согласно любым организациям и RFC, но его спецификация описана на гитхабе.
В качестве модели данных он может использовать YANG (а может и не использовать - в протобафы можно же засунуть всё, что угодно).
Как того требует gRPC, на сетевом устройстве запускается сервер, на системе управления - клиент. На обеих сторонах должна быть одна спецификация, одна модель данных.
Поскольку это конструкт над gRPC, в нём определены конкретные сервисы и RPC:
servicegNMI{ rpcCapabilities(CapabilityRequest) returns(CapabilityResponse); rpcGet(GetRequest) returns(GetResponse); rpcSet(SetRequest) returns(SetResponse); rpcSubscribe(streamSubscribeRequest) returns(streamSubscribeResponse); }
Более наглядное представление полного прото-файла можно увидеть на интерактивной карте, которую нарисовал Роман Додин:
Здесь каждый RPC расписан на сообщения и типы данных, и указаны ссылки на прото-спеки и документацию. Не могу сказать, что это удобное место для того, чтобы начать знакомиться с gNMI, но вы точно к нему ещё много раз вернётесь, если сядете на gNMI.
Предлагаю попробовать на практике вместо теорий.
И тут google в лучших традициях делает прекрасные инфраструктурные вещи и ужасный пользовательский интерфейс. gNXI, OpenConfig gNMI CLI client.
gNMIc¶
Нас и тут спасает Роман Додин, поучаствоваший в создании классного клиента gNMI, совместно с Karim Radhouani - gNMIc.
Устанавливаем по инструкции:
bash -c "$(curl -sL https://get-gnmic.kmrd.dev)"
Теперь надо настроить узел.
interface Management1 ip address 192.168.1.11/24 username eucariot secret <SUPPASECRET> management api gnmi transport grpc default ip access-list control-plane-acl-with-restconf-and-gnmi 8 permit tcp any any eq 6030 … control-plane ip access-group control-plane-acl-with-restconf-and-gnmi in
Попробуем выяснить capabilities:
gnmic capabilities \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure
А в ответ пара экранов текста, полного возможностей:
gNMI version: 0.6.0 supported models: - arista-exp-eos-multicast, Arista Networks <http://arista.com/>, - arista-exp-eos, Arista Networks <http://arista.com/>, - openconfig-if-ip, OpenConfig working group, 2.3.0 … supported encodings: - JSON - JSON_IETF - ASCII
gnmic get \ --path "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config"\ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure[ { "source": "bcn-spine-1.arista:6030", "time": "1969-12-31T16:00:00-08:00", "updates": [ { "Path": "interfaces/interface[name=Management1]/subinterfaces/subinterface[index=0]/ipv4/addresses/address[ip=192.168.1.11]/config", "values": { "interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config": { "openconfig-if-ip:ip": "192.168.1.11", "openconfig-if-ip:prefix-length": 24 } } }, { "Path": "interfaces/interface[name=Ethernet3]/subinterfaces/subinterface[index=0]/ipv4/addresses/address[ip=169.254.101.1]/config", "values": { "interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config": { "openconfig-if-ip:ip": "169.254.101.1", "openconfig-if-ip:prefix-length": 31 } } }, { "Path": "interfaces/interface[name=Ethernet2]/subinterfaces/subinterface[index=0]/ipv4/addresses/address[ip=169.254.1.3]/config", "values": { "interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config": { "openconfig-if-ip:ip": "169.254.1.3", "openconfig-if-ip:prefix-length": 31 } } } ] } ]
Из ответа видно полный путь к каждому интерфейсу, запрошенный путь и результат в модели OpenConfig.
--path "/"
- он вернёт просто всё, что может.gnmic get \ --path "/" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure
Ответа будет много.
И оттуда можно понять, что посмотреть конфигурацию BGP-пиров можно, используя путь "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config"
:
gnmic get \ --path "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure[ { "source": "bcn-spine-1.arista:6030", "time": "1969-12-31T16:00:00-08:00", "updates": [ { "Path": "network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor[neighbor-address=169.254.1.2]/config", "values": { "network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config": { "openconfig-network-instance:auth-password": "", "openconfig-network-instance:description": "", "openconfig-network-instance:enabled": true, "openconfig-network-instance:local-as": 0, "openconfig-network-instance:neighbor-address": "169.254.1.2", "openconfig-network-instance:peer-as": 4228186112, "openconfig-network-instance:peer-group": "LEAFS", "openconfig-network-instance:route-flap-damping": false, "openconfig-network-instance:send-community": "NONE" } } }, { "Path": "network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor[neighbor-address=169.254.101.0]/config", "values": { "network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config": { "openconfig-network-instance:auth-password": "", "openconfig-network-instance:description": "", "openconfig-network-instance:enabled": true, "openconfig-network-instance:local-as": 0, "openconfig-network-instance:neighbor-address": "169.254.101.0", "openconfig-network-instance:peer-as": 0, "openconfig-network-instance:peer-group": "EDGES", "openconfig-network-instance:route-flap-damping": false, "openconfig-network-instance:send-community": "NONE" } } } ] } ]
А такой, чтобы проверить состояние пира: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state"
gnmic get \ --path "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure[ { "source": "bcn-spine-1.arista:6030", "time": "1969-12-31T16:00:00-08:00", "updates": [ { "Path": "network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor[neighbor-address=169.254.1.2]/state/session-state", "values": { "network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state": "ACTIVE" } }, { "Path": "network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor[neighbor-address=169.254.101.0]/state/session-state", "values": { "network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state": "ACTIVE" } } ] } ]
И получается, вполне очевидное деление на конфигурационные и операционные данные.
Вот данные по конфигурации ветки system:
gnmic get \ --path "/system/config" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure[ { "source": "bcn-spine-1.arista:6030", "time": "1969-12-31T16:00:00-08:00", "updates": [ { "Path": "system/config", "values": { "system/config": { "openconfig-system:hostname": "bcn-spine-1", "openconfig-system:login-banner": "", "openconfig-system:motd-banner": "" } } } ] } ]
А вот по состоянию:
gnmic get \ --path "/system/state" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure[ { "source": "bcn-spine-1.arista:6030", "time": "1969-12-31T16:00:00-08:00", "updates": [ { "Path": "system/state", "values": { "system/state": { "openconfig-system:boot-time": "164480684820", "openconfig-system:current-datetime": "2022-02-19T13:24:54Z+00:00", "openconfig-system:hostname": "bcn-spine-1", "openconfig-system:login-banner": "", "openconfig-system:motd-banner": "" } } } ] } ]
Ну, и последний практический пример в этой секции: настроим чего полезного на железке, Set RPC
.
Сначала посмотрим значение AS у одного из BGP-пиров:
gnmic get \ --path "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address=169.254.1.2]/config/peer-as" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p passowrd \ --insecure[ { "source": "bcn-spine-1.arista:6030", "time": "1969-12-31T16:00:00-08:00", "updates": [ { "Path": "network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor[neighbor-address=169.254.1.2]/config/peer-as", "values": { "network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-as": 4228186112 } } ] } ]
Теперь поменяем значение:
gnmic set \ --update-path "/network-instances/network-instance[name=default]/protocols/protocol[name=BGP]/bgp/neighbors/neighbor[neighbor-address=169.254.1.2]/config/peer-as" \ --update-value "4228186113" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p passowrd \ --insecure{ "source": "bcn-spine-1.arista:6030", "timestamp": 1645281264572566754, "time": "2022-02-19T06:34:24.572566754-08:00", "results": [ { "operation": "UPDATE", "path": "network-instances/network-instance[name=default]/protocols/protocol[name=BGP]/bgp/neighbors/ neighbor[neighbor-address=169.254.1.2]/config/peer-as" } ] }
Проверяем ещё раз:
gnmic get \ --path "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address=169.254.1.2]/config/peer-as" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure[ { "source": "bcn-spine-1.arista:6030", "time": "1969-12-31T16:00:00-08:00", "updates": [ { "Path": "network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor[neighbor-address=169.254.1.2]/config/peer-as", "values": { "network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-as": 4228186113 } } ] } ]
Уиии! Я чуть не вскочил с места, когда получилось.
А ещё у gNMIc есть автокомплишн.
Сам gNMIc может быть импортирован как зависимость в Go-программу, поскольку имеет зрелую подсистему API.
pyGNMI¶
Эта библиотека написана Антоном Карнелюком (и снова русский след). Заметно удобнее всего остального и активно развивается.
Да на неё даже ссылается Arista из своей Open Management.
Соберём capabilities:
#!/usr/bin/env python from pygnmi.client import gNMIclient import json host = ("bcn-spine-1.arista", 6030) if __name__ == "__main__": with gNMIclient(target=host, username="eucariot", password="password", insecure=True) as gc: result = gc.capabilities() print(json.dumps(result))
По-get-аем что-нибудь:
#!/usr/bin/env python from pygnmi.client import gNMIclient import json host = ("bcn-spine-1.arista", 6030) if __name__ == "__main__": paths = ["openconfig-interfaces:interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config"] with gNMIclient(target=host, username="eucariot", password="password", insecure=True) as gc: result = gc.get(path=paths, encoding='json') print(json.dumps(result))
Ну и осталось теперь что-то поменять, например, тот же hostname:
#!/usr/bin/env python from pygnmi.client import gNMIclient import json host = ("bcn-spine-1.arista", 6030) set_config = [ ( "openconfig-system:system", { "config": { "hostname": "bcn-spine-1.barista-karatista" } } ) ] if __name__ == "__main__": with gNMIclient(target=host, username="eucariot", password="fpassword", insecure=True) as gc: result = gc.set(update=set_config) print(json.dumps(result))python gc_set.py | jq { "timestamp": 1645326686451002000, "prefix": null, "response": [ { "path": "system", "op": "UPDATE" } ] }
В репе ADSM можно найти пример по изменению BGP peer-as.
Нам следует разделить сетевое устройство, к которому мы всю жизнь относились как к чему-то в целостному, потому что покупаем сразу всё это в сборе, на следующие части:
- железный хост - коммутаторы и маршрутизаторы, со всеми их медными и оптическими проводочками, куском кремния под вентилятором и трансиверами,
- операционная система - софт, который управляет жизнью железа и запускаемыми приложениями,
- приложения, реализующие те или иные сервисы или доступ к ним - аутентификация, интерфейсы, BGP, VLAN’ы, или gNMI, дающий доступ к ним ко всем.
Да, влияние проблем на сетевом устройстве имеет больший охват. Да, можно оторвать себе доступ одним неверным движением. Да, поддержка целевого состояния на все 100% - всё ещё сложная задача.
Но чем, в конце концов это отличается от обычного Linux’а, на котором крутится сервис?
То есть сервисный интерфейс (gNMI, gRPC, REST, NETCONF) следует рассматривать как способ управления собственно сервисами, в то время как для обслуживания хоста никуда не девается SSH+CLI - для отладки, обновления, управления приложениями. Впрочем и тут есть Ansible, Salt. Вот только идеально для этого, чтобы сетевая железка стала по-настоящему открытой - с Linux’ом на борту.
Кроме того есть
gNOI¶
На самом деле там на сегодняшний день уже достаточно внушительный список операций.
А ещё по аналогии с gNMIc существует и gNOIc.
Telemetry¶
Начнем с того что под словом «телеметрия» каждый вендор может понимать свое. У Huawei своя реализация поверх gRPC (местами даже платная!), у Cisco есть Model-driven Telemetry (например Cisco Model-Driven Telemetry (MDT) Input Plugin), у Juniper тоже есть своя реализация - JTI. Последние два еще параллельно поддерживают gNMI.
Если говорить в целом про сбор метрик, то есть смысл использовать то, что поддерживается вендором в первую очередь. SNMP - надежный как швейцарские часы, тонны библиотек для работы с ним, MIBы устоялись и его не стыдно использовать даже в 2022. gNMI крутой, но реализация может подкачать - неизбежны детские болячки типа отсутствия поддержки IPv6 и требования админских прав для получения метрик.
Модели данных¶
fe80::1/64
, fe80::1 64
, fe80::1 link-local
, address: fe80::1, mask: 64
, FE8:0:0:0:0:0:0:1
, 0000111111101000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000001
или там вообще не поддерживается IPv6. И надо ли сначала как-то энейблить IPv6, а MTU заимствуется с интерфейса или для IPv6 отдельный?NETCONF поел овса из-за того, что не предложил никакой стандартизации для моделирования данных. Именно поэтому вендоры успели наплодить своих собственных, несовместимых моделей, про которые мы и поговорим ниже.
Собственно то, в какой иерархии представлена конфигурация - и есть модель данных. Говорится об этом или нет, но такая модель есть всегда и у любого интерфейса. Она может быть плоской или иерархической, может быть простой или запутанной. Если бы её не было, то вы бы просто не смогли настроить устройство, а команды конфигурации могли бы видоизвиняться случайным образом. Говорят, в Router OS 7 подвезли такую функцию.
system->login
, чтобы настроить нового пользователя, а формат команды будет set <USERNAME> <OTHER PARAMETERS>
.interface -> em0 -> unit 0 -> family inet
. И так будет всегда. Во всяком случае на этой железке и этой версии софта.То есть модель данных - это контракт между пользователем и операционной системой - как она интерпретирует переданные команды в зависимости от контекста.
Это верно для CLI, SNMP, NETCONF, gNMI и даже прямых вызовов чипового SDK.
Native¶
Так было всегда, но это поменялось с приходом автоматизации. Вендоры, как будто бы думали, что рост сетей можно поддерживать постоянным докидыванием людей на их настройку. Но людям это не нравилось, они начали писать инструменты автоматизации на perl’ах, php, python’ах с expect’ами, попытками отловить все возможные ответы CLI, правильно на них среагировать. Но количество скорби в этом мире только множилось. Все рано или поздно приходили к пониманию, что долго притворяться робот человеком не может.
И так появились первые модели данных - NATIVE. У каждого вендора своя, но уже модель, и уже открытая. Вендоры с достаточно высокой социальной ответственностью выкладывают свои модели в публичный репозиторий.
Вендор-нейтральные модели¶
Настройка интерфейса:
Juniper:
configure set interfaces ge-0/0/0 unit 0 family inet address 10.0.0.1/30 commit and-quit
- Nokia:
router interface "test" address 10.0.0.1 port 1/1/1 no shutdown exit- Cisco:
conf t interface gigabitethernet1 ip address 10.0.0.1 255.255.255.252 no shut exitНастройка BGP:
Juniper:
configure set routing-options router-id 10.0.0.1 set routing-options autonomous-system 65000 set protocols bgp group test type internal set protocols bgp group test peer-as 65000 set protocols bgp group test neighbor 10.0.0.2 redistribute-connected set policy-options policy-statement redistribute-connected from protocol direct set policy-options policy-statement redistribute-connected then accept commit and-quit
- Nokia:
router autonomous-system 6500 router-id 10.0.0.1 bgp group "ibgp" type internal neighbor 10.10.10.2 exit- Cisco:
conf t router bgp 65000 bgp router-id 10.0.0.1 neighbor 10.0.0.2 remote-as 65000 redistribute connected exit
Сложность ведь не в транспорте и не в интерфейсе, а в модели данных. Сделать у каждого вендора Configuration State Management - одноразовая решаемая (а много где и решённая) задача. А вот договориться между всеми производителями, как должна выглядеть модель - так же сложно, как и любая другая задача, где людям нужно договориться.
Но ни один из зарождавшихся и выживших стандартов или не ставил целью унификацию вообще, или пытался поднять этот вопрос, но был выброшен в окно штаб-квартиры вендора.
Хотя вру. IETF предприняли отчасти успешную попытку написать универсальную модель.
IETF-модель¶
Так гугл придумал OpenConfig. Он не стал размениваться на IETF-модели и торги со стариканами из института.
OpenConfig - мечта, становящаяся явью¶
Возможно, впервые за шестидесятилетнюю историю телекоммуникаций у нас появился шанс изобрести свой USB Type C. Представьте мир, в котором Cisco, Juniper, Arista и Mikrotik настраиваются одними и теми же командами и это к тому же приводит к одинаковому результату?
Я не могу.
OpenConfig - это открытая YANG-модель, которая предполагается единой для всех вендоров. Одна стандартизированная модель для управления конфигурацией, сбора операционных данных с устройства и телеметрии. Одна для всех поддерживающих OC вендоров.
Итак, OpenConfig появился в 2015 году в Google как ответ на следующие вызовы:
- 20+ ролей сетевых устройств
- Больше полудюжины вендоров
- Множество платформ
- 4M строк в конфигурационных файлах
- 30K изменений конфигураций в месяц
- Больше 8M OIDs опрашиваются каждые 5 минут
- Больше 20K CLI-команд выполняется каждые 5 минут
- Множество инструментов и поколений софта, куча скриптов
- Отсутствие абстракций и проприетарные CLI
- SNMP не был рассчитан на столь большое количество устройств и на столько большие объёмы данных (RIB)
OpenConfig сегодня даёт возможность настройки базовых сервисов. Безусловно речь не идёт про вещи, завязанные на аппаратные особенности: QoS, управление буферами и ресурсами чипа, сплиты портов, работа с трансиверами. И в каком-то хоть сколько-то обозримом будущем этого ждать не стоит.
Хуже того, на сегодняшний день многие вендоры, ввязавшиеся в поддержку OC, не реализуют все 100%, а лишь часть.
Но BGP с OSPF настроить точно можно.
Что делать в этом случае?
Другой - использовать вендорские Native модели, покрытие которых намного больше.
Абсолютно нормально совмещать OC и Native - главное, не настраивать одно и то же с помощью разных моделей. В целом рекомендуют (даже сами вендоры), использовать OC там, где это возможно, а где нет - прибегать к native.
Источник: доклад на Cisco Live
Так что же это за мифический YANG?
YANG¶
YANG, а точнее модели, написанные на нём, не стали серебряной пулей, как не стали ей (пока) NETCONF, OpenConfig, gNMI.
И вообще YANG - вещь весьма академическая. Это просто язык описания моделей. Модели у каждого производителя и под каждую задачу могут быть совершенно разными, но, учитывая, что они все написаны на одном языке, мы можем применять одни и те же подходы и инструменты для работы с ними, и не отращивать ещё новые нейронные связи.
Вообще-то модели может не быть вовсе, или она может быть описана по-английски или по-русски, вместо YANG. Но при этом в JunOS/VRP/IOS по-прежнему будет какая-то структура данных. Просто в этом случае у вас не будет контракта, и в суд вы обратиться не сможете. Это собственно то, как мы и жили прежде.
Но гугл пошёл ещё дальше - gNMI может работать как с YANG-моделями, так и нет. Свободу вариативности! Что, впрочем, вполне логично, ведь в основе gNMI - protobuf’ы gRPC. А они могут как быть созданы на основе YANG-модели, так и просто придуманы из головы, или модель может быть написана не на YANG.
Как видно, благими намерениями уже тогда - был устлан путь к хьюман-ридабл, мэшин-парсибл.
Давайте мы не будем закапываться в сам язык - пожалуй, это нужно не очень большому числу людей. Нам интереснее практическая сторона - конкретные модели, как в них найти глазами нужные вещи, как с ними работать программно, какая вообще от них польза.
Дальше в качестве практики возьмём модель OpenConfig.
Препарируем YANG-модель¶
Давайте ещё раз вспомним пример, как мы собирали данные по конфигурации интерфейсов.
gnmic get --path "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config" \ -a bcn-spine-1.arista:6030 \ -u eucariot \ -p password \ --insecure
Откуда взялся этот замысловатый путь?
Для этого нам понадобится взглянуть на открытый репозиторий OpenConfig.
pyang
.Pyang¶
Продолжим с примером про интерфейсы.
sudo pip install pyang
В рабочий каталог склоним репу:
git clone https://github.com/openconfig/public
И дадим вот такую команду:
pyang -f tree -p yang/oc/public/release/models/ \ yang/oc/public/release/models/interfaces/openconfig-interfaces.yang
И дальше вываливается много текста:
module: openconfig-interfaces +--rw interfaces +--rw interface* [name] +--rw name -> ../config/name +--rw config | +--rw name? string | +--rw type identityref | +--rw mtu? uint16 | +--rw loopback-mode? boolean | +--rw description? string | +--rw enabled? boolean +--ro state | +--ro name? string | +--ro type identityref | +--ro mtu? uint16 | +--ro loopback-mode? boolean | +--ro description? string | +--ro enabled? boolean | +--ro ifindex? uint32 | +--ro admin-status enumeration | +--ro oper-status enumeration | +--ro last-change? oc-types:timeticks64 | +--ro logical? boolean | +--ro management? boolean | +--ro cpu? boolean | +--ro counters | +--ro in-octets? oc-yang:counter64 | +--ro in-pkts? oc-yang:counter64 | +--ro in-unicast-pkts? oc-yang:counter64 | +--ro in-broadcast-pkts? oc-yang:counter64 | +--ro in-multicast-pkts? oc-yang:counter64 | +--ro in-discards? oc-yang:counter64 | +--ro in-errors? oc-yang:counter64 | +--ro in-unknown-protos? oc-yang:counter64 | +--ro in-fcs-errors? oc-yang:counter64 | +--ro out-octets? oc-yang:counter64 | +--ro out-pkts? oc-yang:counter64 | +--ro out-unicast-pkts? oc-yang:counter64 | +--ro out-broadcast-pkts? oc-yang:counter64 | +--ro out-multicast-pkts? oc-yang:counter64 | +--ro out-discards? oc-yang:counter64 | +--ro out-errors? oc-yang:counter64 | +--ro carrier-transitions? oc-yang:counter64 | +--ro last-clear? oc-types:timeticks64 +--rw hold-time | +--rw config | | +--rw up? uint32 | | +--rw down? uint32 | +--ro state | +--ro up? uint32 | +--ro down? uint32 +--rw subinterfaces +--rw subinterface* [index] +--rw index -> ../config/index +--rw config | +--rw index? uint32 | +--rw description? string | +--rw enabled? boolean +--ro state +--ro index? uint32 +--ro description? string +--ro enabled? boolean +--ro name? string +--ro ifindex? uint32 +--ro admin-status enumeration +--ro oper-status enumeration +--ro last-change? oc-types:timeticks64 +--ro logical? boolean +--ro management? boolean +--ro cpu? boolean +--ro counters +--ro in-octets? oc-yang:counter64 +--ro in-pkts? oc-yang:counter64 +--ro in-unicast-pkts? oc-yang:counter64 +--ro in-broadcast-pkts? oc-yang:counter64 +--ro in-multicast-pkts? oc-yang:counter64 +--ro in-discards? oc-yang:counter64 +--ro in-errors? oc-yang:counter64 +--ro in-unknown-protos? oc-yang:counter64 +--ro in-fcs-errors? oc-yang:counter64 +--ro out-octets? oc-yang:counter64 +--ro out-pkts? oc-yang:counter64 +--ro out-unicast-pkts? oc-yang:counter64 +--ro out-broadcast-pkts? oc-yang:counter64 +--ro out-multicast-pkts? oc-yang:counter64 +--ro out-discards? oc-yang:counter64 +--ro out-errors? oc-yang:counter64 +--ro carrier-transitions? oc-yang:counter64 +--ro last-clear? oc-types:timeticks64
pyang -f tree -p yang/oc/public/release/models/ \ yang/oc/public/release/models/interfaces/openconfig-if-ip.yang | head -n 10 module: openconfig-if-ip augment /oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/oc-if:subinterface: +--rw ipv4 +--rw addresses | +--rw address* [ip] | +--rw ip -> ../config/ip | +--rw config | | +--rw ip? oc-inet:ipv4-address | | +--rw prefix-length? uint8
Вот и он во всей красе. И тут видно, что это аугментация к модели /oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/oc-if:subinterface
.
А что такое oc-if?
less yang/oc/public/release/models/interfaces/openconfig-interfaces.yang | grep '^ *prefix' prefix "oc-if";
pyang -p yang/oc/public/release/models/ \ yang/oc/public/release/models/interfaces/openconfig-interfaces.yang
-f
позволяет конвертировать в разные форматы: tree
, yin
, yang
, jstree
, uml
и другие.tree
и uml
, потому что вот такие крутые картинки можно рисовать для визуаловЧтобы конвертировать uml в png можно воспользоваться пакетом plantuml. Ссылка на картинку побольше
С помощью pyang, конечно, можно работать не только с моделями OpenConfig, но и с любыми другими, написанными на языке YANG.
Место, где неплохо описан pyang.
А тут бравые парни из Австралии пишут свою модель. С этой работой будет полезно ознакомиться тем, кто хочет разобраться поглубже в языке YANG.
На сегодняшний день многие вендоры поддерживают YANG-схему для своих NETCONF/gNMI-интерфейсов.
Есть несколько мест, где их можно раздобыть:
- Официальный репозиторий YANG. Тут есть ссылки на гитхабы вендоров, которые публикуют свои модели.
- Но Huawei, например, хранит свои YANG-и в нескольких местах (https://github.com/Huawei/yang/ и https://github.com/HuaweiDatacomm/yang)
- Отдельно так же лежат схемы аристы: https://github.com/aristanetworks/yang
- Некоторые вендоры могут хранить их на своих сайтах.
В общем, собираем с репы по коммиту.
<get_schema>
.Что нужно знать про YANG?¶
Я бы взял на себя смелость сказать, что NETCONF/YANG - это как TCP/IP. То есть там и про NETCONF, и про YANG. Однако не только NETCONF.
Model Driven Programmability¶
Так что же это такое? Ведомая моделью программируемость? Теперь, после двух статей, нам хватит пары минут, чтобы разобраться что это такое.
Давайте вернёмся к 4-й части АДСМ, где я использовал позаимствованную у Дмитрия Тесля картинку.
Она ведь очень понятная? Inventory, Git с шаблонами конфигурации, рендер, валидация, применение.
Проблема в том, что шаблоны мы составляем руками на основе изучения документации, интерфейса коробки и действуем методом проб и ошибок, вообще-то. Если нужна проверка типов, диапазонов, если меняется иерархия - будьте добры сами всё это написать и обработать. И, окончив, уехать в сумасшедший дом, учить друзей джинджа-программированию.
Model Driven меняет картину следующим образом:
Model Driven означает тут, что мы
Всё вместе¶
Транспорт
- SSH,
- HTTP,
- HTTP/2
- SNMP тоже, конечно же, возможен, но не нужен.
Интерфейс
- CLI
- SNMP
- NETCONF
- RESTCONF
- gRPC
Формат данных
- Текст
- XML
- JSON
- Protocol Buffers
Способ описания спецификации - он же может называться схемой
- XSD
- JSON schema
- Protocol Buffers
- MIB
- Проприетарный способ, придуманный вендором и описанный в документации.
YANG-модели данных конфигурации
- OpenConfig
- Проприетарные модели
- IETF
- Проприетарная модель, придуманная вендором и неописанная в документации
Языки описания моделей
YANG
SMI/SMIng
Проприетарный язык, придуманный вендором и не описанный в документации
И ещё другими словами¶
- YANG - язык моделирования данных, но не сами модели,
- YANG-модели - конкретные модели, написанные на языке YANG, но ещё не сами данные и не их схема,
- OpenConfig - вендор-независимая YANG-модель данных конфигурации сетевого оборудования,
- Native-модели - вендорские проприетарные YANG-модели данных сетевой конфигурации,
- XML, JSON, Protobuf - синтаксис по представлению структур данных в виде, пригодном для передачи (например, строка), иными словами - сериализация,
- XML-схемы (XSD), JSON-схемы, proto-спецификации - репрезентация YANG-модели в соответствующем формате, схема
- NETCONF - протокол взаимодействия с сетевым железом, работающий поверх SSH. В качестве формата данных использует XML. Структура XML может быть основана на YANG-модели, но не обязательно,
- RESTCONF - аналог NETCONF, но работающий через HTTP. Данные представляются в JSON или XML на основе какой-либо YANG-модели,
- gRPC - фреймворк для межсервисного взаимодействия, которые реализует интерфейс, протокол, формат данных и спецификации (protocol buffers). Непосредственно к сетям отношения не имеет,
- Protobuf - он же protocol buffers - спецификация для gRPC, а так же формат передачи данных в нём,
- gNMI - протокол на основе gRPC для взаимодействия с сетевым оборудованием. Всегда основан на модели, представленной в формате protobuf-спецификации, но это не обязательно должна быть YANG-модель.
Модель же определяет то, как будет выглядеть сама спецификация/схема. То есть это ещё более абстрактная конструкция. И нужна модель для того, чтобы на её основе была возможность создать как proto-спеку, так и JSON-схему, так и XSD.
Полезные ссылки¶
Главные RFC:
Концепция RPC: Remote Procedure Call (RPC)
Про форматы данных: YAML: The Missing Battery in Python
Детальный разбор XML во всём его многообразии: XML Tutorial
XML разобран по кусочкам и мило иллюстрирован (русский): Что такое XML
Сайт OpenConfig: openconfig.net
Хорошая вводная в NETCONF и немного YANG: YANG Data Modelling and NETCONF
Продолжение про YANG и немного про NETCONF: The Road to Model Driven Programmability
На русском про NETCONF. Начало
Если жить не можете, хочется на русском про YANG, и вы воспринимаете художественную речь: YANG — это имя для вождя
Спецификация gNMI в его же репозитории
Совершенно чудесный и обязательный к употреблению сайт от Аристы с описанием и примерами разных интерфейсов и инструментов: Open Management
RESTCONF Tutorial - Everything you need to know about RESTCONF in 2020
Серия статей про RESTCONF, но рекомендую я её из-за того, что там хорошо разобраны примеры с YANG-моделями: RESTCONF, NETCONF and YANG
Достаточно обстоятельный разбор телеметрии поверх gNMI: Три года Телеметрии в IOS XR
Блог Романа Додина в общем. И в частности:
Блог Антона Карнелюка в общем. И в частности:
OpenConfig. Part 1. How open is OpenConfig
- Но на сайте поехала вёрстка - до чего-то приходится докапываться самому.
Блог Михаила Кашина в общем. И в частности:
Репозиторий с PyangBind, позволяющим генерировать классы Python из YANG-моделей
Для любителей изучать Питон по Луцу: NETCONF XML Management Protocol Developer Guide
Сложный, но полезный джуниперовский документ: Understanding OpenConfig and gRPC on Junos Telemetry Interface. Я не читал ;)
Заключение¶
Думаю, что хорошее заключение было в пятой части книги, к которой я и отсылаю читателя. | Путь нас ожидает неблизкий. Туман медленно рассеивается, открывая новые развилки дорожек, из которых нужно выбирать перспективную. | Но вот что следует держать в голове. Нам обо всём этом рассказывают на конференциях и пишут в длинных статьях как о свершившемся факте, в то время, как многие вещи всё ещё не работают, а в конце обычно есть приписка «Adding support of OpenConfig gNMI paves the way for future network automation».
Благодарности¶
- Роману Додину за дельные комментарии как по теоретической, так и по практической частям. А так же за полезный блог и инструменты. GitHub.
- Кириллу Плетнёву за наведение порядка с NETCONF и YANG - язык, модели, спецификации, форматы данных. И за уместные и остроумные замечания по языкам и библиотекам. GitHub, fb.
- Александру Лимонову за несколько идеологических замечаний и исправлений фактических ошибок.
- Александру Балезину за написанную часть про Telemetry.