
Меня зовут Григорий Орлов, я руководитель команды разработки сетевых сервисов гибридных облаков в Yandex Cloud. В статье расскажу про детали работы наших сервисов на уровне Config Plane — это уровень, на котором пользователь может задавать целевое состояние системы. А именно речь пойдёт про CIC‑API — сервисе управления железом, которое стоит на наших точках присутствия и участвует в работе Cloud Interconnect, необходимого для создания приватных выделенных сетевых соединений.
Я работаю в Yandex Cloud уже четыре года и всё это время занимаюсь развитием сервиса Cloud Interconnect: два года в качестве IC, остальное время как руководитель команды. И поскольку я успел стать свидетелем развития этого продукта, то покажу не только текущую работу, но и немного предыстории, как мы пришли к таким решениям.
Про гибридные облака
Гибридные облака — это вид IT‑инфраструктуры, который объединяет внутренние IT‑ресурсы компании с инфраструктурой и услугами сторонних поставщиков. Мультиоблака — это частный случай гибридной инфраструктуры, где используются несколько разных облаков. В статье всё это я буду называть гибридными облаками.
Если у нас есть какие‑то две площадки, и их необходимо объединить в гибридную инфраструктуру, то нужно обеспечить сетевую связность.

Принципиальная схема такая: есть on‑premises, есть Yandex Cloud и есть сетевая связность.
Чтобы обеспечить сетевую связность, есть разные способы. Рассмотрим два самых популярных. Первый — это virtual network appliance, то, что нередко называется VPN.

Площадки соединяются через интернет, обычно с помощью шифрованного канала. Плюсы понятны — это дёшево. Минус подхода тоже очевидный — QoS там никакой, потому что это промежуточная интернет‑инфраструктура, пакетики летят, где‑то дропаются, где‑то зависают. Соответственно, задержка и джиттер увеличиваются.
Второй подход — Cloud Interconnect.

Это прямое физическое подключение между вашей сетью и облаком в точках присутствия (PoP на схеме). Поверх этого мы настраиваем сетевые сервисы. Плюсы: высокая надёжность и скорость, низкие задержки. Минус: значительно дороже VPN.
Подробнее эту тему разбирал мой коллега Саша Лимонов: если хочется разобраться подробнее, рекомендую его доклады с прошлого nexthop и с прошлого about:cloud.
Нам же для понимания того, как выглядит уровень Config Plane в сервисе Cloud Interconnect, необходимо сначала рассмотреть систему целиком, а потом перейти к частностям.
Верхнеуровневое устройство и алгоритм работы CIC-API
Чтобы понять, как устроена работа CIC‑API и что происходит внутри в ответ на запрос пользователя, возьмём реальный сценарий заведения одного из ресурсов. Начнём сверху вниз, со знакомства с ресурсной (доменной) моделью.

Здесь у нас несколько уровней, есть вертикальное и горизонтальное измерение: Private API это наши приватные ресурсы, которые участвуют в работе всей схемы. Сверху над красной линией — Public API, или публичные ресурсы, те, которыми может управлять пользователь. Здесь есть три сервиса: это CIC‑API, CloudRouter и VPC‑API — ручки, в которых нужно что‑то настроить, чтобы в Cloud Interconnect появилась связность. Дальше я буду рассказывать только про левую часть нашей схемы, про CIC‑API.
Мы будем рассматривать все настройки с точки зрения администратора, у которого есть полный доступ.

Так для него выглядит наша ролевая модель.
Принципиальная схема работы такая:

Пользователь отправляет запрос в CIC‑API.
На каждый запрос система создаёт специальный конфиг.
Конфиг создаётся отдельно для каждого кластера (группы серверов).
Мы называем это кластером, потому что внутри него несколько серверов, но мы считаем их единым целым.
Для этого у нас есть CIC‑агент — это специальный Python‑компонент с менеджером конфигураций Annet (или Аннушкой) внутри, который опрашивает CIC‑API, достаёт оттуда конфиги и накатывает на кластер.
Схема деплоя такая:

