Привет, Хабр! Я Владимир Гурьянов, технический директор команды, которая отвечает за наблюдаемость во всех продуктах Deckhouse. За более чем 8 лет эксплуатации Prometheus в Kubernetes мы накопили большой опыт — и заметили, что вопросы клиентов о мониторинге можно в основном разделить на две группы.

Первая сводится к тому, что Prometheus потребляет слишком много ресурсов. Наш ответ на неё — замена ванильной версии на Deckhouse Prom++. Это Open Source-продукт, который потребляет до десяти раз меньше памяти. Вторая же группа вопросов связана с интерпретацией данных, полученных из системы мониторинга. На многие из них ответит эта статья.

Сегодня мы посмотрим, в каких случаях не стоит доверять данным Prometheus, и разберём особенности работы lookback-delta, оконных функций и Federation API. В итоге вы сможете корректно интерпретировать данные с учётом особенностей этой и других систем мониторинга на базе TSDB.

Посмотреть театрализованную видеоверсию статьи

Эта статья написана по мотивам нашего совместного доклада с Евгением Бастрыковым на Deckhouse Conf 2025. Там мы рассказываем ту же информацию, но в интерактивном формате. А ещё примеряем на себя роли Шерлока Холмса и Доктора Ватсона, которые расследуют загадочную гибель пода. Если вам комфортнее смотреть, а не читать, выбирайте удобную площадку:

Содержание

Пример: как верные данные могут привести к неверным выводам

Представим себе транспортную компанию со своим парком автобусов — ООО «Доедем». Проезд оплачивают водителю на входе. Компания хочет убедиться, что деньги не утекают мимо кассы, и решает каждые 5 минут замерять количество человек в конкретном автобусе.

После замеров ООО «Доедем» строит график: люди заходят в автобус в начале маршрута, их количество постепенно растёт, потом люди начинают выходить, и их количество снижается. Компания собирает все цифры, находит максимальное значение и умножает его на стоимость билета. Получается 350 рублей выручки за маршрут.

Но есть нюанс. На практике по итогам таких расчётов водители получили неплохую прибавку к зарплате, ведь фактически пассажиры купили билетов на 770 рублей. Откуда такая разница? Разгадка проста: в любой момент, который не попал в замеры, людей в автобусе могло быть больше семи.

Нельзя сказать, что собранные компанией данные некорректны. Тем не менее в ООО «Доедем» получили бы правильную сумму выручки, если бы использовали другой тип замеров. Нужно было считать не количество людей в автобусе в конкретный момент, а количество входящих и выходящих пассажиров. Собранные таким образом данные заодно позволили бы оценить загруженность автобусов в зависимости от времени и открыли бы пространство для дополнительных оптимизаций.

Неправильная интерпретация данных приводит к ошибкам. И с системами мониторинга, построенными на базе TSDB, возникают аналогичные проблемы. Только эти проблемы не логические, когда мы выбрали неправильную метрику, а концептуальные. О том, как учитывать подобные детали при работе, мы и поговорим.

Загадка постоянного значения метрики

Начнём с Instant Query — самого простого типа запросов в Prometheus. Он позволяет получить данные на конкретный момент времени. Например, есть метрика cpu_usage, и мы запрашиваем её значение для какого-то пода на 12:00:00. Оно равно трём попугаям:

Запросим метрику повторно в первую и во вторую секунду. Значение по-прежнему равно трём попугаям, что странно:

Возможны два варианта, почему значение не меняется:

  1. Под работает очень стабильно (а это маловероятно).

  2. Все лгут, в том числе система мониторинга.

Попробуем разобраться, где истина. Для этого нужно погрузиться в особенности работы Prometheus.

Как Prometheus собирает и отдаёт данные

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

Через равные промежутки времени, которые называются scrape interval, Prometheus повторяет операцию. Мы возьмём scrape interval в 30 секунд:

В итоге получается временной ряд, из которого мы знаем, какую температуру градусник показывал каждые 30 секунд.

Интересное начинается, когда мы хотим прочитать собранные данные. Если мы точно знаем, когда производился сбор метрики, то можем запросить данные на этот момент. Например, на 12:30:00. Prometheus просто прочитает значение из базы и отдаст нам.

