Всем привет, меня зовут Володя. Я работаю в Yandex Infrastructure и занимаюсь развитием систем балансировки нагрузки. В статье расскажу, как развивалась наша новая система управления конфигураций с момента её создания в 2018 году, а ещё о том, как мы переходили на новый Data Plane балансировки и какие новые интересные вызовы это породило с точки зрения массовости задач и управления ресурсами.
Опишу новые проблемы и особенности, в том числе планирование ресурсов для большого динамичного парка клиентов. Также обсудим, какие бывают долговременные негативные последствия у слишком удобных систем балансировки нагрузки и что мы планируем с этим делать.
Статья написана по материалам доклада на nexthop 2024, конференции по сетевым технологиям. Если вам интересны темы развития сервисов управления сетями, также подключайтесь к nexthop 2025.
Как всё начиналось
На первой конференции nexthop в 2018 году я представил доклад «Балансировка нагрузки в Яндексе: проблемы роста от зарождения и до наших дней», и с тех пор прошло семь лет.
За эти годы многое изменилось: инфраструктура балансировки переехала на новый Data Plane на DPDK (YANET), задачи управления конфигурациями решает L3 Manager, а все изменения в Data Plane балансировки происходят в лучших традициях CI/CD, без участия человека.
В 2018-м мы обрабатывали потоки трафика так: это был классический Anycast на бордерах и Anycast‑уровень балансировщиков с горизонтальным масштабированием, внутреннее IPv6-облако и возврат трафика через декапсуляторы, находящиеся на границе IPv4- и IPv6-облаков.

Тогда мы ставили перед собой такие задачи: построить идеальную систему балансировки, когда функция балансировки трафика есть, а выделенных приборов, выполняющих эту функцию, нет. Мы предполагали, что балансировщики у нас когда‑то станут равноправными с обычными серверами в облаке. А новыми задачами будут изоляция сервисов, исключение влияния трафика одного сервиса на другой и горизонтальное масштабирования в облаке.
В том же 2018-м мы начали переходить на новую систему управления конфигурациями, рассчитанную на то, что балансировкой управляют не выделенные люди, а любой клиент системы. Изначально под такими клиентами подразумевались заказчики внутри Яндекса. Используя интерфейс, клиент может получить сконфигурированный балансировщик, который через несколько минут появится в Data Plane.
Систему управления конфигурацией балансировки мы назвали L3-менеджер. Вот её главный экран со списком сервисов, здесь можно видеть их состояния.

Это «читерский» экран: в таком виде он доступен только людям, обладающим абсолютными правами в системе. Всего здесь чуть более 15 000 сервисов, то есть абстракций, описывающих полностью картину балансировки.
Человек, владеющий одним‑двумя сервисами, увидит на главном экране только свои сервисы. Проваливаясь глубже, мы попадём на страницу виртуального сервиса: это абстракция, описывающая все IP‑адреса, порты и протоколы, которые принадлежат этому сервису и необходимы для обработки нагрузки. Тут же можно видеть историю конфигураций и их состояний.

Посмотрим, что мы можем настроить.
L3 Manager: конфигурация сервиса
Внутри сервиса уже более близкие нам описания: IP, порт, протокол, описание ресурсов и наша внутренняя «кухня» — список балансировщиков.

На старте в 2018 году под капотом этой системы было наследие нашей предыдущей системы — Racktables — с активной моделью доставки конфигураций. Control Plane через SSH‑сессии доставлял конфигурацию до эндпоинтов.
Как это было устроено тогда? Генерируется очередь новых конфигураций, где они распределяются по физическим балансерам, и воркеры эту очередь разбирают.
Воркер захватывает какую‑то конфигурацию для деплоя и, чтобы никто ему не мешал, ставит lock на ID сервиса, который он начинает изменять. Следующие воркеры конфигурации этого сервиса уже не трогают, а разбирают свободные. Далее начинается деплой, он тоже происходит под блокировкой: захватывается lock на конкретный физический балансер.

