Мониторинг EVPN-фабрики и BGP. Часть 2

от автора

Привет, Хабр! Меня зовут Елена Сахно, я — старший сетевой инженер в группе сетевых сервисов отдела сетевых технологий в Ozon.

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

В этой части подробно разберём решение и посмотрим внимательнее на каждый компонент системы.

Выбор решения

При выборе решения мониторинга BGP в Ozon мы опирались на следующие характеристики:

  • Хранение большого объёма данных и быстрое обращение к ним для аналитики.

  • Обработка данных по десяткам и сотням сессий BMP с разных устройств.

  • Обработка большого объёма данных в рамках одной сессии.

  • Поддержка мониторинга BGP EVPN.

  • Отказоустойчивость.

  • Возможность перевода на Платформу Ozon (о том, что такое Платформа Ozon и почему это важно для нас, будет ниже).

И вот какие решения вошли в наш шорт-лист.

1. OpenBMP

OpenBMP было первым протестированным решением. Основная причина, по которой мы на нём не остановились, — отсутствие поддержки EVPN. Кроме того, дорабатывать данное решение было затруднительно, так как продукт написан на языке С++, в котором у нас нет экспертизы, и он не входит в список языков разработки Ozon.

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

Это хорошее готовое решение, которое можно очень быстро развернуть и протестировать. В его составе уже есть готовый docker-compose, который позволяет развернуть все необходимые контейнеры: Grafana с готовыми дашбордами, Postgres с созданными таблицами, Kafka, ZooKeeper и др.

2. Pmacctd

Интересным оказалось решение Pmacctd, в составе которого есть коллектор Netstream, Netflow (nfacctd) и встроенный коллектор BMP-сообщений. Но, к сожалению, как и у OpenBMP, отсутствует поддержка EVPN. При направлении трафика BMP и сообщений c информацией EVPN сервис распознал, обработал и отправил в Kafka только сообщения Peer Up/Down, Initiation, а сообщения с информацией о самих префиксах не прошли обработку.

Pmacctd обладает большей функциональностью, и мы его используем в другом проекте. При помощи данного сервиса можно собирать статистику по трафику IPv4 и обогащать её данными из BMP.

Доработка данного решения была невозможна по той же причине: нет экспертизы и язык разработки не входит в список поддерживаемых. Стоит отметить, что Pmacctd активно развивается, дорабатывается, поэтому после внесения изменений в код Pmacctd было бы очень сложно обновлять.

3. Gobmp

Последним протестированным решением был Open Source проект Gobmp. Тестирование прошло не совсем успешно. Мы сразу получили ошибку с переполнением номера AS, так как у нас используются 4-байтовые номера. Просмотрев код, написанный на Go, довольно быстро удалось найти причину и исправить ошибку переполнения AS. Большим плюсом было то, что сервис без доработок разбирал EVPN-сообщения и написан на Go, который поддерживается в Ozon.

В сервисе нет ничего, кроме самого парсера сообщений BMP. Он ожидает входящие TCP-сессии, получает BMP-сообщения, разбирает их на поля, формирует из них JSON и отправляет сформированные сообщения на выход.

Gobmp позволяет записывать итоговые сформированные сообщения в файл, выводить на консоль или отправлять в брокер сообщений Kafka. Это нас более чем устраивало.

Кроме того, Gobmp уже поддерживает много типов NLRI:

  • IPv4 Unicast, Labeled Unicast

  • IPv6 Unicast, Labeled Unicast

  • L3VPN IPv4 unicast

  • L3VPN IPv6 unicast

  • Link-state

  • L2VPN (VPLS)

  • L2VPN (EVPN)

  • SR Policy for IPv4

  • SR Policy for IPv6

Тестирование проводилось локально: на рабочем ноутбуке запускался сервис Gobmp, контейнеры с Kafka и Kafka-UI, в которые отправлялись обработанные данные и Wireshark для проверки правильности разбора всех сообщений BMP. Мы видели исходное сообщение в дампе трафика и итоговое сообщение в формате JSON в Kafka-UI.

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

Самым большим недостатком было отсутствие IP-адреса и/или имени сетевого устройства, с которого строилась BMP-сессия. Вместо этого была только информация об IP-адресе физического интерфейса, с которого устанавливалась BGP-сессия, поставленная на мониторинг. Таким образом, чтобы определить, с какого устройства мы получили данные BMP, нужно было держать таблицу соответствия всех физических интерфейсов и имён сетевых устройств, что было неудобно.

Было принято решение немного доработать коллектор BMP.

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

Итоговое решение

Общая схема

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

Что в итоге получилось

1. Наш кастомизированный BMP‑коллектор, который получил имя bmp‑collector, развёрнут в Kubernetes на трёх подах. Подключение к нему осуществляется по общему виртуальному адресу Kubernetes Cluster IP:

  • Если ставим на мониторинг те NLRI, которые поддерживаются сетевым устройством, сессия BMP устанавливается с сетевого устройства до коллектора BMP.

  • Если хотим мониторить EVPN, настраиваем промежуточный сервер FRR, с которого получаем все обновления по протоколу BMP.

