Культура и компания
1 июля 2021

Как уменьшить размерность метрик в Prometheus, если вы не DevOps

Иногда команда разработки сталкивается с задачей, в которой у неё мало экспертного опыта, и через пробы и ошибки она находит неочевидное решение. Так произошло и с нами, когда понадобилось перенести сбор метрик из Infux в Prometheus. Их итоговая размерность оказалась 1,5 миллиона, и мы решили ее уменьшать. Инфраструктуру по сбору метрик (Prometheus, k8s, деплой через Helm) создавали DevOps-инженеры из другой команды, у которых не было ресурсов для нашей задачи. Поэтому мы заручились их советами, изучили документацию и решили снижать размерность метрик силами разработки.

Эта статья не подойдет опытным DevOps-инженерам, но будет полезна разработчикам, которые хотят уменьшить размерность метрик и не желают погружаться в документацию. Или тем, кто намеренно отказывается от иерархической федерации и ищет обходное решение, но не хочет наступить на наши грабли. Расскажем:

  • как в два шага уменьшить размерность метрик с помощью двух ServiceMonitor,
  • какой есть эталонный способ уменьшить размерность метрик без «костылей»,
  • почему не стоит тратить время на снижение размерности метрик с помощью Pushgateway.

Почему понадобилось уменьшать размерность метрик

Наша команда отвечает за один из продуктов Mindbox — рекомендации товаров на сайте и в рассылках. Мы собирали время обработки рекомендаций в реальном времени в Influx, и чтобы помочь бизнесу оценить работу продукта, понадобилось считать еще и Apdex (Application Performance Index). Компания постепенно переносит метрики из Influx в Prometheus, поэтому решили обе метрики собирать сразу в Prometheus с помощью гистограмм.

Гистограмма с метриками
Гистограмма с метриками, которую хотели создать, чтобы оценивать работу продуктов

Наши сервисы развернуты в Kubernetes. Метрики в Prometheus мы собираем с помощью ServiceMonitor. В приложении используем Prometheus.NET. В дефолтной конфигурации к каждой метрике, которую экспортирует pod, добавляется лейбл pod с соответствующим значением.

Сбор метрик в Prometheus с помощью ServiceMonitor

Сбор метрик в Prometheus с помощью ServiceMonitor

Чтобы показать среднее время обработки, перцентили (p50, p95, p99) и Apdex, планировали использовать гистограмму с 20 бакетами. С учетом того, что информацию мы хотели получать относительно каждой из 2,5 тысячи механик рекомендаций, суммарная размерность метрик была 50 тысяч. Имена pod’ов меняются на каждой выкладке, а лейбл pod приписывается к каждой метрике, поэтому с ретеншеном в 30 дней и ежедневной выкладкой размерность вырастает до 1,5 миллиона. Метрика с такой размерностью занимала гораздо больше места в Prometheus, чем нам хотелось.

2500 * 20 * 30 = 1 500 000
(число механик) * (число бакетов гистограммы) * (ретеншен) = (итоговая размерность)

Мы решили избавиться от лейбла pod и instance, чтобы размерность не увеличивалась на выкладках. При этом старались найти простое и дешевое решение, которое можно реализовать и поддерживать без привлечения DevOps-инженера.

Важное допущение: метрика, для которой мы хотели снижать размерность, собирается в каждый момент времени только с одного pod’а. Поменяться pod может только при перезапуске приложения, например при выкладке.

Какие варианты решения рассматривали

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

Мы не рассматривали федерацию, потому что хотели найти решение проще, дешевле и быстрее в исполнении. Кроме того, работать над задачей предстояло разработчикам, а не DevOps, поэтому хотелось использовать знакомые инструменты и приемы. Хотя практика показала, что на поиски такого решения мы потратили время, за которое можно было сделать федерацию, а наша реализация оказалась «костылем».

Сформулировали два равнозначных решения:

1. Поднять Pushgateway и пушить туда метрики без лейблов. В компании уже был helm chart для мониторинга стека, в том числе и для Pushgateway.

Плюсы: код и чарты можно переиспользовать, в поднятый Pushgateway можно перенести метрики с других серверов команды, которые находятся вне Kubernetes и еще не успели переехать из Influx в Prometheus.

Минусы: дороже поддержка.

Сбор метрик в Prometheus с помощью Pushgateway

Сбор метрик в Prometheus с помощью Pushgateway

