Monitoring — Prometheus и Grafana
Мониторинг — сбор, хранение, анализ и визуализация данных о состоянии приложений и инфраструктуры. В типичном стеке Prometheus собирает и хранит метрики, PromQL выполняет запросы, Grafana строит дашборды, а Alertmanager маршрутизирует уведомления.
Приложения и exporters ── /metrics ──► Prometheus ──► Grafana
│
└────────────► Alertmanager ──► уведомленияСодержание
- Наблюдаемость и метрики
- Типы метрик
- Counter
- Gauge
- Histogram
- Labels и кардинальность
- Prometheus
- Exporters
- PromQL
- Grafana
- Алертинг и Alertmanager
- Практический пример
- Best practices
- Чек-лист
- Шпаргалка PromQL
Наблюдаемость и метрики
Наблюдаемость показывает, насколько хорошо внутреннее состояние системы можно понять по внешним сигналам. Обычно выделяют:
| Сигнал | Назначение | Примеры |
|---|---|---|
| Метрики | Числовые значения во времени | RPS, CPU, error rate |
| Логи | События и контекст | Исключения, действия пользователей |
| Трейсы | Путь запроса между сервисами | API, БД, очереди |
Метрика состоит из имени, labels и значения:
http_requests_total{method="GET",status="200"} 18432Для HTTP-сервисов полезна модель RED:
- Rate — количество запросов в секунду;
- Errors — количество или доля ошибок;
- Duration — длительность запросов.
Для инфраструктуры применяется USE:
- Utilization — использование ресурса;
- Saturation — накопившаяся работа или ожидание;
- Errors — ошибки ресурса.
Типы метрик
| Тип | Назначение | Примеры |
|---|---|---|
Counter |
Монотонно растущий счётчик | Запросы, ошибки, сообщения |
Gauge |
Значение может расти и уменьшаться | Память, температура, соединения |
Histogram |
Распределение значений по диапазонам | Задержка, размер ответа |
Summary |
Квантили на стороне клиента | Локальные квантили задержки |
В большинстве приложений основными являются Counter, Gauge и Histogram.
Counter
Counter увеличивается и после перезапуска процесса начинается заново:
http_requests_total
http_request_errors_total
jobs_processed_totalСуффикс _total является общепринятым. Число активных соединений не является Counter, поскольку может уменьшаться: для него нужен Gauge.
from prometheus_client import Counter
REQUESTS = Counter(
"http_requests_total",
"Total number of HTTP requests",
["method", "route", "status"],
)
REQUESTS.labels(
method="GET",
route="/api/users",
status="200",
).inc()Абсолютное значение Counter редко полезно само по себе. Обычно используют скорость или прирост:
rate(http_requests_total[5m])
increase(http_requests_total[1h])rate() лучше подходит для графиков и алертов. irate() реагирует быстрее, но сильнее подвержен шуму.
Gauge
Gauge хранит текущее значение:
active_connections
queue_size
process_resident_memory_bytes
temperature_celsiusfrom prometheus_client import Gauge
ACTIVE_CONNECTIONS = Gauge(
"active_connections",
"Current number of active connections",
)
ACTIVE_CONNECTIONS.inc()
ACTIVE_CONNECTIONS.dec()
ACTIVE_CONNECTIONS.set(42)Примеры PromQL:
active_connections
avg_over_time(queue_size[1h])
max_over_time(queue_size[24h])Histogram
Histogram распределяет наблюдения по заранее заданным диапазонам — buckets. Для http_request_duration_seconds появляются связанные ряды:
http_request_duration_seconds_bucket{le="0.1"}
http_request_duration_seconds_bucket{le="0.5"}
http_request_duration_seconds_bucket{le="1"}
http_request_duration_seconds_bucket{le="+Inf"}
http_request_duration_seconds_sum
http_request_duration_seconds_count_bucket— накопительное количество значений до границыle;_sum— сумма наблюдений;_count— количество наблюдений.
from prometheus_client import Histogram
REQUEST_DURATION = Histogram(
"http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "route"],
buckets=(0.05, 0.1, 0.25, 0.5, 1, 2.5, 5),
)
with REQUEST_DURATION.labels(
method="GET",
route="/api/users",
).time():
load_users()Среднее время ответа:
sum(rate(http_request_duration_seconds_sum[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))95-й перцентиль:
histogram_quantile(
0.95,
sum by (le) (
rate(http_request_duration_seconds_bucket[5m])
)
)При агрегации Histogram нужно сохранять label le. Для распределённых сервисов Histogram обычно удобнее Summary: buckets можно агрегировать между инстансами, а клиентские квантили Summary обычно нельзя корректно объединить.
Labels и кардинальность
Label — атрибут временного ряда:
http_requests_total{
method="GET",
route="/api/users",
status="200",
instance="app-1:8080"
} 1520Каждая уникальная комбинация labels создаёт отдельный ряд. Если метрика имеет 5 методов, 100 маршрутов, 10 кодов и 20 инстансов, теоретический максимум — 100 000 рядов.
Не следует помещать в labels значения с высокой или неограниченной кардинальностью:
user_id
request_id
email
session_id
raw_url
error_messageПлохо:
http_requests_total{path="/users/913847/orders/44810"}Лучше:
http_requests_total{route="/users/:user_id/orders/:order_id"}Рекомендуемое именование:
snake_case;- базовые единицы: секунды и байты;
_totalдля Counter;- единый префикс приложения.
Примеры:
shop_http_requests_total
shop_http_request_duration_seconds
shop_response_size_bytesPrometheus
Prometheus — система мониторинга временных рядов. По умолчанию он работает по pull-модели: сам обращается к HTTP-эндпоинтам targets и сохраняет полученные данные.
Prometheus ──GET /metrics──► application
Prometheus ──GET /metrics──► node_exporter
Prometheus ──GET /metrics──► postgres_exporterОсновные компоненты:
- Prometheus Server — сбор, хранение и запросы;
- client libraries — инструментация приложений;
- exporters — адаптеры для внешних систем;
- Pushgateway — публикация данных краткоживущих batch-задач;
- Alertmanager — группировка и доставка уведомлений;
- Grafana — визуализация.
Формат /metrics
# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 1520
# HELP active_connections Current active connections
# TYPE active_connections gauge
active_connections 27Проверка:
curl http://localhost:8080/metricsМинимальная конфигурация
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: prometheus
static_configs:
- targets:
- prometheus:9090
- job_name: application
metrics_path: /metrics
static_configs:
- targets:
- app:8080| Параметр | Назначение |
|---|---|
scrape_interval |
Частота сбора |
scrape_timeout |
Максимальное время scrape |
evaluation_interval |
Частота вычисления rules |
job_name |
Имя группы targets |
metrics_path |
Путь к метрикам |
static_configs |
Список targets |
Состояние targets проверяется в Status → Targets или запросом:
up
up{job="application"}1 означает успешный scrape, 0 — ошибку.
Проверка конфигурации:
promtool check config /etc/prometheus/prometheus.yml
promtool check rules /etc/prometheus/rules.ymlХранение
Prometheus хранит данные в локальной TSDB. В контейнере для этого нужен постоянный volume. Retention можно ограничить:
prometheus \
--storage.tsdb.retention.time=30d \
--storage.tsdb.retention.size=50GBДля длительного хранения и объединения данных нескольких Prometheus применяют remote_write и совместимые внешние системы. При этом контролируют локальный диск, очередь отправки и доступность удалённого хранилища.
Service discovery и relabeling
В небольших окружениях достаточно static_configs. В динамических средах используются Kubernetes, Consul, обачные механизмы или file-based discovery:
scrape_configs:
- job_name: backend
file_sd_configs:
- files:
- /etc/prometheus/targets/*.json
refresh_interval: 30srelabel_configs изменяет labels targets до scrape. metric_relabel_configs работает после получения метрик и может удалить ненужные серии:
metric_relabel_configs:
- source_labels: [__name__]
regex: application_debug_event_total
action: dropRecording rules
Recording rule заранее вычисляет тяжёлое выражение и сохраняет его под новым именем:
groups:
- name: application-recording
interval: 30s
rules:
- record: job:http_requests:rate5m
expr: sum by (job) (rate(http_requests_total[5m]))Это ускоряет дашборды и позволяет одинаково использовать важный показатель в запросах и алертах.
Exporters
Exporter получает данные внешней системы и публикует их в формате Prometheus.
| Exporter | Назначение |
|---|---|
| Node Exporter | CPU, память, диски и сеть Linux |
| Blackbox Exporter | HTTP, HTTPS, TCP, ICMP и DNS-проверки |
| PostgreSQL Exporter | Метрики PostgreSQL |
| Redis Exporter | Метрики Redis |
| cAdvisor | Ресурсы контейнеров |
| SNMP Exporter | Сетевые устройства |
Node Exporter обычно доступен на 9100:
scrape_configs:
- job_name: node
static_configs:
- targets: ["server-1:9100", "server-2:9100"]CPU:
100 - (
avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100
)Память:
100 * (
1 - node_memory_MemAvailable_bytes
/ node_memory_MemTotal_bytes
)Blackbox Exporter использует /probe, а основная метрика результата — probe_success:
probe_successЗначение 1 означает успех, 0 — ошибку.
Exporter и /metrics не следует публиковать в интернет без необходимости. Используйте внутреннюю сеть, firewall, TLS, аутентификацию и минимальные права.
PromQL
PromQL фильтрует, агрегирует и преобразует временные ряды.
Выбор и фильтрация
http_requests_total
http_requests_total{method="GET"}
http_requests_total{status=~"5.."}
http_requests_total{endpoint!~"/health|/ready"}Операторы labels: =, !=, =~, !~.
Скорость и прирост
rate(http_requests_total[5m])
increase(http_requests_total[1h])Общий RPS:
sum(rate(http_requests_total[5m]))RPS по сервисам:
sum by (service) (
rate(http_requests_total[5m])
)Агрегация
sum by (status) (rate(http_requests_total[5m]))
sum without (instance, pod) (rate(http_requests_total[5m]))
topk(5, process_resident_memory_bytes)Процент ошибок
100 *
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))По сервисам:
100 * sum by (service) (
rate(http_requests_total{status=~"5.."}[5m])
)
/ sum by (service) (
rate(http_requests_total[5m])
)Histogram
Среднее время ответа:
sum(rate(http_request_duration_seconds_sum[5m]))
/ sum(rate(http_request_duration_seconds_count[5m]))p95:
histogram_quantile(
0.95,
sum by (le) (
rate(http_request_duration_seconds_bucket[5m])
)
)Другие операции
max_over_time(queue_size[1h])
delta(queue_size[15m])
absent(up{job="application"})
up == 0Сравнение с прошлой неделей:
sum(rate(http_requests_total[5m]))
/ sum(rate(http_requests_total[5m] offset 7d))В Grafana используйте $__rate_interval, чтобы диапазон был согласован с масштабом графика:
sum(rate(http_requests_total[$__rate_interval]))Grafana
Grafana — платформа визуализации и анализа данных.
Основные сущности:
- data source — источник данных;
- dashboard — набор панелей;
- panel — отдельная визуализация;
- variable — фильтр дашборда;
- annotation — событие на временной шкале;
- alert rule — правило предупреждения.
Подключение Prometheus
Connections → Data sources → Add data source → PrometheusЕсли сервисы находятся в одной Docker-сети, URL обычно такой:
http://prometheus:9090localhost внутри контейнера Grafana означает сам контейнер Grafana, а не соседний контейнер Prometheus.
Панели и дашборды
Типовая структура дашборда приложения:
Обзор: доступность, RPS, error rate, p95 latency
Приложение: маршруты, соединения, очереди, зависимости
Ресурсы: CPU, память, диск, сетьОсновные типы панелей:
| Панель | Назначение |
|---|---|
| Time series | Изменения во времени |
| Stat | Одно значение |
| Gauge | Значение относительно диапазона |
| Table | Значения и labels |
| Heatmap | Распределение наблюдений |
RPS:
sum(rate(http_requests_total[$__rate_interval]))p95:
histogram_quantile(
0.95,
sum by (le) (
rate(http_request_duration_seconds_bucket[$__rate_interval])
)
)Настраивайте единицы: секунды для _seconds, байты для _bytes, requests/sec для скорости и проценты для значений, умноженных на 100.
Переменная окружения:
label_values(up, environment)Использование:
sum by (status) (
rate(http_requests_total{
environment="$environment"
}[$__rate_interval])
)Annotations удобно использовать для деплоев и изменений конфигурации. Provisioning позволяет хранить data sources и dashboards как код, загружать их автоматически и проверять в Git.
Алертинг и Alertmanager
Prometheus вычисляет alerting rules, а Alertmanager группирует, подавляет и отправляет уведомления.
Prometheus → Alertmanager → Email / Slack / webhook / on-call-системаAlerting rule
groups:
- name: application-alerts
rules:
- alert: ApplicationTargetDown
expr: up{job="application"} == 0
for: 2m
labels:
severity: critical
team: backend
annotations:
summary: "Application target is unavailable"
description: >-
Target {{ $labels.instance }} is unavailable for more than 2 minutes.
runbook_url: "https://docs.example.com/runbooks/application-down"
- alert: HighHttpErrorRate
expr: |
sum by (service) (
rate(http_requests_total{status=~"5.."}[5m])
)
/
sum by (service) (
rate(http_requests_total[5m])
) > 0.05
for: 10m
labels:
severity: warning
annotations:
summary: "High HTTP error rate"
description: "More than 5% of requests return 5xx."Состояния:
Inactive → Pending → Firingfor уменьшает шум от кратких всплесков, но не должен задерживать критические уведомления.
Alertmanager
global:
resolve_timeout: 5m
route:
receiver: default-webhook
group_by: [alertname, service, environment]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers: [severity="critical"]
receiver: critical-webhook
repeat_interval: 30m
receivers:
- name: default-webhook
webhook_configs:
- url: http://notification-service:8080/alerts
send_resolved: true
- name: critical-webhook
webhook_configs:
- url: http://incident-service:8080/critical-alerts
send_resolved: truegroup_byобъединяет связанные алерты;group_waitзадерживает первую отправку группы;group_intervalзадаёт интервал новых изменений;repeat_intervalопределяет повторные уведомления;inhibit_rulesподавляют вторичные алерты при наличии первичного;silenceвременно отключает уведомления на ограниченный срок.
Хороший алерт содержит событие, сервис, окружение, severity, длительность, ссылку на дашборд и runbook. Алерт должен требовать конкретного действия и не быть постоянным фоном.
Практический пример
Структура:
monitoring/
├── compose.yml
├── prometheus/prometheus.yml
├── prometheus/rules/alerts.yml
├── alertmanager/alertmanager.yml
└── grafana/provisioning/datasources/prometheus.ymlcompose.yml
services:
prometheus:
image: prom/prometheus:<version>
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=15d
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/rules:/etc/prometheus/rules:ro
- prometheus-data:/prometheus
ports:
- "127.0.0.1:9090:9090"
networks: [monitoring]
alertmanager:
image: prom/alertmanager:<version>
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
ports:
- "127.0.0.1:9093:9093"
networks: [monitoring]
grafana:
image: grafana/grafana:<version>
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
ports:
- "127.0.0.1:3000:3000"
networks: [monitoring]
node-exporter:
image: prom/node-exporter:<version>
command: ["--path.rootfs=/host"]
volumes:
- /:/host:ro,rslave
networks: [monitoring]
volumes:
prometheus-data:
grafana-data:
networks:
monitoring:В реальном проекте <version> заменяется фиксированной проверенной версией. Для внешнего доступа используйте reverse proxy с HTTPS и аутентификацией. Не публикуйте Prometheus и exporters напрямую в интернет.
prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- /etc/prometheus/rules/*.yml
alerting:
alertmanagers:
- static_configs:
- targets: [alertmanager:9093]
scrape_configs:
- job_name: prometheus
static_configs:
- targets: [prometheus:9090]
- job_name: node
static_configs:
- targets: [node-exporter:9100]Запуск и проверка:
docker compose config
docker compose up -d
docker compose ps
docker compose logs -f prometheusАдреса при локальном запуске:
Prometheus: http://localhost:9090
Alertmanager: http://localhost:9093
Grafana: http://localhost:3000Best practices
- начинайте с доступности, RPS, error rate и latency;
- выбирайте тип метрики по смыслу значения;
- используйте базовые единицы — секунды и байты;
- не помещайте уникальные идентификаторы в labels;
- нормализуйте URL до шаблонов маршрутов;
- подбирайте Histogram buckets под реальные SLO;
- храните данные Prometheus на постоянном volume;
- применяйте recording rules для тяжёлых и часто повторяемых запросов;
- храните конфигурацию, rules и dashboards как код;
- защищайте
/metrics, exporters, Prometheus и Grafana; - проверяйте
up, состояние rules, диск и память самого мониторинга; - связывайте каждый важный алерт с runbook;
- тестируйте firing, resolved, grouping и маршрутизацию уведомлений;
- не создавайте алерты, на которые невозможно или не нужно реагировть.
Полезные метрики самого Prometheus:
prometheus_tsdb_head_series
rate(prometheus_rule_evaluation_failures_total[5m])
prometheus_rule_group_last_duration_secondsЧек-лист
Метрики
- Выбран правильный тип: Counter, Gauge или Histogram.
- Counter имеет
_total. - Указана базовая единица измерения.
- Labels не содержат персональные данные и уникальные ID.
- Маршруты нормализованы.
- Buckets соответствуют ожидаемой задержке.
-
/metricsотвечает быстро.
Prometheus
- Targets имеют
up = 1. - Настроены scrape interval и timeout.
- Настроен retention и постоянный volume.
- Конфигурация и rules проверяются через
promtool. - Тяжёлые выражения вынесены в recording rules.
- Контролируется кардинальность.
- Prometheus не открыт напрямую в интернет.
Grafana
- Prometheus подключён как data source.
- Дашборд содержит доступность, трафик, ошибки и latency.
- Панели имеют понятные названия и единицы.
- Есть переменные для окружения и сервиса.
- Деплои отображаются annotations.
- Provisioning и dashboards хранятся в Git.
Алертинг
- Каждый алерт требует конкретного действия.
- Используется
for, когда краткий всплеск допустим. - Указаны severity, service, environment и team.
- Уведомления группируются.
- Есть inhibition и ограниченные silences.
- Важные алерты содержат dashboard и runbook.
- Проверены firing и resolved-уведомления.
- Мониторится сам Prometheus и Alertmanager.
Шпаргалка PromQL
# Доступность
up
up == 0
# Общий RPS
sum(rate(http_requests_total[5m]))
# Запросы за час
sum(increase(http_requests_total[1h]))
# Ошибки 5xx, проценты
100 * sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))
# Средняя задержка
sum(rate(http_request_duration_seconds_sum[5m]))
/ sum(rate(http_request_duration_seconds_count[5m]))
# p95
histogram_quantile(0.95,
sum by (le) (
rate(http_request_duration_seconds_bucket[5m])
)
)
# CPU
100 - avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100
# Память
100 * (1 - node_memory_MemAvailable_bytes
/ node_memory_MemTotal_bytes)
# Максимальная очередь за час
max_over_time(queue_size[1h])
# Отсутствующая метрика
absent(up{job="application"})Итог
Prometheus собирает и хранит временные ряды, PromQL превращает их в полезные показатели, Grafana визуализирует данные, а Alertmanager доставляет уведомления. Хороший мониторинг отвечает на практические вопросы: доступен ли сервис, как быстро он отвечает, сколько ошибок возникает, какая зависимость ограничивает систему и требуется ли вмешательство человека.