2. Основная задача bmp‑collector — принять пакет, разобрать на поля, сформировать сообщение в формате JSON и отправить в брокер Kafka, который сглаживает всплески сообщений.

3. Нам понадобился дополнительный сервис bmp‑inserter, который читает из топиков Kafka сообщения и отправляет их в Clickhouse. В нашем случае этот сервис помогает ещё и обогатить данные. Он написан на Go, но его можно заменить дополнительной таблицей Clickhouse с движком Kafka, если у вас нет ограничения на его использование.

4. Данные попадают сначала в основные таблицы базы данных Clickhouse, в которых хранится полная информация, то есть вся история изменения состояния BGP‑сессий и префиксов:

  • Таблица «peer» — информация о всех событиях Peer Up/Down.

  • Таблица «prefix_v4» — информация по префиксам IPv4, получаемая из BMP‑сообщений типа Route Monitoring.

  • Таблица «evpn» — история изменения EVPN‑префиксов.

  • Таблица «statistics» — информация, получаемая из BMP‑сообщений типа Stats Report.

Всё это исторические данные. После этого через материализованное представление данные попадают в таблицы с текущим состоянием BGP‑соседей и BGP‑таблиц: peer_current, prefix_v4_current, evpn_current.

5. Весь алертинг и отображение графиков в Grafana осуществляются через запросы в Clickhouse, но в ближайших планах — написать API для сервиса.

BMP-коллектор

Как было сказано ранее, в качестве BMP-коллектора мы используем кастомизированный Open Source продукт Gobmp. Рассмотрим, в чём заключаются изменения и для чего они были сделаны.

Перевод на платформенные библиотеки

В Ozon целый департамент (Платформа) разрабатывает ресурсы, необходимые для поддержки всего жизненного цикла приложений, включая проектирование, разработку, тестирование, развёртывание и размещение. При создании своего сервиса вам не надо разворачивать самостоятельно кластер Kafka, Clickhouse, Grafana, систему трейсинга и логирования, не нужно становиться их администраторами и заботиться об организации их отказоустойчивости и масштабируемости. Все уже подготовлено и настроено.

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

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

Чтобы воспользоваться всеми преимуществами платформы во время доработки сервиса, нами было принято решение перевести сервис bmp-collector на платформенные библиотеки.

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

Внесённые изменения

Кроме перевода на платформенные библиотеки, самой крупной доработкой было добавление информации о сетевом устройстве, с которого устанавливается BMP-сессия до коллектора.

Как было ранее сказано, это был самый большой недостаток коллектора. Сервис не записывал ни IP-адрес, ни Hostname сетевого устройства, с которого строилась BMP-сессия. Вместо этого была только информация об IP-адресе физического интерфейса, с которого строилась BGP-сессия, поставленная на мониторинг. Таким образом, чтобы определить, с какого устройства мы получили данные BMP, нужно было держать таблицу соответствия всех физических интерфейсов c Hostname, что было неудобно.

Первая реализация была совсем простой. Из содержимого BMP-пакета собиралась вся нужная информация, а из IP-заголовка этого же пакета забирался source IP-адрес сетевого устройства, с которого пришёл этот пакет. На тестовом стенде всё работало замечательно. Но при разворачивании в Kubernetes оказалось, что Source IP всех пакетов скрывается за NAT при отправке данных на общий адрес всех подов сервиса — Cluster IP. Отказаться от использования Cluster IP и отправлять данные на конкретные адреса подов мы не могли, это снижало отказоустойчивость всего сервиса.

Второй подход полностью решил эту проблему. Как мы помним, первый пакет, который приходит при установке BMP-сессии, — это Initiation, в нём содержится информация об устройстве: описание устройства и Hostname. И для использования этой информации мы написали обработчик сообщений Initiation.

В код была добавлена следующая логика. Если после установки первым пакетом пришёл не Initiation, то это ошибка, и мы разрываем соединение. Если первый пакет Initiation, то создаём новую структуру с полями Hostname и IP-адресом клиента BMP. Далее для текущего подключения для всех пакетов BMP используем полученные данные для обогащения.

Кроме обработчика Initiation-пакетов, был написан обработчик пакетов типа Termination.

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

Ниже приведен пример лога коллектора.

Initiation

«timestamp»: «2024-10-18T06:55:28.892Z»,

«message»: «bmp initiation message: sysDescr: Routing Platform Software VRP (R) software, Version 87.2 (9006) Copyright (C) 2012-2022 Network Technologies Co. sysName: leaf2.dc1.stg»

Termination

«timestamp»: «2024-10-18T06:55:28.892Z»,

«message»: «bmp termination message: reason: session administratively closed»

Также были дописаны обработчики для некоторых атрибутов и типов TLV-статистики.

Был написан обработчик причины завершения BGP-сессии в соответствии с кодами, перечисленными в RFC 4489, RFC 9384 и RFC 4271, что позволило настроить уведомления разных уровней важности.

Ниже приведен пример событий Peer Up/Down.

Timestamp

Action

PeerIP

Reason

ErrorText

2024-02-09 14:56:42.041000

up

10.1.1.1

0

2024-02-09 14:58:27.903000

down

10.1.1.1