Но что будет, если запросить значение метрики в момент между двумя замерами? Система мониторинга не может в таком случае отдавать no_data — иначе ей просто никто не будет пользоваться. Выходит, что она должна угадать значение.

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

Но бывают ситуации, когда метрика давно пропала. Например, под, с которого собирались данные, был удалён. Что будет, если мы запросим значение метрики спустя длительный период, например несколько часов? Будет странно, если Prometheus или любая другая система мониторинга будет отдавать нам в такой ситуации последнее значение из прошлого. Иначе мы не сможем ей доверять: пода давно нет, а система показывает, что он по-прежнему потребляет память и CPU.

Для ограничения взгляда в прошлое в Prometheus есть lookback-delta, которая в других системах мониторинга может иметь другое название. По умолчанию lookback-delta равна 5 минутам. Если система находит значение в течение последних 5 минут, то возвращает его. Если нет, возвращает no_data.

Промежуточные итоги

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

Что может происходить в точках незнания
Что может происходить в точках незнания

Мы видим красивые графики на экране, но на деле знаем только конкретные значения в отдельно взятых точках. Правильнее было бы смотреть на последние, но это неудобно: такая информация тяжело воспринимается.

То, что мы видим в системе мониторинга, и то, что мы на самом деле знаем
То, что мы видим в системе мониторинга, и то, что мы на самом деле знаем

Во-вторых, значение метрики может быть взято из прошлого, вплоть до 5 минут.

Как работает lookback-delta

Вернёмся к истории с удалённым подом. Представим, что у нас был такой в кластере, и попробуем с учётом знаний о lookback-delta определить, когда под исчез.

Дано:

  • Метрика pod_cpu_usage для исчезнувшего пода.

  • Scrape interval в 30 секунд.

  • График в системе мониторинга.

Последнее значение на графике появилось в 12:05:00. Исходя из наших новых знаний, можно предположить, что под был удалён пятью минутами ранее — в 12:00:00. И это похоже на правду: с этого момента линия на графике прямая.

Но на самом деле под был удалён в период с 12:05:00 до 12:05:30. Это связано с особенностями работы lookback-delta, о которых мы ещё не поговорили.

Итак, Prometheus получает последнее значение метрики и продолжает использовать его для каждого замера в течение ещё пяти минут. Всё это время в базу будет записываться одно и то же значение — последнее на момент начала 5-минутного интервала.

Но если за эти пять минут Prometheus встречает специализированную точку, то lookback-delta перестаёт работать. Такая точка называется StaleNaN и является признаком окончания серии.

Prometheus записывает StaleNaN, если:

  1. Серии больше не существует.

  2. Источника данных больше не существует:

    • пропал из service discovering;

    • удалён из конфига.

Серии больше не существует

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

  • Если метрика была и в прошлый, и в этот раз, Prometheus запишет её текущее значение.

  • Если метрика отсутствовала в прошлый раз, но присутствует в этот, Prometheus запишет её текущее значение.

  • Если метрика была в прошлый раз, но отсутствует в этот, Prometheus скопирует её название и в качестве значения запишет StaleNaN.

Предыдущий target scrape

Текущий target scrape

Метрика

Значение

Метрика

Значение

cpu_used_by_pod{pod=”1”} 

1.0

cpu_used_by_pod{pod=”1”} 

0.9

— — —

— 

cpu_used_by_pod{pod=”3”} 

4.0

cpu_used_by_pod{pod=”2”} 

0.2

— — —

— 

cpu_used_by_pod{pod=”2”} 

StaleNaN

Более общий случай записи StaleNaN — когда источник данных больше не существует. Например, если мы использовали для сбора метрик service discovering и удалили под, то для Prometheus источник данных пропадёт. В результате система мониторинга ко всем полученным на предыдущем шаге сериям проставит StaleNaN.

Аналогичная история происходит, если мы используем статические конфиги, удаляем из них какой-то таргет и перезагружаем конфиг. Prometheus проставит метрикам из такого источника данных StaleNaN.

Для серий, которых больше нет по тем или иным причинам, lookback-delta не применяется.

Когда StaleNaN не записывается

Из правил обычно есть исключения. StaleNaN не запишется, если в процессе получения данных произошла ошибка, например если Prometheus получил 502-й код ответа или источник слишком долго не отвечал. В случае любых ошибок при scraping lookback-delta будет работать.