Таких точек с блокировками и сериализацией в нашей системе было много. Упомяну основные точки: в общении с VCS, с сохранением стейта и так далее. Проблема понятная: чем больше нагрузка на систему, тем больше времени тратится на ожидание воркеров в блокировках.
Глобальный тайм‑аут на применение конфигурации сервиса нам пришлось увеличить до часа. Даже за это время выкладка конфигурации на Data Plane не всегда сходилась. Это не устраивало и наших клиентов, и нас самих.
Как перешли с Pull на Push
Давно зрела идея что‑то поменять в этой системе и максимально избавиться от сериализации и блокировок. Очевидное изменение: модель доставки конфигурации меняется с push на pull, когда балансировщики сами вытягивают необходимое из Control Plane, который является здесь источником правды.
На балансировщиках у нас появился агент, который вытягивает данные из источника правды. А внутри Control Plane — стейт‑машина, следящая за процессом деплоя конфигураций. Она же распределяет задачи по псевдоочередям.

Для каждого физического балансировщика генерируется очередь конфигурации для применения на нём. Агент на балансировщике читает эту очередь и применяет. Есть специфичные ограничения: о неконкуренции и для достижения консистентности, — но в целом мы избавились от блокировок между агентами и между конфигурациями.
В результате получили 50-й перцентиль доставки конфигурации: от момента её сохранения до Data Plane — менее 4 минут. Система гарантирует eventual consistency. Никаких глобальных тайм‑аутов, после которых в системе что‑то прерывается, уже нет.

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

Мы тащили это с собой, объясняли людям, как это работает, и бесконечно страдали от этого. В конце концов выяснилось, что клиенту всё это не нужно. Логи показали, что тонкие настройки не востребованы. Мы начали их аккуратно выпиливать или прятать за несколько кликов в интерфейсе.
Неявная ошибка, которую мы совершили, — не очень хорошо проанализировали клиентские потребности. Люди внутри Яндекса, для которых мы всё это строили, хотели очень простого решения: отдать какой‑то набор ресурсов и нажать кнопку, которая превратит этот набор в готовый сервис с балансировкой нагрузки.
Но оказалось, что пользователей‑людей у системы очень мало. Как только она стала большой и проинтегрировалась с облаками, 99% клиентов заменили роботы. Роботам нужна автоматизация: реализация terraform‑провайдеров, k8s‑операторов — всё это совершенно не нужно в веб‑интерфейсе системы. Поэтому интерфейс для людей остался таким, каким был в самом начале.
Таким образом, интерфейс мы поломали, но система всё‑таки, как ни странно, улучшилась.
Как развивался Data Plane и как переезжали на YANET
В 2018 году у нас был IPVS, и ограничения, которые мы наблюдали тогда, были критичными. Десять миллионов активных сессий и не более 25–30 Гбит/с через сервер‑балансировщик. А у нас уже тогда балансировщики имели интерфейсы 2×25 Гбит/с, и были заказчики на более высокий сервисный трафик. Наш файрвол на Iptables был с тяжёлым поиском и плохо справлялся, даже с оптимизациями: у некоторых сервисов могло быть до 20 000 правил линейного поиска. Это было больно, а общий файрвол для физического прибора не помещался уже даже в одно сообщение Netlink на 512 Мб.
Надо было что‑то менять, и вот какие решения мы приняли.
Во‑первых, сделать балансировщик на нашем внутреннем стеке YANET, который также доступен в опенсорсе. Почему именно YANET?
Мы несколько лет анализировали существующие решения (DPVS, VPP, Katran…). В итоге выяснили, что они нам не подходят либо потому, что не поддерживают функционально критичные вещи типа Jumbo‑фрейм, либо потому, что интеграция с нашими системами управления будет сложной.
YANET Firewall легко может в 100 Гбит/с и имеет оптимизированный ruleset с константной глубиной поиска.
Самый главный плюс — команда разработки YANET сидела в соседней с нами комнате.
Мы начали эту работу в 2021 году, когда у нас не было ничего, и три года развивали проект. Смена Data Plane потребовала переписать все мониторинги и реализовать новый мониторинговый демон живости бэкендов, взаимодействующий с Data Plane.