2

FSM: TcpConnectionFails

2024-02-09 14:58:55.135000

down

10.1.7.1

1

Case: Administrative Shutdown

2024-02-09 14:59:52.535000

down

10.1.3.1

1

Case: Administrative Reset

Изначально коллектор тестировался с Postgres. BGP-атрибуты префиксов хранились в отдельной таблице, а в качестве ключа выступал хеш списка всех атрибутов. Но в итоге окончательным выбором стала база данных Clickhouse. В Clickhouse все BGP-атрибуты лежат рядом с префиксом, за ними не нужно обращаться в другие таблицы.

Под Clickhouse был изменён исходящий формат данных BMP-коллектора, убрана иерархия в JSON и убран расчёт хеша для списка атрибутов. О том, как мы выбирали базу данных для нашего сервиса, будет рассказано дальше.

Мы изменили формат ExtCommunityList. Вместо строки стали использовать map, что позволило при выполнении запросов в базу данных Clickhouse обращаться к отдельным ExtСommunity по их названию, а не анализировать каждый раз строку.

Исправленные ошибки

Во время доработки сервиса в коллекторе были исправлены несколько небольших ошибок.

  • Исправлен тип атрибута originAS. У нас используются 4-байтовые номера, и мы получали переполнение и отрицательные значения.

  • Исправлена ошибка, из-за которой некорректно выставлялись в некоторых случаях флаги принадлежности к таблицам Adj-RIB-In pre/post-policy и Adj-RIB-Out pre/post-policy.

  • Исправлен расчет Timestamp. Устанавливалось некорректное время.

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

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

Посмотрим, как выглядят сообщения в формате JSON сразу после обработки BMP-коллектором.

Примеры сообщений JSON

Ниже приведён пример сообщения типа Peer Up. Вся информация из TCP-пакета с BMP попала в соответствующие поля. Кроме этого, из сообщений Initiation была добавлена информация об источнике BMP-сообщений — это поля client_ip и client_sysname.

В этом сообщении и во всех остальных некоторые поля заполняются на основе заголовка Per Peer. А именно:

  • Поле timestamp содержит время обновления информации.

  • Поля peer_ip, peer_type, peer_asn, peer_bgp_id, peer_rd включают информацию о BGP-соседе.

  • Информация о флагах, сохраняется в полях is_adj_rib_post_policy, is_adj_rib_out, is_loc_rib_filtered, is_ipv4.

Формат полей adv_cap и recv_cap мы изменили на числовой список, так как хранить полное описание BGP-capability в виде списка строк для нас было неудобно.

Поле peer_hash вычисляется коллектором на основе peer_ip, peer_asn, peer_bgp_id, peer_rd и используется в базе данных для определения уникальности BGP-соседа.

Пример сообщения Peer Up
{   "action": "add",   "timestamp": "2024-08-08T13:12:42.022Z",   "client_ip": "10.1.2.96:30995",   "client_sysname": "core.dc1",   "peer_hash": "c3cbd0e5573dddf0d688cc238a4dfc77",   "peer_ip": "10.1.5.11",   "peer_type": 1,   "peer_asn": 42009012,   "peer_bgp_id": "10.1.5.11",   "peer_rd": "0:0",   "peer_port": 36101,   "local_ip": "10.1.2.1",   "local_asn": 42009001,   "local_bgp_id": "10.1.1.4",   "local_port": 179,   "is_ipv4": true,   "adv_cap": [ 1, 2, 64, 65 ],   "recv_cap": [ 1, 2, 6, 64, 65, 70 ],   "recv_holddown": 180,   "adv_holddown": 180,   "is_adj_rib_post_policy": true,   "is_adj_rib_out": false,   "is_loc_rib_filtered": false }

Дальше приведён пример BMP-сообщения типа Route Monitoring для IPv4-префикса. Как и для Peer Up сообщений, поля timestamp, peer_ip, peer_type, peer_asn, peer_bgp_id, peer_rd, is_adj_rib_post_policy, is_adj_rib_out, is_loc_rib_filtered, is_ipv4 формируются на основе Per Peer заголовка сообщения.

Все остальные поля формируются на основе информации из самого сообщения. Видим изменённый формат поля ext_community_list. Как было отмечено выше, он был изменён для удобства доступа к отдельным extended community.

Поле hash используется для определения уникальности префикса и представляет собой хеш от полей peer_hash, prefix_full, is_adj_rib_post_policy, is_adj_rib_out, is_loc_rib_filtered.

Пример сообщения Route Monitoring — IPv4
{   "action": "add",   "timestamp": "2024-08-09T07:36:52.334422Z",   "client_ip": "10.1.2.122:51311",   "client_sysname": "bg.dc2",   "peer_hash": "dde9634904e7dd058a7bb1e047e46335",   "peer_ip": "0.0.0.0",   "peer_type": 3,   "peer_asn": 42009002,   "peer_bgp_id": "10.1.3.3",   "peer_rd": "0:0",   "hash": "6859183c2100fa00f283a183c39d32b7",   "prefix": "187.165.39.0",   "prefix_len": 24,   "prefix_full": "187.165.39.0/24",   "is_ipv4": true,   "nexthop": "80.64.102.24",   "is_nexthop_ipv4": true,   "origin": "igp",   "as_path": [ 20764, 8359, 3356, 174, 8151 ],   "as_path_count": 5,   "community_list": [ "44386:60000" ],   "ext_community_list": {     "ro": [ "507:2" ]   },   "origin_as": 8151,   "is_adj_rib_post_policy": false,   "is_adj_rib_out": false,   "is_loc_rib_filtered": true }

