# Prometheus: scrape, TSDB, PromQL и production-pitfalls _Observability и мониторинг · LinuxLab Knowledge Base_ **TL;DR:** Prometheus - сервер мониторинга: сам опрашивает приложения по HTTP, собирает числовые метрики, хранит во встроенной БД ~15 дней. По ним строят графики в Grafana и алерты через Alertmanager. Стандарт de-facto в Kubernetes. ## Зачем Prometheus До Prometheus monitoring был push-based: агент на хосте слал метрики в Graphite/Nagios. Проблемы: - Агент знает адрес сервера, файрвол открыт обратно - При падении сервера agent копит локально или теряет - Конфигурация дублируется на каждом хосте - Service discovery, отдельная боль **Prometheus (2012, SoundCloud, потом CNCF)** перевернул модель: сервер сам **scrape** (HTTP GET) target'ы. Преимущества: - Один центральный конфиг - Service discovery встроенный (k8s, Consul, EC2, file) - Down-detection бесплатно (`up == 0`) - Pull можно из интранета через push gateway если нужно Сегодня Prometheus + Grafana, де-факто стандарт metrics в Kubernetes и cloud-native. ## Архитектура ``` ┌─────────────┐ scrape (HTTP /metrics) │ Prometheus │ ◄─────────────┬──────────┐ │ server │ │ │ │ ┌─────────┐ │ ┌──┴──┐ ┌───┴───┐ │ │ TSDB │ │ │ app │ │ node- │ │ │ (15d) │ │ │ /m │ │ exp │ │ └─────────┘ │ └─────┘ └───────┘ │ ┌─────────┐ │ │ │ rules │ ──► alerts ──► Alertmanager ──► PagerDuty/Slack │ │ engine │ │ │ └─────────┘ │ │ │ │ remote_write│ ──► VictoriaMetrics / Thanos / Mimir (long-term) └─────────────┘ ▲ │ PromQL HTTP ┌─────┴────┐ │ Grafana │ + ad-hoc queries └──────────┘ ``` Key components: - **scrape engine**, pulls `/metrics` каждые 15-60s - **TSDB**, local time-series DB, blocks по 2 часа - **rules engine**, оценивает recording + alerting rules каждые 15s - **HTTP API**, `/api/v1/query`, `/query_range`, для Grafana и `promtool` ## Pull-модель: scrape Каждый target экспортит `/metrics` в text format ([[metric-types|OpenMetrics]]): ``` # HELP http_requests_total Number of requests # TYPE http_requests_total counter http_requests_total{method="GET",status="200"} 12345 http_requests_total{method="POST",status="500"} 12 ``` Конфиг scrape: ```yaml scrape_configs: - job_name: my-app scrape_interval: 15s scrape_timeout: 10s metrics_path: /metrics static_configs: - targets: ['app1:8080', 'app2:8080'] ``` В реальности, не static, а через [[service-discovery-prometheus|service discovery]] (k8s endpoints, Consul, file_sd). ## TSDB, как хранится Time-series identifier: `metric_name{label1=val1,label2=val2}`. Каждая уникальная комбинация = **отдельный series**. На диске: ``` /var/lib/prometheus/ ├── 01HABC.../ # 2-часовой block │ ├── chunks/ │ ├── index # postings: label → series IDs │ ├── tombstones │ └── meta.json ├── 01HABD.../ └── wal/ # write-ahead log (current 2h window) ``` Compaction: 2h blocks → 16h → 5d → 14d, как LSM-tree. После retention (default 15d), удалить. Memory ≈ **3 KB на active series** + WAL. 1M series ≈ 3-4 GB RAM. ## PromQL, query language 4 типа запросов: **Instant query**, значение в момент времени: ``` up # все 'up' gauge сейчас rate(http_requests_total[5m]) # rate за последние 5 мин ``` **Range query**, series за период (для графиков): ``` GET /api/v1/query_range?query=up&start=...&end=...&step=15s ``` **Aggregation** через `sum`, `avg`, `max`, `min`, `count`, `topk`: ``` sum by (job)(up) # сколько up по job topk(5, rate(http_requests_total[5m])) # топ-5 по rate histogram_quantile(0.99, sum by (le)(rate(req_duration_bucket[5m]))) ``` **Functions**: - `rate(counter[5m])`, per-second rate, handles reset - `irate(counter[1m])`, instant rate, для волатильных - `increase(counter[1h])`, total за час (= `rate * window`) - `delta(gauge[1h])`, изменение - `predict_linear(gauge[1h], 4*3600)`, прогноз через 4ч (для disk-full) PromQL **векторный**: операции on labels: ``` rate(http_requests_total{status="500"}[5m]) / rate(http_requests_total[5m]) > 0.05 # error rate >5% ``` ## Recording rules, pre-compute Дорогие queries (особенно с `histogram_quantile` и multi-join) кэшируем как **recording rules**, Prometheus сам их вычисляет каждые 15s и пишет как обычные series: ```yaml groups: - name: app_slo interval: 30s rules: - record: app:http_request_rate:5m expr: sum by (job, status)(rate(http_requests_total[5m])) - record: app:http_request_duration:p99:5m expr: histogram_quantile(0.99, sum by (job, le)(rate(http_request_duration_bucket[5m]))) ``` Convention: `aggregation_level:metric:window`. Используется в dashboards и [[alerting-rules-alertmanager|alerting]]. ## Alerting rules ```yaml groups: - name: app rules: - alert: HighErrorRate expr: app:http_error_rate:5m > 0.05 for: 5m # должно держаться 5 мин labels: severity: critical annotations: summary: "Error rate {{ $value | humanizePercentage }}" runbook: "https://wiki/runbooks/high-error-rate" ``` Если expr возвращает не-empty vector `for: 5m`, alert fires в Alertmanager. См. [alerting-rules-alertmanager](/kb/alerting-rules-alertmanager.md). ## Retention и remote_write Local TSDB, короткое хранение (default 15d, max ~2 месяца). Для долгосрочного, **remote_write** в системы заточенные под scale: | Backend | Storage | Tenancy | Compat | |---------|---------|---------|--------| | Thanos | object (S3/GCS) | per-tenant | PromQL | | Cortex / Mimir | object | multi-tenant | PromQL | | VictoriaMetrics | own LSM | YES (cluster) | PromQL+MetricsQL | | M3DB | own | multi-tenant | PromQL | | InfluxDB v3 | own | yes | InfluxQL/Flux | ```yaml remote_write: - url: https://victoria-metrics:8480/api/v1/write queue_config: max_samples_per_send: 5000 capacity: 50000 ``` Production setup: Prometheus как scraper+short-term, VictoriaMetrics cluster для 1+ year retention и querying. ## Federation, иерархия Prometheus Региональный Prom скрейпит локальные сервисы. Глобальный Prom скрейпит агрегаты с региональных через `/federate`: ```yaml scrape_configs: - job_name: federate honor_labels: true metrics_path: /federate params: match[]: - '{__name__=~"job:.*"}' # только recording rules static_configs: - targets: ['region-prom-eu:9090', 'region-prom-us:9090'] ``` Не federate raw метрики, только агрегаты. Иначе cardinality умножится. ## Когда что-то пошло не так - **Prometheus OOM**, обычно cardinality. `topk(20, count by (__name__)({}))` покажет top metrics по series count. См. [cardinality-explosion](/kb/cardinality-explosion.md). - **`up == 0` для target**, scrape failed. Проверь `/targets` UI: timeout, DNS, 401, TLS. Логи Prom скажут точнее. - **`rate()` возвращает NaN**, counter сбросился между samples (рестарт), или нет samples в окне. Расширь окно или проверь scrape. - **Расхождение между Prom и Grafana**, `step` в range query. Маленький step = больше точек = тяжелее query. - **WAL replay долгий после рестарта**, 2-3 минуты на 1M series. Решение: `--storage.tsdb.wal-segment-size=128MB` или Thanos sidecar. - **Высокий 5xx rate в `/api/v1/query`**, длинные queries timeout. Используй recording rules или увеличь `--query.timeout`. - **`out of bounds` ошибки в logs**, clock skew между Prom и target, или target отдаёт sample из будущего. Поправь NTP ([chrony-and-ntp](/kb/chrony-and-ntp.md)). - **Метрики прыгают на 0 раз в N минут**, scrape duration > scrape interval, target не успевает. Уменьши metrics или увеличь interval. ## Альтернативы | Инструмент | Когда выбирать | |-----------|----------------| | **Prometheus** | дефолт для k8s, < 10M active series | | **VictoriaMetrics** | 10×lighter RAM, кластер, drop-in PromQL | | **Thanos** | хочешь S3-storage без отдельного DB-стэка | | **Mimir** | Grafana Labs, multi-tenant SaaS-like | | **InfluxDB** | tagged events, tracing-like queries | | **Datadog** | hosted, не хочется OPS Prom-стэка | ## Команды ```bash promtool query instant 'up == 0' ``` Найти все scrape targets которые сейчас down ```bash promtool tsdb analyze /var/lib/prometheus/ ``` Анализ TSDB: топ метрик по cardinality, размеры blocks, WAL state ```bash curl -X POST localhost:9090/-/reload ``` Reload конфигов и rules без рестарта (требует --web.enable-lifecycle) ```bash curl -s 'localhost:9090/api/v1/query?query=count by (__name__)({}) > 1000' ``` Метрики с >1000 series - первые кандидаты на cardinality cleanup ```bash promtool check rules /etc/prometheus/rules/*.yml ``` Валидация recording + alerting rules перед reload (CI-friendly) ```bash promtool query range --start=1h --end=now --step=15s 'rate(http_requests_total[5m])' ``` Range query за час с шагом 15s - то же что Grafana дёргает ```bash curl -s localhost:9090/api/v1/status/tsdb | jq '.data.headStats' ``` Stats по in-memory head: active series, chunks, samples - быстрая cardinality-проверка ## См. также - [Типы метрик: counter, gauge, histogram, summary](/kb/metric-types.md) - [Service discovery в Prometheus: k8s, Consul, file_sd, relabel](/kb/service-discovery-prometheus.md) - [Alertmanager: route tree, inhibit, dedup, on-call](/kb/alerting-rules-alertmanager.md) - [Cardinality explosion: как убить Prometheus и как чинить](/kb/cardinality-explosion.md) - [OpenTelemetry: signals, OTLP, Collector pipeline](/kb/opentelemetry.md) - [kubelet - архитектура агента ноды Kubernetes](/kb/kubelet-internals.md)