200+ серверов под контролем: внедряем Prometheus и Grafana для хостинг-провайдера

Задача клиента

К нам обратился технический директор хостинг-провайдера из Казани. Компания обслуживала более 200 серверов в двух дата-центрах — VPS клиентов, выделенные серверы, managed-хостинг и собственная инфраструктура. Нагрузка серьёзная, и ответственность соответствующая.

Существующая система мониторинга — самописные Bash-скрипты плюс Nagios — трещала по швам. Вот с чем они к нам пришли:

  • Алерты приходили с опозданием 5-10 минут — клиенты узнавали о падениях раньше, чем техподдержка. Представьте, как это выглядит со стороны заказчика.
  • Нет исторических данных — что происходило неделю назад? Никто не знает. Анализировать тренды и планировать мощности просто невозможно.
  • Ручное добавление каждого сервера — инфраструктура росла, а мониторинг хронически за ней не успевал
  • Никаких дашбордов — чтобы понять, что вообще происходит, приходилось подключаться к каждому серверу отдельно. 200 серверов. Вручную.
  • Нет группировки алертов — один сетевой сбой порождал сотни одинаковых уведомлений. Инженер открывал почту и видел мусор вместо сигнала.

Мы предложили связку Prometheus + Grafana + Alertmanager. Это сейчас де-факто стандарт в индустрии — от небольших стартапов до Google и Amazon. Инструмент проверенный, экосистема огромная.

Проектирование архитектуры мониторинга

Мониторинг — это глаза и уши команды. Без него о проблеме вы узнаёте от злого клиента. С нормальным мониторингом — за 5 минут до того, как кто-то вообще что-то заметит. Разница принципиальная.

Почему именно Prometheus + Grafana, а не что-то другое? Объясняю по-честному:

  • Prometheus — собирает и хранит метрики, умеет агрегировать их через мощный язык запросов PromQL
  • Grafana — платформа визуализации с живыми интерактивными дашбордами. Показываем клиентам — сразу влюбляются.
  • Alertmanager — управляет алертами: маршрутизирует, группирует, подавляет дубли. Именно то, чего не хватало клиенту.
  • Exporters — агенты сбора метрик для любого стека: node, mysql, postgres, nginx и сотни других

Чем это лучше Nagios, с которым они работали раньше? Конкретно:

  • Pull-модель — Prometheus сам ходит за метриками, никаких push-агентов настраивать не нужно
  • Service discovery — новые серверы подхватываются автоматически, без ручных правок конфигов
  • PromQL — язык запросов, которым действительно можно выразить что угодно
  • Cloud-native — отлично дружит с Kubernetes и Docker, если клиент туда движется
  • Экосистема — тысячи готовых экспортёров и дашбордов. Почти под любой сервис найдёте готовое решение.

Схема, которую мы спроектировали

Архитектура мониторинга для 200+ серверов выглядела так:

Серверы (targets):
  ├── node_exporter (:9100)     → метрики ОС (CPU, RAM, Disk, Network)
  ├── mysqld_exporter (:9104)   → метрики MySQL
  ├── postgres_exporter (:9187) → метрики PostgreSQL
  └── nginx_exporter (:9113)    → метрики Nginx

Prometheus (:9090)
  ├── Scrape targets → Pull метрики каждые 15 секунд
  ├── TSDB → Хранение метрик (по умолчанию 15 дней)
  ├── PromQL → Вычисление и агрегация
  └── Alert Rules → Определение условий алертов
        ↓
Alertmanager (:9093)
  ├── Группировка → Объединение однотипных алертов
  ├── Маршрутизация → Направление по командам/каналам
  ├── Silencing → Подавление при обслуживании
  └── Receivers → Email, Telegram, Slack, PagerDuty

Grafana (:3000)
  ├── Data Source → Prometheus
  ├── Dashboards → Визуализация метрик
  └── Alerts → Дополнительные правила алертов

Расчёт ресурсов для 200+ серверов

Первый вопрос, который всегда возникает — сколько ресурсов нужно под Prometheus-сервер? Мы посчитали.

СерверовRAM PrometheusДиск (15 дней)CPU
1-101-2 ГБ10-20 ГБ1 ядро
10-504-8 ГБ50-100 ГБ2 ядра
50-20016-32 ГБ200-500 ГБ SSD4 ядра

