В высоконагруженных системах балансировка трафика быстро перестаёт быть просто задачей распределения запросов. Сегодня на реальном опыте разбираем путь от BGP Anycast к L4-балансировке и XDP: зачем она понадобилась, как помогла справиться с ограничениями Anycast, повысить отказоустойчивость и производительность, а также почему балансировщик стал точкой входа для защиты от L4-DDoS. Статья будет полезна инженерам, которые проектируют и развивают инфраструктуру под высокий трафик и пиковые нагрузки.

Привет, Хабр! Меня зовут Алексей Медошин, я отвечаю в Wildberries & Russ за защиту от DDoS-атак и ботов, работаю в компании с 2017 года. За этот время прошёл путь от инженера до руководителя инфраструктуры. А ещё я люблю петь в караоке.

Но сегодня статья не про любимые песни, а про необходимость L4-балансировщика в системе.

Расскажу о том, как мы пришли к необходимости L4-балансировщиков, как мы их выбирали, почему вдруг решили их обходить, и как с помощью L4-балансировщиков можно защищаться от L4-флудов.

Этот текст потребует хотя бы общее представление о том, как работает TCP, по крайней мере в Three-way Handshake, и базовые познания в маршрут��зации. Иначе вам придется поверить мне на слово.

Необходимость L4-балансировщика 

Чтобы начать разговор о необходимости L4-балансировщиков, надо сделать небольшое отступление. Обычно такие истории начинаются с чёрных лебедей, но так как мы маркетплейс, у нас она начинается с чёрной пятницы.

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

Чтобы нормально обрабатывать этот трафик, нам нужно наращивать мощности, добавляя серверы в систему. Ниже — ультра-упрощенная схема, как выглядит наша инфраструктура.

У нас построен типичный двухуровневый Clos с уровнем спайнов и уровнем лифов. Серверы подключаются в лифы. Все подключения у нас L3. В качестве протокола маршрутизации используем BGP. У каждого устройства в этой сети своя собственная AS, то есть мы используем external BGP. На группе серверов живет какой-то сервис, и для него на loopback настраивается один VIP-адрес (виртуальный IP-адрес).

Пара слов про BGP Anycast

Немного о том, что это такое для контекста. 

BGP Anycast — это отправка пользователя на группу серверов, которые анонсируют один IP-адрес (VIP) с помощью протокола BGP. Эти серверы способны нести одинаковую нагрузку, независимо от того, на каком сервере обрабатывается запрос.

Ситуация, когда сервис живет на нескольких серверах и шарит один VIP-адрес, называется Anycast. Так как мы используем BGP, то это BGP Anycast. В статье использую оба названия, но это одно и то же.

Anycast — это отправка пользователя на любой ближайший сервер в группе. В Clos-инфраструктурах такое построение позволяет довольно просто балансировать и обеспечивать отказоустойчивость. Но не все так радужно, у этого подхода есть недостатки.

Посмотрим, как происходит распространение трафика с помощью Anycast в Clos-инфраструктурах.

В схеме выкинул один спайн, потому что на нем будет происходить все то же самое, что и на первом. 

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

  • В первую стойку подключен сервер 1,

  • В третью стойку подключен сервер 4, 

  • Во вторую стойку подключены сервер 2 и сервер 3. 

У нас приходит 30 TCP соединений. Нам очень хочется, чтобы все они были равномерно распределены по серверам. На схеме видно, как отбалансировала этот трафик система, но нужно пояснить, почему именно так.

Соединения приходят на спайн, он распределяет их на 3 лифа по 10 штук. Каждый из лифов балансирует на серверы, которые подключены к нему. Получается, что на первый и четвертый сервер попадет по 10 соединений, на второй и третий — по 5. Вроде бы каждый из уровней делал все правильно, а в итоге получили разбалансировку. Это происходит по банальной причине — каждый узел действует независимо. Ни спайн, ни лиф не знает ни о количестве серверов в инфраструктуре, ни о том, как они подключены.

Даже здесь ещё можно обойтись без L4-балансировщика и решить эту задачу с помощью BGP, но это очень больно. Есть такое мазохистское решение — BGP Link Bandwidth Extended Community.

