Привет, Хабр! На связи Саша Лопинцев, SRE в группе разработки сетевой инфраструктуры и мониторинга Yandex Infrastructure. Я очень люблю мониторинг — а когда дело касается видимости сетевого трафика, нам не обойтись без анализа flow-данных. 

Сегодня расскажу, как и почему мы переехали с устаревшего flow-коллектора на GoFlow2, реализовали запись в БД и через etcd решили проблемы с шаблонами.  Новая система обрабатывает 85 тысяч пакетов статистики в секунду, обеспечивает отказоустойчивость и помогает создавать отчёты. Если вам интересно узнать чуть больше об архитектуре, экспериментах, ошибках и решениях, полезных для инфраструктурного мониторинга в продакшн-среде, читайте далее.

Что, где и зачем мы собираем

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

Статистику мы снимаем на границах дата‑центров и на интерфейсах взаимодействия с пиринг‑партнёрами. Внутри собственной сети flow‑данные передаются на коллекторы по протоколу sFlow, а на внешних стыках используется IPFIX. Объём телеметрии при этом огромен, поэтому сбор реализован с использованием сэмплирования, что позволяет снизить нагрузку на оборудование и систему хранения данных.

Сэмплирование в контексте flow‑статистики — это механизм выборочного учёта пакетов, при котором из общего потока отбирается фиксированная доля, например каждый N‑й пакет. Информация только по выбранным пакетам отправляется на flow‑коллектор.

У нас много лет использовался коллектор, который собирал статистику только в агрегированном виде. Агрегация выполнялась по сервисам, сетевым префиксам, дата‑центрам и автономным системам. Детальная информация о конкретных IP‑адресах и потоках между ними не сохранялась.

Для работы коллектора применялся дополнительный UDP‑балансировщик, который отвечал за распределение IPFIX‑шаблонов и параметров сэмплирования между всеми экземплярами системы. Сами коллекторы были развернуты в LXD‑контейнерах, а данные сохранялись в ClickHouse. 

Старый коллектор не тянет — ищем новый

Но с legacy‑коллектором были сложности. Дополнительный UDP‑балансировщик добавлял лишний сетевой хоп между маршрутизаторами и коллекторами, а при росте нагрузки пакеты терялись, что приводило к неполноте статистики. Архитектура плохо масштабировалась, поскольку балансировщик должен был поддерживать полносвязное взаимодействие со всеми экземплярами коллекторов. 

Дополнительную сложность вносило использование LXD‑контейнеров для всех компонентов. И самое важное — к нам всё чаще и чаще приходили с вопросами о том, кто конкретно вызвал перегрузку канала связи. Единственное, что мы могли сказать: что это сервисы, между которыми прошёл трафик. В какой‑то момент времени такое положение дел перестало нас устраивать, и мы начали искать альтернативы. 

Работу над новым решением начали со сбора требований к коллектору:

  • Поддерживать основные протоколы экспорта flow‑данных — IPFIX, sFlow и NetFlow. 

  • Устойчиво переживать отказ одного из дата‑центров без потери статистики.

  • Обеспечивать горизонтальную масштабируемость. 

  • Иметь внутренние метрики, позволяющие отслеживать текущее состояние системы, например размер очередей.

  • Своевременно выявлять потерю данных. 

При этом коллектор планировали развернуть в Kubernetes. Что до производительности — нам требовалась способность обрабатывать не менее 100 тысяч запросов в секунду. В качестве БД решили оставить ClickHouse. 

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

В нашей команде NOCDEV мы много пишем на Go, поэтому искали коллектор, написанный на этом же языке. Вариантов оказалось немного: фактически четыре, причём часть из них уже неактивна. Например, VFlow не обновлялся с 2022 года, поэтому его детально не рассматривали. Аналогичная ситуация была с GoFlow — но этот проект по крайней мере получил развитие в виде GoFlow2.