2. Поднять второй ServiceMonitor и настроить роутинг метрик между ними. В одном через релейблинг убирать лейблы pod/instance, а в другом оставлять как есть.

Плюсы: дешевле — надо только развернуть ServiceMonitor, проще в поддержке.

Минусы: ops-реализация, с которой не были знакомы разработчики.

Сбор метрик в Prometheus с помощью второго ServiceMonitor

Сбор метрик в Prometheus с помощью второго ServiceMonitor

Как провалилось решение c Pushgateway

Первое решение. Для начала выбрали очевидную реализацию через Pushgateway. Подняли Pushgateway, запушили туда метрики и использовали в качестве лейбла instance константу. Запрос выглядел примерно так:

curl -i -X POST \
     -d 'some_metric{bar=\"fooo\"} 3.22' \
     'https://pushgateway:9091/metrics/instance/constant/'

Мы быстро справились с задачей и первое время результат радовал — метрики собирались, а размерность не росла. Но скоро мы стали замечать крупные провалы в метриках у некоторых механик. При этом прослеживалась странная закономерность — когда у одних механик метрика передавалась корректно и непрерывно, у других начинались провалы. Из-за этого складывалось впечатление, что в единый момент времени репортится только одна группа механик. Таких групп было несколько, и они менялись в произвольном порядке.

Почему не сработало. Тот, кто знаком с устройством Pushgateway, вероятно, сразу понял, что наше решение нерабочее. В Pushgateway лейблы передаются двумя способами: через путь запроса или в теле запроса. При этом набор лейблов и их значений, которые передаются через путь, работают как ключ для словаря, где хранятся метрики. Все, что передалось через тело запроса, попадает в значение по этому ключу. То есть в нашем случае каждый запрос с pod’а перезаписывал все метрики, которые пушились с других pod’ов. Поскольку Prometheus собирает метрики с интервалом, то в него попадали метрики только с того pod’а, который последним успел запушить.

Чтобы корректно отправлять метрики в Pushgateway, пришлось бы написать кастомный С# код. Но такое решение не были ни простым, ни дешевым, поэтому от него отказались.

Второе решение. Мы решили снова уцепиться за Pushgateway: собирать исходные метрики и пушить со всеми лейблами, а потом убирать лейбл pod с помощью ServiceMonitor, который собирает метрики с Pushgateway. Но уже на старте поняли, что идея не сработает.

Почему не реализовали. У Pushgateway есть несколько особенностей, которые делают такое решение невозможным. Главная — данные не очищаются автоматически, по ретеншену. Значит, нужно отслеживать размер диска и писать код очистки вручную. Другая проблема в том, что после релейблинга метрики с одинаковым набором лейблов, но с разным лейблом pod, будут конфликтовать. В итоге останется только последняя метрика в порядке Pushgateway. При этом метрики отсортированы не по дате последнего изменения, а в алфавитном порядке. Так при выкладке значения с новых pod’ов могут не попасть в Prometheus.

Как сработало решение со вторым ServiceMonitor

Мы вернулись ко второму исходному варианту и сделали два ServiceMonitor. Дополнительно в коде проставили специальный лейбл (в нашем случае — business) для тех метрик, чью размерность снижаем:

  • на одном ServiceMonitor отбросили все метрики со специальным лейблом, а остальные оставили как есть;
  • на другом оставили только метрики со специальным лейблом и убрали с них лейблы pod и instance.

Сделали все через релейблинг, в конфигурацию первого ServiceMonitor добавили код:

metricRelabelings:
  - action: drop
    sourceLabels:
      - business
    regex: "[Tt]rue"

В конфигурацию второго ServiceMonitor прописали:

metricRelabelings:
  - action: keep
    sourceLabels:
      - business
    regex: "[Tt]rue"
  - action: labeldrop
    regex: instance|pod|business

Что мы вынесли из истории с поиском решений

  1. Ни Pushgateway напрямую, ни релейблинг в нем не подходят для снижения размерности метрик.
  2. Если использовать релейблинг, то метрики с одинаковыми наборами лейблов не должны одновременно репортиться с разных pod’ов.
  3. Второй ServiceMonitor — «костыль», который просто и быстро реализовать, если не хочется тратить ресурсы на федерацию.
  4. Лучшее решение для снижения размерности метрик — федерация:
    • Prometheus с низким ретеншеном,
    • собирают агрегаты (recording rules),
    • отправляют в Prometheus с высоким ретеншеном.