Почему BGP Link Bandwidth Extended – это больно:

  • Настраивается на каждом коммутаторе в сети по отдельности. Bandwidth Extended Community не передается между автономными системами.

  • Значения, которые нам нужно настроить, придется высчитывать самостоятельно для каждого устройства. Чем выше устройство в иерархии, тем больше информации надо учесть. Уже на уровне спайнов нам надо знать всю топологию, сколько серверов подключено в каждый лиф. Но в жизни бывают еще супер-спайны, border-роутеры, core-роутеры, и на них надо учитывать еще больше информации с каждого нижнего уровня.

  • При изменении количества серверов надо все пересчитывать и перенастраивать

Недостатки BGP Anycast балансировки

Кратко подытожим недостатки, которые есть у Anycast балансировки:

  • «Неуправляемая» балансировка. Мы не можем гарантировать, на какой сервер сколько придет трафика.

  • Невозможность плавного вывода сервера в обслуживание. BGP — это протокол маршрутизации, маршрут либо есть, либо нет, то есть ВКЛ/ВЫКЛ.

  • Нет встроенных health checks. Может сложиться ситуация, что BGP клиент, который на сервере вещает маршрут, маршрут отправляет, а приложение, которое должно обработать трафик, еще не готово к этому или вообще упало.

  • Необходимость установки дополнительного софта на сервер.

Всё выше описанное навело на мысль о том, что необходим L4-балансировщик, который комплексно решит эти проблемы.

L4-балансировщик

Введем терминологию, что мы будем понимать под L4-балансировкой. 

Балансировка трафика на основе информации из IP и UDP/TCP заголовков: IP адрес источника и назначения, протокола, номеров TCP/UDP портов (5-tuple).

То есть мы будем решать, куда направить трафика на основе информации из заголовков третьего и четвертого уровня: IP-адреса источника назначения, самого протокола UDP или TCP портов.

Теперь давайте разберёмся, что мы хотим от балансировщика.

Требования к балансировщику

Балансировщик должен решать несколько проблем:

  • нивелировать недостатки BGP Anycast балансировки.

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

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

  • должен переживать миграцию 10G → 25G → 100G → 400G. Хочется, чтобы мы могли при необходимости масштабироваться вертикально, то есть переживать апгрейд скоростей, меняя в сервере сетевую карточку, и чтобы при этом всё было хорошо.

Какие есть решения на рынке

Посмотрим, из чего можно выбирать. Решения, которые мы рассматривали:

  • HAProxy, 

  • Nginx, 

  • DPDK-решения,

  • IPVS

  • Katran. 

Коротко пробежимся по всем. 

От HAProxy и Nginx мы сразу отказались. Во-первых, потому что они работают в Userspace. Во-вторых — Proxy-режим, то есть не выполняются наши требования по точке отказа. 

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

На IPVS и Katran остановимся поподробнее. Я сделал сравнительную табличку.

Оба варианта решают проблемы BGP и Anycast, но в Katran отсутствуют встроенные healthchecks.

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

DSR — это не технология, а просто режим работы, когда через балансировщик проходит только входящий клиентский трафик. Ответ клиенту идёт напрямую. Достигается это за счет того, что на балансировщике происходит упаковка изначального пакета в какую-нибудь инкапсуляцию, например IPIP, VXLAN или GRE.

DSR также решает проблему точки отказа. Как я уже говорил, решение о выборе сервера, на котором мы балансируем трафик, принимается на основе 5-tuple. Если у нас идентичная конфигурация балансировщиков, то оба балансировщика примут одинаковое решение о том, куда направить клиентский пакет.

Maglev Hashing — это просто алгоритм консистентного хеширования. Разработан Google, так же как их одноименный балансировщик, тоже L4. Он нужен для решения двух задач:

  1. Равномерно распределить запросы между конечными узлами

  2. Минимально триггериться на изменения, то есть на падение реальных серверов. 

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

У IPVS это линуксовый модуль ядра, а у Katran — это XDP. Очевидно, что XDP быстрее, потому что решение по обработке трафика принимается в драйвере сетевой карты. Плюс, в XDP гораздо проще вносить изменения, чем в модуль ядра, и гораздо безопаснее, потому что в модуле ядра при ошибке можно повесить всю систему, чего нам, конечно, не хотелось бы.

В итоге мы остановились на Katran. 

Почему Katran?