В начале 2022 года мы подошли к точке внедрения в продакшн того, что написали совместно с разработчиками YANET. Около полугода накатывали‑откатывали то, что получилось, исправляя баги в сотрудничестве с коллегами из сервисов storage, которые выступали в качестве early adopters.
Когда мы поняли, что это работает в продакшн‑среде, выяснили, что ошиблись с объёмом файрвола. В YANET компиляция файрвола гарантирует константную глубину поиска, но занимает достаточно большое время. Для большого числа сервисов она стала занимать более пяти минут. Коллеги починили это и спасли нас.
После этого мы решили переходить к трафику внешних сервисов: реализовали поддержку сигнальных сообщений (ICMP) и ещё один планировщик least connection.
В 2023 году, с появлением на балансировщиках интерфейсов на 100 Гбит/с, мы начали тестировать на балансировщиках внешний трафик основных пользовательских страниц yandex.ru и ya.ru. За квартал мы перенесли туда весь пользовательский трафик этих сервисов. С этого момента началась эпоха быстрого внедрения — раскатывания YANET по всему Data Plane балансировки.
Параллельно мы оптимизировали чековый демон, когда число проверок одного физического балансировщика стало превышать несколько десятков тысяч.
Появилась обработка UDP‑трафика, и все наши сервисы DNS переехали на балансировку YANET. Там очень интересные цифры в абсолютных значениях.
Чтобы показать, что разработка продолжается, мы поддержали GRPC. Сейчас у нас 90% физических балансировщиков и почти весь трафик на YANET, на остатке поддерживаются легаси‑сервисы поверх Windows. Миграцию с IPVS при этом действительно никто не заметил.
Сложности проекта
Сейчас YANET — это монолит, и изменение конфигураций в нём остаётся достаточно тяжёлым по времени загрузки процессом. Статистика в модуле балансировки, с нашей точки зрения, оставляет желать лучшего. Ожидаем, что это починится в новой версии YANET, и с каждым годом он будет улучшаться. Приходите контрибьютить в этот опенсорс‑проект.
Итак, есть классная система, у которой два стогигабитных интерфейса, несколько десятков тысяч бэкендов и огромное количество обрабатываемых пакетов или сессий. Это порождает свою странную специфику. Надо заниматься управлением и учётом ресурсов.
Так исторически сложилось, что сервис у нас (в Яндексе) считается ресурсом. Любой ресурс в Яндексе принадлежит какому‑то проекту. Мощности балансировщиков для сервиса — это тоже ресурс. Вроде бы они в проекте скомбинировались, все счастливы, и не должно быть никакой проблемы.
Проекты в Яндексе представлены в специальном сервисе в древовидной структуре. Любой фиолетовый квадратик владеет какими‑то ресурсами:

С точки зрения управления балансировкой из нескольких десятков тысяч проектов всего лишь около пятисот владеют 12 000 виртуальных сервисов. Эти сервисы распределены для обслуживания по четырёмстам балансировщикам. Мэппинг владения статический. Это историческое наследие тех времён, когда ещё не было дерева проектов в Яндексе и ручного переезда в предыдущие два года с IPVS Data Plane на YANET.
Ручное управление: зачем и почему
Мы гарантируем своим клиентам две вещи. Первое: их сервисы не деградируют при серьёзных инцидентах в одном из наших дата‑центров, второе — при отказе половины мощностей балансировки в любом из ДЦ. Назначение ресурсов балансировки для проектов, чтобы поддерживать эти две задачи, было статическим и выполнялось администраторами системы.
Появлялись новые сервисы, количество точек в графе росло. Следить за нагрузкой на четыре сотни приборов вручну�� непросто. Мы начали что‑то менять для улучшения ситуации.
В первую очередь сделали иерархическое наследование: не обязательно владеть ресурсами в каждой точке графа. Достаточно, чтобы ресурсами владела какая‑то верхняя точка графа, а все её потомки ресурсы будут наследовать. Получилось примерно так: каждый проект из нашего внутреннего сервиса учёта проектов владел в ДЦ конкретными физическими балансировщиками, и так было в каждом дата‑центре.

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