Очень похожий формат имеют сообщения Route Monitoring для EVPN-префиксов. На этом примере лучше видно, насколько удобнее использовать ext_community в виде хеш-таблицы.

Пример сообщения Route Monitoring — EVPN
{   "action": "add",   "timestamp": "2024-10-11T19:24:07.491383Z",   "client_ip": "10.1.1.11:3545",   "client_sysname": "evpnfrr03.stg",   "peer_hash": "de3a42f739d73ea6836afabdf9143bd7",   "peer_ip": "10.1.0.12",   "peer_type": 0,   "peer_asn": 42009011,   "peer_bgp_id": "10.1.2.21",   "peer_rd": "0:0",   "hash": "f968c4f2cb3e255e9ca13feb9e816c24",   "route_type": 2,   "is_ipv4": true,   "nexthop": "10.1.0.94",   "is_nexthop_ipv4": true,   "mac": "02:00:00:05:03:01",   "mac_len": 48,   "vpn_rd": "10.1.2.5:11000",   "vpn_rd_type": 0,   "origin": "incomplete",   "as_path": [ 42009012, 42009011, 42009010, 42009011 ],   "as_path_count": 4,   "ext_community_list": {       "encap": [ "8" ],       "macmob": [ "1:0" ],       "rt": [ "903:5001", "903:11000" ]   },   "origin_as": 42009011,   "labels": [ 0 ],   "rawlabels": [ 0 ],   "eth_segment_id": "00:00:00:00:00:00:00:00:00:00",   "is_adj_rib_post_policy": true,   "is_adj_rib_out": false,   "is_loc_rib_filtered": false }

Посмотрим ещё на один тип BMP-сообщений — Stats Report. Сообщение содержит уже знакомые нам поля из Per Peer заголовка: timestamp, peer_ip, peer_type, peer_asn, peer_bgp_id, peer_rd, is_adj_rib_post_policy, is_adj_rib_out, is_loc_rib_filtered, is_ipv4. И два поля с данными из первого BMP-пакета Initiation: client_ip и client_sysname.

Все остальные поля — это ненулевые счётчики.

Пример сообщения Stats Report
{   "timestamp": "2024-08-09T07:44:32.840399Z",   "client_ip": "10.1.2.81:63722",   "client_sysname": "leaf-d1.dc3",   "peer_hash": "5720c33ab0232ea4a033698e4bbb135c",   "peer_ip": "10.0.1.26",   "peer_type": 0,   "peer_asn": 42009020,   "peer_bgp_id": "10.0.3.3",   "peer_rd": "0:0",   "duplicate_prefix": 935,   "duplicate_withdraws": 6,   "invalidated_due_aspath": 185,   "adj_rib_in": 411,   "local_rib": 1,   "adj_rib_out": 417,   "rejected_prefix": 1,   "is_adj_rib_post_policy": true,   "is_adj_rib_out": true,   "is_loc_rib_filtered": false }

Брокер сообщений Kafka

После формирования JSON-сообщений BMP-коллектор отправляет их в брокер Kafka, который позволяет сгладить большие всплески сообщений.

Что такое брокер сообщений Kafka?

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

Часто при упоминании о нём используются два термина: producer (издатель/продюсер сообщений) и consumer (подписчик/консьюмер).

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

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

В нашем случае BMP-коллектор — это продюсер, он отправляет сообщения, а в качестве подписчика используется дополнительный сервис, о котором чуть позже.

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

  • топик bmp_peer содержит информацию об изменениях статусов BGP-соседей;

  • топик bmp_prefix_v4 — информацию о всех префиксах ipv4;

  • топик bmp_evpn — информацию о EVPN-префиксах;

  • топик bmp_statistics — хранит все Stats Report.

База данных

Выбор базы данных

Сервис bmp-collector, в отличие от OpenBMP, — это только парсер. Его основная задача — получить пакет BMP, разобрать его на поля и отдать на выход: в файл, на консоль или в брокер сообщений Kafka.

С сервисом bmp-collector встала задача выбора хранилища и написание консьюмера — сервиса, который читает сообщения из топиков Kafka и отправляет их в базу данных.

Сперва была протестирована база данных Postgres, к которой был написан консьюмер, забирающий сообщения из Kafka и добавляющий их в таблицы.

Атрибуты и информация о соседях BGP были вынесены в отдельные таблицы, а таблицы с префиксами IPv4 и EVPN ссылались на них. При этом для IPv4 и EVPN было по две таблицы: с текущим представлением и с историческими данными.

Каждое новое событие по префиксу дописывалось в конец таблицы с историей, а в таблице с текущим представлением происходил поиск и обновление записи или удаление (если получен withdraw).

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