Кратко перечислю, почему:

  • DSR.

  • Modified Maglev Hashing. Он поддерживает разные веса у реальных серверов, можно балансировать неравномерно. Это впоследствии ещё сыграет роль.

  • IP-туннелирование (IPv4 in IPv4, IPv4 in IPv6, IPv6 in IPv6). Оно позволяет внутри мигрировать с IPv4 на IPv6-only инфраструктуру.

  • XDP

Но кое-что пришлось дописать:

  • TCP/UDP health checks.

  • HTTP/HTTPS health checks, чтобы еще больше повысить надежность и ждать, когда приложение точно сможет обрабатывать трафик.

  • BGP client, чтобы не платить за межпроцессное взаимодействие.

По фичам мы определились, теперь нужно протестировать производительность XDP. 

Тестирование производительности

Для проверки мы собрали такую лабу. 

В качестве генератора нагрузки использовали Cisco TRex. Да, он DPDK, но дописывать туда ничего не надо. Katran запущен на нашей обычной Compute Node. Процессор и версия операционки — на иллюстрации. Все соединения 100G.

С сервера rnd-trex (слева) генерируется различная L4-нагрузка. Она уходит на Katran, который называется rnd-l4b-100g. Katran балансирует это на реальные серверы rnd-real1 и rnd-real2. 

Проверяли работу в двух режимах:

  1. Syn-Flood, как наименее оптимизируемый с точки зрения балансировщика.

  2. UDP IMIX — тестирование пакетами переменной длины: от 64 до 1300 байт. 

Самое интересное для нас — это Syn-Flood, где PPS (Packets Per Second) был 45 миллионов. По BPS (Bits Per Second), естественно, UDP IMIX выигрывают, потому что длина пакета больше.

Зная производительность 100 гигабитной карточки, 45 миллионов может показаться не очень большой цифрой. Но так как все познается в сравнении, давайте сравним это с типичной нашей нагрузкой в обычный день.

Как правило, к нам от клиентов приходит меньше 35 миллионов PPS. Получается, что нам хватит даже одного сервера с Katran. Естественно, у нас их больше для отказоустойчивости.

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

Клиент входит в инфраструктуру. Здесь нарисовано облако, потому что нам уже не важно, спайн это или не спайн. Например, для внешнего трафика мы подключаем балансировщики сразу в border-роутеры, а для внутреннего трафика — в спайны, потому что это наиболее близкая точка для всех лифов в этом по��е.

Клиент пришел к нам в инфру, попал на балансировщик. Балансировщик на основе конфигурации выбрал, на какой реальный сервер отправить этого клиента, упаковал пакет в IPIP, отправил на реальный сервер. На реальном сервере пакет распаковался, обработался и ушел к клиенту напрямую благодаря DSR.

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

Что получили

Кратко просуммируем, что получили в итоге:

  • Решили проблемы BGP Anycast — ради чего все и затевалось.

  • Благодаря DSR и Maglev получили возможность масштабироваться и не стали точкой отказа, не переживаем за выход из строя ноды балансировщика. 

  • Гибкость в миграции между поколениями и конфигурациями.

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

Но самый неожиданный бонус — это возможность балансировки на виртуальные машины

Удивительно но факт, с Anycast мы не могли себе этого позволить в нашей инфраструктуре. 

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

Байпасим балансировщик

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

Мы делаем это, потому что сервис стал очень популярным.  К примеру, вот скриншот загрузки одного из внутренних балансировщиков с карточкой на 25 гигабит.

Как видите, во время пиковой нагрузки сетевая карточка бывает заполнена. Если ничего не менять, в какой-то момент мы начнём терять соединения. Естественно, нам этого не хочется.

Если перефразировать в более понятные термины, мы хотим:

  • Снизить нагрузку на балансировщики, чтобы не ставить их бесконечное количество ради одиночных пиков.

  • Снизить возможные задержки прохождения трафика.

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

Сразу важная оговорка: байпасить возможно, только если мы контролируем обе стороны — и клиентов, и реальный сервер, то есть только внутри инфраструктуры. Вряд ли ваш внешний клиент поставит себе какое-то ещё ПО добровольно.

Обходить будем только для TCP, потому что в UDP может быть односторонняя связь и мы не сможем гарантировать, что там решение будет работать.

Первым делом нам надо взглянуть на TCP Header.