После того как мы сделали владельцам самую верхнюю точку и в ней несколько проектов, мы получили возможность перейти к гранулярности, при которой можно разрезать сервисы на отдельные кусочки и раскладывать их по балансировщикам.
Векторы укладки, которые у нас есть на старте, — это ограничения по трафику: для каждого виртуального сервиса мы измеряем трафик, пакеты и учитываем их в укладке в Control Plane. Пакеты влияют на использование CPU, а трафик ограничен физическим интерфейсом. Двух, казалось бы, очевидных векторов в нашем случае оказалось недостаточно. Любая большая система преподносит свои сюрпризы в неожиданных местах:
Файрвол. Можно было это предсказать по нашей истории. Много сервисов, много правил, которые нужно скомпилировать в дерево с константной глубиной поиска. Время компиляции большое: от 40 до 90 секунд. Хочется получить меньше. Модульный YANET эту задачу решит.
Массовые операции. У нас есть наша укладка, и в Яндексе система управления ресурсами позволяет всем проектам внутри какого‑то большого сервиса измениться одновременно. Они принесут огромное количество заявок на изменение конфигурации балансировки, потому что они переехали в облаке, переаллоцировали ресурсы. Здесь мы начинаем страдать, так как пропускная способность через одну точку (один физический балансировщик) — примерно 20–30 конфигураций за 4 минуты. Сейчас пытаемся прийти к такой схеме, когда проекты с их сервисами по физическим балансировщикам разделены примерно равномерно.
Количество эндпоинтов в балансировке. Состояние эндпоинта надо проверять. На одном физическом балансировщике у нас десятки тысяч эндпоинтов, и мы пытаемся сбалансировать сервисы так, чтобы не получалось на одном 100 000, а на другом 5000, шардируем их между собой.
Я попытался нарисовать общий трафик через систему. Получилось 1,4 Тбит/с, но может быть и больше, потому что наша система мониторинга отказывалась суммировать такое количество линий и, возможно, что‑то потеряла.

Под капотом системы ещё примерно 10–12 постоянно происходящих деплоев. На слайде перцентили доставки конфигураций до Data Plane от её сохранения.

На 99-м перцентиле можно видеть гладкие пики в определённое время — это селф‑хилинг системы. Она находит отклонения, когда что‑то пошло не так, и догоняет их до консистентного состояния. На 80-м перцентиле менее пяти минут проходит от момента сохранения конфигурации до её появления в Data Plane.
Всей этой системой сейчас управляют два человека, но в идеальном мире хочется видеть хотя бы четыре.
Горизонтальный трафик через неё составляет около 90% от 1,5 Тбит/с. Это трафик, строго локализованный в наших дата‑центрах, а не трафик внешних клиентов.
Быстрая система балансировки: что получили в итоге
У нас в компании достаточно слабо развиты механизмы балансировки на уровне приложений — в том смысле, что они есть в группах больших проектов, но единого быстрого и удобного механизма для всех так и не сформировалось. Механизмы балансировки на уровне приложений разные и продолжают появляться новые.
Достаточно слабо развиты процессы Service Discovery для realtime‑задач. На уровне приложений, как правило, не отслеживается живость бэкенда перед отправкой запроса. Отказоустойчивость строится на тайм‑аутах и ретраях. Означает ли это, что нужно ломать UX нашей системы балансировки, чтобы развиваться более последовательно с внешним миром, где service discovery и L7-балансировка идут на первых местах? Какие‑то изменения мы планируем.
Топология сети. Как устроена наша сеть: Clos‑топология в несколько уровней, где‑то уже Dragonfly появляется. Рядом с этой топологией распределения ресурсов в дата‑центре находится кусочек сети, ответственный за балансировку. Балансировщики, собранные за коммутаторами агрегации.

Между этими двумя частями есть достаточно узкий канал, в котором гранулярность маршрутов очень высокая, /32 или /128 для разных стеков, а в левом облаке всё‑таки агрегаты. В левом облаке коробки, кроме самых верхних, не в состоянии обрабатывать такое большое количество маршрутов, которые у нас есть: 12 000 сервисов и множество ECMP‑групп.
В этом узком промежутке бегают 1,4 Tбит/c трафика, которые более чем на две трети должны быть сосредоточены в части, где написано «серверы», и не должны забегать направо.
Наши планы. В ближайшее время мы хотим оставить на балансировщике только функцию балансировки. Классика: клиент посылает пакет, балансировщик принимает решение, куда его сфорвардить, и отправляет в сервер. На сервере должна сработать магия с обратным пакетом. Ответный пакет по DSR есть, но мы добавляем туда TCP‑опцию, содержащую в себе ID сервера. Эту функцию будет выполнять eBPF‑программа. На клиенте аналогичная eBPF‑программа, прочитав TCP‑опцию, формирует обратный пакет уже не через балансировщик, но через такой же ipencap‑туннель, который использует балансировщик, сразу в сервер.

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