При отправке примерно 700 тыс. префиксов с такой конфигурацией мы получали задержку примерно в 15 минут, и на тот момент это казалось приемлемым, но хотелось оптимизировать решение и снизить задержку.

Далее был поиск других решений. Мы перешли к тестированию коллектора в связке с колоночный базой данных Clickhouse.

Сам принцип работы колоночной базы данных лучше ложился на концепцию мониторинга BGP-префиксов: мы получаем большое количество данных с большим числом атрибутов, и в основном будут выполняться запросы вида «префикс плюс некоторые атрибуты». Для такого вида запросов Clickhouse отправляет только колонки с запрашиваемыми атрибутами, а не весь их список. Это решение очень хорошо подходит для хранения исторических данных.

При этом встала задача, как организовать хранение текущего представления таблиц BGP. Если исторические таблицы — это вставка большого числа записей, которая очень быстро работает в Clickhouse, то ведение таблицы с текущим представлением — это постоянное удаление и обновление записей в таблице. Оно работает значительно медленнее, более ресурсозатратно, и, вообще, Clickhouse не рекомендует так делать.

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

И кажется, что это отличное решение, сама БД делает замену. Но есть минус: замена происходит не мгновенно при появлении новой записи, а после вставки, спустя какое-то время в фоновом режиме, и, как написано в документации, время нельзя прогнозировать:

«Дедупликация данных производится лишь во время слияний. Слияния происходят в фоне в неизвестный момент времени, на который вы не можете ориентироваться».

В итоге мы всё же остановились на этом решении, а задержку обновления закрыли за счёт запросов в БД опцией DISTINCT ON, которая удаляет дубликаты во время запроса.

Этот же движок мы стали чуть позже использовать и для таблиц, которые хранят всю историю изменения префиксов, но добавив в поле сортировки временную метку Timestamp.

Для чего это было сделано?

Как описано в RFC 7854, при установке BMP-сессии сетевое устройство отправляет весь дамп маршрутов, которые сейчас присутствуют во всех добавленных в мониторинг таблицах. Таким образом, разрыв и повторная установка сессии BMP значительно добавляет данных в таблицы, при этом время обновления Timestamp у этих маршрутов одинаковое. К примеру, если произойдёт переустановка сессии BMP, которая мониторит full view, то все маршруты будут лежать в двойном экземпляре в таблице, если произойдёт повторная переустановка сессии — то уже в тройном. И эти данные никуда не исчезнут и будут лежать всё то время, которое мы храним историю. Движок ReplacingMergeTree позволяет в фоновом режиме избавляться от таких дублей.

Задержка при обработке 700 тыс. маршрутов сейчас составляет примерно 1 минуту, результат нас более чем устраивает. Но поиски наболее оптимального решения продолжаются, и, возможно, под хранение текущего представления BGP мы выберем другую базу данных. А вот с хранением исторических данных по префиксам Clickhouse справляется отлично.

Со сменой базы данных изменилась и схема: мы отказались от хранения атрибутов в отдельной таблице, и теперь все атрибуты хранятся рядом с префиксом. Минимально необходимая информация о BGP-соседстве тоже находится в таблице с префиксами. Это позволило не использовать запросы с объединением данных из нескольких таблиц.

Как было сказано выше, сервис BMP-коллектор позволяет отправлять данные в Kafka. Во время тестирования базы данных Postgres был написан консьюмер, который перекладывал данные из Kafka-топиков в таблицы Postgres.

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

Но во время тестирования и доработки сервиса BMP-коллектор в Ozon решили отказаться от использования движка Kafka в базе данных Clickhouse. Для того чтобы транспортировать данные из топиков Kafka в таблицы Clickhouse, команда платформы разработала библиотеки, которые позволили написать консьюмер с минимальными усилиями. Немного позже в нашем решении консьюмер стал не только выполнять транспорт данных, но и обогащать данные дополнительной информацией.

Clickhouse

Ниже на схеме представлено полное решение и список используемых таблиц.

Консьюмер забирает данные из топиков Kafka и отправляет их в соответствующие таблицы:

  • данные топика с информацией о BGP-соседях попадают в таблицу peer;

  • данные топика bmp_prefix_v4 — в таблицу prefix_v4;

  • данные топика bmp_evpn — в таблицу EVPN;

  • данные топика bmp_stats — в таблицу statisctics.

В перечисленных выше таблицах хранятся исторические данные, то есть все обновления, которые прислал BMP-коллектор, за исключением дубликатов, возникающих при переустановке сессии BMP. У таких дубликатов все поля одинаковые, включая timestamp (время возникновения события на сетевом устройстве).

Движок у данных таблиц используется ReplacingMergeTree, а в качестве полей сортировки (ORDER BY) указаны столбцы ClientSysName, Hash и Timestamp.

Для получения текущего состояния таблиц BGP из исторических сообщений используются материализованные представления в Clickhouse. Материализованное представление используется только для копирования данных из исторических таблиц в таблицы с текущим представлением, в которых также используется движок ReplacingMergeTree, но в качестве полей сортировки указаны только столбцы ClientSysName и Hash, что обеспечивает хранение только последних апдейтов.