За два первых пакета, которые происходят при установлении TCP-соединения, клиенту надо как-то сообщить серверу, что он поддерживает режим байпас. Серверу в ответ надо сообщить реальные адреса, которые клиент должен впоследствии использовать. Кажется, что идеальное поле для этого Options, и мы действительно будем его использовать, но только для сигнализации того, что мы работаем в режиме байпас. В принципе, его можно было бы использовать и для передачи информации о реальных адресах. Согласно RFC, там достаточно места для такой операции. Для IPv4 нам надо 4 байта, для IPv6 16 байт, чтобы передать 2 адреса, то есть всего 20. Но мы не будем этого делать, потому что поле опций широко используется, мы можем начать конфликтовать с какими-то другими опциями, которые есть в системе. Поэтому для отправки информации об IP-адресах мы будем использовать Payload.

С этой теоретической информацией давайте посмотрим, как можно реализовать обход балансировщика. Тут нам поможет eBPF. Программа будет запоминать сессии на клиенте и сервере.

На клиенте у нас будет клиентская часть eBPF-программы. Она добавляет  в SYN-пакет опцию, сигнализирующую, что мы поддерживаем режим обхода. Этот первый SYN-пакет, как обычно, проходит через балансировщик. Балансировщик выбирает нужный реальный сервер. На нем находится серверная часть eBPF программы. Она видит, что клиент пришёл и в ответном SYN-ACK-пакете, просто вставит в Payload свои реальные адреса. Клиент получит этот SYN-ACK-пакет, распакует eBPF и будет использовать его в дальнейшем. То есть всё дальнейшее общение у нас будет проходить напрямую между клиентом и сервером в обход балансировщика.

Таким образом, мы получили полный байпас, кроме одного первого пакета.

Байпас vs L4B

Давайте сравним два подхода.

  • Значительное (вплоть до 35%) увеличение производительности по bps для долгоживущих соединений с большим upload. 

  • Незначительное (до 1%) уменьшение производительности по RPS для короткоживущих запросов.

По перфомансу для долгоживущих соединений с большим upload, которые генерили пиковую нагрузку, мы получили 35% рост полосы для таких соединений. Но, к сожалению, в этой жизни редко бывает что-то бесплатно, поэтому мы получили порядка 1% импакт на перфоманс в плане мелких запросов типа HTTP GET.

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

С балансировкой закончили — поговорим про флуды.

L4 DDoS

Согласно отчёту CloudFlare проблемы с DDoS существуют. Они не выдуманные.

Количество и мощность атак, как L4, так и L7, растет год от года, и мы не исключение. Например, в прошлом году была атака порядка 380Gb/s. Забавно, что была она 3 декабря во время конференции HighLoad.

Другая атака была в 3 сентября 2025 года.

Здесь представлены данные в миллионах пакетов в секунду из Netflow, поэтому надо сделать небольшую поправку где-то на 15%. В пиках мощность атаки была порядка 80 миллионов PPS. Было несколько волн, самые большие по PPS — это SYN-flood, дальше SYN-ACK flood. На графике не видно, но был довольно продолжительный хвост, который длился порядка двух дней, мелкого SYN-flood в районе нескольких гигабит. Но самое примечательное в этой атаке — это не размер или длительность, а её дата — 3 сентября, сразу после полуночи. Злоумышленник попался с юмором.

Как оказалось, L4 балансировщик — это нормальное место для того, чтобы защищ��ться от флудов. Трафик и так проходит через него, а eBPF/XDP позволяет довольно гибко и быстро его обрабатывать.

Как я уже говорил, для внешнего трафика мы ставим L4-балансировщик максимально близко к точке входа. К тому же лежащие в его основе и BPF, и XDP, заточены на обработку пакетов, быструю и гибкую. Поэтому кажется, что действительно лучшего кандидата для встречи с DDoS не найти.

Что можно сделать, чтобы отбить атаку:

  1. Отключать ненужный трафик — dst-порты, протоколы.

Это L4-балансировщик. Значит, мы ждем какое-то сочетание в destination IP-адреса, протокола и порта. Если это не совпадает с тем, что мы ждем, можем смело дропнуть.

2. Дропать трафик, который мы не ждём, например, SYN-ACK пакеты. 