Под 200+ серверов мы выделили машину с 32 ГБ RAM, 500 ГБ NVMe SSD и 4 ядрами CPU. Этого хватает с запасом. Для долгосрочного хранения метрик — 6 месяцев и дольше — заложили второй этап с VictoriaMetrics. Это уже отдельная история, но важная.

Установка Prometheus на выделенный сервер мониторинга

Prometheus ставили из официальных бинарей. В production — только так, никаких сюрпризов с пакетными менеджерами дистрибутива.

Установка и systemd unit

Пошаговая установка, которую мы выполнили:

# Создаём пользователя
useradd -r -s /usr/sbin/nologin -M prometheus

# Создаём директории
mkdir -p /etc/prometheus /var/lib/prometheus
chown prometheus:prometheus /var/lib/prometheus

# Скачиваем последнюю версию
PROM_VER="2.52.0"
wget https://github.com/prometheus/prometheus/releases/download/v${PROM_VER}/prometheus-${PROM_VER}.linux-amd64.tar.gz
tar xzf prometheus-${PROM_VER}.linux-amd64.tar.gz
cd prometheus-${PROM_VER}.linux-amd64

# Копируем бинарники и конфиги
cp prometheus promtool /usr/local/bin/
cp -r consoles console_libraries /etc/prometheus/
chown -R prometheus:prometheus /etc/prometheus

# Создаём systemd unit
cat > /etc/systemd/system/prometheus.service << 'EOF'
[Unit]
Description=Prometheus Monitoring
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/local/bin/prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --storage.tsdb.path=/var/lib/prometheus \
  --storage.tsdb.retention.time=30d \
  --storage.tsdb.retention.size=50GB \
  --web.console.templates=/etc/prometheus/consoles \
  --web.console.libraries=/etc/prometheus/console_libraries \
  --web.enable-lifecycle \
  --web.enable-admin-api
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now prometheus
systemctl status prometheus

Конфигурация Prometheus с file-based service discovery

Для 200+ серверов мы выбрали file-based service discovery. Главное преимущество — добавляешь сервер без перезапуска Prometheus. На такой инфраструктуре это критично.

# /etc/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  scrape_timeout: 10s

# Подключение файлов с правилами алертов
rule_files:
  - "/etc/prometheus/rules/*.yml"

# Подключение Alertmanager
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - "localhost:9093"

# Targets для сбора метрик
scrape_configs:
  # Метрики самого Prometheus
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  # Метрики серверов через node_exporter
  - job_name: "nodes"
    static_configs:
      - targets:
          - "10.0.0.10:9100"
          - "10.0.0.11:9100"
          - "10.0.0.12:9100"
        labels:
          env: "production"
      - targets:
          - "10.0.0.20:9100"
        labels:
          env: "staging"

  # Метрики Nginx
  - job_name: "nginx"
    static_configs:
      - targets: ["10.0.0.10:9113"]

  # Метрики PostgreSQL
  - job_name: "postgresql"
    static_configs:
      - targets: ["10.0.0.11:9187"]

  # File-based service discovery
  - job_name: "file_sd"
    file_sd_configs:
      - files:
          - "/etc/prometheus/targets/*.json"
        refresh_interval: 30s

Для динамического добавления серверов создали JSON-файлы:

# /etc/prometheus/targets/webservers.json
[
  {
    "targets": ["10.0.0.10:9100", "10.0.0.11:9100"],
    "labels": {
      "job": "webservers",
      "env": "production",
      "team": "platform"
    }
  }
]

Добавить новый сервер в мониторинг — просто обновить JSON-файл. Prometheus подхватит изменения сам, в течение 30 секунд. Никаких перезапусков, никаких простоев.

Развёртывание Node Exporter на 200+ серверах

Экспортёры собирают метрики и отдают их Prometheus по запросу. Раскатывать их на 200+ серверов вручную — безумие, поэтому мы написали Ansible-роль и закрыли вопрос раз и навсегда.

Установка Node Exporter

Node Exporter ставится на каждый сервер. Вот конфигурация, которую мы стандартизировали по всему парку:

# Создаём пользователя
useradd -r -s /usr/sbin/nologin -M node_exporter

# Скачиваем
NE_VER="1.8.1"
wget https://github.com/prometheus/node_exporter/releases/download/v${NE_VER}/node_exporter-${NE_VER}.linux-amd64.tar.gz
tar xzf node_exporter-${NE_VER}.linux-amd64.tar.gz
cp node_exporter-${NE_VER}.linux-amd64/node_exporter /usr/local/bin/