Есть три зоны доступности, в каждой из которых по одному инстансу CIC‑API, одному инстансу CIC‑agent и Monapp для мониторинга и биллинга. Также у нас есть точки присутствия, PoP1 и PoP2. На схеме их две, но на самом деле может быть больше, и в каждой по несколько кластеров. Также есть API и агенты, которые управляют кластерами на этих точках присутствия.
Чтобы связность появилась в сервисе VPC, то есть в оверлее, мы создаём сеть, а в ней создаём подсеть (subnet). С другой стороны, в CIC создаётся транковое подключение, внутри него — приватное подключение. А дальше в сервисе CloudRouter мы провязываем приватное подключение с созданной сетью. Когда всё это материализуется на конкретном железе, появляется связность.
На уровне команд выглядит так:

Алгоритмы работы в деталях
Теперь рассмотрим подробнее.
Предварительная часть алгоритма

Создаём транк между пользователем и CIC‑API.
Запрашиваем валидацию. В Config Plane самое главное — это не давать пользователю делать то, что он делать не должен. Поскольку доменная модель у нас трёхуровневая, нужно управлять сложными параметрами валидации. Также если пользователь хочет сделать что‑то не то, нужно ему доходчиво объяснить, где он ошибается и как правильно завести нужный параметр.
Создаём Job и кладём её в базу. У нас мутирующий API, который возвращает операции, которые могут сойтись сразу, а могут и когда‑нибудь потом. Многие из них материализуются где‑то на железе, так что это может быть достаточно долго.
Пользователю мы обычно отдаём Operation в ответ, в котором может быть какой‑то результат, а может не быть.
Основная часть алгоритма

Инстанс CIC‑API опрашивает базу, достаёт оттуда Job и начинает его обрабатывать.
У нас есть квоты, значит нужно их проверить.
Нужно ещё раз сделать внутренние валидации, потому что с момента их создания прошло какое‑то время и стейт во внешних системах мог разойтись.
Затем самое интересное — шедулинг. Транк — это по сути логическая труба. Основной смысл в том, чтобы посадить его на какой‑то порт. Пользователь при этом задаёт параметры: bandwidth и тип SFP. Нам нужно запланировать то, что он хочет, на те ресурсы, которые свободны.
Соответственно, мы трекаем кластеры, которыми управляем, знаем, что у нас свободно, и пытаемся куда‑то приземлить нагрузку. Если это не удалось, отдаём ошибку.
Дальше похожая история, но не с портами, а с кластерами. У кластеров также есть различные ресурсы, типа VLAN ID. Они лежат в специальных пулах, которые нам нужно также трекать, выделять что‑то свободное, и если выделить не можем, тоже отдавать ошибку.
Рассмотрим, как выглядит принципиальная схема жизненного цикла в наших пулах ресурсов.

К обычному двухстадийному пулу Free и Busy у нас добавляется промежуточный пункт — это Limbo. При удалении ресурсов из Config Plane и до того, как их отдавать на переиспользование, нам нужно удостовериться, что на железе они уже не используются.
Поэтому при удалении на Config Plane мы переводим ресурс в Limbo, а когда от нашего агента приходит отбивка, что всё нормально, всё почистилось, их можно снова переводить во Free.

Затем конфиг у нас варится и кладётся в очередь — по сути в таблицу в базе. Она существует отдельно, но на самом деле это часть сервиса (а не Kafka, например).
И тут появляется CIC‑Agent. Мы прошли часть, когда пользователь что‑то сделал, у нас нечто наварилось и поместилось в очередь.
Затем CIC‑Agent поллит очередь. Первое, что он делает — запрашивает указатели в этой очереди. Вот её схема:

Здесь есть три указателя.
Когда конфиги варятся, очередь растёт там, где передвигается стрелочка
last_new_version— это «голова».dirty_version— это версия, которую агент взял, но ещё не выкатил, потому что делает что‑то в процессе.А
last_deployed version— это последняя корректная версия, которая выкатилась.
Версии, которые меньше last_deployed, можно спокойно почистить. Здесь большую очередь хранить не нужно, никаких высоких нагрузок нет. Внутри API мы поддерживаем инвариант, чтобы эти версии не перемешивались: промежуточные версии не убегали вперёд быстрее, чем предыдущие.
Далее CIC‑Agent забирает эти указатели.

