Всем привет! Меня зовут Антон Ильичев, я сетевой инженер в Авито. В этой статье расскажу, зачем мы централизованно собираем и анализируем маршрутную информацию с сетевых устройств, причём тут протокол 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:

Внутри находится обычный BGP-update, к которому добавлена метаинформация
Внутри находится обычный BGP-update, к которому добавлена метаинформация

Какие данные умеет передавать 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, а если со временем изменятся требования или нагрузка на систему, то и возможны архитектурные корректировки.

Всем спасибо за внимание! Пишите комментарии и задавайте вопросы!

Кликни здесь и узнаешь