# Systemd unit
cat > /etc/systemd/system/node_exporter.service << 'EOF'
[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter \
  --collector.systemd \
  --collector.processes \
  --collector.tcpstat \
  --collector.textfile.directory=/var/lib/node_exporter/textfile_collector \
  --web.listen-address=:9100
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

mkdir -p /var/lib/node_exporter/textfile_collector
chown node_exporter:node_exporter /var/lib/node_exporter/textfile_collector

systemctl daemon-reload
systemctl enable --now node_exporter

# Проверяем
curl -s http://localhost:9100/metrics | head -20

Экспортёры для БД и веб-серверов

Кроме node_exporter настроили специализированные экспортёры под конкретные сервисы клиента:

# === PostgreSQL Exporter ===
useradd -r -s /usr/sbin/nologin postgres_exporter

# Создаём пользователя мониторинга в PostgreSQL
sudo -u postgres psql << 'SQL'
CREATE USER monitoring WITH PASSWORD 'MonitorPass123';
GRANT pg_monitor TO monitoring;
SQL

# Устанавливаем
wget https://github.com/prometheus-community/postgres_exporter/releases/download/v0.15.0/postgres_exporter-0.15.0.linux-amd64.tar.gz
tar xzf postgres_exporter-0.15.0.linux-amd64.tar.gz
cp postgres_exporter-0.15.0.linux-amd64/postgres_exporter /usr/local/bin/

cat > /etc/systemd/system/postgres_exporter.service << 'EOF'
[Unit]
Description=PostgreSQL Exporter
After=network.target

[Service]
User=postgres_exporter
Environment=DATA_SOURCE_NAME=postgresql://monitoring:MonitorPass123@localhost:5432/postgres?sslmode=disable
ExecStart=/usr/local/bin/postgres_exporter
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now postgres_exporter

# === Nginx Exporter (требует stub_status) ===
wget https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v1.1.0/nginx-prometheus-exporter_1.1.0_linux_amd64.tar.gz
tar xzf nginx-prometheus-exporter_1.1.0_linux_amd64.tar.gz
cp nginx-prometheus-exporter /usr/local/bin/

cat > /etc/systemd/system/nginx_exporter.service << 'EOF'
[Unit]
Description=Nginx Exporter
After=network.target

[Service]
ExecStart=/usr/local/bin/nginx-prometheus-exporter --nginx.scrape-uri=http://127.0.0.1:8080/nginx_status
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now nginx_exporter

Кастомные метрики: бэкапы, SSL, бизнес-показатели

Отдельная история — кастомные метрики через textfile collector. Это позволило вытащить в мониторинг не только железо, но и бизнес-критичные вещи, которые стандартными экспортёрами не покрыть:

# Метрика о последнем бэкапе (из скрипта бэкапа)
cat > /var/lib/node_exporter/textfile_collector/backup.prom << EOF
# HELP backup_last_success_timestamp Last successful backup timestamp
# TYPE backup_last_success_timestamp gauge
backup_last_success_timestamp{job="borg"} $(date +%s)
# HELP backup_last_size_bytes Size of last backup in bytes
# TYPE backup_last_size_bytes gauge
backup_last_size_bytes{job="borg"} 5368709120
# HELP backup_last_duration_seconds Duration of last backup
# TYPE backup_last_duration_seconds gauge
backup_last_duration_seconds{job="borg"} 342
EOF

# Метрика SSL-сертификата
DOMAIN="example.ru"
EXPIRY=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_TS=$(date -d "$EXPIRY" +%s)
cat > /var/lib/node_exporter/textfile_collector/ssl.prom << EOF
# HELP ssl_certificate_expiry_timestamp SSL certificate expiry timestamp
# TYPE ssl_certificate_expiry_timestamp gauge
ssl_certificate_expiry_timestamp{domain="$DOMAIN"} $EXPIRY_TS
EOF

В итоге в Grafana стало видно срок жизни каждого SSL-сертификата и время последнего бэкапа на каждом сервере. Клиент был приятно удивлён — раньше об истёкших сертификатах узнавали от пользователей.

PromQL: запросы, которые мы настроили для дашбордов

PromQL — язык запросов Prometheus. Мощный, местами непривычный, но когда разбираешься — оторваться сложно. Мы написали несколько десятков запросов под дашборды клиента и попутно объяснили команде, как это работает.

Основы PromQL, которые мы использовали

Базовые типы данных и селекторы:

# Простой селектор — все значения метрики
node_cpu_seconds_total

# Фильтрация по лейблам
node_cpu_seconds_total{mode="idle"}
node_cpu_seconds_total{mode!="idle", instance="10.0.0.10:9100"}

# Regex-фильтрация
node_cpu_seconds_total{mode=~"user|system"}
node_filesystem_avail_bytes{mountpoint!~"/snap.*|/boot.*"}

# Range vector — диапазон значений за период
node_cpu_seconds_total{mode="idle"}[5m]

# Offset — сдвиг во времени (для сравнения)
node_memory_MemAvailable_bytes offset 1h

Четыре типа метрик — объяснили дежурным инженерам клиента на конкретных примерах:

  • Counter — только растёт: запросы, ошибки, байты. В запросах всегда используется с rate() или increase(), иначе смысла нет
  • Gauge — текущее значение: температура, объём RAM, загрузка CPU. Берёте как есть, без преобразований.
  • Histogram — распределение значений. Классический пример — время ответа сервера.
  • Summary — квантили, percentiles. Удобно, когда нужно знать, например, 95-й перцентиль времени ответа.

Готовые запросы для ежедневного мониторинга

Вот библиотека PromQL-запросов, которую мы собрали для этого проекта:

# === CPU ===
# Загрузка CPU в процентах (все ядра)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Загрузка CPU по ядрам
100 - (rate(node_cpu_seconds_total{mode="idle"}[5m]) * 100)

# === RAM ===
# Использование RAM в процентах
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

# Свободная RAM в ГБ
node_memory_MemAvailable_bytes / 1024^3

# === Диск ===
# Использование диска в процентах
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) * 100