Если у агента deployed и dirty одинаковые, то он берёт следующую версию — на схеме это 11-й шаг get config.
Если же версия deployed меньше dirty, то он снова берёт dirty и снова идет её накатывает. В CIC‑Agent никакого стейта нет, весь стейт лежит в API.
На 12-м шаге агент ставит dirty и дальше деплоит конфиги на кластер. Как раз здесь в дело вступает менеджер конфигураций Аннушка, она же Annet — опенсорс‑компонент, который поддерживают ребята из Yandex Infrastructure.
В Аннушку мы отправляем конфиг, затем она идёт на железо, которым управляет, и достаёт оттуда текущие конфиги. Она умеет работать с разным железом, так что нам не нужно заморачиваться с поддержкой разных вендоров и разных моделей. В результате она считает diff — разницу между тем конфигом, что мы ей дали, и тем, что она собрала с кластера.
Из diff Аннушка транзакционно накатывает изменения. Причём, ей явно можно сказать: «Даже если diff нет, всё равно накатывай то, что взяла с железа». Это позволяет защищаться от ситуации, что между поллингом агента могло что‑то произойти: скажем, пришли сетевики и что‑нибудь поназаводили. В этом случае мы просто наложим поверх тот конфиг, который передали Аннушке.
Если всё нормально, транзакции закрываются. В случае ошибки, если мы не учли чего‑то в схеме валидации, само железо защищает нас от того, чтобы разломать стейт. Цикл наката будет повторяться и завершаться с ошибкой. Мы увидим алерт и пойдём разбираться.
Завершающая часть алгоритма. После того как агент всё накатил, он ставит признак deployed для версии в очереди.

По этому признаку API идёт и освобождает все ресурсы. Если мы освобождали порт, она переведёт порты в свободные, освободит VLAN ID. Все ресурсы, что находились в стадии Limbo, она выведет в соответствии с версиями.
В финале мы поставим done в операцию: клиент уже на следующем поле API увидит, что операция завершилась, и активирует ресурсы. У ресурсов есть статусы CREATING, UPDATING, DELETING, ACTIVE. По ним можно отслеживать, что сейчас с ресурсом происходит. И пока статус не равен AСTIVE, то изменять или удалять ресурс нельзя.
Немного про observability
Ещё один важный процесс — это клиентский мониторинг. Когда клиенты заводят транки, приватные подключения, они хотят знать, что у них происходит. Основная функция — это форвардинг трафика.
Для этого нас есть Monapp — ещё один компонент, который лежит рядом с CIC‑агентом.

Monapp поллит CIC‑API, забирает оттуда ресурсы, и поскольку их много, он выкидывает всё, что не нужно. Дальше он запрашивает на железе метрики для этих ресурсов: счётчики, RX и TX, дропы, и всё остальное.
Дальше Monapp всё это дело матчит и пушит в мониторинг по фолдерам клиентов. Они могут зайти и посмотреть, что у них происходит.
Деньги, биллинг — важная функция, которая тоже остаётся у Monapp. Для этого он берёт ресурсы и метрики, идёт в CIC‑API и в созданных транках проставляет специальный флажок, то есть активирует их. Пользователя мы биллим за транки, которые были хотя бы раз подняты с BGP‑сессии, или прошло определённое число времени с момента их создания, 90 дней. Дальше CIC‑API уже будет пушить в биллинг метрики именно по активированным транкам.
Из истории создания: как внедряли
Когда Cloud Interconnect только появился, у нас не было вообще никакой автоматики. Были кластеры и были инженеры NetInfra, которые ходили и руками всё настраивали. Такой подход не позволял масштабироваться. Когда количество кластеров и количество железа стало увеличиваться, начали появляться проблемы:
Нужно было задействовать для конфигурирования сетевиков. У них и так много других дел, плюс это редкие и дорогие специалисты. Из‑за этого цикл внесения изменений был долгим и требовал вовлечения нескольких участников (саппорты, сетевые инженеры).
Нужно было работать на уровне ресурсной модели железа, а не на ресурсной сервисной модели, это было слишком низкоуровнево, хотелось более абстрактного подхода.
Не было сквозной истории изменений, никакого GitOps. Нельзя просто посмотреть, кто, когда и что именно поменял в настройках. Эта информация была, только если само сетевое оборудование вело журнал изменений. Но чтобы собрать эти данные со всех кластеров, приходилось обходить каждое устройство вручную — а это долго и сложно.
Был риск пересечения пуловых значений ресурсов (например, VLAN ID). Это тоже проблема.
Необходимо руками управлять транзакционностью, консистентностью в кластерах при накате.
Как мы это решали?