Промежуточные итоги

Итого, чтобы точно определить момент исчезновения пода или любого другого таргета, важно понимать, был ли зафиксирован StaleNaN.

В нашем примере lookback-delta не всегда работает. Если объект или конкретная метрика больше не доступны, то мы сразу же увидим это на графиках.

Как работает параметр step

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

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

Если до этого мы говорили, что берём первую точку, затем — вторую и так далее, то теперь у нас есть параметр, который позволяет это регулировать. Допустим, есть метрика со scrape-интервалом в 30 секунд и аналогичным параметром step. Мы хотим посмотреть все значения метрики за 15 минут.

Мы берём первую точку, и дальше в работу включается step. Мы смещаем наш timestamp: ищем в окошке в 30 секунд следующее значение. Поскольку step совпадает со scrape-интервалом, то мы находим следующую точку, следующую, следующую — пока не доберёмся до конца интервала:

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

15 × 60 сек. = 900 сек.

Разделим полученное значение на scrape interval — раз в 30 секунд:

900 сек. / 30 = 30 точек

Именно эти 30 точек отображаются на графике. Запомните это значение, оно потребуется нам ещё несколько раз.

Но что будет, если мы сделаем step в два раза меньше? Возьмём тот же интервал в 15 минут, ту же метрику и тот же scrape-интервал в 30 секунд. Мы берём первую точку и смещаем наше окно на 15 секунд — наш step. 15 секунд меньше, чем 30, и в окошке мы не находим ничего:

Но благодаря lookback-delta Prometheus берёт предыдущее значение и записывает его как текущее. Мы смещаемся на 15 секунд, находим реальную точку, ещё через 15 секунд снова попадаем в точку, где нет значения, и записываем предыдущее. И так до конца выбранного временного интервала.

В результате мы получаем на тот же интервал в 900 секунд не 30 точек, как было, а 60. Эти 60 точек и будут отображены на графике.

Глядя на график, мы рассчитываем, что значение в каждой точке отражает происходящее на серверах или в Kubernetes-кластере. Но в реальности значения могут быть другими.

Вывод: параметр step не должен быть меньше scrape-интервала.

Особенности запросов за большой период времени

Посмотрим, есть ли какой-то подвох, если мы существенно увеличим интервал и возьмём исторические данные по запросам за большой период времени. Для примера запросим значения метрики за 20 дней со step, равным 30 секундам:

Посчитаем общее количество точек. В каждом дне 24 часа, а в часе — 3600 секунд:

24 × 3600 = 86 400 секунд

Всего дней у нас 20:

20 × 86 400 = 1 728 000 секунд

Полученное количество секунд делим на наш step, получаем количество точек:

1 728 000 ÷ 30 = 57 600 точек

И здесь появляется неочевидная сложность: обычное разрешение экрана — это 1920 × 1080. Да, у кого-то есть 4К, но это в любом случае меньше 57,6 тысяч точек. Такой график просто физически не поместится на экране.

Чтобы снизить количество точек, нужно брать их реже, то есть увеличить step. Рассчитать, насколько именно, несложно. Ориентироваться будем на ограничение Grafana, которое составляет 1020 точек. Возьмём всё наши секунды за 20 дней и разделим на это число:

1 728 000 сек. ÷ 1020 = 1694,12 сек. = 28,24 мин.

Для верности — чтобы точно влезло на график — возьмём 29 минут. Теперь после получения значения в первой точке мы смещаемся на эти 29 минут, пока не дойдём до конца всего 20-дневного интервала.

В результате мы получим график, который визуально будет похож на график за 17 часов по нашей метрике при step в 30 секунд. И там и там будет 1020 точек, только в первом случае расстояние между ними будет 29 минут, а во втором — 30 секунд. И эта схожесть графиков может вводить в заблуждение: если даже между замерами за половину минуты может произойти что угодно, то что говорить о получасе.

Кстати, важный момент. Если мы запросим данные за большой период времени — например, за 20 или 30 дней — и укажем короткий step, система мониторинга неясно скорректирует этот параметр «под капотом». Она сама рассчитает количество точек, которое может вернуть.

Итак, step в 29 минут просто огромный. На первый взгляд кажется, что хранить в системе мониторинга данные больше, чем за 1-2 дня, бессмысленно. Но это не так.

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