Как мы получаем информацию о MAC mobility, VLAN и коммутаторе

Как видно из примеров сообщений в формате JSON выше, в сообщениях EVPN нет отдельных полей с информацией о MAC Mobility, VLAN и адресе коммутатора, с которого пришло обновление. Для обогащения сообщений этой информацией используется сервис bmp-inserter, который транспортирует информацию из топиков Kafka в Clickhouse.

Поля MAC Mobility и MAC Mobility Type заполняются на основе информации, полученной из Extended Community MAC Mobility «macmob»: [ «1:0» ] , где 1 указывает на то, что адрес статический и не может перемещаться, а для типа 0 перемещение возможно, и лучше отслеживать резкий рост этого счётчика.

С получением адреса коммутатора и VLAN в нашем случае всё очень просто. Эта информация содержится в поле vpn:rd. Vpn:rd назначается по принципу <switch_address>:<vlanid + 1000>.

Если из vpn:rd нет возможности напрямую получить эту информацию, то всегда можно подключить словарь в Clickhouse с этой информацией.

Grafana

После того как мы сохранили все данные в таблицы Clickhouse, нам необходимо их отображать. Для отображения, создания дашбордов, панелей и графиков в Ozon используется Grafana.

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

Настройка мониторинга BMP

Мониторинг BMP IPv4 сессий

Настройка мониторинга IPv4 BMP на сетевых устройствах отличается для разных вендоров, но обычно для работы протокола BMP необходимо:

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

  • Указать BGP-соседей или отдельные VRF, для которых будет включён мониторинг, и перечислить те таблицы, по которым будут отправляться данные BMP.

  • Для получения статистических отчётов необходимо указать периодичность их отправки.

Мониторинг BMP EVPN

Как было сказано выше, многие производители сетевого оборудования на сегодняшний день не поддерживают мониторинг BMP EVPN. Для получения всех маршрутов EVPN мы пока используем промежуточный сервер FRR, который устанавливает EVPN-сессию с одним из коммутаторов ЦОД и по BMP отправляет все получаемые обновления. Такая схема накладывает свои ограничения: мы видим только те маршруты, которые посчитал лучшими соседний c FRR лиф, но даже в этой конфигурации BMP EVPN мониторинг открывает большие возможности.

Настройка FRR сервера

Сервис FRR является Open Source решением и представляет собой программный маршрутизатор. Мы протестировали и сейчас используем версию 9.0.1.

Установка FRR простая, подробно описана в официальной документации, доступны готовые пакеты Debian и Redhat.

Перед запуском сервиса FRR необходимо в конфигурационном файле /etc/frr/daemons включить поддержку bgp и bmp, для этого необходимо изменить следующие настройки:

  1. bgpd=yes

  2. bgpd_options=»   -A 127.0.0.1 -M bmp»

После подготовки конфигурационного файла можно запустить сервис командой «systemctl start frr» и далее настраивать сервис через интерфейс vtysh, который очень напоминает обычный CLI. Чтобы попасть в интерфейс vtysh, после старта FRR сервиса необходимо набрать команду «vtysh».

Для сохранения настроек используется команда «wr mem». Ниже приведён пример конфигурации.

Настройка FRR
frr defaults traditional hostname evpnfrr log syslog informational no ip forwarding no ipv6 forwarding service integrated-vtysh-config ! ip route 10.1.100.0/23 Null0 ! router bgp 42009014  no bgp ebgp-requires-policy  no bgp default ipv4-unicast  neighbor 10.1.0.15 remote-as 42009015  bgp allow-martian-nexthop  !  address-family l2vpn evpn   neighbor 10.1.0.15 activate   neighbor 10.1.0.15 allowas-in 1   neighbor 10.1.0.15 route-map block out  exit-address-family  !  bmp targets BMP_PROD   bmp monitor l2vpn evpn post-policy   bmp connect 10.1.1.2 port 5000  exit  !  bmp targets BMP_STG   bmp monitor l2vpn evpn post-policy   bmp connect 10.1.1.4 port 5000  exit exit ! route-map block deny 10

Важно отметить, что FRR не отправляет информацию по протоколу BMP для EVPN-маршрутов, если у него нет маршрута до Nexthop.

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

Проверка настроек FRR

После настройки FRR необходимо проверить статус BGP- и BMP-сессий. Для этого можно использовать команды:

show bgp l2vpn evpn summary
BGP router identifier 10.1.0.16, local AS number 42009014 vrf-id 0 BGP table version 0 RIB entries 22437, using 2103 KiB of memory Peers 1, using 20 KiB of memory   Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc 10.1.0.15     4 42009015     83106      2602     9906    0    0 1d19h20m        45124        0 N/A   Total number of neighbors 1