Мы можем фильтровать трафик нормальный с точки зрения заголовков, но ненормальный с точки зрения каких-то флагов. Например, мы как сервер не ожидаем, что клиент пришлет нам SYN-ACK, потому что всеми SYN-ACK’ами будем отвечать мы. Соединение к нам инициирует клиент, а не мы к нему.

3. Дропать трафик с подозрительных адресов. 

То есть если мы насобирали какую-то пачку, в принципе, можно дропать. Но тут надо это делать, конечно, аккуратно, потому что можно зацепить не тех, кого надо.

4. Дропать фрагментированный трафик. 

На самом деле Katran и так не работает с фрагментированным трафиком, поэтому мы ничего не потеряем, и это будет бесплатно.

Окей, с явно нелегитимным трафиком все понятно. Теперь разберёмся, что делать, если трафик выглядит легитимно.

Можно делать проверку клиента методом сброса сессии.

Клиент присылает SYN-пакет, мы перехватываем его до балансировщика, в SYN-ACK ставим какое-нибудь значение — либо куки, то есть криптографически высчитываем это значение, либо рандомно, либо статически, неважно. Легитимный клиент в ответ на такую дерзость с нашей стороны пришлет reset, скажет: «Что ты мне прислал? Это какая-то ересь, я этого не ожидал». Естественно, мы увидим, что клиенту это не понравилось, значит, он легитимный, он инициировал connect, и запишем его IP и порт к себе в базу аутентифицированных клиентов. Потом клиент пришлет первоначальный SYN Retransmit, как того требует протокол TCP, и мы его уже дальше пропустим в обработку балансировщику, где он пройдет все стадии балансировки, которые мы уже обсуждали, и попадёт на сервер.

Так как это решение по защите от DDoS, ему важен перфоманс. Производительность проверяли в той же лабе, где тестировали балансировщик.

Получили производительность для такого метода порядка 50 миллионов PPS. Это всё бездроповые режимы, с дропами, естественно, будет больше.

До этого я рассказал, что пакет передаётся в обработку балансировщику, но не сказал, как. Конечно, можно все добавить в код самого балансировщика, но тогда у нас будут проблемы, что два функционала в одном бинаре, и как их обновлять, не очень понятно. Поэтому можно использовать XDP- chaining.

XDP- chaining

В Linux-мире XDP- chaining — это возможность собрать в одном сетевом интерфейсе несколько разных XDP-программ. Они загружаются в виде цепочки и исполняются друг за другом. Это полезно для комбинирования нескольких eBPF-модулей.

Плюсы и минусы XDP-chaining

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

  • Модульность, гибкость, меньше конфликтов между eBPF программами.

  • Позволяет совмещать LB + мониторинг + фильтрацию.

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

Мы можем комбинировать программы разного типа между собой: фильтрацию, балансировщик, мониторинги, flow-экспортеры (sFlow, NetFlow и IPFIX) и т. д. Это упрощает нам административную жизнь, потому что мы можем обновлять независимо, быстрее довозить код, изменения и прочее.

Минусы:

  • Добавляет немного latency. 

  • Требует контроля порядка.

Так как количество программ у нас увеличивается, пакет несколько раз передается в обработку, немного вырастает latency. Такой подход требует чуть больше административных усилий для контроля порядка, потому что если мы где-то ошибемся с порядком обработки пакетов, то могут возникнуть проблемы. 

Выводы (не советы)

  • Внедрять L4-балансировку 

Если так же, как и мы, вы столкнулись с недостатками Anycast, рекомендую внедрять L4-балансировку. Это не обязательно должен быть XDP Katran, возьмите любое решение, главное, чтобы оно вас устраивало. 

  • Делать байпас там, где это возможно 

Байпасьте все, что можно, снижайте нагрузку. Не надо закапывать деньги там, где это не нужно. 

  • Совмещать роли устройств, используя XDP chaining

Упрощайте себе административную жизнь и делайте бол��е крутые решения более гибкими.

Скрытый текст

А чтобы узнать больше о мире высоконагруженных систем, приходите на Saint HighLoad 2026! В этом году SHL пройдёт в формате конференции развития. Конференция становится больше практикумом, чем лекциями, а вы — участником, а не слушателем. Больше интерактивных форматов и нетворкинга, знаний, новых контактов и инсайтов!