Ещё был коллектор Akvorado, с которого мы начали. Выяснилось, что для разбора пакетов он использует GoFlow2, при этом других существенных преимуществ, критичных для наших требований, он не давал. Так что мы решили остановиться на GoFlow2.

Мы форкнули GoFlow2 и развернули тестовый стенд в Kubernetes: flow‑трафик поступал через L3-балансировщик и распределялся между подами коллектора, разнесёнными по разным дата‑центрам. В качестве хранилища использовался облачный ClickHouse. Для хранения информации о шаблонах и sampling‑rate мы развернули отдельный кластер etcd, который был нужен для корректной работы системы — об этом ниже.

Здесь важно учитывать особенность протокола IPFIX. В отличие от NetFlow v5, где формат пакета фиксирован и заранее известен, в IPFIX структура экспортируемых данных описывается шаблонами и может меняться. Для корректного разбора flow‑данных у каждого коллектора должны быть актуальные шаблоны, которые экспортёры периодически пересылают.

Примеры IPFIX-шаблонов
Примеры IPFIX-шаблонов

В GoFlow2 хранение шаблонов реализовано в памяти. Нам это не подходило, нужно было сделать централизованное хранилище. В качестве вариантов рассматривали Redis и etcd. 

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

При запуске каждый экземпляр коллектора открывает сессию и подписывается в etcd на изменения шаблонов и параметров сэмплирования. Когда любой из коллекторов получает новый IPFIX‑шаблон, он сохраняет его в общий кластер etcd, после чего остальные экземпляры получают обновление в течение нескольких секунд и начинают использовать актуальную схему разбора данных. Чтобы коллектор мог разбирать пакеты с статистикой сразу после старта, он подгружает ранее полученные шаблоны из etcd.

Разбираемся с хранилищем шаблонов и ищем оптимальный способ записи данных в ClickHouse

В GoFlow2 нет механизма записи данных напрямую в базу. Проект поддерживает отправку данных в Kafka, однако в NOCDEV Kafka не используется, и такое решение добавило бы отдельный сервис и дополнительную точку отказа. Поэтому мы решили реализовать запись в базу данных напрямую.

Оказалось, что ClickHouse предоставляет несколько способов загрузки данных, включая SQL‑ и нативный протоколы, а также запись в локальные и распределённые таблицы. Для обеспечения максимальной производительности мы выбрали нативный протокол, который даёт кратный выигрыш по скорости по сравнению с SQL‑вставками. 

В первом подходе запись реализовали в распределённые таблицы, при этом ClickHouse самостоятельно распределяет данные по шардам с использованием встроенного механизма шардинга.

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

Это нас не устроило, поэтому мы перешли к варианту с записью в локальные таблицы. 

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

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

Как видно на графике, запись через распределённые таблицы потребляет примерно на 30% больше полосы пропускания и на 20% больше памяти по сравнению с записью в локальные таблицы. После перехода на локальную запись данные начали стабильно сохраняться, и мы было возрадовались. Но через 4 часа запись остановилась.

Что-то пошло не так…

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

После выявления причины мы быстро нашли решение: сократили частоту вставок и увеличили размер батчей. В соответствии с рекомендациями ClickHouse запись перевели на режим не чаще одного раза в секунду с размером батча не менее 100 тысяч записей. После этого запись восстановилась.

Однако сами по себе flow‑данные на уровне IP‑адресов оказались недостаточно информативными. Практическая ценность существенно возрастает при наличии контекста: информации о сервисах, которым принадлежат адреса, дата‑центрах, между которыми передавался трафик, и автономных системах. 

Это привело нас к следующему этапу — обогащению flow‑данных.

Первый подход был чуть наивным: мы планировали, что коллектор будет сохранять данные в базу, а периодически запускаемая задача — дополнять их информацией по IP (сервисы, автономные системы, дата‑центры). 

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