Как работают оконные функции

Оконные функции смотрят на данные за промежуток времени и выдают одно итоговое значение для каждой метрики.

У Prometheus есть целый ряд оконных функций, например max_over_time(), min_over_time(), avg_over_time() или rate(). Посмотреть все можно в документации. У них специальный синтаксис: помимо самой функции и метрики, нужно указать размер окна в квадратных скобках:

max_over_time(metric{}[XXm])

Представим, что мы расследуем инцидент, когда на каком-то поде начал «утекать» процессор. Нам нужно найти всплески на большом интервале и понять, в какой момент начались проблемы. В этом случае мы можем использовать функцию max_over_time(). Для примера возьмём окно в 29 минут.

У нас есть запрос за 20 дней, где значения мы берём каждые 29 минут: первую точку, вторую и так далее, пока не дойдём до конца интервала. Дальше среди всех полученных значений функция найдёт самое большое.

Теперь нам нужно сдвинуть окно, чтобы охватить весь временной интервал. То есть задать параметр step. И здесь вопрос в том, на сколько сдвигать.

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

Дальше мы находим максимум в первом окне, во втором и так далее, пока не добираемся до конца 20-дневного периода. Теперь у нас есть все максимальные значения, которые можно отобразить на графике.

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

В использовании оконных функций есть свои нюансы. Рассмотрим их на примере функции rate(), которая вычисляет среднюю скорость изменения счётчика за заданный временной интервал. По сути, это первая производная.

Для вычисления итогового значения rate() всегда требуются минимум две точки. Синтаксис такой же, как и у всех оконных функций:

rate(metric{}[XXm])

Возьмём окно в 5 минут — [5m] — и 2 метрики. У первой scrape-интервал будет 30 секунд, у второй — 10 минут. Что вернёт rate() по каждой из них?

Начнём с первой метрики. Данные собираются каждые 30 секунд, соответственно, за 5 минут будет 10 значений. Нам же требуются всего два — те самые две точки, а значит, мы получим в ответ какое-то значение.

Во втором случае всё тоже кажется достаточно простым. В 5-минутное окно попадает только одна точка. Её недостаточно, чтобы получить результат выполнения функции rate(). Следовательно, мы получим no_data.

И здесь на сцену выходят нюансы. У функции rate(), да и у любых оконных функций, есть также синтаксис, где появляется параметр step. Он указывается через двоеточие. Допустим, у нас будет step в 30 секунд:

rate(metric{}[5m:30s])

Как это изменение повлияет на поведение функции? В случае первой метрики ничего не изменится: 10 точек больше, чем 2 необходимые. То есть мы получим какое-то значение. В случае второй метрики, кажется, тоже ничего не должно поменяться: точек вроде бы меньше двух, а значит, рассчитать результат выполнения функции нельзя.

На практике не так. Мы берём первую точку и смещаемся на 30 секунд, потому что размер шага внутри окна — 30 секунд. Значения там нет, но мы помним о существовании lookback-delta. А она вернёт последнее значение за 5 минут. И так последовательно со смещением до конца выбранного интервала:

В итоге мы получим 10 одинаковых значений и функция rate() вернёт 0. А это совершенно не соответствует действительности.

Если step внутри окна меньше scrape-интервала, мы получаем некорректные данные.

Есть ещё один вариант синтаксиса оконных функций: нечто среднее между первым и вторым. У нас есть размер окна и двоеточие, но размер step не указан:

rate(metric{}[XXm:])

Было бы логично, чтобы в таких случаях Prometheus использовал в качестве шага scrape-интервал. Но так не работает: Prometheus не хранит информацию о scrape-интервалах и забывает про них, как только метрики были записаны. В качестве step внутри окна он будет использовать evaluation-интервал.

rate(metric{}[XXm:$evaluation_interval])

Evaluation-интервал — это то, как часто в Prometheus исполняются record- и alert-правила. По умолчанию он равен 1 минуте.

Зачем вообще указывать step в рамках оконных функций? В примерах выше мы рассматривали работу функции на небольшом окне в 5 минут. Но иногда мы используем бо́льшие окна, и в таком случае бывает удобно доуточнить запрос. Нам может быть не нужна та точность, с которой данные собирались: например, можно взять каждое второе, четвёртое или пятое значение метрики.

