Метрики — один из трёх базовых типов телеметрии и основа мониторинга любого приложения. Но что, если необходимо собирать их в рамках крупной и высоконагруженной экосистемы? Как получить метрики с десятков тысяч хостов разных ЦОДов и сотен типов приложений? И заодно упростить инженерам настройку правил алертинга и создание дашбордов?
Привет, Хабр! Я Филипп Бочаров, руководитель стрима мониторинга и наблюдаемости в МТС Digital. Мы занимаемся всеми типами телеметрии: логами, трассировкой и, конечно, метриками. Единое хранилище метрик экосистемы — часть нашей платформы наблюдаемости. Для этих целей мы используем агент Telegraf и большой кластер VictoriaMetrics, принимающий 10+ миллионов сэмплов в секунду.
В этой статье расскажу, как мы реализовали централизованное управление конфигурацией агентов, удобный интерфейс для настройки алертинга и правил сбора метрик. Покажу, как менялась архитектура решения с ростом нагрузки, как мы боролись с отставанием и потерей данных. Посмотрим, как это позволило собрать все метрики в единое хранилище и построить дашборды здоровья по ключевым продуктам.
Предпосылки для создания единого хранилища метрик
В экосистеме МТС множество дочерних компаний, у каждой из которых собственное IT-подразделение и свой децентрализованный мониторинг на базе Zabbix или Prometheus. Такая конфигурация создавала проблему: чтобы узнать о состоянии продукта, сначала приходилось разбираться, какая система мониторинга за него отвечает. Это было неудобно. А с ростом нагрузки и сложности экосистемы хотелось иметь единое централизованное, недорогое, надёжное и легко масштабируемое решение для хранилища метрик всей экосистемы, которое упростило бы работу продуктовых команд.
Основные требования:
Контроль здоровья экосистемы и ключевых бизнес-сценариев в единой точке.
Масштабируемость под растущую нагрузку 600+ продуктов.
Всё «из коробки» чтобы снизить трудозатраты команд на настройку мониторинга.
Поиск решений и выбор системы хранения метрик
На старте мы отказались от Zabbix, несмотря на то, что он уже был частью экосистемы. Сначала пытались его использовать, но столкнулись с серьёзными ограничениями:
Высокие требования к ресурсам. При нагрузке в 30 тысяч nvps Zabbix упёрся в пределы ресурсов — виртуальный сервер с 32 ядрами CPU и 248 GB RAM не справлялся, и система начинала испытывать проблемы.
Низкая степень сжатия данных. Zabbix плохо сжимал данные, и в результате они стали занимать слишком много места. Мы понимали, что с нашим ростом он не справится.
Вопрос совместимости с Kubernetes. В экосистеме активно внедрялся Kubernetes, заточенный под другой стек мониторинга, основанный на Prometheus.
В поиске альтернатив проанализировали несколько вариантов вроде Cortex, Mimir, Thanos. В итоге остановились на VictoriaMetrics по нескольким причинам:
Потребляет наименьшее количество ресурсов и лучше альтернативных сервисов сжимает данные.
Это хорошо видно из таблицы. Для 10 миллионов уникальных рядов она использует в пять раз меньше памяти по сравнению с ближайшими конкурентами.
Отлично горизонтально масштабируется. Это позволяет легко добавлять мощности по мере роста нагрузки.
Принимает данные по разным протоколам. Это удобно для интеграции с разными стеками и системами мониторинга.
Поддерживает данные высокой кардинальности. Далее поговорим об этом подробно.
Настройка и масштабирование VictoriaMetrics
Наша стартовая архитектура выглядела так. Множество продуктов отправляли метрики в единое хранилище через балансировщик нагрузки. Всё это попадало на пул компонентов vminsert — сервисов, ответственных за запись данных в VictoriaMetrics. В свою очередь, vminsert записывал всё в сервис хранения vmstorage.
В схеме было два читателя:
Grafana — система визуализации метрик на дашбордах.
vmalert — компонент, который следит за правилами алертинга и предупреждает, когда метрика вышла за допустимый порог.
Вся нагрузка на чтение шла через балансировщик и попадала на компоненты vmselect, ответственные за обработку запросов на чтение. Они же извлекали данные из vmstorage.
Такая архитектура оказалась гибкой. Компоненты можно было масштабировать независимо, чтобы улучшить чтение или запись. Но с ростом нагрузки начались трудности.
Проблема: падение vminsert
С ростом количества хостов под мониторингом увеличилось и число агентов, отправляющих метрики, а также число соединений с компонентами vminsert. Оказалось, что последний плохо с этим справляется, начинает деградировать и медленнее обрабатывает входящий трафик. Поэтому компоненты vminsert нужно было защитить.
Решение: vmagent для буферизации
Для защиты vminsert от перегрузки мы использовали ещё один компонент VictoriaMetrics — vmagent. Это легковесный агент, умеющий принимать данные метрик по разным протоколам и скрейпить перед их отправкой на vminsert. Такой «легковесный Prometheus» или промежуточное звено.
В результате мы получили:
Радикально снизилось количество соединений. Это произошло за счёт того, что у vmagent есть внутренний буфер, где он может накапливать метрики, буферизировать и уменьшать коннекты.
Получили защиту от кратковременных сбоев на нашем контуре. Метрики могут полежать какое-то время в буфере vmagent, дождаться, когда у vminsert и vmstorage всё стабилизируется, и дозаписаться.
vmagent умеет пережимать метрики, поэтому мы использовали алгоритм сжатия Zstd и уменьшили поток передаваемого трафика в четыре раза.
В итоге получилось хорошее рабочее решение. Но проблема падения vminsert оказалась не единственной, потому что следом возникли сложности с чтением данных.
Проблема: алертинг и дашборды конкурируют за ресурсы
Нагрузка на чтение от двух читателей — Grafana и vmalert — создавала их конкуренцию. В централизованной системе каждый пользователь мог создавать сложные дашборды, настраивать любые алерты, а значит запросы параллельно нагружали VictoriaMetrics. В результате сами дашборды загружались медленнее. Поэтому хотелось разделить эту нагрузку.
Решение: запись vminsert в два контура
Чтобы снизить влияние одного процесса на другой, решили сделать два отдельных контура VictoriaMetrics: один для Grafana и дашбордов, а другой для vmalert и алертинга.
Для этого нужно было зеркалировать трафик, чтобы в обоих контурах VictoriaMetrics лежала одинаковая копия данных. На тот момент такой возможностью обладал только компонент vminsert. С его помощью мы начали писать одновременно в два контура. Схема работала, но в процессе стало ясно, что vminsert не всегда хорошо справляется.
Проблема: запись vminsert в два контура не работает
Когда один из контуров VictoriaMetrics сталкивался с проблемами или был недоступен, vminsert переставал записывать данные сразу в оба контура. Это касалось не только аварийных ситуаций. Даже при простом запуске retention или штатных фоновых активностях VictoriaMetrics производительность немного снижалась. В этот момент мы ощущали, что vminsert деградирует, и нас это не устраивало.
Решение: зеркалирование трафика на vmagent
С выходом версии v1.69+ у компонента vmagent появилась поддержка зеркалирования трафика. Это позволило заменить эту функцию с vminsert на vmagent.
Когда трафик стал зеркалироваться на vmagent, мы получили поведение, которое хотели. В случае проблем в одном контуре данные продолжали записываться во второй, а метрики временно хранились в буфере vmagent, ожидая стабилизации проблемного контура. Но и это решение не было идеальным.
В конфигурации, где один контур выделен под Grafana, а второй — под vmalert, при падении одного из них мы оставались либо без дашбордов, либо без алертинга. А это плохо для mission-critical системы. Нужно было сделать так, чтобы при падении одного контура продолжало работать и то, и другое. Для этого мы создали верхнеуровневый слой vmselect между читателями контуров.
Решение: два пула vmselect
Мы выделили два отдельных пула vmselect — один для vmalert, а другой для Grafana. Каждый работал с обоими контурами VictoriaMetrics.
Таким образом, мы разделили запись и чтение, распределили нагрузку между пулами, и в случае сбоя одного контура запросы от Grafana и vmalert могли направляться к исправно работающему. Но мы всё ещё не избавились от потери данных.
Проблема: потери данных
У vmagent есть внутренний ограниченный по объему буфер, который позволяет временно копить метрики. Если проблемы в контуре затягиваются, например, сбой в vmstorage длится слишком долго, vmagent может создавать очень много коннектов и падать, теряя данные.
Тогда у нас не получится поддерживать работу во время длительных простоев VictoriaMetrics, проводить обслуживание, потому что буфера агента просто не хватит. Поэтому в качестве решения договорились сделать внешний буфер — отдельную очередь, где наши метрики могли бы храниться дольше.
Решение: используем внешнюю очередь
Чтобы реализовать очередь для записи метрик, использовали Kafka. Написали собственные компоненты VMBuffer-Producer и VMBuffer-Consumer — сервисы, которые доставляют данные в Kafka и читают из неё.
Те, кто сталкивался с VictoriaMetrics, могут спросить: «Зачем вы сделали свой велосипед? VictoriaMetrics и так умеет работать с Kafka». Это было вынужденное решение. Мы тестировали реализацию консьюмеров и продюсеров, которые есть в VictoriaMetrics из коробки, и она нас не устроила. Проблемы были теми же, что и у vminsert. В случае деградации одного из контуров консьюмер начинал медленнее записывать данные (или вообще переставал) и даже после восстановления не возвращался в рабочее состояние. Поэтому нам пришлось делать собственную реализацию.
В новой схеме vmagent отправляет данные не напрямую в компонент vminsert, а в VMBuffer, который полностью повторяет интерфейс vminsert.
В обоих контурах стоит консьюмер, который вычитывает данные из Kafka. Каждая запись в Kafka — это батч метрик размером примерно 100 MB (от 10 до 100 тысяч сэмплов). Дополнительно в Kafka мы создаём отдельные топики для важных продюсеров метрик, например, кластеров Kubernetes. Это сделано, чтобы их метрики не пересекались с метриками продуктов и обрабатывались вне очереди.
Финальная архитектура
Наша итоговая архитектура включает ещё одно важное дополнение. Экосистема МТС геораспределённая и центры обработки данных (ЦОД) расположены по всей стране. Поэтому идея гонять метрики из Владивостока в Москву кажется неэффективной. Хотелось сделать так, чтобы они максимально быстро доставлялись до платформы и не передавались между ЦОДами. Для этого необходимо, чтобы данные направлялись в ближайшее развёртывание платформы.
Мы реализовали это с помощью GSLB (Global Server Load Balancing) — балансировки трафика по географическому признаку:
Мы разместили vmagent в каждом ЦОДе, и теперь продукт, используя GSLB, пишет свои метрики в ближайший доступный vmagent.
Когда vmagent записывает метрики в контуры VictoriaMetrics, он также использует GSLB, чтобы выбрать территориально ближайший контур.
Таким образом, каждый контур VictoriaMetrics получает только часть трафика. А чтобы в каждом было по 100%, компоненты VMBuffer работают со всеми Kafka в каждом контуре. Другими словами, VMBuffer в одном контуре VictoriaMetrics получает данные из всех контуров VictoriaMetrics.
Теперь, когда мы узнали финальную архитектуру, обсудим, как поддерживать это решение и обеспечивать доступность на высоком уровне. Для этого был нужен самомониторинг.
Как мониторить мониторинг?
По классике мониторинг должен быть whitebox и blackbox. А мы должны получать метрики работы системы изнутри и одновременно видеть всё глазами пользователя.
Метрики whitebox (изнутри):
Kafka — лаг по топикам. Это ключевая метрика. Если лаг растёт, значит мы не успеваем обрабатывать метрики и есть проблема с производительностью.
Nginx — процент ошибочных запросов на чтение. Метрика, позволяющая отслеживать корректность запросов.
vmselect — 95 перцентиль скорости ответа. Показывает, насколько быстро клиенты получают ответы на запросы по метрикам. Это легко мониторится и настраивается, потому что все компоненты VictoriaMetrics отдают метрики в формате Prometheus.
vmagent — объём трафика на входе. Ещё одна ключевая метрика. Для геораспределённой системы важно мониторить проблемы во всех сегментах сети. Всегда есть риск, что мы лишимся трафика на одном из участков, поэтому его объём хорошо подсвечивает возможные проблемы. Как только мы видим падение ниже допустимого коридора, значит один из сегментов выпал, и надо разбираться, почему это произошло.
Blackbox метрики помогают контролировать систему снаружи, как это видят пользователи, а значит, мы пробуем доступность всех публичных интерфейсов:
API VictoriaMetrics.
Grafana.
Осталось решить, куда складывать метрики, кто будет мониторить мониторинг и проверять за проверяющим. Конечно, можно хранить их в самой VictoriaMetrics, но если с ней что-то случится, мы лишимся данных. Поэтому решили развернуть рядом с каждым контуром VictoriaMetrics отдельный vmsingle.
Компонент vmsingle — это, по сути, VictoriaMetrics, собранная в одном инстансе. Он не приспособлен к большому объёму данных, но может использоваться для их хранения о мониторинге самого кластера VictoriaMetrics. Это позволяет держать метрики в безопасности даже в случае проблем с основным контуром и не лишиться их, если он выйдет из строя.
Результаты системы мониторинга в цифрах
Мы сделали классный контур, научились его масштабировать и хранить данные:
Производительность позволяет обрабатывать до 12 миллионов сэмплов в секунду.
Масштаб системы охватывает более 300 продуктов экосистемы и более 2000 активных
инфраструктуры.
Как собрать метрики
Мы столкнулись сразу с несколькими вызовами:
Объём эксосистемы: более 90 тысяч различных хостов в разных сегментах сети.
Разнообразный стек: СУБД, веб-сервера и довольно большой зоопарк ПО.
Потребность в структуре метрик: мы хотели привести метрики к понятной схеме, в которой при необходимости было бы легко найти всё нужное для создания дашбордов, алертов, настройки мониторинга.
Минимизация затрат продуктовых команд: помним про условие, что мы делаем платформу. Поэтому сбор метрик для продуктов должен быть максимально дешёвым и простым.
Добиться этого можно двумя способами.
Способ 1: экспортёры Prometheus
Классический подход, с которым многие наверняка сталкивались — использование экспортёров Prometheus. Это зарекомендовавший себя временем подход, когда рядом с каждым ПО ставится скачанный с GitHub экспортёр, настраивается, ставится дополнительный сборщик, который эти метрики скрейпит.
Подход нормальный, но очень «многодельный» и имеет недостатки:
Для каждого ПО свой экспортёр. А значит, потребуется отдельная настройка и поддержка.
Разный формат метрик. Для некоторых популярных типов ПО экспортёров может быть несколько. Использование командами разных экспортёров приводит к тому, что одинаковые по смыслу метрики называются по-разному.
Централизованно нельзя обновить и настроить. Никто не может гарантировать, что экспортёр будет обновлён до последней версии после обновления самого ПО. И что команда за ним достаточно следит и поднимет в случае падения.
Мы пошли по другому пути, основанном на агенте Telegraf и щепотке магии.
Способ 2: агент Telegraf
Telegraf — это легковесный агент, написанный на Go. Он умеет не только собирать, но и отправлять метрики в любое количество точек по разным протоколам и организовывать полноценный конвейер их обработки.
Концепция Telegraf выстроена вокруг плагинов. input-плагины умеют работать с разными типами ПО и сразу встроены в образ Telegraf. В отличие от экспортёров, здесь ничего не нужно ставить дополнительно.
Мы пошли чуть дальше и сам Telegraf встроили в золотой образ операционных систем в МТС. Это значит, что при развёртывании любой виртуальной машины в МТС, Telegraf на ней уже установлен и готов к работе. Но магия даже не в этом! Мы научились централизованно им управлять.
Управление Telegraf
Чтобы управлять агентом Telegraf на каждом хосте, мы разработали Telegraf Helper — сервис, встроенный в золотой образ. Он разворачивается вместе с Telegraf, и инженер не тратит время на установку и ручную настройку каждого агента. Telegraf Helper умеет:
Отслеживает работу агента Telegraf, собирает статистику, перезапускает агента в случае ошибок и следит за его здоровьем.
При первом запуске и далее по расписанию Telegraf обновляет конфигурацию агента из централизованного хранилища правил через API.
Объединяет правила сбора метрик для одного хоста от разных команд в общую конфигурацию.
Упрощённо правила выглядят так: «Для всех хостов по маске pg* активируй плагин сбора метрик с Postgres». Это действует сразу на группу хостов, и его не нужно настраивать для каждого хоста отдельно. Более того, для одного хоста может действовать несколько правил. Для нас вполне нормальная ситуация, когда для какого-то Unix-хоста профильная команда Unix создаёт правила для сбора метрик операционной системы, необходимых для работы. А команда DBA для того же хоста создаёт правила для сбора метрик с Postgres. Оба правила действуют на один хост. Telegraf Helper умеет объединять их и превращать в валидную конфигурацию агента Telegraf. А она загружается в агент, чтобы он начал собирать нужные метрики.
Такой подход радикально снижает затраты инженеров. Не нужно ничего устанавливать. Можно сразу настроить мониторинг на целый кластер Postgres. И даже если появится новая нода или новый сервер, они автоматически попадут под этот мониторинг.
У централизованного сбора метрик есть ещё несколько приятных последствий:
Метрики «из коробки»
Это то, чего мы хотели достичь. У нас как энтерпрайз-компании есть внутреннее облако, которое предоставляет managed ресурсы: DBaaS (база данных как сервис), Managed Kubernetes, Kafka as a Service и другие. Все эти средства разработки по умолчанию подключены к платформе, а их метрики собираются автоматически. Поэтому продукты выстраивая своё решение на базе этих сервисов, получают не только базу данных как сервис, но и мониторинг этой базы данных как сервис:
Все метрики автоматически собираются и визуализируются в публичных дашбордах.
Инженеры сразу видят данные по своим базам данных или кластерам.
Также по умолчанию создаются типовые правила алертинга, и команда не тратит время на их настройку.
Централизованный сбор метрик не только упрощает мониторинг, но и позволяет стандартизировать расчёт индикаторов качества или Service Level Indicator (SLI).
Типовые формулы SLI
Чтобы отслеживать здоровье экосистемы, мы просим все продукты описывать своё здоровье набором SLI. Это формулы, написанные на языке PromQL (MetricsQL), которые оценивают работу бизнес-операций продукта в процентах: от 0%, когда всё плохо, до 100%, когда всё отлично. Благодаря сбору метрик в общем хранилище и едином формате, мы можем делать эти формулы типовыми.
Для примера приведу правило, которое задаёт золотой сигнал «ошибки» — процент ошибочных запросов.
Для продуктов на Kubernetes мы можем вычислить процент ошибок запросов через метрики Nginx с помощью формулы. Она берёт все успешные запросы к ingress-контроллеру и делит на сумму всех пришедших запросов. Получаем процент результативности, успешности выполнения операции. Эта формула будет одинаковой для всех продуктов, которые используют Kubernetes. Остается подставить туда название продукта и получить типовой индикатор.
Итак, всё звучит слишком позитивно, и, кажется, мы решаем все проблемы. Поэтому пора добавить ложку дёгтя.
Как сломать коммунальное хранилище: вредные советы
Поскольку наше хранилище — это общее пространство, любой пользователь может подключиться и сделать что-то не так. Ошибки в самом простом случае ухудшат клиентский опыт, но могут и глобально повлиять на здоровье хранилища и другие продукты.
Одна из проблем, влияющих на клиентский опыт — это метрики с огромным лагом доставки.
Метрики с огромным лагом
Если метрики достигают хранилища через 10 минут, ни о каком оперативном мониторинге говорить не приходится. За это время пользователи уже успеют обратиться в контакт-центр и вы узнаете о проблеме без мониторинга. Поэтому важно стремиться сократить лаг.
Лаг метрик складывается из двух частей:
На стороне платформы — время замера в очереди Kafka. Это наша зона ответственности, и мы её мониторим.
На стороне продукта — время скрейпа и ожидания в буфере vmagent. Это часть инфраструктуры продукта, и её мониторинг более сложен.
Нужно средство контроля полного времени доставки метрики. Готового решения не было, поэтому его сделали самостоятельно. Написали отдельный компонент — Stream Metrics. Он параллельно читает весь поток метрик из Kafka, находит для каждого замера время событий, вычисляет время доставки Kafka, сравнивает их и получает дельту. Она и является лагом той метрики, которая говорит, своевременно ли продукт доставляет метрики.
Дальше мы отслеживали лаг и смотрели, у каких продуктов он превышал допустимые показатели. Затем разбирались с причинами: на стороне продукта выделено мало ресурсов для vmagent, не включено сжатие, не выставлены какие-то настройки или что-то ещё. Масса вариантов, поэтому нужно работать с командой и помогать им правильно настроить отправку метрик.
Высокая кардинальность в VictoriaMetrics
Кардинальность — это количество уникальных временных рядов. Например, у нас в VictoriaMetrics метрика http_request_total показывает количество HTTP-запросов. У нее два разреза — instance и path.
Кардинальность здесь равна двум, потому что у метки instance одно уникальное значение (цифра 1), а у path — два (read и write).
Если мы добавим в эту метрику метку user_id с уникальным идентификатором пользователей, а их у нас миллион, то суммарная кардинальность получится два миллиона.
Это важно! Для каждого уникального сочетания меток VictoriaMetrics создаёт новый временной ряд и тратит на это дополнительные ресурсы. Когда таких уникальных временных рядов много, нагрузка на систему резко возрастает.
Есть отдельная метрика Churn Rate, которая показывает количество новых временных рядов в единицу времени. На графике выше подсвечен аварийный момент, когда Churn Rate скакнула. Кто-то создал очень много новых временных рядов. В этот момент VictoriaMetrics было плохо. Дальше показан момент, когда мы поработали с командами, снизили кардинальность и упростили жизнь контурам VictoriaMetrics.
Но сначала надо понять, чью кардинальность мы снижаем, и найти виновника проблемы. Для этого можно воспользоваться Cardinality Explorer. Это интерфейс VMUI, который показывает статистику по всем метрикам VictoriaMetrics и позволяет найти с наиболее высокой кардинальностью.
VMUI Cardinality Explorer
В данном случае мы видим метрику, которая значительно увеличивает свою кардинальность. Можем углубиться в детали и выяснить, какие метки влияют на её прирост.
Здесь причиной стал peer-адрес, поскольку там наибольшее количество уникальных значений. Если эту метку убрать, кардинальность сильно снизится.
Снижаем кардинальность
Когда мы находим такую проблему, начинаем работать с командой, чтобы оптимизировать метку. В этом помогает секция Relabel в vmagent, которая позволяет выполнять любые операции с метками, включая удаление.
Реальный пример работы с командой DBA. У них была метрика для Postgres, в одной из меток записывался текст SQL-запроса. Поскольку текст SQL-запросов очень вариативен, кардинальность сильно выросла. Но метка была некритичной для команды, поэтому мы её убрали, сократив кардинальность.
Выводы
Теперь подведу итоги и перечислю рекомендации на основе нашего опыта.
Работайте с командами для снижения кардинальности и лага
При создании общего хранилища данных, в нашем случае метрик, важно предвидеть возможные ошибки пользователей, учиться их мониторить и автоматически отлавливать. Они могут серьёзно ухудшить как опыт самого продукта, так и в целом повлиять на здоровье системы. Здесь командам часто нужна помощь со стороны.
Создайте стандарты и best practices для сбора метрик
Вторая задача при создании централизованного хранилища — это единые правила, которые нужно задать для всего вашего ландшафта. Здорово, если они сделаны в виде какого-то инструмента, который вообще не даёт ошибиться. Например, как Telegraf с централизованным управлением. Там этот стандарт вшит в инструмент. Но если этого нет, стандарты нужно фиксировать в виде нормативной документации, проводить демо, обучать команды и рассказывать, как правильно подключаться.
Рассмотрите возможность использования VictoriaMetrics Enterprise
Мы используем Community-версию из-за лицензионных ограничений. Но в Enterprise-версии много таких полезных функций, как downsampling, расширенная статистика по тенантам и vmanomaly (компонент занимающийся машинным обучением). Если вы можете справиться с лицензионными ограничениями, Enterprise версия сэкономит много человеко-часов и нервов.
Распределяйте vmagent-ы по разным ЦОДам и балансируйте трафик по GSLB
Если вы делаете геораспределённую систему, то здорово сразу заложить возможность распределения записи трафика по нескольким vmagent-ам, разбросанным по ЦОДам. Это поможет избежать масштабных изменений в будущем, если придётся переконфигурировать инфраструктуру продукта.