show bmp
BMP state for BGP VRF default:     Route Mirroring         0 bytes (0 messages) pending                           0 bytes maximum buffer used     Targets "BMP_PROD":     Route Mirroring disabled     Route Monitoring l2vpn evpn post-policy     Listeners:       Outbound connections:  remote              state                       timer      local        ----------------------------------------------------------------------  10.1.1.2:5000   Up      10.1.1.2:5000   1d15h20m   (unspec)          1 connected clients:  remote              uptime     MonSent   MirrSent   MirrLost   ByteSent   ByteQ   ByteQKernel     ------------------------------------------------------------------------------------------------  10.1.1.2:5000   1d15h20m   104529    0          0          19504688   0       0                  Targets "BMP_STG":     Route Mirroring disabled     Route Monitoring l2vpn evpn post-policy     Listeners:       Outbound connections:  remote               state                        timer      local        ------------------------------------------------------------------------  10.1.1.4:5000   Up      10.1.1.4:5000   1d19h18m   (unspec)          1 connected clients:  remote               uptime     MonSent   MirrSent   MirrLost   ByteSent   ByteQ   ByteQKernel     -------------------------------------------------------------------------------------------------  10.1.1.4:5000   1d19h18m   113576    0          0          21239802   0       0

Таблицы Clickhouse

О том, как проходил выбор базы данных и движка таблиц, мы рассказали выше. В итоговом решении у нас используются следующие таблицы и движки.

Таблица

Поля сортировки

Описание

evpn

ClientSysName

Hash

Timestamp

Хранение полной истории EVPN-префиксов

evpn_current

ClientSysName

Hash

Текущие таблицы EVPN

peer

ClientSysName

PeerHash

Timestamp

История изменения BGP-сессий

peer_current

ClientSysName

PeerHash

Текущее состояние BGP-сессий

prefix_v4

ClientSysName

Hash

Timestamp

История изменения IPv4-префиксов

prefix_v4_current

ClientSysName

Hash

Текущие таблицы IPv4

statistics

Timestamp

Статистическая информация

Во всех таблицах, кроме statistics, используется движок ReplicatedReplacingMergeTree. Таблица statistics осталась на движке ReplicatedMergeTree, так как сообщения Stats Reports не отправляются повторно ни при каких обстоятельствах.

Помимо перечисленных таблиц используются служебные:

  • Distributed-таблицы объединяют данные, разнесённые по разным серверам кластера Clickhouse.

  • Materialized view служит для реализации материализованного представления из исторической таблицы в таблицы с текущими данными.

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

В вычислении хеша для BGP-соседа используются поля: Peer Distinguisher, Peer Address, Peer AS, Peer BGP ID.

Для вычисления хеша IPv4-префикса используются поля: Peer Hash, префикс, длина маски, таблица, по которой получена информация (Adj-RIB-In/Out Pre/Post policy).

Для EVPN используются поля: Peer Hash, тип маршрута, префикс, длина маски, адрес шлюза, MAC-адрес, тип и значение VPN RD, а также принадлежность к таблице, по которой получена информация (Adj-RIB-In/Out Pre/Post policy).

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

Route Monitoring IPv4

Route Monitoring EVPN

Peer Up/Down

Stats Report

Timestamp

Action

ClientIP

ClientSysName

PeerHash

PeerIP

PeerType

PeerASN

PeerBGPID

PeerRD

Hash

Prefix

PrefixLen

PrefixFull

IsIPv4

Nexthop

IsNexthopIPv4

Origin

ASPath

ASPathCount

MED

LocalPref

IsAtomicAgg

Aggregator

CommunityList

OriginatorID

ClusterList

OriginatorID

AS4Path

AS4PathCount

AS4Aggregator

TunnelEncapAttr

LgCommunityList

OriginAS

PathID

Labels

PrefixSID

RIB

IsAdjRIBPost

IsAdjRIBOut

Timestamp

Action

ClientIP

ClientSysName

PeerHash

PeerIP

PeerType

PeerASN

PeerBGPID

PeerRD

Hash

RouteType

Prefix

PrefixLen

PrefixFull

IsIPv4

Nexthop

IsNexthopIPv4

GWAddress

MAC

MACLen

VPNRD

VPNRDVlan*

VPNRDIP*

VPNRDType

Origin

ASPath

ASPathCount

MED

LocalPref

IsAtomicAgg

Aggregator

CommunityList

OriginatorID

ClusterList

ExtCommunityList

MacMobility*

MacMobilityType*

AS4Path

AS4PathCount

AS4Aggregator

TunnelEncapAttr

LgCommunityList

OriginAS

PathID

Labels

RawLabels

ESI

EthTag

IsAdjRIBPost

IsAdjRIBOut

IsLocRIBFiltered

Timestamp

Action

ClientIP

ClientSysName

PeerHash

PeerIP

PeerType

PeerASN

PeerBGPID

PeerRD

PeerPort

LocalIP

LocalASN

LocalBGPID

LocalPort

Name

IsIPv4

TableName

AdvCapabilities

RcvCapabilities

RcvHolddown

AdvHolddown

Reason

ErrorText

IsAdjRIBPost

IsAdjRIBOut

IsLocRIBFiltered

Timestamp

ClientIP

ClientSysName

PeerHash

PeerIP

PeerType

PeerASN

PeerBGPID

PeerRD

DuplicatePrefixes

DuplicateWithdraws

InvalidatedDueCluster

InvalidatedDueAspath

InvalidatedDueOriginatorID

InvalidatedDueNexthop

InvalidatedAsConfed

AdjRIBsIn