Итого, для всех оконных функций работают следующие правила:

 Синтаксис оконной функции

 Как работает

rate(metric{}[XX])

Используются сырые данные, так называемые matrix-селекторы. Lookback-delta не применяется. Мы читаем только те точки, которые фактически есть

rate(metric{}[XX:YY])

Выполняется подзапрос, для которого действуют все правила обычных запросов, в том числе и lookback-delta

rate(metric{}[XXm:])

Выполняется подзапрос и действует lookback-delta. В качестве шага используется evaluation_interval, который по умолчанию равен 1 минуте

Оконные функции могут как читать сырые данные, так и выполнять подзапросы. Это влияет на использование или неиспользование Lookback-delta. Step подзапроса не должен быть меньше scrape-интервала, иначе мы получим некорректные данные.

Особенности работы Prometheus Federation API

Остаётся ещё один пример ситуации, когда мы можем некорректно интерпретировать данные, которые представляет система мониторинга. Он менее распространён, чем прошлые сценарии, но его диагностика бывает непростой, даже если вы знаете устройство Prometheus. Речь про Federation API.

Federation API — это возможность получить вертикальный срез по всем метрикам в Prometheus на текущий момент.

Приведу пример кейса, когда можно использовать эту возможность. Например, есть два Prometheus: первый собирает метрики с небольшой частотой — 30 секунд, а второй используется для даунсемплинга. Данные во второй сохраняются раз в 5 минут. Для этого используется Federation API: он забирает вертикальный срез значений метрик из первого Prometheus один раз в указанный интервал. Такой подход позволяет сэкономить на ресурсах и хранить данные чуть дольше.

Конечно, у работы с Federation API есть свои особенности.

Начнём с занимательного факта: получение данных с разных источников в Prometheus не происходит одномоментно. Обычно есть несколько таргетов, с которых система мониторинга собирает данные с разбросом по времени. И это влияет на Federation API.

Мы пришли за данными в первый Prometheus и получили свой вертикальный срез. Через 5 минут мы возвращаемся, но какие-то метрики ещё не получены. Было бы странно не записать имеющиеся данные, но тогда в некоторых местах останутся пропуски, что не менее странно:

К счастью, Federation API — точно такой же запрос, как и обычный запрос на получение метрик. Здесь работает lookback-delta, и мы получим предыдущее значение. Да, это немного снижает точность, но механизм в целом рабочий.

Однако есть ещё пара интересных нюансов. Например, что будет, если у основного Prometheus разные scrape-интервалы? Допустим, одна часть метрик собирается раз в 30 секунд, а другая — раз в 10 минут.

В этом случае в первый раз силами Federation API мы получим полный срез. Спустя 5 минут какие-то значения будут взяты за счёт Lookback-delta, и во втором Prometheus появятся точки, которых не существует в основном. Из-за этого может казаться, что scrape-интервал составляет не 10 минут, а 5.

И чем дальше, тем хуже ситуация. Мы приходим за данными в третий раз, значение метрики ещё не получено, lookback-delta смотрит на 5 минут назад, но она меньше scrape-интервала. Значение не находится, и мы получаем пропуск в данных. В итоге во втором Prometheus получаются непонятные и непредсказуемые результаты, на которые мы не очень-то можем повлиять.

Вывод простой: для всех источников данных в Prometheus стоит использовать одинаковый scrape-интервал.

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

Выводы: что важно учитывать при интерпретации данных мониторинга

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

  • Значение метрики может быть из прошлого — вплоть до 5 минут.

  • Для серий, которых больше нет, lookback-delta не применяется.

  • Step не должен быть меньше scrape-интервала.

  • Чем больше период запроса, тем меньше точность данных.

  • Для больших периодов система мониторинга может неявно увеличить step.

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

  • Step подзапроса для оконных функций не должен быть меньше scrape-интервала.

  • Для Federation API справедливы все предыдущие пункты.

И напоследок вернёмся к кейсу транспортной компании ООО «Доедем». Лучше использовать counter, a не gauge там, где у вас есть возможность. Его поведение более предсказуемо, и он позволяет строить графики чуть-чуть точнее.

P. S.

Читайте также в нашем блоге:

Комментарии (0)