Иногда команда разработки сталкивается с задачей, в которой у неё мало экспертного опыта, и через пробы и ошибки она находит неочевидное решение. Так произошло и с нами, когда понадобилось перенести сбор метрик из Infux в Prometheus. Их итоговая размерность оказалась 1,5 миллиона, и мы решили ее уменьшать. Инфраструктуру по сбору метрик (Prometheus, k8s, деплой через Helm) создавали DevOps-инженеры из другой команды, у которых не было ресурсов для нашей задачи. Поэтому мы заручились их советами, изучили документацию и решили снижать размерность метрик силами разработки.
Эта статья не подойдет опытным DevOps-инженерам, но будет полезна разработчикам, которые хотят уменьшить размерность метрик и не желают погружаться в документацию. Или тем, кто намеренно отказывается от иерархической федерации и ищет обходное решение, но не хочет наступить на наши грабли. Расскажем:
Наша команда отвечает за один из продуктов Mindbox — рекомендации товаров на сайте и в рассылках. Мы собирали время обработки рекомендаций в реальном времени в Influx, и чтобы помочь бизнесу оценить работу продукта, понадобилось считать еще и Apdex (Application Performance Index). Компания постепенно переносит метрики из Influx в Prometheus, поэтому решили обе метрики собирать сразу в Prometheus с помощью гистограмм.
Гистограмма с метриками, которую хотели создать, чтобы оценивать работу продуктов
Наши сервисы развернуты в Kubernetes. Метрики в Prometheus мы собираем с помощью ServiceMonitor. В приложении используем Prometheus.NET. В дефолтной конфигурации к каждой метрике, которую экспортирует pod, добавляется лейбл pod с соответствующим значением.
Сбор метрик в 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
2. Поднять второй ServiceMonitor и настроить роутинг метрик между ними. В одном через релейблинг убирать лейблы pod/instance, а в другом оставлять как есть.
Плюсы: дешевле — надо только развернуть ServiceMonitor, проще в поддержке.
Минусы: ops-реализация, с которой не были знакомы разработчики.
Сбор метрик в Prometheus с помощью второго ServiceMonitor
Первое решение. Для начала выбрали очевидную реализацию через Pushgateway. Подняли Pushgateway, запушили туда метрики и использовали в качестве лейбла instance константу. Запрос выглядел примерно так:
Мы быстро справились с задачей и первое время результат радовал — метрики собирались, а размерность не росла. Но скоро мы стали замечать крупные провалы в метриках у некоторых механик. При этом прослеживалась странная закономерность — когда у одних механик метрика передавалась корректно и непрерывно, у других начинались провалы. Из-за этого складывалось впечатление, что в единый момент времени репортится только одна группа механик. Таких групп было несколько, и они менялись в произвольном порядке.
Почему не сработало. Тот, кто знаком с устройством Pushgateway, вероятно, сразу понял, что наше решение нерабочее. В Pushgateway лейблы передаются двумя способами: через путь запроса или в теле запроса. При этом набор лейблов и их значений, которые передаются через путь, работают как ключ для словаря, где хранятся метрики. Все, что передалось через тело запроса, попадает в значение по этому ключу. То есть в нашем случае каждый запрос с pod’а перезаписывал все метрики, которые пушились с других pod’ов. Поскольку Prometheus собирает метрики с интервалом, то в него попадали метрики только с того pod’а, который последним успел запушить.
Чтобы корректно отправлять метрики в Pushgateway, пришлось бы написать кастомный С# код. Но такое решение не были ни простым, ни дешевым, поэтому от него отказались.
Второе решение. Мы решили снова уцепиться за Pushgateway: собирать исходные метрики и пушить со всеми лейблами, а потом убирать лейбл pod с помощью ServiceMonitor, который собирает метрики с Pushgateway. Но уже на старте поняли, что идея не сработает.
Почему не реализовали. У Pushgateway есть несколько особенностей, которые делают такое решение невозможным. Главная — данные не очищаются автоматически, по ретеншену. Значит, нужно отслеживать размер диска и писать код очистки вручную. Другая проблема в том, что после релейблинга метрики с одинаковым набором лейблов, но с разным лейблом pod, будут конфликтовать. В итоге останется только последняя метрика в порядке Pushgateway. При этом метрики отсортированы не по дате последнего изменения, а в алфавитном порядке. Так при выкладке значения с новых pod’ов могут не попасть в Prometheus.
Мы вернулись ко второму исходному варианту и сделали два ServiceMonitor. Дополнительно в коде проставили специальный лейбл (в нашем случае — business) для тех метрик, чью размерность снижаем:
Сделали все через релейблинг, в конфигурацию первого ServiceMonitor добавили код:
В конфигурацию второго ServiceMonitor прописали:
Юрий Соколов, разработчик
Эта статья не подойдет опытным DevOps-инженерам, но будет полезна разработчикам, которые хотят уменьшить размерность метрик и не желают погружаться в документацию. Или тем, кто намеренно отказывается от иерархической федерации и ищет обходное решение, но не хочет наступить на наши грабли. Расскажем:
- как в два шага уменьшить размерность метрик с помощью двух ServiceMonitor,
- какой есть эталонный способ уменьшить размерность метрик без «костылей»,
- почему не стоит тратить время на снижение размерности метрик с помощью Pushgateway.
Почему понадобилось уменьшать размерность метрик
Наша команда отвечает за один из продуктов Mindbox — рекомендации товаров на сайте и в рассылках. Мы собирали время обработки рекомендаций в реальном времени в Influx, и чтобы помочь бизнесу оценить работу продукта, понадобилось считать еще и Apdex (Application Performance Index). Компания постепенно переносит метрики из Influx в Prometheus, поэтому решили обе метрики собирать сразу в Prometheus с помощью гистограмм.
Гистограмма с метриками, которую хотели создать, чтобы оценивать работу продуктов
Наши сервисы развернуты в Kubernetes. Метрики в Prometheus мы собираем с помощью ServiceMonitor. В приложении используем Prometheus.NET. В дефолтной конфигурации к каждой метрике, которую экспортирует pod, добавляется лейбл pod с соответствующим значением.
Сбор метрик в 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
2. Поднять второй ServiceMonitor и настроить роутинг метрик между ними. В одном через релейблинг убирать лейблы pod/instance, а в другом оставлять как есть.
Плюсы: дешевле — надо только развернуть ServiceMonitor, проще в поддержке.
Минусы: ops-реализация, с которой не были знакомы разработчики.
Сбор метрик в 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
Что мы вынесли из истории с поиском решений
- Ни Pushgateway напрямую, ни релейблинг в нем не подходят для снижения размерности метрик.
- Если использовать релейблинг, то метрики с одинаковыми наборами лейблов не должны одновременно репортиться с разных pod’ов.
- Второй ServiceMonitor — «костыль», который просто и быстро реализовать, если не хочется тратить ресурсы на федерацию.
- Лучшее решение для снижения размерности метрик — федерация:
- Prometheus с низким ретеншеном,
- собирают агрегаты (recording rules),
- отправляют в Prometheus с высоким ретеншеном.
Юрий Соколов, разработчик