Меня зовут Саша Шилкин, я работаю в Yandex Infrastructure и занимаюсь автоматизацией сети. Сегодня расскажу про обновление конфигурации сетевых устройств и про то, как мы его делаем в нашей команде: как начинали, как менялась конфигурация, какие для этого были предпосылки. 

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

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

Статья написана по материалам моего выступления на nexthop, конференции по сетевым технологиям, — с небольшими дополнениями, которые произошли за год.

В этом году на nexthop 2025 я также расскажу об автоматизации масштабируемой сети для BareMetal‑серверов — так что, если эта тема интересна, заглядывайте к нам 19 ноября

Как мы видим автоматизацию управления конфигурациями в облаке 

Наша облачная платформа устроена типично для гиперскейлера. Наша сеть большая, и она очень динамично развивается. При этом управляется она сравнительно небольшой командой: семь человек — это те, кто занимается design operations и непосредственно управляет сетевой инфраструктурой. Вся команда чуть больше, потому что в ней также есть ребята, которые занимаются DevOps, поддерживают наши информационные системы, мониторинг и так далее. 

Когда мы говорим о развитии управления сетью, хочется понять, а куда мы хотим попасть, что для нас будет целевой оптимизацией конфигурации.
Мы у себя в команде выделили четыре пункта: 

  • Мы хотим делать изменения централизованно. Это может быть один источник данных (source of truth, SoT) или какое‑то ограниченное их количество.

  • Мы хотим видеть, как изменения, которые мы планируем внести, отображаются в конфигурации железа. 

  • Мы хотим очень быстро выкатывать всё это параллельно на весь парк устройств.

  • Если у нас образуется разница в конфигурации, так называемый configuration diff внепроцессных изменений, мы хотим это устранить, желательно автоматически.

Вооружившись сформулированными целями, посмотрим на нашу первоначальную автоматизацию. 

С чего начинали

Стартовали мы с достаточно стандартного стека:

В первоначальной версии ещё не было таск-менеджера, сейчас он уже есть. Но в статье я не буду его касаться, поскольку это отдельная большая тема. 
В первоначальной версии ещё не было таск-менеджера, сейчас он уже есть.
Но в статье я не буду его касаться, поскольку это отдельная большая тема. 

В качестве SoT использовали NetBox для инвентаризации оборудования и VCS (Git), чтобы хранить информацию, связанную с префиксами, правилами и так далее 

Логика у нас была реализована в Ansible и частично в кастомном Python‑коде. Драйверы мы использовали стандартные для Python: Paramiko, Netmiko, Scrapli. 

Конечно же, в некоторых процессах мы выкатывали конфиг вручную на устройство.

Все конфигурационные процессы можно было разделить на три больших этапа. Начинали с того, что болело: у нас достаточно часто были выкатки на Cloud Border (группировку граничных физических маршрутизаторов на точках присутствия, которые выпускают трафик наружу). Менялись префикс‑листы, политики маршрутизации, и это первое, что мы хотели автоматизировать.

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

Далее заходили на устройство, проверяли коммит, если всё хорошо, подтверждали. 

После этого мы захотели такую же концепцию применить к другим изменениям, которые происходят реже: связаны с пусконаладкой или какими‑то большими инфраструктурными изменениями. Так как инвентарка хранилась в NetBox, нам для Ansible нужно было эти данные подготовить, поэтому мы пользовались кастомным скриптом, который готовил данные, чтобы можно было их использовать в Ansible. 

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

Кроме того, встречались и настолько частые изменения, что смотреть каждый раз и подтверждать diff по ним никаких ресурсов не хватит. Такие изменения включают в себя, например, ввод трафика с линка с ошибками либо же переключение стейта downlink‑порта для серверов. Такие атомарные изменения мы просто обложили тестами и катили уже без ревью и подтверждения. 

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

Diff же для нас очень важен, так как мы эксплуатируем инфраструктуру, которая поддерживает информационные сервисы внешних заказчиков. Проблемы в сети несут значительные риски и для нас, и для клиентов. Поэтому нам нужно смотреть diff, а все изменения нужно согласовывать. 

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

Почему понадобились новые инструменты

Мы уже строили нашу автоматизацию на Ansible, логично было решать эту задачу с ним. Есть большой плюс, что все знают Ansible: это стандарт де‑факто, и вновь приходящие коллеги уже готовы с ним работать с нуля. 

Но если смотреть на Ansible через призму diff, то это не его нативная функция. Да, есть готовые рецепты, есть рекомендации, как это можно сделать. Однако для этого нужно десериализовать конфигурацию в какую‑то структуру. Если мы говорим про CLI и про конфигурацию в несколько тысяч строк, то это достаточно трудно и сделать, и потом поддерживать. 

Что наиболее важно, если мы говорим про CLI, с Ansible мы не видим конфигурационные команды, которые будут применены для изменений. Соответственно, мы не знаем, как будет удаляться и создаваться ресурс. 

Тем временем у нас в компании есть генератор конфигураций Аннушка, который работает с текстовой конфигурацией. Для нас это очень хорошо: как сетевому инженеру, который проводит ревью изменений, мне проще проанализировать текстовую конфигурацию и понять, к чему она приведёт. Плюс ко всему мы можем отфильтровать изменения вплоть до какой‑то строчки, можем посмотреть патч — как применяются изменения. Это очень удобно. 

Как работает Аннушка 

Аннушка как внутренний инструмент в Яндексе появилась ещё 7–9 лет назад, а уже затем была выложена в опенсорс под именем Annet. А это значит, что по ней накоплена достаточная экспертиза. Да, она внутренняя, но, как показала практика, даже не погруженным в специфику внешним пользователям достаточно знать Python, чтобы начать пользоваться Аннушкой. 

Как у любого менеджера конфигурации, задача Аннушки — взять данные из SoT, применить какую‑то внутреннюю логику и получить на выходе конфигурацию. 

Для этого у Аннушки есть следующие блоки:

  1. Первым делом данные SoT необходимо нормализовать — десериализовать какую‑то структуру. Фактически это обычные Python‑объекты, в атрибутах которых лежат данные из SoT, инвентарки либо данные, как устройства соединяются между собой.

  2. Дальше данные этой структуры поступают на вход главной части Аннушки — генератора. Фактически это код, который из объектов делает конфигурационные строки согласно своей внутренней логике. Этот код сгруппирован: BGP, интерфейсы. Их можно включать отдельно и генерировать конфигурацию только по одному генератору либо по их набору, получая какой‑то конфиг. 

  3. Если у нас уже есть конфигурация с устройства и мы хотим сравнить её с тем, что приходит из SoT, то нужно рассчитать diff. На выходе из расчёта получается diff‑конфигурация с перечнем того, что нужно удалить или добавить. 

  4. Это можно пропустить через фильтры: отфильтровать по заготовленным критериям, например по интерфейсу либо по BGP; либо использовать файл с регулярными выражениями и более гранулярно, вплоть до одной строчки какой‑то команды, отфильтровать получившуюся конфигурацию. 

  5. То, как конфигурация рождается при генерации, не совсем отражает то, как она применяется на устройстве. Каждый вендор имеет свои правила, в каком порядке вводить команды, как правильно удалять и создавать конфигурационные элементы. Не всегда это просто keyword, могут быть совершенно разные конфигурационные строки для создания и удаления ресурса. 

    Если мы выкатываем это через какой‑то драйвер, нужно ему говорить, как отвечать на интерактивные вопросы, когда они задаются командой интерфейса коробки. За это отвечает модуль под названием rulebook — там перечислены правила, как правильно отсортировать конфигурацию, как удалять и создавать элементы, как работать с драйвером, который выкатывает на коробку данную конфигурацию.

Где это можно применить

Самый банальный сценарий — это сгенерировать конфигурацию. У нас есть source of truth, есть несколько BGP‑пиров, мы хотим сгенерировать BGP‑конфигурацию и отфильтровать её по одному конкретному пиру.

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

Другой сценарий — это сравнить конфигурацию, которую мы генерируем из SoT, с той, что приходит к нам из коробки. Скажем, с коробкой приходит конфиг, и в нём мы видим, что address family напутаны. Сгенерированная конфигурация сравнивается с существующей, и мы видим, что будет удалено, что будет добавлено. Но это ещё не то, что мы можем выкатить. Это удобно читать и анализировать сетевому инженеру, но как понять, какие команды будут в итоге применены на коробке? 

Для этого есть команда ann patch — она генерирует команды, которые нужно применить на коробке, чтобы снулить diff конфигурации. В этом примере то же самое: данные из SoT, конфигурация из коробки. Пропускаем сгенерированный конфиг через фильтры, оставляем один peer и через rulebook применяем правила, которые нам необходимы, чтобы правильно удалить или создать объект.

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

Что мы можем получить с внедрением

Адаптировав Аннушку, мы получаем процесс управления конфигурацией:

  1. Автоматика либо человек вносит изменения в SoT.

  2. Запускается ann diff, который показывает разницу между текущей конфигурацией на коробке и вновь сгенерированной.

  3. Получаем diff, который отправляется на согласование.

  4. Получаем подтверждение и дальше с ann deploy выкатываем конфигурацию.

Наш первоначальный стек, таким образом, превратился в следующую схему:

Мы поработали над SoT. Под GraphQL API мы скрыли несколько источников данных, чтобы к ним было удобно обращаться. 

Вся логика переехала в Аннушку, а в качестве драйверов для выкатки на оборудование мы использовали встроенные драйверы Аннушки и другие наши процессы автоматизации. Например, по-прежнему использовали стандартные библиотеки Python для того, чтобы работать с железом через SSH. 

Помимо того, что мы можем видеть diff при выкатке на устройство, мы можем периодически собирать конфиг с устройств и отслеживать изменения с помощью ann diff. Результаты складывать в базу данных и в системе аналитики строить графики, смотреть, какая есть разница у нас в конфигурации по каждому генератору, в общем, какой у нас дрейф конфигурации на сети. 

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

По такой схеме мы работаем во время миграции. Вот здесь небольшой пример, как это выглядит. Мы использовали DataLens, построили графики, на которых видно, сколько и по какому генератору Аннушка хочет добавить. 

Ступеньки на диаграмме — это данные в динамике, потому что мы обновляли конфигурацию. Например, справа можно видеть, как мы снуляли BGP: практически за месяц уменьшили разницу в семь раз.

Рабочая рутина с Аннушкой

Если ты сетевой инженер и работаешь с Аннушкой, то обычно день работы с ней выглядит следующим образом:

Если нужно внедрить какую‑то фичу, мы вносим изменения в те генераторы, логика которых создаёт конфигурационную команду по данным из SoT. После этого делаем коммит в репозитории, и наш процесс запускает так называемые регрессионные тесты. Что это такое? Аннушка запускается на мастер‑ветки и на ветки с изменениями. Потом они сравниваются, и может получиться diff. 

Это первая контрольная точка, чтобы не ошибаться: нужно сравнить, какие изменения может потенциально принести пул‑реквест. Дальше инженер подтверждает его, и изменения мёрджатся. За счёт того, что мы периодически собираем конфигурацию и натравливаем на неё ann diff, мы можем видеть, как эти изменения маппируются на реальное сетевое оборудование. 

Если всё хорошо и изменения ожидаемые, мы понемногу будем их снулять. Дальше цикл повторяется, поскольку изменения постоянные. 

Как был устроен переход на Аннушку

Как мы проходили миграцию и сколько времени у нас это заняло? Какие‑то вещи мы делаем до сих пор: пишем генераторы и восстанавливаем diff, поскольку это непрерывный процесс, постоянно происходят какие‑то изменения в конфигурации.

По реализации миграции видно, что около полугода у нас заняла подготовка SoT и модели. Параллельно мы писали генераторы и где‑то в конце года уже могли видеть, как отличается наша текущая конфигурация от той, которая запланирована. 

Через год после старта работ мы начали управлять новым оборудованием через Аннушку, но при этом остался огромный поток того, что было не снулено. Постепенно мы начинали снулять diff на оборудовании. Этот процесс продолжается до сих пор. Он очень долгий, мы не можем это делать быстро, поскольку стабильность инфраструктуры Yandex Cloud — приоритет номер ноль.

Сложности и детали реализации 

Коллеги заметили, что работать с Аннушкой очень просто, потому что это Python. Это даёт очень большую гибкость в сравнении с любым domain‑специфичным языком (DSL). Чтобы писать генераторы и модели, не нужно быть экспертом по раскрашиванию красно‑чёрных деревьев, достаточно просто иметь крепкие знания. Современный сетевой инженер с этим совершенно точно справится. 

Наиболее сложным был переходный период: когда у нас большой флот неснулённой конфигурации и новые устройства, на которых конфигурация снулена. Приходилось выкусывать кусочки конфигурации, чтобы не задеть то, что ещё, например, не снулено и не запланировано к обновлению. Благо у Аннушки есть access‑листы, которые позволяют это сделать достаточно комфортно.

Кроме того, появлялись различные тонкости, которые сложно предусмотреть сразу. Например, ты написал генератор, всё отлично работает. Приходит коробка с новым ПО, и та строчка, которая предсказуемым образом работала в твоём генераторе, теперь работает по‑другому. Приходилось эти штуки выкусывать. Это приводило к тому, что генераторы становились очень большими, сложно было ориентироваться. Старались как‑то соблюдать культуру разработки: всё это вносить в отдельные модули и держать свои генераторы элегантными и простыми.

Применив Аннушку, мы закрыли второй пункт идеального процесса автоматизации: видим влияние сильных изменений на конфигурацию и в момент выкатки конфигурации, и в целом на сети. 

Далее мы взялись за третий пункт, и с ним были свои сложности, когда мы выкатывали конфигурацию Аннушкой со своего ноутбука. Если выкатка была для сотен устройств, то приходилось тюнить окружение, иногда выкатки завершались неуспешно. Получалась достаточно хрупкая конструкция: например, если мы делали выкатку за VPN, RTT прыгал, процесс становился долгим, работать было неудобно.

Помимо этого, возникали архитектурные трудности. Если у нас есть несколько стеков инструментов автоматизаций со своими драйверами внутри, то их нужно поддерживать, а это накладно. Плюс ко всему нужно у каждого хранить минимум SSH‑ключ и ротировать его. А когда ротация нужна сразу для множества учёток, это увеличивает сложность: нужно делать сетевые доступы для каждого инстанса и тому подобное Также нужно управлять правами доступа на железе, это ещё один виток сложности. Было бы удобно, если бы был какой‑нибудь кубик, который бы делал всё это за нас. 

У разработчиков Яндекса был такой кубик — сервис под названием GlavNOC. Он, с одной стороны, предоставляет gRPC‑интерфейс, а с другой — через консоль либо через netconf работает с железом. Он может отдать информацию железу в виде уже распарсенной и десериализованной модели. Это очень удобно, потому что мы работаем с сетью как с API. Либо же используем команды или netconf, если это необходимо. 

Плюс ко всему он внутри обеспечивает RBAC, и мы можем ограничить доступ к каждому элементу сети не внутри коробки, а на этой промежуточной точке. 

Авторизация стала выглядеть следующим образом. Нам не нужно поддерживать каждый драйвер внутри отдельного модуля, всё ходит через GlavNOC, там же и происходит RBAC, и ключ фактически мы держим только на GlavNOC. А здесь уже авторизация, там либо OAuth, либо YC IAM.

Наш стек стал стройным. InvAPI — это наш SoT, за которым скрыт GraphQL API. Логика вся в Аннушке, а драйвер — в GlavNOC. Так мы закрыли третий целевой пункт и вплотную подступили к четвёртому. 

Регулярное автоматическое устранение diff

Нужно быть готовыми к тому, что этап регулярного снуления diff может быть довольно долгим. Что мы сделали на этом этапе. Некоторые генераторы в Аннушке мы пометили как safe. Это значит, что результаты работы этих генераторов не сломают нам сеть, мы можем их безопасно применять, если по ним есть diff. После небольшого тестирования мы на регулярной основе стали запускать эти генераторы. 

Если у нас есть изменения в description, dns, syslog, vty, которые появились вне процессных изменений (кто‑то зашёл и что‑то сделал), то они автоматически снуляются. Периодически мы проходимся по очереди и снуляем такие diff.

Зелёные точки показывают, где мы отработали diff. Часть генераторов мы не можем пометить как safe, потому что они потенциально опасные, и пока мы с ними ничего не делаем. 

Для тех, кто хочет повторить наш опыт

Как я уже говорил, оба этих инструмента выпущены в опенсорс: 

  • Аннушка под именем Annet

  • GlavNOC под именем gnetcli

Что есть в коробке с gnetcli

Это моя любимая тулза, поскольку её можно использовать как библиотеку Go. Если у вас есть код на Go, который нужно автоматизировать, его можно встраивать в библиотеку и использовать. 

Также можно использовать как CLI‑утилиту. Например, не заходить в коробку, просто отправить запрос и получить команду сразу с парка устройств. Помимо этого, её можно применять в какой‑то автоматике, которая работает локально, или можно развернуть как gRPC‑сервер и вообще использовать не с Аннушкой, а с какой‑то другой автоматизацией. 

Единственное, чего нет внутри, — gRPC RBAC. И нет моделей: gnetcli‑сервер не отдаёт информацию в распарсенном виде в рамках какой‑то модели. Если запросили CLI, то отдаст CLI; если запросили netconf, отдаст netconf. 

Что есть в коробке с Annet

В репозитории есть core‑часть Annet (расчет diff'а, фильтры и rulebook), генераторы и модель предлагается сделать самостоятельно на основе примеров.

По сравнению с тем, что делали мы, есть ещё два блока. Первый — это адаптеры, как работать с SoT в коробке. Есть возможность работать с YAML‑файлом и собирать информацию из NetBox. Добавился модуль, который отвечает за деплой на устройство. Из коробки gnetcli поддержится, но можно написать и свой адаптер, там есть свой интерфейс. 

Помимо самих инструментов, можно скачать лабы и посмотреть, как это всё работает. Там достаточно интересные сценарии, которые охватывают ключевые пункты применения. Например, если у вас уже есть какой‑то свой SoT, то по образцу можно написать адаптер, как с ним работать. 

В лабе есть и примеры генераторов: как работать с интерфейсом, как работать с BGP. Они покрывают минимальные задачи, но с получением лабораторного опыта будет достаточно легко сделать что‑то своё. 

Также я рекомендую присоединиться к Annet‑комьюнити: в сообществе мы обсуждаем разные сценарии применения инструмента, а пользователи регулярно подкидывают идеи для развития, так что там постоянно появляется что‑то новое. 

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

Комментарии (0)