Мониторинг: Prometheus и Grafana

Prometheus — система сбора и хранения временных рядов метрик (CPU, memory, HTTP requests); Grafana — инструмент визуализации этих метрик в виде дашбордов и алертов.

Зачем нужно

Без мониторинга нет видимости в работу продакшн-системы: не знаешь, нормальна ли нагрузка, растёт ли error rate, не заканчивается ли диск. Prometheus + Grafana — open-source стандарт для метрик: Prometheus собирает данные, Grafana строит красивые дашборды и отправляет алерты.

Где используется

  • Мониторинг Node.js API: запросы в секунду, latency, error rate
  • Системные метрики серверов (CPU, RAM, disk) через node_exporter
  • Мониторинг Kubernetes-кластера через kube-state-metrics
  • Алерты при деградации производительности

Основной контент

Запуск через docker-compose

# docker-compose.monitoring.yml
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - promdata:/prometheus
    ports:
      - "9090:9090"
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.retention.time=30d

  grafana:
    image: grafana/grafana:latest
    volumes:
      - grafanadata:/var/lib/grafana
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    depends_on:
      - prometheus

  node-exporter:            # системные метрики хоста
    image: prom/node-exporter:latest
    network_mode: host
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro

volumes:
  promdata:
  grafanadata:

prometheus.yml — конфигурация сбора метрик

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: [localhost:9090]

  - job_name: node-exporter
    static_configs:
      - targets: [node-exporter:9100]

  - job_name: myapp
    static_configs:
      - targets: [api:3000]
    metrics_path: /metrics     # эндпоинт приложения

Метрики в Node.js (prom-client)

npm install prom-client
const client = require('prom-client');

// Системные метрики (CPU, memory) — автоматически
const register = new client.Registry;
client.collectDefaultMetrics({ register });

// Кастомные метрики
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
  registers: [register],
});

const httpRequestsTotal = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status'],
  registers: [register],
});

// Express middleware
app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer;
  res.on('finish', () => {
    end({ method: req.method, route: req.route?.path || req.path, status: res.statusCode });
    httpRequestsTotal.inc({ method: req.method, route: req.route?.path || req.path, status: res.statusCode });
  });
  next;
});

// Эндпоинт для Prometheus
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics);
});

PromQL — язык запросов

# Запросов в секунду
rate(http_requests_total[5m])

# Error rate (4xx + 5xx)
rate(http_requests_total{status=~"[45].."}[5m]) / rate(http_requests_total[5m])

# P99 latency
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# CPU usage
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

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

  • Хранить Prometheus данные без persistence volume — все метрики теряются при перезапуске контейнера
  • Не настроить retention period — данные накапливаются и заполняют диск
  • Слишком маленький scrape_interval (< 10s) — нагружает приложение частыми запросами к /metrics
  • Не защитить /metrics endpoint — Nginx должен ограничивать доступ к нему (только внутренняя сеть)

Связанные темы

Ресурсы