# Прогноз заполнения диска (через сколько часов закончится)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[24h], 3600 * 24 * 7) / 1024^3

# Скорость записи на диск (MB/s)
rate(node_disk_written_bytes_total[5m]) / 1024^2

# === Сеть ===
# Входящий трафик (Мбит/с)
rate(node_network_receive_bytes_total{device!~"lo|veth.*"}[5m]) * 8 / 1024^2

# Исходящий трафик
rate(node_network_transmit_bytes_total{device!~"lo|veth.*"}[5m]) * 8 / 1024^2

# === Системные ===
# Uptime в днях
(time() - node_boot_time_seconds) / 86400

# Количество запущенных процессов
node_procs_running

# Load average (1m) нормализованный по ядрам
node_load1 / count without (cpu) (node_cpu_seconds_total{mode="idle"})

Агрегация для обзорных дашбордов

Для обзорного дашборда по всей инфраструктуре написали запросы агрегации — чтобы видеть картину целиком, а не сервер за сервером:

# Средняя загрузка CPU по окружению
avg by (env) (100 - rate(node_cpu_seconds_total{mode="idle"}[5m]) * 100)

# Суммарная RAM всех серверов
sum(node_memory_MemTotal_bytes) / 1024^3

# Топ-5 серверов по загрузке CPU
topk(5, 100 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Количество серверов по окружению
count by (env) (up{job="nodes"} == 1)

# Quantile — 95-й перцентиль времени ответа
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# Математические операторы
# Объём свободного места на всех дисках (ГБ)
sum by (instance) (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}) / 1024^3

# Рост диска за неделю
node_filesystem_size_bytes - node_filesystem_avail_bytes
  - (node_filesystem_size_bytes offset 7d - node_filesystem_avail_bytes offset 7d)

Обзорный дашборд показывал все 200+ серверов на одном экране. Дежурный инженер заходил и за секунды понимал, где проблема. Не нужно переключаться между вкладками и держать в голове, что где смотреть.

Grafana: дашборды для разных уровней

Grafana — это то, что превращает поток цифр в картинку, понятную даже не-технарю. Под конкретного клиента мы выстроили несколько уровней визуализации: для дежурного инженера, для тимлида и для руководства.

Установка Grafana

Ставим Grafana OSS из официального репозитория:

# Добавляем репозиторий
apt install -y apt-transport-https software-properties-common
wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" > /etc/apt/sources.list.d/grafana.list

# Устанавливаем
apt update && apt install grafana -y

# Настраиваем
# /etc/grafana/grafana.ini
[server]
http_port = 3000

[security]
admin_user = admin
admin_password = SecureGrafanaPass

[auth.anonymous]
enabled = false

# Запускаем
systemctl enable --now grafana-server
systemctl status grafana-server

После запуска веб-интерфейс открывается по адресу http://server:3000.

Подключение Prometheus и импорт дашбордов

Prometheus подключается как Data Source — буквально за минуту. Дальше мы импортировали проверенные готовые дашборды:

# Подключение через API
curl -X POST http://admin:SecureGrafanaPass@localhost:3000/api/datasources \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Prometheus",
    "type": "prometheus",
    "url": "http://localhost:9090",
    "access": "proxy",
    "isDefault": true
  }'

# Или через GUI:
# Configuration -> Data Sources -> Add data source -> Prometheus
# URL: http://localhost:9090
# Save & Test

Импорт готовых дашбордов и создание кастомных

Часть дашбордов взяли готовыми с Grafana.com, часть написали с нуля — под специфику хостинг-провайдера стандартные шаблоны подходят процентов на шестьдесят, не больше:

# Через GUI: Dashboards -> Import -> ID

# Node Exporter Full — ID: 1860 (самый популярный)
# Node Exporter for Prometheus — ID: 11074
# Nginx — ID: 12708
# PostgreSQL — ID: 9628
# MySQL — ID: 7362
# Docker — ID: 893
# Blackbox Exporter — ID: 7587

# Через API (пример импорта Node Exporter Full)
curl -s https://grafana.com/api/dashboards/1860/revisions/latest/download | \
curl -X POST http://admin:SecureGrafanaPass@localhost:3000/api/dashboards/import \
  -H 'Content-Type: application/json' \
  -d "{
    \"dashboard\": $(cat),
    \"overwrite\": true,
    \"inputs\": [{\"name\": \"DS_PROMETHEUS\", \"type\": \"datasource\", \"pluginId\": \"prometheus\", \"value\": \"Prometheus\"}],
    \"folderId\": 0
  }"

В итоге у клиента получилось три уровня: обзорный экран, где все 200+ серверов видны разом, детальный вид по конкретной машине — и отдельный дашборд с бизнес-метриками: SLA, доступность услуг, всё что нужно руководству на утреннем совещании.

Alertmanager: умные оповещения вместо спама

Главная боль, с которой пришёл клиент: при любом сетевом сбое Nagios поднимал такую волну алертов, что дежурный просто переставал на них реагировать. Классический alert fatigue. Alertmanager закрывает эту проблему тремя механизмами — группировкой, маршрутизацией и подавлением дублей.

Установка Alertmanager

Установка — по той же схеме, что и Prometheus:

useradd -r -s /usr/sbin/nologin -M alertmanager
mkdir -p /etc/alertmanager /var/lib/alertmanager

AM_VER="0.27.0"
wget https://github.com/prometheus/alertmanager/releases/download/v${AM_VER}/alertmanager-${AM_VER}.linux-amd64.tar.gz
tar xzf alertmanager-${AM_VER}.linux-amd64.tar.gz
cp alertmanager-${AM_VER}.linux-amd64/alertmanager /usr/local/bin/
cp alertmanager-${AM_VER}.linux-amd64/amtool /usr/local/bin/

chown -R alertmanager:alertmanager /etc/alertmanager /var/lib/alertmanager

cat > /etc/systemd/system/alertmanager.service << 'EOF'
[Unit]
Description=Alertmanager
Wants=network-online.target
After=network-online.target

[Service]
User=alertmanager
Group=alertmanager
Type=simple
ExecStart=/usr/local/bin/alertmanager \
  --config.file=/etc/alertmanager/alertmanager.yml \
  --storage.path=/var/lib/alertmanager \
  --web.listen-address=:9093
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now alertmanager

Конфигурация с Telegram-уведомлениями и маршрутизацией

Маршрутизацию настроили по уровню критичности: critical летит и в Telegram, и на почту, warning — только в Telegram. Ночью никто не хочет получать письмо про предупреждение, которое само рассосётся:

# /etc/alertmanager/alertmanager.yml
global:
  resolve_timeout: 5m
  smtp_from: 'monitoring@itfresh.ru'
  smtp_smarthost: 'mail.itfresh.ru:587'
  smtp_auth_username: 'monitoring@itfresh.ru'
  smtp_auth_password: 'MailPassword'
  smtp_require_tls: true
  telegram_api_url: 'https://api.telegram.org'

templates:
  - '/etc/alertmanager/templates/*.tmpl'

