Инструментирование своего приложения

Урок про то, как заставить собственное приложение отдавать метрики Prometheus.

Инструментирование — это добавление в код приложения метрик с помощью клиентской библиотеки, чтобы оно само публиковало их на endpoint /metrics.

Экспортеры дают метрики хоста и баз, но про бизнес-логику знает только ваше приложение: сколько заказов оформлено, сколько писем отправлено, как долго идёт обработка. Эти метрики добавляете вы сами.

Клиентские библиотеки

Под популярные языки есть официальные библиотеки (Python, Go, Java, Node.js и др.). Они дают объекты Counter, Gauge, Histogram и сами поднимают endpoint /metrics. Логика везде одинакова: создать метрику, обновлять её в коде, выставить endpoint.

Пример логики на чистом Python

Чтобы прочувствовать суть без внешних библиотек, смоделируем counter и histogram стандартными средствами. Реальный код использовал бы prometheus_client, но идея та же.

from collections import Counter

# counter: считаем запросы по статусу
requests_total = Counter()

def handle(status):
    requests_total[status] += 1

for s in ["200", "200", "500", "200", "404"]:
    handle(s)

for status, count in sorted(requests_total.items()):
    print(f'http_requests_total{{status="{status}"}} {count}')

Вывод:

http_requests_total{status="200"} 3
http_requests_total{status="404"} 1
http_requests_total{status="500"} 1

Гистограмма задержек вручную

buckets = [0.1, 0.5, 1.0]
samples = [0.05, 0.2, 0.2, 0.8, 1.5]
counts = {b: 0 for b in buckets}
inf = 0

for s in samples:
    placed = False
    for b in buckets:
        if s <= b:
            counts[b] += 1
            placed = True
            break
    if not placed:
        inf += 1

for b in buckets:
    print(f'le="{b}" count={counts[b]}')
print(f'le="+Inf" count={inf}')

Вывод:

le="0.1" count=1
le="0.5" count=2
le="1.0" count=1
le="+Inf" count=1

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

Клиентская библиотека держит метрики в памяти процесса. Counter инкрементируется в коде, histogram при каждом наблюдении кладёт значение в подходящую корзину и обновляет _sum и _count. На запрос /metrics библиотека сериализует текущее состояние в текстовый формат Prometheus, а scrape забирает снимок. Никакой истории приложение не хранит — она строится в TSDB.

Частые ошибки

  • Лейблы с высокой кардинальностью. URL с ID или email в лейбле метрики порождают тысячи рядов — кладите такое в логи.
  • Создавать метрику внутри обработчика. Метрики объявляют один раз при старте, иначе теряется накопленное.
  • Забыть endpoint /metrics. Без него Prometheus нечего скрейпить.

Итог

  • Инструментирование добавляет бизнес-метрики через клиентскую библиотеку.
  • Counter растёт в коде, histogram кладёт наблюдения в корзины.
  • Избегайте высокой кардинальности лейблов и объявляйте метрики один раз.
Проверьте себя
1. Зачем инструментировать приложение, если уже есть node_exporter?
Anode_exporter даёт бизнес-метрики приложения
BТолько приложение знает свои бизнес-метрики: заказы, длительность операций и т. п.
CЧтобы заменить Prometheus
DЧтобы ускорить scrape
2. Почему опасно класть ID пользователя в лейбл метрики?
AЭто запрещено синтаксисом
BРезко растёт кардинальность — тысячи рядов и нагрузка на TSDB
CМетрика станет gauge
DPrometheus перестанет скрейпить
3. Где должны объявляться метрики приложения?
AВнутри каждого обработчика запроса
BОдин раз при старте приложения
CВ prometheus.yml
DВ Alertmanager