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

Приложение может отвечать с нормальным средним временем в 120 мс и одновременно оставлять каждого сотого пользователя ждать несколько секунд. Поэтому мониторинг на одном показателе «сервер жив» не работает. В этом материале — практическая схема для HTTP-сервиса: четыре базовые группы метрик, инструментирование на Python, настройка Prometheus и правила, по которым видно не только факт сбоя, но и его цену.
Что именно должен показать мониторинг
Prometheus собирает значения с HTTP-эндпоинта приложения по расписанию. Само приложение отвечает за то, чтобы эти значения были полезными: сколько запросов пришло, сколько завершилось ошибкой, как долго они выполнялись и сколько ресурсов осталось.
Для веб-сервиса начните с модели RED:
- Rate — скорость запросов;
- Errors — доля и количество ошибок;
- Duration — распределение времени ответа.
К ним добавьте метрики насыщения: загрузку CPU, память, число соединений с базой, длину очереди и время ожидания внешних сервисов. Если приложение обрабатывает заказы, платежи или письма, нужны и бизнес-метрики: число созданных заказов, отклонённых платежей или необработанных задач.
Метрика должна отвечать на вопрос, который возникнет во время инцидента: пользователи не могут войти, запросы стали медленнее или система перестала обрабатывать работу.
Кардинальность1 — число разных комбинаций значений меток. Чем оно выше, тем больше временных рядов хранит Prometheus и тем дороже обходится ошибка в дизайне метрики.
Какие метрики выбрать
| Задача | Тип метрики | Пример | Чего избегать |
|---|---|---|---|
| Посчитать события | Counter | http_requests_total | Уменьшать значение вручную или сбрасывать его в работающем процессе |
| Показать текущее состояние | Gauge | active_connections | Использовать для счётчика всех запросов |
| Измерить распределение времени | Histogram | http_request_duration_seconds | Делать вывод по одному среднему значению |
| Сохранить одно наблюдение | Summary | Перцентили на стороне клиента | Сравнивать такие квантили между несколькими экземплярами без понимания ограничений |
Для времени ответа обычно выбирайте Histogram. Он складывает наблюдения в интервалы и позволяет Prometheus вычислять квантили по нескольким экземплярам приложения. Важный нюанс: квантили не появляются сами по себе — для них понадобится запрос PromQL и подходящие границы buckets.
Метками нельзя делать всё подряд
Метку method с несколькими значениями добавить безопасно. Метки user_id, полного URL, идентификатора заказа или текста ошибки опасны: каждый новый пользователь или URL создаёт новый временной ряд.
Вместо полного пути используйте шаблон маршрута, например /users/{id}, а не фактический адрес /users/184921. Имена пользователей и идентификаторы заказов не должны попадать в метрики вообще.
Инструментируем HTTP-приложение на Python
Ниже — фрагмент для Flask с библиотекой prometheus-client. Подход рассчитан на версию Prometheus 2.45 и новее и актуальные версии официального Python-клиента. Перед установкой проверьте совместимость выбранной версии клиента в его документации.
Библиотека отдаёт метрики в формате Prometheus через отдельный endpoint. Для простого сервиса это быстрее всего внедрить прямо в приложение.
from time import perf_counter
from flask import Flask, Response, request
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
app = Flask(__name__)
REQUESTS = Counter(
"http_requests_total",
"Total number of HTTP requests",
["method", "route", "status"],
)
REQUEST_DURATION = Histogram(
"http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "route"],
# Границы подходят для большинства пользовательских HTTP-запросов,
# но их стоит проверить по собственным задержкам.
buckets=(0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10),
)
@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id):
return {"id": user_id, "name": "Example"}
@app.before_request
def start_timer():
request.metrics_started_at = perf_counter()
@app.after_request
def record_metrics(response):
route = request.url_rule.rule if request.url_rule else "unknown"
duration = perf_counter() - getattr(request, "metrics_started_at", perf_counter())
labels = {"method": request.method, "route": route}
REQUEST_DURATION.labels(**labels).observe(duration)
REQUESTS.labels(status=str(response.status_code), **labels).inc()
return response
@app.route("/metrics")
def metrics():
return Response(generate_latest(), content_type=CONTENT_TYPE_LATEST)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
В HTML-строке символы угловых скобок в маршруте показаны как < и >. В Python-файле они должны быть обычными: /users/<int:user_id>. После запуска откройте http://localhost:8000/metrics. В ответе должны появиться строки вроде http_requests_total и http_request_duration_seconds_bucket.
Для production-приложения не стоит без проверки копировать этот middleware в проект: нужно учесть исключения, streaming-ответы и правила конкретного WSGI-сервера. Главное здесь — принцип: маршрут берётся из шаблона, длительность измеряется вокруг обработки запроса, а статус записывается после формирования ответа.
Подключаем Prometheus к приложению
Создайте конфигурацию Prometheus и укажите адрес сервиса. Ниже — минимальный пример для локального запуска Prometheus 2.45+:
global:
scrape_interval: 15s
scrape_configs:
- job_name: "example-app"
static_configs:
- targets: ["host.docker.internal:8000"]
Если Prometheus и приложение работают в одной Docker Compose-сети, вместо host.docker.internal:8000 укажите имя сервиса и порт, например app:8000. После изменения конфигурации перезапустите Prometheus или примените конфигурацию способом, принятым в вашей инфраструктуре. В разделе статуса targets цель должна иметь состояние UP.
Первые запросы PromQL
В веб-интерфейсе Prometheus проверьте не только наличие данных, но и их смысл:
sum(rate(http_requests_total[5m]))— скорость запросов за последние пять минут;sum(rate(http_requests_total{status=~"5.."}[5m]))— скорость ответов с кодами 5xx;sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))— доля 5xx;histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))— приблизительный p95 времени ответа.
Для p95 группируйте данные по le — это специальная метка границы bucket. Если добавить к запросу группировку по сервису или маршруту, список измерений должен оставаться ограниченным и заранее понятным.
Три подхода к сбору метрик
Для приложения, которое отвечает на HTTP-запросы, базовым вариантом остаётся pull-модель: Prometheus сам периодически обращается к endpoint метрик. Но не все задачи одинаковы.
| Подход | Когда выбирать | Плюс | Цена и риск |
|---|---|---|---|
Pull через /metrics | Долгоживущий HTTP-сервис | Prometheus видит доступность цели и сам контролирует расписание | Endpoint должен быть доступен из сети мониторинга |
| Pushgateway | Короткие batch-задачи, которым некому дождаться scrape | Результат можно отправить после завершения процесса | Это не замена обычному сбору; нужно отдельно управлять удалением старых метрик |
| Экспортёр | Сторонняя система не умеет Prometheus-формат | Метрики базы, брокера или ОС собираются без изменения их кода | Появляется дополнительный компонент и ещё одна точка отказа |
Если сервис живёт часами и принимает трафик, начинайте с pull. Pushgateway оставьте для коротких процессов, например ночного импорта, и не отправляйте туда метрики каждого HTTP-сервера.
Что ломается первым
Prometheus показывает target DOWN
Проверьте, что приложение слушает не только 127.0.0.1, порт доступен из сети Prometheus, а путь действительно равен /metrics. В Docker особенно часто ошибаются с адресом: localhost внутри контейнера указывает на этот же контейнер, а не на соседний сервис.
Метрики есть, но графики пустые
Counter не даст полезной скорости, пока не накопится несколько наблюдений. Для запросов используйте rate() или increase(), а не ожидайте, что абсолютное значение счётчика будет показывать нагрузку за текущую минуту.
Число временных рядов растёт без остановки
Ищите динамические значения в метках: URL с идентификаторами, исключения, email и заголовки запросов. Уберите их из instrumentation и перезапустите приложение. Уже созданные ряды не исчезнут мгновенно: срок хранения и очистку данных настраивают отдельно.
p95 выглядит странно
Проверьте границы Histogram. Если почти все запросы дольше последнего bucket, распределение становится малополезным. Сначала соберите реальные задержки, затем выберите интервалы, которые покрывают быстрые, обычные и медленные запросы. Среднее время при этом всё равно не заменяет p95 и p99.
Где эта схема ломается
Один процесс приложения может иметь несколько worker-процессов. Для Python-клиента нужно отдельно проверить multiprocess-режим и требования к переменным окружения: обычное локальное хранилище метрик каждого worker не всегда корректно объединяется. Не подключайте такую схему вслепую.
Сбор метрик также не заменяет логи и трассировку. Метрика покажет рост 500-х, лог поможет найти исключение, а трассировка — конкретный медленный вызов базы или внешнего API. Инвалидация метрик и построение дашбордов — отдельная тема; здесь важнее не смешивать эти сигналы в один показатель.
Что делать дальше
Сначала добавьте метрики Rate, Errors и Duration на один критичный маршрут. Затем сгенерируйте обычную и ошибочную нагрузку, проверьте UP, долю 5xx и p95. После этого зафиксируйте пороги для уведомлений: не «метрика выросла», а конкретное условие, например доля ошибок выше рабочего уровня в течение заданного интервала. Следующим шагом подключите метрики базы данных и внешних зависимостей, чтобы на инциденте видеть не только симптом приложения, но и источник задержки.
1 Кардинальность — количество уникальных комбинаций меток у метрики. Высокая кардинальность увеличивает объём памяти и число рядов, поэтому пользовательские идентификаторы для labels почти всегда плохая идея.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Prometheus documentation: Metric types, Prometheus Python client
Частые вопросы
Нужно ли ставить Prometheus на тот же сервер, что и приложение?
Почему для времени ответа лучше Histogram, а не среднее?
Можно ли добавить user_id в labels?
Что делать, если у меня Prometheus другой версии?
Почему Pushgateway не подходит для обычного веб-приложения?
Метрики собираются, но приложение стало медленнее. Что проверить?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.