На первом шаге мы добавили config.yaml, в котором уже были настройки в сервисном виде, и положили его в Git. Инженер NetInfra проводил изменения, какие‑то из них делал в config.yaml, дальше передавал это Python‑утилите duty‑tool.py, которая всё это дело наваривала и на выходе выплёвывала в виде команд CLI. Дальше NetInfra брали CLI‑команды и применяли в кластерах.
Здесь решили проблему с ресурсной моделью: теперь у нас есть сквозная история изменений, но часть проблем ещё осталась.
Дальше у нас появился CIC‑Agent.

Автоматизацию начали не сверху вниз, а снизу вверх, то есть с железа. Теперь config.yaml скармливали вручную CIC‑Agent, то есть тут ещё никакого поллинга нет. Потом CIC‑Agent шёл и транзакционно, сам с автоматикой пытался разложить настройки на кластеры. Так мы решили проблему с ручным применением.
На следующем этапе у нас появляется CIC‑API. На схеме это показано отдельно: пока мы его создавали, работал старый пайплайн. Это приводило к интересной ситуации, потому что у нас есть старый пайплайн, и новый. В старом у нас config.yaml — это source of truth, а здесь у нас CIC‑API — source of truth. Новая трудность: нам надо как‑то на этой едущей машине поменять колёса на ходу. При этом желательно ещё не словить никаких инцидентов и ничего не разломать.
Мы придумали такую штуку как режим «Сверка», и начали применять этот подход во многих других сервисах.

Работает старый пайплайн, CIC‑Agent поллит config.yaml на изменения. Если он видит изменения, он их пушит через публичные API, которые потом пользователи будут использовать в CIC‑API. И сам оттуда же забирает конфиги.
Так у нас получается дополнительная петля в процессе, но при этом CIC‑Agent всё равно как source of truth использует config.yaml, то есть старый пайплайн всё равно работает. При этом он, когда забирает конфиг из CIC‑API, сравнивает его с тем, что взято из config.yaml. Если возникает какой‑то diff, он создаёт алерт. Мы идём, смотрим и разбираемся, но при этом пайплайн у нас продолжает работать.
На следующем этапе произошла миграция, когда мы за несколько месяцев пофиксили все баги и поняли, что в целом самые частотные сценарии уже работают.

Можно избавляться от этой истории со сверкой и проводить наливку всё равно из config.yaml, но теперь уже по прямой: из config.yaml в CIC‑API, агент, кластер и так далее.
Поскольку мы переехали в API, у нас появилась вот эта трехстадийная модель с пулами, то проблему с пересечением ресурсов мы убрали. Там ещё добавили кучу дополнительных валидаций, которые в Python было реализовывать не очень удобно. Поэтому в целом система стала ещё надежнее.
Ну и последняя проблема — это то, что у нас всем занимались сетевики. В целом самой частотной операцией оставалось заведение клиентских ресурсов. Таких операций было очень много, но сетевики уже были не нужны, было решено отдать это в техподдержку.
На этом этапе инженеры поддержки уже наравне с NetInfra ходили в API и заводили транки, а также приватные и публичные подключения через CIC‑API.
Дальше мы просто удалили config.yaml. NetInfra теперь ходит в свою часть — в инвентарное API. А техподдержка заводит клиентские ресурсы. Вся эта схема работает, и больше нет никаких проблем, автоматизация закончена.
На этом работа над сервисом не заканчивается — во втором квартале 2026 года планируем добавить «ручки» для управления ресурсами, о которых нас уже просили пользователи. Так что если вы уже используете Cloud Interconnect, буду рад вашей обратной связи — в комментариях, в нашем сообществе Inside Yandex Cloud, где мы показываем, что под капотом платформы Yandex Cloud, или на наших митапах about:cloud — infrastructure (ближайший — 16 апреля).