route:
  receiver: 'telegram-critical'
  group_by: ['alertname', 'instance']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
    # Критические алерты — Telegram + Email
    - match:
        severity: critical
      receiver: 'telegram-critical'
      repeat_interval: 1h

    # Предупреждения — только Telegram
    - match:
        severity: warning
      receiver: 'telegram-warning'
      repeat_interval: 4h

    # Информационные — только email
    - match:
        severity: info
      receiver: 'email-info'
      repeat_interval: 24h

receivers:
  - name: 'telegram-critical'
    telegram_configs:
      - bot_token: '1234567890:ABCdefGHIjklMNOpqrsTUVwxyz'
        chat_id: -1001234567890
        parse_mode: 'HTML'
        message: |-
          {{ range .Alerts }}
          {{ if eq .Status "firing" }}🔴{{ else }}🟢{{ end }} {{ .Labels.alertname }}
          Instance: {{ .Labels.instance }}
          Severity: {{ .Labels.severity }}
          Description: {{ .Annotations.description }}
          {{ end }}
    email_configs:
      - to: 'admin@itfresh.ru'
        send_resolved: true

  - name: 'telegram-warning'
    telegram_configs:
      - bot_token: '1234567890:ABCdefGHIjklMNOpqrsTUVwxyz'
        chat_id: -1001234567890
        parse_mode: 'HTML'
        send_resolved: true

  - name: 'email-info'
    email_configs:
      - to: 'team@itfresh.ru'
        send_resolved: true

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'instance']

Секция inhibit_rules делает простую, но важную вещь: если по хосту уже пришёл critical — все warning по нему подавляются. Дежурный получает одно сообщение, а не пятьдесят.

Правила алертов для хостинг-провайдера

Написали полный набор правил, который покрывает все типовые инциденты, с которыми мы сталкивались за годы работы с хостерами:

# /etc/prometheus/rules/node_alerts.yml
groups:
  - name: node_alerts
    rules:
      # Сервер недоступен
      - alert: InstanceDown
        expr: up == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Instance {{ $labels.instance }} is down"
          description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 2 minutes."

      # CPU загрузка > 85%
      - alert: HighCpuUsage
        expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage on {{ $labels.instance }}"
          description: "CPU usage is {{ $value | printf \"%.1f\" }}% on {{ $labels.instance }}."

      # CPU загрузка > 95%
      - alert: CriticalCpuUsage
        expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Critical CPU usage on {{ $labels.instance }}"
          description: "CPU usage is {{ $value | printf \"%.1f\" }}% on {{ $labels.instance }}."

      # RAM использование > 90%
      - alert: HighMemoryUsage
        expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage on {{ $labels.instance }}"
          description: "Memory usage is {{ $value | printf \"%.1f\" }}% on {{ $labels.instance }}."

      # Диск заполнен > 85%
      - alert: DiskSpaceLow
        expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) * 100 > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Disk space low on {{ $labels.instance }}"
          description: "Disk {{ $labels.mountpoint }} is {{ $value | printf \"%.1f\" }}% full on {{ $labels.instance }}."

      # Диск заполнен > 95%
      - alert: DiskSpaceCritical
        expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) * 100 > 95
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Disk space critical on {{ $labels.instance }}"
          description: "Disk {{ $labels.mountpoint }} is {{ $value | printf \"%.1f\" }}% full."

      # Высокий Network receive errors
      - alert: NetworkErrors
        expr: rate(node_network_receive_errs_total[5m]) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Network errors on {{ $labels.instance }}"
          description: "{{ $labels.device }} has {{ $value | printf \"%.0f\" }} receive errors/sec."

      # Бэкап устарел (> 26 часов)
      - alert: BackupStale
        expr: time() - backup_last_success_timestamp > 93600
        for: 0m
        labels:
          severity: warning
        annotations:
          summary: "Backup is stale on {{ $labels.instance }}"
          description: "Last backup was {{ $value | humanizeDuration }} ago."

      # SSL-сертификат истекает через 14 дней
      - alert: SSLCertExpiring
        expr: (ssl_certificate_expiry_timestamp - time()) / 86400 < 14
        for: 0m
        labels:
          severity: warning
        annotations:
          summary: "SSL certificate expiring for {{ $labels.domain }}"
          description: "Certificate expires in {{ $value | printf \"%.0f\" }} days."

Проверяем конфиг и применяем:

# Проверяем правила
promtool check rules /etc/prometheus/rules/node_alerts.yml

# Перезагружаем Prometheus
curl -X POST http://localhost:9090/-/reload

# Проверяем алерты в UI
# http://prometheus:9090/alerts

Best practices и автоматизация развёртывания

Вручную раскатать что-либо на 200+ серверов — затея заведомо провальная. Мы автоматизировали весь деплой через Ansible и сразу закрыли вопрос с безопасным доступом к компонентам мониторинга.

Лейблы, naming conventions и recording rules

Сразу договорились о naming conventions — без этого через полгода в инфраструктуре такого размера наступает хаос:

# Используйте осмысленные лейблы
- targets: ["10.0.0.10:9100"]
  labels:
    env: "production"      # Окружение
    role: "webserver"       # Роль сервера
    team: "platform"        # Ответственная команда
    dc: "msk-1"             # Дата-центр
    project: "website"      # Проект

# Naming conventions для recording rules (предвычисленные метрики)
# /etc/prometheus/rules/recording_rules.yml
groups:
  - name: node_recording_rules
    interval: 15s
    rules:
      # Предвычисляем частые запросы для ускорения дашбордов
      - record: instance:node_cpu_utilisation:rate5m
        expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

      - record: instance:node_memory_utilisation:ratio
        expr: 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes

      - record: instance:node_disk_utilisation:ratio
        expr: 1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}

Recording rules — это предвычисленные результаты тяжёлых запросов. Дашборд на 200+ серверов без них открывался бы секунд десять. С ними — мгновенно.

Безопасность и reverse proxy для Grafana

Все компоненты мониторинга спрятали за Nginx с базовой аутентификацией — наружу ничего лишнего не торчит:

# Reverse proxy для Prometheus с аутентификацией
# /etc/nginx/conf.d/prometheus.conf
server {
    listen 443 ssl http2;
    server_name monitoring.example.ru;

    ssl_certificate /etc/letsencrypt/live/monitoring.example.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/monitoring.example.ru/privkey.pem;

    # Basic Auth
    auth_basic "Monitoring";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location /prometheus/ {
        proxy_pass http://127.0.0.1:9090/;
    }

    location /grafana/ {
        proxy_pass http://127.0.0.1:3000/;
        proxy_set_header Host $host;
    }

    location /alertmanager/ {
        proxy_pass http://127.0.0.1:9093/;
    }
}

# Создаём файл паролей
htpasswd -c /etc/nginx/.htpasswd admin

Долгосрочное хранение метрик — это второй этап. Вот что планируем:

  • VictoriaMetrics — drop-in замена Prometheus, на практике даёт 10x экономию по RAM и дисковому пространству
  • Thanos — бесконечное хранение в S3-совместимом хранилище, работает поверх существующего Prometheus
  • Grafana Mimir — горизонтально масштабируемое хранилище метрик, если инфраструктура продолжит расти

Ansible-роль для автоматического развёртывания

Для раскатки node_exporter на все машины написали Ansible-роль — один раз написал, больше не трогаешь:

# ansible/roles/node_exporter/tasks/main.yml
---
- name: Create node_exporter user
  user:
    name: node_exporter
    system: yes
    shell: /usr/sbin/nologin
    create_home: no

- name: Download node_exporter
  get_url:
    url: "https://github.com/prometheus/node_exporter/releases/download/v{{ ne_version }}/node_exporter-{{ ne_version }}.linux-amd64.tar.gz"
    dest: "/tmp/node_exporter.tar.gz"

- name: Extract and install
  unarchive:
    src: "/tmp/node_exporter.tar.gz"
    dest: "/tmp/"
    remote_src: yes

- name: Copy binary
  copy:
    src: "/tmp/node_exporter-{{ ne_version }}.linux-amd64/node_exporter"
    dest: "/usr/local/bin/node_exporter"
    mode: '0755'
    remote_src: yes

- name: Create systemd service
  template:
    src: node_exporter.service.j2
    dest: /etc/systemd/system/node_exporter.service
  notify: restart node_exporter

- name: Start node_exporter
  systemd:
    name: node_exporter
    state: started
    enabled: yes
    daemon_reload: yes

Деплой на весь парк — одной командой:

ansible-playbook -i inventory.ini playbook.yml --tags node_exporter

200+ серверов — 15 минут. Это и есть смысл автоматизации.

Результаты внедрения

Уложились в 8 рабочих дней. Вот что клиент получил в цифрах:

  • Время обнаружения инцидента — 30 секунд вместо прежних 5-10 минут: алерт в Telegram приходит через 30 секунд плюс 2 минуты на срабатывание условия for
  • 200+ серверов на едином дашборде — дежурный с первого взгляда видит всю инфраструктуру, не переключаясь между системами
  • Автоматическое обнаружение новых серверов — добавил строчку в JSON-файл, через 30 секунд сервер уже в мониторинге
  • Группировка алертов — при сетевом сбое приходит 1 уведомление, а не сотня одинаковых сообщений
  • История за 30 дней — можно смотреть тренды, планировать закупку железа, а не реагировать постфактум
  • Прогноз заполнения дисков — predict_linear честно говорит, через сколько дней кончится место, а не когда уже кончилось
  • Мониторинг бэкапов и SSL — алерт, если бэкап не запускался больше 26 часов или сертификат сгорает через 14 дней
  • SLA-дашборд — руководство в любой момент видит процент доступности по каждому клиенту, без запросов к инженерам

Через 2 месяца после внедрения обращения в техподдержку по поводу недоступности упали на 60%. Инженеры начали закрывать проблемы до того, как клиенты успевали их заметить. Если хотите то же самое у себя — обратитесь в АйТи Фреш. Настроим мониторинг и для одного сервера, и для сотен нод.

Часто задаваемые вопросы

Prometheus — очевидный выбор для cloud-native и контейнерных сред: Kubernetes, Docker, микросервисы, динамические окружения. Zabbix сильнее там, где есть SNMP-устройства, сетевое оборудование и нужен классический agent-based мониторинг. Для этого клиента — хостинг-провайдера на Linux — Prometheus подошёл идеально: PromQL мощнее любого встроенного языка запросов, а под Linux-серверы готовых экспортёров хватает на любой случай.

Грубая прикидка по потреблению: Prometheus съедает примерно 1-2 байта RAM на сэмпл в памяти. При интервале сбора 15 секунд и 500 метриках с сервера получается: 10 серверов — около 1 ГБ RAM, 50 серверов — около 4 ГБ, 200 серверов — около 16 ГБ. По диску — порядка 1-2 байт на сэмпл после сжатия, за 30 дней на 10 серверах уходит 10-15 ГБ на SSD. Клиенту с его 200+ машинами выделили сервер с 32 ГБ RAM — с запасом, но без перекладывания денег на ветер.

На практике есть четыре рабочих способа. Первый — Pushgateway: экспортёры сами пушат метрики на промежуточный сервер, Prometheus забирает их оттуда. Второй — VPN: объединяете все серверы в одну сеть через WireGuard, и Prometheus видит их как локальные. Третий — SSH-туннель: ssh -L 9100:localhost:9100 user@remote-server, быстро и без лишней инфраструктуры. Четвёртый — Prometheus Federation: локальный Prometheus собирает метрики своего сегмента, а центральный агрегирует всё. Для одного хостинг-провайдера мы как раз выбрали WireGuard между двумя дата-центрами — проще всего оказалось именно так.

Решается через textfile collector в node_exporter — элегантно и без лишних зависимостей. В конце скрипта бэкапа одной строкой пишете timestamp в файл: echo 'backup_last_success_timestamp{job="borg"} '$(date +%s) > /var/lib/node_exporter/textfile_collector/backup.prom. Дальше создаёте правило алерта: time() - backup_last_success_timestamp > 93600 — это 26 часов, с небольшим запасом на случай задержки. Если бэкап не прошёл, алерт прилетит вам раньше, чем вы успеете забыть о проблеме. Мы раскатали эту схему на 200+ серверах одного клиента — работает без сюрпризов.

Да, Windows мониторится отлично. Используйте windows_exporter — он же бывший wmi_exporter. Собирает всё нужное: CPU, RAM, диски, сеть, состояние служб и IIS. Устанавливается через MSI-пакет или одной командой в Chocolatey: choco install prometheus-windows-exporter. Порт по умолчанию — 9182, не перепутайте с Linux-экспортёром на 9100. В Grafana сразу берёте готовый дашборд с ID 14694 — настраивать с нуля не придётся.

Нужна помощь с настройкой?

Если не хочется разбираться со всем этим самостоятельно — команда АйТи Фреш настроит мониторинг под ваши серверы. Больше 15 лет в деле, обслуживание от 15 000 ₽/мес.

📞 Связаться с нами
#prometheus мониторинг#grafana установка#node exporter#promql запросы#alertmanager настройка#мониторинг серверов linux#grafana dashboard#prometheus настройка