LocalRib

AdjRIBsOutPostPolicy

RejectedPrefixes

UpdatesAsWithdraw

PrefixesAsWithdraw

IsAdjRIBPost

IsAdjRIBOut

IsLocRIBFiltered

* Поля, добавленные при помощи обогащения.

Настройка Grafana

Для отображения графиков, диаграмм и таблиц на основе полученных данных по протоколу BMP используется Grafana. Разберём настройку одной панели на примере.

На графике изображено количество анонсированных и отозванных маршрутов за 30 секунд по каждому из VLAN.

evpn_mac_per_vlan_graph

evpn_mac_per_vlan_graph

Настройка панели Grafana представляет из себя два запроса в базу данных за период времени, который выбран в настройках дашборда. Один запрос — сколько маршрутов было добавлено:

SELECT    $__timeInterval(Timestamp) as time,   VPNRDVlan,   count(MAC) AS mac FROM evpn_dis PREWHERE   $__timeFilter(Timestamp) AND   ( Timestamp  >= $__fromTime AND Timestamp <= $__toTime ) AND   Action = 'add' AND   PrefixFull = '' GROUP BY VPNRDVlan, time ORDER BY time ASC

Второй запрос — сколько маршрутов было отозвано. Умножение результата на -1 позволяет график сделать более наглядным и отобразить отозванные маршруты под временной шкалой.

SELECT    $__timeInterval(Timestamp) as time,   VPNRDVlan,   count(MAC)*(-1) AS mac FROM evpn_dis PREWHERE   $__timeFilter(Timestamp) AND   ( Timestamp  >= $__fromTime AND Timestamp <= $__toTime ) AND   Action = 'del' AND   PrefixFull = '' GROUP BY VPNRDVlan, time ORDER BY time ASC

Все панели в Grafana создаются аналогичным образом: составляется запрос в Clickhouse, добавляются параметры при необходимости настройки фильтрации данных и выбирается тип представления (график, таблица, диаграмма).

Ниже приведены идеи дашбордов для отображения информации, полученной по протоколу BMP для EVPN и IPv4.

Примеры дашбордов EVPN

На дашборде можно использовать фильтры по ЦОД, коммутатору, VLAN и временнЫе фильтры. На панелях ниже выводится информация о наиболее нестабильных MAC-адресах, общем их количестве и их изменениях.

evpn_bmp_dashboard

evpn_bmp_dashboard

На следующем дашборде представлена сводная таблица с полной информацией об изменениях в маршрутах. На нём можно выбрать любой IP- или MAC-адрес и открыть подробную информацию только о нём. Настроены наиболее часто используемые фильтры.

Можно посмотреть информацию по текущим маршрутам или исторические данные. Фильтр находится в верхнем левом углу.

evpn_table

evpn_table

Следующий дашборд наглядно демонстрирует, как с помощью BMP можно оценить утилизацию адресного пространства.

evpn_prefix_usage_dashboard

evpn_prefix_usage_dashboard

Примеры дашбордов IPv4

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

ipv4_dash

ipv4_dash

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

Приведенный выше дашборд помогает оценить, как отработали политики BGP; для этого необходимо просто выбрать интересующую сеть.

ipv4_policy_change_table

ipv4_policy_change_table

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

ipv4_looking_glass

ipv4_looking_glass

Отказоустойчивость

Изначально в протоколе BMP не была заложена отказоустойчивость. По этому поводу в RFC 7854 есть небольшой раздел 3.2 Connection Establishment and Termination, который говорит о том, что отказоустойчивость может быть реализована вендором. Сам стандарт предусматривает только сообщение о разрыве BMP-сессии с причиной «Reason = 3: Redundant connection».

К сожалению, на наших устройствах нет поддержки отказоустойчивого подключения BMP. Кроме того, на одном из устройств был обнаружен баг. При настройке двух сессий BMP, если одна сессия переходит в Down, то в активную сессию BMP с периодичность отправки статистики, а у нас она составляет 30 секунд, начинает отправляться полный список всех маршрутов. С учетом того, что мониторинг был настроен для двух таблиц, содержащих full-view, в сервис стало приходить около 2 млн сообщений каждые 30 секунд.

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

Отказоустойчивость BMP в Ozon реализована за счет размещения сервиса в Kubernetes и анонса общего IP-адреса для всех подов сервиса. Сейчас сервис BMP в Kubernetes использует три пода, на которые распределяются сессии с сетевых устройств. При проблемах на одном из подов, сессии перераспределяются на оставшиеся доступные поды. Для сервиса неважно, какой используется под Kubernetes, так как в итоге после обработки все сообщения попадают в общие топики и далее в распределённые таблицы базы данных Clickhouse.

Заключение

Статья получилось объёмной, пришлось разделить её на две части. Спасибо всем, кто дочитал до конца.

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

Начиная проект, мы не имели большого опыта работы с Clickhouse, Kafka и опыта разработки на Go, но это не стало большой проблемой. Наоборот, проект нам помог освоить на практике новые инструменты и сейчас мы их активно применяем в других задачах.

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


ссылка на оригинал статьи https://habr.com/ru/articles/861376/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *