Всем привет! Меня зовут Антон Ильичев, я сетевой инженер в Авито. В этой статье расскажу, зачем мы централизованно собираем и анализируем маршрутную информацию с сетевых устройств, причём тут протокол BMP и как устроена наша система мониторинга. В конце вас будет ждать лаба на docker-compose, которую вы можете запустить у себя и посмотреть на систему в действии.
Статья будет полезна в первую очередь сетевым инженерам, командам SRE и мониторинга, которые отвечают за доступность и качество сервиса.
Сразу отмечу, что в качестве протокола маршрутизации здесь мы рассматриваем только BGP. Помимо связи автономных систем в интернете, он фактически стал стандартом в сетях дата-центров крупных гиперскейлеров.
В этой статье
Для чего нужен мониторинг маршрутной информации
Сбор и анализ маршрутной информации используется не только для диагностики проблем, но и для решения разных эксплуатационных задач. Рассказываю подробнее.
→ Looking-glass-сервис как для внутренних заказчиков, так и для внешних пользователей (внешние системы предоставляют ISP; например, один из таких известных сервисов — у Retn). Используется, чтобы посмотреть, как выглядит BGP-анонс из разных точек сети и как изменялись его атрибуты по пути.
→ Продвинутый траблшутинг control-plane составляющей маршрутизации. Можно собирать информацию о BGP-соседствах, получать маршрутную информацию из разных BGP-таблиц (RIB-IN, Local RIB, RIB-OUT). На основании этихданных можно проводить анализ и строить комплексные отчёты и алертинги на определённого рода изменения в сети. Например:
мониторить определённые маршруты на сетевых устройствах и при их флапе или исчезновении зажигать алерт в системе;
узнать, какие префиксы или BGP-пиры чаще всего флапают;
отслеживать динамику update- и withdraw-сообщений в сети и коррелировать это с авариями в роутинге;
получать статистику по каждому роутеру, например, сколько входящих анонсов он отбросил по каждому пиру из-за inbound-политик.
→ Обогащение сторонних систем маршрутной информацией. Например, для traffic engineering или для системы менеджмента anycast-префиксов, о которой мы поговорим дальше.
→ Мониторинг утечек маршрутов внешних префиксов. Это не наш кейс, но есть доклад на Nexthop, где коллеги применяют такую систему для отслеживания route leaks.
→ Capacity planning — возможность динамически получать информацию, сколько сейчас префиксов в сети и сколько host-routes.
Этим список применений не ограничивается: почти всегда можно найти уникальный кейс, где сбор такой информации может оказаться полезным. Я привёл лишь самые популярные варианты использования.
Какими способами получают маршрутную информацию
В этой главе расскажу, как собирать необходимые данные с сетевых устройств. Есть три основных способа, о них и поговорим.

Способ первый — screen scraping
Мы подключаемся по SSH с коллектора к узлу, выполняем на нём нужную диагностическую команду, получаем вывод, парсим его, то есть анализируем и извлекаем нужные данные, и передаём их дальше по конвейеру системы мониторинга.
Вот простой пример на Python:
import yaml import re from netmiko import (ConnectHandler, NetmikoAuthenticationException, NetmikoTimeoutException) from concurrent.futures import ThreadPoolExecutor from itertools import repeat def send_show_command(device, command): try: with ConnectHandler(**device) as ssh: ssh.enable() output = ssh.send_command(command) return output except (NetmikoTimeoutException, NetmikoAuthenticationException) as error: print(error) def parsing(show_output): regex = r'Routing entry for (?P<prefix>\S+/\d+).+Known via "(?P<proto>\S+ \d+)".+ on (?P<int>\S+),.+' result = re.search(regex, show_output, re.DOTALL).groups() return result if __name__ == "__main__": with open("devices.yaml") as f: devices = yaml.safe_load(f) with ThreadPoolExecutor(max_workers=3) as executor: result = executor.map(send_show_command, devices, repeat('sh ip route 4.4.4.4')) for device, output in zip(devices, result): print(f"Raw output for router {device['host']}:\n{output}\n") parse_result = parsing(output) print(f"Parsed output for router {device['host']}:\n{parse_result}\n")
Скрипт подключается к роутеру, выполняет на нём команду show ip route 4.4.4.4, сначала выводит сырые данные с устройства, а затем — результат после парсинга.
admin@netlab:~/netops/netmiko# python run_show_command.py Raw output for router 192.168.1.166: Routing entry for 4.4.4.4/32 Known via "ospf 1", distance 110, metric 2, type intra area Last update from 10.0.0.9 on GigabitEthernet4, 1w2d ago SR Incoming Label: 16004 Routing Descriptor Blocks: * 10.0.0.9, from 4.4.4.4, 1w2d ago, via GigabitEthernet4, prefer-non-rib-labels, merge-labels Route metric is 2, traffic share count is 1 MPLS label: implicit-null MPLS Flags: NSF Parsed output for router 192.168.1.166: ('4.4.4.4/32', 'ospf 1', 'GigabitEthernet4')
Помимо Local-RIB, так можно собирать информацию и из BGP-таблиц соседств — RIB-IN и RIB-OUT.
У такого подхода есть некоторые недостатки:
При опросе скриптами вы подключаетесь к маршрутизатору, выполняете диагностическую команду и получаете большой объём текстового вывода. Этот текст нужно обработать, парсить и извлечь нужные данные, что особенно проблематично, если в сети много вендоров и используются разные версии ПО. У каждого производителя вывод организован по-разному, поэтому под каждого приходится писать кастомные регулярные выражения и скрипты либо использовать готовые фреймворки, например такой как TextFSM.
Pull-модель опроса. Если в сети происходит сбой, важно узнавать об этом сразу, оперативно определить текущее состояние сети. Со скриптами это не всегда возможно, потому что они запускаются периодически, например раз в 10–20 секунд, и проблема может возникнуть именно в этом промежутке. В таком случае её можно просто не задетектить. Скриптами сложно обеспечить ту же гранулярность мониторинга, потому что роутер сам не отправляет обновления, а данные собираются только по расписанию.
Способ второй — BGP-коллектор
В этом случае с коллектора устанавливаются BGP-сессии со всеми сетевыми устройствами, которые необходимо мониторить, и по BGP принимаются анонсы. У такого подхода тоже есть нюансы. Вы фактически держите активную BGP-сессию с продакшн-устройствами, и в случае ошибок существует риск, что с этого коллектора можно что-то переанонсировать и повлиять на работу сети. А ещё такой вариант не позволит просматривать все BGP-таблицы маршрутизатора. Вы получаете только те анонсы от пира, которые выиграли в процессе BGP best path selection и прошли его output-фильтры.
При этом у решения есть и плюсы:
Push-модель мониторинга. Сам пир отслеживает BGP-события и сразу уведомляет вас в случае изменений.
Те устройства, которые не поддерживают BMP (о котором дальше пойдёт речь), можно мониторить с помощью BGP-коллектора.
Способ третий — BMP
Это протокол для сбора BGP-данных, который при этом не требует установления активных BGP-сессий. В этом смысле он безопаснее: BMP является мониторинговым протоколом, он не участвует в обмене маршрутами и не влияет на принятие решений в маршрутизации, в отличие от BGP. Благодаря этому не нужно опасаться, что через него можно что-то проанонсировать в сеть и нарушить её работу.
А ещё с помощью BMP можно получать информацию сразу из нескольких BGP-таблиц маршрутизатора, если вендор поддерживает такой функционал. Ещё одно важное преимущество, как и у BGP-коллектора, — push-модель получения данных. BMP-агент на устройстве сам отслеживает изменения в маршрутизации и сразу отправляет обновления коллектору.
У этого подхода есть и недостатки. Протокол относительно молодой (по меркам сетевых технологий), и не все вендоры одинаково хорошо и полностью его поддерживают. Например, есть устройства, где реализована только поддержка самого первого RFC (привет Cisco NX-OS), и получить данные из Local RIB в таком случае невозможно. Как это часто бывает в сетях, разные производители могут по-разному интерпретировать RFC.
Получается, что в мультивендорной сети, где поддержка BMP реализована по-разному, часто приходится использовать комбинацию нескольких подходов. Например, связку BGP- и BMP-коллекторов, чтобы компенсировать нехватку данных.
Из всех вариантов мы будем рассматривать только протокол BMP как основу нашей системы мониторинга. Мы остановились именно на нём, потому что наше оборудование хорошо его поддерживает: сеть полностью построена на BGP, используется L3 до хоста, во всех дата-центрах применён однотипный дизайн, а на устройствах, с которых мы собираем данные, BMP-агент соответствует требованиям RFC.
Немного теории о протоколе BMP
BMP (BGP Monitoring Protocol) — это протокол для мониторинга работы BGP-сессий на сетевых устройствах. Он описан в трёх основных RFC: 7854, 8671 и 9069.
Если говорить кратко, BMP работает так: маршрутизатор сам инициирует соединение с коллектором, передаёт на него данные, при этом по стандарту коллектор ничего не должен отправлять обратно на устройство. Как я писал выше, BMP не участвует в принятии решений маршрутизации и не создаёт рисков для продакшн-сети.
Под капотом BMP-агент на сетевом узле устанавливает TCP-сессию с коллектором, при этом конкретный dst port в RFC за BMP не закреплён. На коллекторе можно задать любой свободный порт, после чего передаются BMP-сообщения, вложенные в TCP.
Вот так выглядит дамп сообщения Route Monitoring:

Какие данные умеет передавать BMP-агент:
→ Сообщения статистики (Stats Reports) по каждому пиру, который на роутере поставлен на BMP-мониторинг. Например, сколько конкретный пир зареджектил префиксов в своей входящей политике, сколько BGP-updates находятся в состоянии invalid из-за AS-PATH loop и многое другое. Такие сообщения отправляются периодически — через определённый интервал времени. Если вендор это позволяет, можно сконфигурировать таймер отправки или отключить такие сообщения вовсе.
→ Сообщения Route Monitoring / Route Mirroring (они же BGP-update/withdraw) из разных точек локального BGP-процесса. На этом остановимся подробнее.
В BGP есть несколько таблиц: то, что мы получаем от пира (Adj-RIB-In), локальная BGP-таблица после механизма best-path-selection и то, что мы отдаём соседу (Adj-RIB-Out). BMP позволяет снимать данные из этих точек. Более того, он даёт возможность для каждой таблицы, кроме Local-RIB, посмотреть, как маршруты выглядели до применения политик (pre-policy) и после них (post-policy).
Для наглядности всё это изображено на рисунке ниже:

Хочу сказать, что BGP-данные со всех вышеперечисленных мест будут доступны только в том случае, если ваш вендор поддерживает все три RFC. Изначально BMP поддерживал только таблицу Adj-RIB-In, и только в двух последних RFC появилась поддержка Adj-RIB-Out и Local RIB.
→ Сообщения о состоянии BGP-сессий (peer-up и peer-down) уведомляют о появлении сессии с новым пиром и о причине разрыва сессии.
Я привёл краткую выжимку о работе протокола. Читателям, которым интересно погрузиться глубже во все нюансы, советую другую замечательную статью.
Зачем BMP в Авито и какие проблемы это решает
До внедрения BMP у нас в Авито отсутствовал какой-либо мониторинг маршрутов. Мы собирали метрики с сетевых устройств средствами SNMP/GNMI, логи и использовали матрицу доступности. Мы запускали тестовые соединения с одной группы серверов на другие, в том числе между разными ДЦ, и смотрели, какие возникают задержки, какие серверы недоступны и где могут быть проблемы с приёмом трафика. При этом мы не могли напрямую наблюдать изменения, которые происходят на сетевых устройствах в части control plane. Случались аварии, которые показали, что более глубокая информация о маршрутизации могла бы помочь в будущем.
В итоге, чтобы закрыть эту проблему, а также менеджмент anycast-префиксами, мы выбрали BMP. С его помощью решили две основные задачи.
Мониторинг anycast-адресации. В сети есть сервисы, которые используют anycast-адресацию. Коллеги из внутренней платформы выделяют из специального диапазона адреса для своих сервисов, при этом инфраструктурной команде важно понимать, какие префиксы реально задействованы, а какие выделены, но фактически не используются. Нам требовалась система, которая отслеживает активные и неактивные anycast-префиксы в сети.
Система сверяет фактическое состояние префиксов в сети с теми, что выделены в базе. Если префикс не используется, например в течение месяца, он помечается как неактивный, высвобождается, и коллегам автоматически приходит уведомление о возврате префикса в пул свободных.
Улучшение траблшутинга, поиск и устранение неисправностей. Вторая задача была связана с мониторингом сети и траблшутингом. В сети периодически возникали сложные поломки, флапы маршрутов и сбои внутри оборудования. С BMP мы можем анализировать такие кейсы: дежурный может посмотреть, что происходило с маршрутизатором в момент аварии, и получить необходимую диагностику. Так мы можем видеть, какие флапы происходили внутри, как менялись атрибуты и как изменялись анонсы в сети в момент инцидента. Такой исторический анализ помогает понять, что именно произошло, что привело к аварии и что нужно предпринять, чтобы избежать подобных ситуаций в будущем.
Архитектура системы мониторинга маршрутов
Система мониторинга разрабатывалась на основе уже существующих в компании практик деплоя сервисов: лёгкость масштабирования и отказоустойчивость компонентов на каждом уровне системы, использование стека технологий, в котором есть компетенции и который уже обкатан в компании, а ещё, по возможности, применение платформенных сервисов для базы данных и шин данных. При этом важно было сохранить простоту дизайна и лёгкость интеграции с другими нашими системами. Итоговая архитектура системы — на иллюстрации ниже:

Давайте пройдёмся по каждому компоненту системы подробнее.
BMP-коллектор и сетевые устройства
Мы развернули BMP-коллекторы на трёх физических хостах в разных дата-центрах, которые уже использовались для других задач NOC-мониторинга. На каждом сервере в Docker-контейнере работает BMP-коллектор, который принимает сообщения от сетевых устройств, парсит их и направляет дальше по конвейеру обработки данных.
Развёртывание и конфигурация коллекторов полностью автоматизированы с помощью Puppet, который отвечает за доставку контейнеров на серверы, настройку конфигурационных файлов и интеграцию вспомогательных утилит. Когда контейнер с коллектором задеплоен, маршрутизаторы автоматически устанавливают BMP-сессии с коллектором, который принимает и разбирает сообщения, далее отправляя их в кластер Kafka. Коллектор выступает не просто приёмником данных, а полноценным звеном, которое превращает сетевые события в структурированный поток информации.
Выбор конкретного коллектора стал результатом нашего R&D. Рассматривали несколько open-source решений: OpenBMP, GoBMP и pmacct.
→ OpenBMP исключили, потому что проект фактически не развивается с 2022 года, имеет слабую документацию и мало реальных кейсов, а ещё содержит некоторые баги в парсинге BMP-сообщений. Один из критичных кейсов — некорректная обработка поля Next Hop в IPv6-сценариях в режиме RFC5549, когда между роутерами поднимается IPv6-сессия, но передаются IPv4-префиксы с IPv6 Next Hop. В таком режиме OpenBMP неправильно декодировал поле nexthop в сообщении, что делало его непригодным для продакшена. Ещё, например, было непонятно, как определить, пришло ли сообщение для таблицы Adj-RIB-Out или Local-RIB, так как после парсинга коллектор отдаёт только булевые поля isprepolicy и isadjribin.
→ GoBMP по функциональности оказался ближе к требованиям, но на момент тестирования был ещё незрелым продуктом со своими багами. Например, он не мог корректно обработать сообщения, в которых автономная система была представлена как 4 байта. А ещё, как и у предыдущего коллектора, у него довольно скудная документация.
→ В итоге выбор пал на pmacct, а точнее — на его компонент pmbmpd. Это зрелый проект с историей, активным сообществом и хорошей документацией. Он поддерживает несколько протоколов сбора телеметрии: помимо BMP и BGP, pmacct умеет работать с NetFlow и SNMP. Это означает, что в будущем мы сможем обогащать BMP-данные NetFlow-статистикой, используя тот же софт.
От Kubernetes для развёртывания коллекторов отказались осознанно — из-за надёжности мониторинга в момент сетевых аварий внутри самого k8s. Сетевые инциденты могут затронуть и сетевой слой Kubernetes, а значит существует риск потерять BMP-данные именно в тот момент, когда они наиболее важны. Использование выделенных физических серверов снижает количество точек отказа и делает систему более предсказуемой.
Для обеспечения отказоустойчивости коллекторов используется Anycast. Всем коллекторам назначен один Anycast IP-адрес, который анонсируется в сеть по BGP с каждого сервера. Маршрутизаторы настраиваются только на одну BMP-сессию — с этим Anycast-адресом. Конкретный коллектор выбирается автоматически на основе лучшего маршрута, как правило внутри своего дата-центра.
Если один из коллекторов падает, например из-за сбоя приложения, нехватки ресурсов или планового обслуживания сервера, его анонс снимается, и роутеры автоматически переподнимают BMP-сессии на другой коллектор с тем же Anycast-адресом. Это решает сразу несколько задач:
обеспечивает отказоустойчивость без необходимости настраивать несколько BMP-сессий на каждом устройстве;
избавляет от дублирования данных: роутер отправляет BMP-сообщения в конкретный момент времени только в один коллектор, а не в несколько одновременно. Это упрощает обработку данных на бэкенде и не требует дополнительной дедупликации в базе.
Живость коллекторов контролируется на уровне сервера. Для этого используется отдельная самописная Python-утилита watchanycast, которая проверяет живость Docker-контейнера с сервисом pmbmpd и корректность его работы по логам. При обнаружении сбоя утилита отдаёт команду демону FRR на снятие Anycast-анонс с сервера. В результате маршрутизаторы автоматически перестают отправлять BMP-сообщения на проблемный узел и перестраивают сессии на оставшиеся рабочие коллекторы. Настройка утилиты описана в Puppet-манифестах и не требует ручного вмешательства.
Приведём пример конфигурации pmbmpd-контейнера для подключения к кластеру Kafka в файле /etc/pmacct/pmbmpd.conf:
!common daemon settings logfile: /var/log/pmbmpd.log bmp_daemon: true bmp_daemon_port: 6000 bmp_daemon_max_peers: 100 !kafka settings bmp_daemon_msglog_output: json bmp_daemon_msglog_kafka_topic: bmp !kafka cluster DNS record bmp_daemon_msglog_kafka_broker_host: kafka-cluster.example.org bmp_daemon_msglog_kafka_broker_port: 9192 bmp_daemon_msglog_kafka_config_file: /etc/pmacct/librdkafka.conf
Так как мы используем sasl_ssl для безопасного подключения к Kafka, нужно ещё добавить настройки для библиотеки librdkafka:
global,security.protocol,SASL_SSL global,sasl.mechanisms,SCRAM-SHA-512 global,sasl.username,<username> global,sasl.password,<password> global,ssl.ca.location,/etc/pmacct/certs/ca-certificates.crt global,bootstrap.servers,kafka-cluster.example.org:9192
BMP-данные собираются с ключевых точек сети: DCI-роутеров, которые соединяют дата-центры между собой, и edge-роутеров, принимающих внешний пользовательский трафик. Эти устройства дают полную картину происходящего в маршрутизации. Со временем список устройств может расширяться, но уже на этом этапе система покрывает основные узлы, где сходится вся маршрутная информация.
Вот пример настройки BMP-агента для нескольких вендоров.
Пример конфигурации на Junos:
#Указываем режим работы, в нашем случае всегда active, роутер первым должен инициировать TCP-соединение к коллектору set routing-options bmp connection-mode active #Указываем, с какого адреса роутера мы хотим строить сессию. set routing-options bmp station pmbmpd local-address 10.4.96.2 # Указываем, какие BGP-таблицы хотим мониторить. set routing-options bmp station pmbmpd route-monitoring loc-rib set routing-options bmp station pmbmpd route-monitoring pre-policy set routing-options bmp station pmbmpd route-monitoring post-policy set routing-options bmp station pmbmpd route-monitoring rib-out pre-policy set routing-options bmp station pmbmpd route-monitoring rib-out post-policy # Указываем период сбора статистики по bgp-пирам, по умолчанию каждый час. set routing-options bmp station monitor02 statistics-timeout 60 #Адрес коллектора и порт. set routing-options bmp station pmbmpd station-address 10.0.2.0 set routing-options bmp station pmbmpd station-port 6000 Note: BMP на junos собирает информацию по всем BGP-пирам (как c global пиров, так и с пиров в vrf-ах), которые у него есть.
Пример конфигурации для Cisco NX-OS:
# Настройка аналогичная, как и у juniper. router bgp <AS> bmp-server 1 description bmp_test address 10.104.65.122 port 5000 stats-reporting-period 720 update-source loopback0 # За исключением, что для каждого пира или шаблона нужно активировать bmp. template peer DCI bfd singlehop bmp-activate-server 1
Поток данных: Kafka → ClickHouse
После приёма и парсинга данные отправляются в Kafka. Каждый коллектор выступает продюсером и пишет данные в отказоустойчивый Kafka-кластер, распределённый по дата-центрам. Подключение выполняется по хостнейму с DNS-балансировкой — за одним именем скрываются несколько IP-адресов брокеров, и при установке соединения коллектор автоматически подключается к одному из них.
Сообщения передаются в формате JSON.
Пример распарсенного Route Monitoring, отправленного коллектором в Kafka-топик:
{ "seq": 20774, "log_type": "update", "timestamp": "2025-07-24 09:58:45.056991", "is_post": 0, "is_in": 1, "event_type": "log", "afi": 1, "safi": 1, "ip_prefix": "10.4.96.252/32", "bgp_nexthop": "fe80::d:9:2", "as_path": "4200109999", "comms": "65100:0", "origin": "i", "timestamp_arrival": "2025-08-18 13:58:38.902214", "bmp_router": "10.4.96.2", "bmp_router_port": 64395, "peer_ip": "fe80::d:9:2", "peer_tcp_port": 0, "bmp_msg_type": "route_monitor", "writer_id": "default/1" }
Важно помнить, что в новых версиях pmbmpd могут измениться поля сообщений — такое уже происходило ранее.
Kafka в этой архитектуре используется как центральная шина передачи данных: она принимает поток событий от нескольких коллекторов и обеспечивает их надёжную доставку дальше по конвейеру. Кластер Kafka развёрнут как платформенный сервис и предоставляется команде NOC в виде PaaS-инсталляции. Это означает, что команда сетевых инженеров не тратит ресурсы на администрирование и поддержку кластера: мы только заказываем нужные ресурсы и выстраиваем конвейер данных.
Все типы BMP-сообщений коллектор отправляет в один Kafka-топик — bmp. Это связано с тем, что на данный момент pmbmpd не поддерживает запись разных типов сообщений в разные топики. Это не создаёт проблем, поскольку вся логическая фильтрация и разделение данных выполняются на следующем этапе — в базе данных ClickHouse.
Далее данные считываются кластером ClickHouse, который также развёрнут в нескольких дата-центрах и предоставляется команде NOC как PaaS-сервис. ClickHouse выбран не случайно: такая колоночная СУБД хорошо подходит для аналитики больших объёмов данных, имеет встроенный механизм подключения к Kafka и поддерживает шардирование.
В ClickHouse создана база, внутри которой есть специальная таблица с движком Kafka Engine. Именно она подключается к Kafka-топику и вычитывает поступающие сообщения. Напрямую читать данные из такой таблицы нельзя — она служит только точкой входа данных в систему аналитического хранения.
Инсталляция в данном случае пока относительно небольшая: объём данных в базе составляет порядка 100 Гб, а поток сообщений не требует параллельного чтения. Поэтому в текущей конфигурации в каждый момент времени Kafka-топик читает только одна нода ClickHouse, выступающая активным консьюмером, а остальные ноды находятся в резерве. Если активная нода выходит из строя, другая нода автоматически берёт на себя роль консьюмера Kafka-топика и продолжает вычитывать данные без потери сообщений.
Пример скрипта для создания таблицы с движком Kafka Engine в кластере ClickHouse:
CREATE TABLE IF NOT EXISTS bmp.bmp_kafka ON CLUSTER '{cluster}' ( seq UInt64, timestamp DateTime64, timestamp_arrival DateTime64, bmp_router String, bmp_router_port UInt16, writer_id String, bmp_msg_type String, log_type String, is_loc UInt8, is_in UInt8, is_out UInt8, is_post UInt8, is_filtered UInt8, bmp_rib_type String, peer_ip String, peer_tcp_port UInt16, peer_asn UInt32, peer_type UInt8, peer_type_str String, bmp_peer_up_info_string String, afi UInt8, safi UInt8, ip_prefix String, bgp_nexthop String, as_path String, comms String, ecomms String, origin String, local_pref UInt32, med UInt32, rd String, rd_origin String, bgp_id String, mpls_label String, as_path_id String, aigp String, psid_li String, counter_type UInt32, counter_type_str String, counter_value UInt64, reason_type UInt8, reason_str String, local_port UInt16, remote_port UInt16, local_ip String, bmp_init_info_sysdescr String, bmp_init_info_sysname String, bmp_term_info_reason String ) ENGINE = Kafka(kafka_storage_ch_bmp_collector) SETTINGS kafka_topic_list = 'bmp', kafka_group_name = 'clickhouse', kafka_format = 'JSONEachRow', kafka_skip_broken_messages = 1, kafka_thread_per_consumer = 0, kafka_num_consumers = 1, kafka_commit_on_select = true COMMENT 'This table consumes bmp data from kafka broker';
Для подготовки данных к анализу в ClickHouse используются Materialized Views. Для каждого интересующего типа BMP-сообщений создаётся отдельное материализованное представление, которое отфильтровывает нужные события из таблицы с Kafka Engine и перекладывает их в целевую таблицу. В результате в этих таблицах хранятся уже структурированные данные с распарсенными полями: например, статистика по BGP и сообщения об изменениях маршрутов находятся в разных целевых таблицах. Именно к ним затем подключается инструмент визуализации — Grafana.
Получается, что для каждого типа данных используется связка из трёх сущностей: таблица с Kafka Engine, Materialized View и целевая таблица хранения. Такой подход позволяет гибко управлять схемой данных и изолировать разные типы сообщений друг от друга, не усложняя работу коллектора.
Все данные целевых таблиц в базе реплицированы между нодами кластера, разнесёнными по разным дата-центрам. Это обеспечивает отказоустойчивость: даже при полном выходе из строя дисков в одном ДЦ данные сохраняются на оставшихся репликах.
Пример Materialized View для BMP-сообщений типа Stats:
CREATE MATERIALIZED VIEW IF NOT EXISTS bmp.bmp_stats_mv ON CLUSTER '{cluster}' TO bmp.bmp_stats AS SELECT seq, timestamp, timestamp_arrival, bmp_router, bmp_router_port, writer_id, bmp_msg_type, is_loc, is_in, is_out, is_post, is_filtered, bmp_rib_type, peer_ip, peer_asn, peer_type, rd, rd_origin, bgp_id, counter_type, counter_type_str, counter_value FROM bmp.bmp_kafka WHERE bmp_msg_type = 'stats' COMMENT 'This MV forwards only BMP stats report messages into dedicated stats table';
Пример целевой target-таблицы для BMP-сообщений типа Stats:
CREATE TABLE IF NOT EXISTS bmp.bmp_stats ON CLUSTER '{cluster}' ( seq UInt64, timestamp DateTime64, timestamp_arrival DateTime64, bmp_router String, bmp_router_port UInt16, writer_id String, bmp_msg_type String, is_loc UInt8 DEFAULT 0, is_in UInt8 DEFAULT 0, is_out UInt8 DEFAULT 0, is_post UInt8 DEFAULT 0, is_filtered UInt8 DEFAULT 0, bmp_rib_type String DEFAULT '', peer_ip String DEFAULT '', peer_asn UInt32 DEFAULT 0, peer_type UInt8 DEFAULT 0, rd String DEFAULT '', rd_origin String DEFAULT '', bgp_id String DEFAULT '', counter_type UInt32 DEFAULT 0, counter_type_str String DEFAULT '', counter_value UInt64 DEFAULT 0 ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/bmp_stats','{replica}') ORDER BY seq TTL toDateTime(timestamp_arrival) + INTERVAL 30 DAY DELETE COMMENT 'This table saves BMP statistic report messages (Mess type 1)';
Обратите внимание, что движок ReplicatedMergeTree в целевой таблице как раз используется для репликации данных между узлами кластера.
Целевые таблицы мы решили разделить для себя на два типа: те, что хранят текущее состояние BGP, и исторические данные. Основное отличие этих типов — используемые движки таблиц. В таблицах с текущим состоянием мы используем движок ReplicatedReplacingMergeTree, так как хотим убирать дубли и получать актуальное состояние записей. В исторических используется движок ReplicatedMergeTree.
Подробнее об их отличиях можно почитать в официальной документации по ClickHouse — тут и тут. Исторические данные хранятся в системе в течение 30 дней, и мы можем анализировать аварии задним числом и сравнивать текущее состояние сети с прошлым.
Итоговая схема таблиц получилась такой:

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

Использование и визуализация данных
После того как данные проходят весь конвейер обработки и попадают в аналитические таблицы, инженеры могут использовать их в повседневной работе с сетью и при разборе инцидентов. При авариях или неожиданных изменениях маршрутизации можно сравнить текущее состояние BGP на роутере с тем, что было в сети за день, неделю или месяц до этого. Поскольку BMP даёт информацию с разных этапов обработки маршрутов, видно, на каком именно этапе произошло изменение — на входе, после применения политик или уже в Local RIB. Это помогает понять, когда и почему произошли изменения, без необходимости восстанавливать картину по косвенным признакам.
А ещё система даёт наглядную картину динамики маршрутов: частоту анонсов и отзывов префиксов, пики активности и нетипичное поведение. Например, резкая волна withdraw, за которой следует массовый повторный анонс, часто указывает на вероятный перезапуск BGP-процесса на том или ином узле. Можно формировать списки наиболее нестабильных роутеров и префиксов и находить проблемные места в сети ещё до того, как они приведут к пользовательским инцидентам.
Выше я писал, что другие сервисы также могут использовать BMP-данные для своих задач, например, система менеджмента Anycast-префиксов. Кроме того, такие данные можно использовать в аналитических системах, включая LLM-модели, для более интеллектуального анализа и прогнозирования событий в сети.
Дальше приведём примеры дашбордов, которые можно построить на основе уже собранных данных. В качестве инструмента визуализации данных у нас используется Grafana.
Самый очевидный пример — это Looking-glass-сервис.
Показать текущее состояние маршрутов на узле с фильтрами по пирам и BGP-таблицам:

То же самое, но только исторические данные:

Получить статистику по конкретным BGP-пирам выбранного узла:

Получить данные по количеству BGP withdraw и update-сообщений в каждый временной интервал, а ещё общий график динамики изменений маршрутов в сети:

Узнать, сколько уникальных префиксов в сети:

Или посмотреть состояние BMP-сессий с роутерами:

BMP-лаба
И обещанный сюрприз — демо, которое каждый может попробовать, чтобы посмотреть, как выглядит система мониторинга BGP на практике. На GitHub выложен MVP. Система состоит из четырёх контейнеров под управлением Docker Compose: коллектор pmbmpd, брокер Kafka, БД ClickHouse и Grafana. В README есть инструкция, как всё это запустить.
Чтобы в Grafana начали появляться метрики, не забудьте настроить BMP-агента на вашем роутере.
Планы развития
С помощью системы мониторинга BMP мы отслеживаем в реальном времени состояние BGP-сессий, анализируем изменения маршрутов, контролируем anycast-префиксы и быстро выявляем аномалии в сети. Система помогает автоматизировать рутинные процессы, упрощает анализ данных и делает ключевые метрики маршрутизации наглядными для команд.
Мы уже решаем основные задачи по поддержанию стабильности сети и наблюдению за маршрутизацией, но планируем развивать систему дальше:
Доработать функциональность Anycast. Планируем доработать связанную с Anycast автоматику, чтобы упростить процессы и сделать высвобождение Anycast-префиксов более предсказуемым и с минимальным участием инженеров.
Масштабировать инфраструктуру. С появлением новых площадок может потребоваться подключение дополнительных сетевых устройств к BMP-коллекторам. Архитектура изначально закладывалась с запасом по масштабированию, поэтому добавление новых устройств — это стандартная операция, а не пересборка системы с нуля.
Улучшить архитектуру. В целом текущая архитектура уже закрывает основные требования к мониторингу маршрутизации и показала себя устойчивой в эксплуатации. Поэтому дальнейшие изменения, скорее всего, будут точечными: обогащение BMP-данными другие системы мониторинга, добавление дашбордов и метрик в Grafana, а если со временем изменятся требования или нагрузка на систему, то и возможны архитектурные корректировки.
Всем спасибо за внимание! Пишите комментарии и задавайте вопросы!