Так у нас появился первый справочник для проставления дата‑центра по префиксу IP (source/destination). Затем — справочник для определения сервиса и ещё один для номеров автономных систем (данные из BMP). 

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

Это было неожиданно. Начали выяснять детали, и обнаружили, что коллектор большую часть времени тратит не на обработку пакетов, а на сборку мусора. Go использует динамическое управление памятью с автоматической сборкой мусора, и частые выделения в цикле обработки статистики перегружали garbage‑коллектор.

Оптимизировали это место: уменьшили частоту аллокаций, добавив синк‑пулы. В итоге коллектор заработал нормально, да ещё и потребление памяти снизилось с 700 ГБ до 16 ГБ на экземпляр. Существенная экономия ресурсов.

Как хранить статистику

За час набирается примерно 3 млрд записей сырых данных. Когда мы начали строить отчёты, стало ясно, что выборки идут слишком долго, а иногда запросы просто падали. Поэтому мы использовали встроенные в ClickHouse механизмы агрегации и сделали таблицы с округлением времени до 5 минут. Это сократило количество записей с 3 млрд до 1 млрд. Тоже немало, и отчёты теперь формируются быстро, и дежурный может за пару секунд понять, какие источники трафика перегружают канал.

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

Настал день X: мы выключили старый коллектор и сразу заметили, что количество запросов на запись в ClickHouse упало — со 160 до 4. В этом нам сильно помогла оптимизация работы с базой и увеличение размеров батчей. Экономия ресурсов тоже радовала глаз: раньше на обработку статистики уходило 240 ГБ памяти, теперь всего 40 ГБ. По CPU тоже была приличная экономия.

Так как статистика отправляется сэмплированной, возник вопрос: не потеряем ли точность? Проверка по SNMP‑счётчикам показала: сэмплированная статистика почти не отличается, серьёзных расхождений нет.

Для удобного доступа к данным мы сделали отчеты на базе DataLens — системы для дашбордов и бизнес‑аналитики. Дежурный теперь может быстро увидеть источники перегрузки каналов или изменения трафика по префиксам. Появились отчёты, мы научились сохранять шаблоны и всё работало стабильно. 

Но тут в цепочке передачи трафика появилось дополнительное оборудование одного из вендоров. И оказалось, что статистика от него шла некорректно.

Мы начали разбираться, в чём проблема. Собрали дампы трафика и сразу заметили: sampling rate передавался некорректно. После анализа шаблонов выяснилось, что оборудование от вендора отправляет эту информацию в формате, который GoFlow2 не поддерживал — данные шли сразу в двух полях. Исправить это было несложно: мы добавили поддержку нового способа передачи sampling rate, и статистика снова стала корректной.

Итоги

В результате за несколько месяцев работы и пару пробежек по полю с граблями мы получили новый коллектор, который умеет писать данные напрямую в базу, одновременно обогащая их бизнес‑метриками через подключаемые модули. Коллекторы горизонтально масштабируются, подписка на etcd позволяет распределять нагрузку. Благодаря централизованному хранилищу шаблонов и sampling rate он стал масштабироваться горизонтально, а сбор информации от вендорского оборудования теперь осуществляется без проблем. Система отчётов и дашбордов на базе DataLens позволяет дежурным и аналитикам быстро получать актуальную информацию, а вся инфраструктура стала более надёжной и управляемой. Ну и на конкретных цифрах мы видим оптимизацию ресурсов: потребление памяти уменьшилось в 6 раз, CPU — минимум в 3 раза. 

Мониторинг состояния коллектора ведётся через множество встроенных метрик. Отслеживаются метрики, описывающие работу коллектора с сервером БД, ClickHouse, а также ошибки и события, связанные с etcd. Это позволяет видеть, что делает коллектор и выявлять проблемы с доставкой статистики. Метрики отдаются в формате Prometheus, поэтому их можно собирать в любые системы визуализации или дашборды, включая Grafana.