How to: Prometheus PromQL joins, group_left, group_right
Заметка по горячим следам, т.к. оно нужно раз в пяти летку и каждый раз вызывает сложности что бы вспомнить с какого боку подойти
Плюс бонусом - навороченный use case
читать все это имеет смысл только после базового ознаномления с прометеем, в сети навалом материалов, есть запись видосика где это делали мы
join left
наиболее часто пользуемый на практике пример - добавить лейбочку овнера к метрике
TLDR:
метрика_которую_мы_крутим
* on(пересекающиеся лейбочки...) group_left(лейбочка_которую_хотим_добавить) max(меткрика-мета-данных) by(пересекающиеся лейбочки...)container_memory_usage_bytes
* on(namespace, pod) group_left(annotation_owner) max(kube_pod_annotations) by (namespace, pod, annotation_owner)Посколку прометей работает с временными рядами и тут всегда все пляшек вокруг значения той или иной метрики, джойнов как таковых нет в принципе
Но, при этом, фигачить в каждую метрику 100500 лейбочек нецелесообразно, т.к. раздует и замедлит базу
Поэтому в прометее принято иметь эдакие метрики-мета-данные, у которых значение всегда равно единичке и на борту все искомые лейбочки
На примере метрики потребления памяти контейнером имеем следующее:
У нас есть метрика, показывающая сколько памяти ест контейнер, у нее есть ряд лейбочек, среди которых есть лейбочка pod
Вокруг подов есть целый ряд метрик kube_pod_*, среди которых есть вот такая:
В чем идея (или как это работает):
Поскольку значение метрики равно единичке, мы должны мочь перемножить
container_memory_usage_bytesнаkube_pod_annotations, значение потребления памяти останется неизменным, но при этом добавятся лейбочки
Реалии: так не работает
Не работает потому что прометей по умолчанию перемножает метрики у которых все все лейбочки совпали, а в нашем случае это не возможно, так как у метрик бонально разные наборы этих самых лейбочек
Для этого у операции умножения, как в прочем и вообще всех остальных операций, есть воможность уточнить по какому набору лейбочек подбирать метрики
Со стороны прометея это выглядит вот так:
container_memory_usage_bytes * on(namespace, pod) kube_pod_annotations
Думать про это есть смысл как о таком SQL запросе:
SELECT
CMUB.*,
CMUB.value * KPA.value
FROM container_memory_usage_bytes AS CMUB
LEFT JOIN kube_pod_annotations AS KPA ON CMUB.namespace = KPA.namespace AND CMUB.pod = KPA.podРеалии:
Запрос не отрабатывает потому что в одном из подмножеств (не важно левом или правом) нашлось больше одной записи с одинаковыми значениями для лейбочек namespace, pod
Соотв нам нужно или больше лейбочек добавить что бы было четкое соотв, НО, это не всегда возможно
Для подобного сценария, в разрезе метрик-метаданных, делается следующий трюк:
max(kube_pod_annotations) by (namespace, pod, label_app)Значение метрики то у нас всегда 1, мы по сути просто убираем все лишнее, группируем, схлопываем
Реалии:
Оно все так же не работает, но нам никто не запрещает сделать что то похожее с левой стороной, условно:
И наконец то запрос отработал, вот только толку от этого мало, так как лейбочки то не добавились
Вот тут в игру и вступает group_left, просто добавляем его следом за on, причем уже не обязательно группировать левую часть
container_memory_usage_bytes * on(namespace, pod) group_left(annotation_owner) max(kube_pod_annotations) by (namespace, pod, annotation_owner)Механика такова:
метрика_которую_мы_крутим
* on(пересекающиеся лейбочки...) group_left(лейбочка_которую_хотим_добавить) max(меткрика-мета-данных) by(пересекающиеся лейбочки...)Примечание: если не делать группировки, запрос может отваливаться с ошибками о дубликатах записей с левой или правой стороны, собенно в момент релиза, когда метрики могут задваиваться, это особенно часто вылазит когда мы меряем rate за период в Х минут, и может так получиться что в промежутке этих Х минут еще была старая метрика и уже появилась новая
Intersections - aka left/right outer/inner joins
Просто замечательная статья еще и с картинками - must have к осознанию
https://iximiuz.com/en/posts/prometheus-vector-matching/
Прометей все так же не имеет такого понятия кака join, НО, при этом очень даже хорошо работает с множествами
Ключевые слова: and, or, unless, при этом у них все так же можно добавить on, как мы делали при умножении
Примечание: ключевое отличие от привычного нам SQL - прометей возвращает левое подмножество которое подпадает под условия пересечения (то есть это не о том что бы добавлять лейбочки или перемножать значения, а о том что бы фильтровать (WHERE) результат)
Что бы было совсем понятнее вот пример запроса который возвращает 274 метрики подов у которых в названии есть слово vacancy
И вот такой запрос к метаданным, для подов у которых овнером числиться Вадим или Аня
Ну и собственно говоря их пересечение: запрос показывает потребление памяти подами у которых в названии есть искомое слово и овнер
Примечание: тут, забегая на перед, я добавил on(namespace, pod) и группировку правой части - по сути тут все один в один то же самое что и в пред примере с джойном лейбочек
В SQL это было бы что то типа:
SELECT CMUB.*
FROM container_memory_usage_bytes AS CMUB
LEFT JOIN kube_pod_annotations AS KPA ON CMUB.namespace = KPA.namespace AND CMUB.pod = KPA.pod
WHERE CMUB.pod LIKE '%vacanc%' AND KPA.annotation_owner IN ('annae@rabota.ua', 'VadimK@rabota.ua')Точно таким же образом работают or и unless
Примечание: на первый взгляд кажеться дичью и мол типа привычнее в SQL, НО, это только на первый взгляд и если рассмотреть более заковыристые примеры как дальше, уже нифига не очевидно будет как написать такой SQL запрос в принципе, а в PromQL оно плюс минус остается удобоваримым
Практический пример: следим за временем ответа сервиса после релиза
Вводные:
- у нас есть метрики ингресса позволяющие ответить на вопрос какое время ответа у конретно взятого сервиса
- абсолютно все запросы идут через ингрес - потому метрика есть для всего
- мы технически можем и даже имели алерты с какими то порогами - типа если сервис начал тупить - булькает
- в тот момент времени для нас это было сильно рано, потому эти алерты выключили
Идея: что если бы алерт был “умным” и триггерился бы после релиза, если сервис начал тупить
Механика:
- релиз
- пересоздаются поды
- как следствие их метрика kube_pod_created будет показывать свежую цифру
- смотрим поды у которых kube_pod_created меньше часа
- для этих подов смотрим время ответа ингресс
- если оно в полтора раза больше чем вчера в это же время
- если все это продолжается десять минут
- алерт
Теперь вопрос как это все в кучу собрать
Время ответа сервиса мы считам так (тут ничего особенного самый обычный запрос которые мы уже разобрали в доль и поперек тут)
Для красоты чиним NaN добавляя условие > 0 в правую часть
Пользуясь заметками о джойнах добавляем овнера
Дальше все точно то же самое только за вчера (добавляем offset 1d в левую и правую часть)
Просим прометей вернуть нам только те метрики где значение за сегодня больше чем вчера
Дальше прелесть прометея - то же самое что и в предыдущем запросе, только просим вернуть только те результаты где значение в полтора раза больше
примечание: на всех скринах, справа сверху, под синей кнопкой execute есть поле result series по которому видно как срезается результат
На этом первая часть запроса готова
Касаемо второй части, нам нужны свеже запущенные поды
Я тут сразу приджойнил лейбочку овнера, по ней мы будем все в купу собирать
Дальше нам надо бы как то это дело схлопнуть, ведь ингресс то у нас один, а подов много, по сути это то с чего начинается заметка про джойны
Значение метрики - кол-во секунд с unix эпохи, в фронте это было бы new Date(1691748000 * 1000)
Соотв если мы хотим забрать только “свежие” поды, делаем вот так:
Все, на этом готова правая часть запроса
Теперь нам нужно их пересекти, дальше мноструозный запрос, но если подумать он делает:
текущее_время_ответа
>
время_ответа_вчера * 1.5
and
врем_жизни_пода < 1чоно уже даже в экран не влазит, наверняка его можно написать грамотнее, компактнее и красивше, но сам факт - запрос махина, вот только что бы его проверить - нужно выкатить что то поломанное-медленное 🤷♂️
но для заметки сгодиться, бо такую штуку с наскоку фиг напишешь