· 15 мин чтения

Prometheus + Grafana с нуля: практичный дашборд для мониторинга серверов небольшой компании

Zabbix у нас стоит почти на всех клиентских контурах и хорошо ловит инциденты по триггерам. Но когда клиент спрашивает «а хватит ли нам диска на файловом сервере через полгода» или «почему сервер 1С подтормаживает по вторникам к обеду», нужна не лампочка «красный-зелёный», а история метрик за месяцы с возможностью покрутить график руками. Для этого мы поднимаем второй, лёгкий слой — Prometheus и Grafana. Рассказываю, как именно мы это делаем: с какими версиями, конфигами и порогами.

Зачем директору IT-аутсорсинга ещё один мониторинг, если уже есть Zabbix

У нас в компании Zabbix 7.0 — рабочая лошадка на большинстве клиентских стендов: он умеет триггеры, эскалации, SNMP по свитчам и коммутируемым ИБП, и мы давно выстроили под него шаблоны. Но у Zabbix есть слабое место, которое проявляется именно в разговоре с руководителем клиента: агрегация истории. Item history hold по умолчанию короткий, тренды хранятся по часам, а не по секундам, и когда нужно показать не «упало-не упало», а плавную кривую нагрузки диска за три месяца с разрешением в минуту — приходится выкручиваться.

Prometheus в этой роли работает иначе: он тянет метрики с интервалом 15 секунд, хранит их как временной ряд с полным разрешением на весь срок retention, и любой график — это не заранее посчитанный тренд, а PromQL-запрос по сырым данным. Для клиента до 50 рабочих мест это означает конкретную вещь: когда мы приходим с разговором «через 4 месяца упрётесь в диск на 1С-сервере», у нас на экране реальная линейная экстраполяция роста node_filesystem_avail_bytes, а не оценка на глаз.

Мы не заменяем Zabbix Prometheus-ом — они решают разные задачи. Zabbix остаётся системой алертинга широкого профиля (SNMP, службы Windows, лог-файлы), а Prometheus плюс Grafana — это наш инструмент для capacity planning и визуальной диагностики нагрузки: диски, память, сеть, CPU. На части клиентских контуров (там, где серверов больше 3-4 и клиент готов платить за более глубокую аналитику) мы ставим оба слоя параллельно, они друг другу не мешают — используют разные агенты и разные порты.

Архитектура нашего стека: четыре компонента и кто кому что отдаёт

Минимальный рабочий стек, который мы разворачиваем на одном небольшом сервере-«коллекторе» (обычно это отдельная VM на 2 vCPU / 4 ГБ RAM / 40 ГБ диска, если серверов под наблюдением до 10-15), состоит из четырёх процессов. Собираем их не в Docker, а нативными бинарниками под systemd — на этом отдельно остановлюсь ниже.

КомпонентРольПортsystemd-юнитВерсия на июль 2026
PrometheusТянет (scrape) метрики по HTTP, хранит TSDB, считает alerting rules9090prometheus.service3.x
node_exporterОтдаёт метрики ОС хоста (CPU, память, диски, сеть, файловые системы)9100node_exporter.service1.x
GrafanaДашборды, панели, шаблонные переменные, алерт-визуализация3000grafana-server.service12.x
AlertmanagerГруппировка, дедупликация и маршрутизация алертов от Prometheus в Telegram/почту9093alertmanager.service0.2x

Точные минорные версии мы фиксируем на дату внедрения по официальным release-страницам — здесь важнее показать состав стека, чем номер сборки. Модель работы простая и однонаправленная: node_exporter стоит на каждом наблюдаемом сервере и пассивно отдаёт метрики в текстовом формате Prometheus по адресу /metrics. Сам Prometheus сидит на сервере-коллекторе и с заданным интервалом ходит («scrape») ко всем node_exporter-ам, забирая срез метрик. Он же вычисляет alerting rules и, если условие сработало, отправляет алерт в Alertmanager, который решает — сгруппировать, подождать, отправить в Telegram-бота или на почту. Grafana ничего не собирает сама — она ходит в Prometheus как data source и рисует то, что запрошено через PromQL.

Установка: почему мы ставим бинарники, а не тянем готовый Docker-compose

На чужом железе клиента лишний слой контейнеризации — это ещё один процесс, который надо мониторить, обновлять и объяснять клиентскому админу при передаче на сопровождение. Поэтому наш стандарт — systemd-юниты поверх обычных бинарников с отдельным непривилегированным пользователем.

Коротко порядок действий на сервере-коллекторе (Debian 12 / Ubuntu 22.04-24.04):

  1. Создаём системного пользователя без шелла: useradd --no-create-home --shell /usr/sbin/nologin prometheus.
  2. Разворачиваем бинарник в /usr/local/bin/prometheus, конфиг — в /etc/prometheus/prometheus.yml, данные TSDB — в /var/lib/prometheus, права всюду на пользователя prometheus.
  3. Флаги запуска, которые мы всегда прописываем явно в systemd-юните: --config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/var/lib/prometheus --storage.tsdb.retention.time=90d --web.listen-address=127.0.0.1:9090. Слушать наружу без обратного прокси и аутентификации мы Prometheus не даём принципиально — у него из коробки нет built-in аутентификации, поэтому порт 9090 либо только на loopback, либо за nginx с basic_auth и TLS.
  4. На каждом наблюдаемом сервере — тот же паттерн для node_exporter, но слушает он на 0.0.0.0:9100 (или на внутреннем VLAN-адресе, если сеть сегментирована), потому что к нему приходит Prometheus снаружи этого хоста.

Базовый prometheus.yml, с которого мы стартуем на любом новом клиентском стенде, выглядит так:

global: scrape_interval: 15s evaluation_interval: 15s rule_files: - "/etc/prometheus/rules/*.yml" alerting: alertmanagers: - static_configs: - targets: ["127.0.0.1:9093"] scrape_configs: - job_name: "prometheus" static_configs: - targets: ["127.0.0.1:9090"] - job_name: "node" scrape_interval: 15s static_configs: - targets: - "10.20.0.11:9100" - "10.20.0.12:9100" - "10.20.0.13:9100" labels: env: "prod"

Интервал в 15 секунд — не догма из документации, а наш практический выбор: для дашборда нагрузки сервера этого достаточно, а более частый опрос (5-10 с) на слабом коллекторе создаёт лишнюю нагрузку на диск TSDB без ощутимой пользы в визуализации. Если серверов под наблюдением больше 20-25, мы разносим их по нескольким job_name с метками env и role, чтобы потом фильтровать в Grafana по одной переменной, а не городить десяток отдельных дашбордов.

node_exporter: какие метрики мы реально выводим на дашборд, а какие оставляем в тени

node_exporter отдаёт несколько сотен метрик из коробки — по CPU, памяти, дискам, сети, файловым системам, systemd-юнитам, температуре, если доступна. На практике для дашборда «здоровье сервера небольшой компании» нам хватает десятка. Вот с чем мы реально работаем каждый день:

Метрика node_exporterЧто показываетНаш практический порог
node_filesystem_avail_bytes / node_filesystem_size_bytesСвободное место на файловой системе (по label mountpoint)Тревога при заполнении > 85%, критично > 92%
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytesРеально доступная память (учитывает кэш и буферы, а не «свободную» в лоб)Тревога при доступности < 10% в течение 10 минут
node_cpu_seconds_total{mode=...}Счётчик времени CPU по режимам (idle, user, system, iowait, steal)Средняя загрузка > 85% на протяжении 15 минут — повод разбираться
node_load1 / node_load5 / node_load15Load average в привычном виде из topСравниваем с числом ядер; load1 > 1.5×nproc — тревога
node_disk_io_time_seconds_totalСколько времени диск был занят операциями ввода-вывода (счётчик, «занятость» диска)rate > 0.9 (то есть диск занят 90%+ времени) — узкое место
node_network_receive_bytes_total / transmit_bytes_totalСчётчики трафика по интерфейсу (label device)Смотрим тренд, жёсткого порога нет — зависит от канала клиента
node_boot_time_secondsВремя последней загрузки — считаем аптайм как time() минус эта метрикаНеожиданный сброс — повод проверить, не было ли аварийной перезагрузки
node_systemd_unit_stateСостояние systemd-юнита (label state: active/failed/...)state="failed" для критичных юнитов (nginx, postgresql, srv1cv8) — мгновенная тревога

Отдельно скажу про node_disk_io_time_seconds_total — эта метрика недооценена в большинстве шаблонных дашбордов, а у нас как раз она чаще всего первой показывает деградацию: если у клиента 1С-сервер на медленном RAID и вечером бухгалтерия жалуется на тормоза, именно занятость диска (а не CPU и не память) первой уходит в полку. Отдельно держим на дашборде i/o wait из node_cpu_seconds_total{mode="iowait"} — рост этой доли при невысоком CPU usage почти всегда означает упирание в диск, а не в процессор.

Настроим мониторинг серверов вашей компании под ключ

Разворачиваем Prometheus, node_exporter, Grafana и Alertmanager на вашей инфраструктуре: пороги алертов под ваши реальные сервисы, дашборд с историей нагрузки за месяцы и уведомления в Telegram вместо разбора логов постфактум. Подходит компаниям до 50 рабочих мест с 1-2 серверами и больше.

PromQL: как считать нагрузку правильно, а не приблизительно

Ключевая ошибка, которую мы видели у клиентов, пытавшихся сами настроить Prometheus «по гайду из интернета» — рисовать графики напрямую по счётчикам (counter) без функции rate(). Счётчик в Prometheus только растёт (и сбрасывается в 0 при рестарте процесса), поэтому голое число ни о чём не говорит — важна скорость роста за окно времени. Вот запросы, которые реально стоят у нас в панелях:

Загрузка CPU в процентах (по инстансу):

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

Заполненность диска в процентах (исключая псевдо-ФС):

100 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"} * 100)

Использование памяти в процентах (через MemAvailable, а не MemFree):

(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

Сетевой трафик на интерфейсе в мегабитах в секунду:

rate(node_network_receive_bytes_total{device="eth0"}[5m]) * 8 / 1000000

Окно [5m] в rate() мы выбрали не случайно: при scrape_interval 15 секунд в окно попадает 20 точек, этого достаточно, чтобы функция не «дёргалась» на единичном пропущенном скрейпе, но при этом график остаётся достаточно отзывчивым для дашборда реального времени. Для панелей, где важна именно мгновенная реакция (например, всплеск сетевого трафика при бэкапе), используем irate() — она берёт только две последние точки в окне, более резкая, но более шумная; на постоянно обновляемом дашборде мы её не ставим, только в разовых расследованиях через Prometheus Explore.

Ещё один момент: MemAvailable в ядре Linux уже учитывает то, что кэш страниц и буферы можно освободить под нужды приложений — это принципиально отличается от простого MemFree, которое почти всегда выглядит пугающе низким на любом сервере с приличным аптаймом просто потому, что ОС активно кэширует диск. Мы через это объясняем клиентам, почему «памяти мало» на графике top — это неправда, если считать честно.

Дашборд в Grafana: от provisioning датасорса до готовых панелей

Датасорс мы никогда не добавляем руками через UI на боевом стенде — заводим через provisioning-файл, чтобы при пересоздании VM или переносе на другой сервер конфигурация поднималась автоматически вместе с systemd-юнитом Grafana. Файл лежит в /etc/grafana/provisioning/datasources/prometheus.yml:

apiVersion: 1 datasources: - name: Prometheus type: prometheus access: proxy url: http://127.0.0.1:9090 isDefault: true jsonData: timeInterval: 15s httpMethod: POST

Режим access: proxy означает, что запросы к Prometheus идут через backend самой Grafana, а не напрямую из браузера клиента — это важно, потому что порт 9090 у нас и так закрыт наружу, а Grafana проксирует всё через себя на своём же 3000-м порту, за которым уже стоит nginx с TLS и базовой авторизацией (или SSO, если у клиента есть).

Сам дашборд мы собираем не с нуля каждый раз, а держим свой JSON-шаблон («ITfresh Base Server Dashboard»), который накатываем через provisioning дашбордов, а потом донастраиваем под конкретного клиента. Ключевая деталь — шаблонная переменная $instance, которую строим запросом label_values(node_uname_info, instance): она даёт выпадающий список всех серверов под наблюдением, и весь дашборд переключается на нужный сервер без копирования панелей.

ПанельТип визуализацииЧто показывает
CPU / LoadTime series (две оси)Занятость CPU в % и load average рядом с числом ядер
MemoryTime series + Stat (текущее значение)Используемая память в % и в абсолютных ГБ
Disk usageBar gauge по mountpointЗаполненность каждой смонтированной файловой системы
Disk I/OTime seriesЗанятость диска (io_time rate) и iowait CPU рядом
NetworkTime series (receive/transmit разными цветами)Трафик по интерфейсам в Мбит/с
Uptime / ServicesStat + таблицаАптайм и состояние критичных systemd-юнитов

На каждой панели мы включаем threshold-заливку (жёлтый на 70%, красный на 90% для дисков и памяти) — это визуальный аналог порогов из alerting rules, чтобы клиент, зашедший в дашборд сам, без объяснений понимал, где горит.

Alertmanager: пороги, которые мы реально готовы получать ночью

Правила тревог мы храним отдельным файлом /etc/prometheus/rules/node.yml, который Prometheus подхватывает через rule_files из основного конфига и переоценивает каждые evaluation_interval — у нас это те же 15 секунд.

groups: - name: node-alerts rules: - alert: HighDiskUsage expr: 100 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"} * 100) > 85 for: 10m labels: severity: warning annotations: summary: "Диск {{ $labels.mountpoint }} на {{ $labels.instance }} заполнен на {{ $value | printf \"%.1f\" }}%" - alert: HighMemoryUsage expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90 for: 10m labels: severity: page annotations: summary: "Память на {{ $labels.instance }} заполнена свыше 90% дольше 10 минут" - alert: ServiceFailed expr: node_systemd_unit_state{state="failed"} == 1 for: 2m labels: severity: page annotations: summary: "Юнит {{ $labels.name }} в состоянии failed на {{ $labels.instance }}"

Обратите внимание на for: 10m у дисков и памяти — мы сознательно не будим никого на единичном всплеске: у 1С и SQL Server бывают легитимные кратковременные пики потребления памяти при построении отчётов, и алерт без выдержки времени превращается в шум, на который через неделю никто не реагирует. А вот ServiceFailed с for: 2m — это уже про упавшую службу, здесь долго ждать нельзя.

Дальше Prometheus шлёт сработавший алерт в Alertmanager, а тот решает — как сгруппировать и куда доставить. Наш alertmanager.yml:

route: receiver: default-telegram group_by: ["alertname", "instance"] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - matchers: - severity="page" receiver: urgent-telegram repeat_interval: 1h receivers: - name: default-telegram webhook_configs: - url: "http://127.0.0.1:9095/hook/warning" - name: urgent-telegram webhook_configs: - url: "http://127.0.0.1:9095/hook/urgent"

group_wait: 30s — сколько Alertmanager ждёт перед первой отправкой, чтобы собрать в одно уведомление сразу несколько связанных алертов (например, если легло сразу три файловые системы на одном сервере — придёт одно сообщение, а не три). group_interval: 5m — минимальный интервал между уведомлениями по уже существующей группе, если появились новые алерты внутри неё. repeat_interval — как часто повторять напоминание, если проблема не разрешилась; для critical («page») мы срезаем его до часа, чтобы не потерять «висящую» аварию в потоке обычных уведомлений. За доставку в Telegram отвечает у нас небольшой self-hosted webhook-бридж — Alertmanager нативно шлёт email, PagerDuty, Slack, OpsGenie, Webex и generic webhook, поэтому телеграм-нотификации всегда идут именно через webhook_configs, а не «из коробки».

Грабли, на которых мы уже успели споткнуться сами

Несколько практических моментов, до которых лучше додуматься заранее, а не после инцидента:

Куда двигаем эту схему дальше

На стендах с внешними веб-сервисами клиента (сайт, личный кабинет, API) мы добавляем к этому стеку Blackbox Exporter — он умеет проверять HTTP/HTTPS/TCP/DNS снаружи и отдаёт те же Prometheus-метрики о доступности и времени ответа, что логично встраивается в тот же дашборд рядом с нагрузкой сервера. Для клиентов с несколькими площадками рассматриваем remote_write в центральный Prometheus или Mimir, чтобы видеть все объекты в одной Grafana без захода в отдельные инстансы. Но это уже следующий уровень зрелости — фундамент, который описан выше (Prometheus плюс node_exporter плюс Grafana плюс Alertmanager на честных systemd-юнитах, с продуманными PromQL-запросами и порогами с выдержкой времени), закрывает 90% практических задач мониторинга сервера небольшой компании и остаётся у нас базовым шаблоном для новых клиентских стендов.

Частые вопросы

Prometheus и Grafana заменяют Zabbix или дополняют его?
В нашей практике — дополняют. Zabbix остаётся системой алертинга широкого профиля (SNMP по сетевому оборудованию, службы Windows, лог-мониторинг), а Prometheus плюс Grafana мы разворачиваем как отдельный слой для истории нагрузки с высоким разрешением и capacity planning. Они работают на разных портах и не конфликтуют.
Сколько ресурсов съедает такой стек на сервере-коллекторе?
Для 10-15 наблюдаемых серверов с описанным набором метрик и retention 90 суток по нашей практике хватает 2 vCPU, 4 ГБ RAM и 20-30 ГБ диска под TSDB — это оценка по опыту внедрений, а не гарантированная цифра из документации, и её стоит уточнять после первой недели работы на конкретном стенде.
На сколько по умолчанию хранятся метрики в Prometheus?
По умолчанию флаг storage.tsdb.retention.time равен 15 суткам. Мы почти всегда увеличиваем его до 90 суток при установке, чтобы дашборд показывал сезонность и тренд роста нагрузки, а не только последние две недели.
Нужно ли закрывать порт node_exporter (9100) от внешнего мира?
Обязательно. У node_exporter нет встроенной аутентификации в базовой поставке, поэтому мы всегда ограничиваем доступ к порту 9100 файрволом — только с адреса сервера-коллектора Prometheus, либо изолируем его во внутреннем VLAN без выхода наружу.
Можно ли получать алерты в Telegram, а не только по почте?
Да, но напрямую Alertmanager этого не умеет — у него из коробки есть email, Slack, PagerDuty, OpsGenie, Webex и generic webhook. Мы ставим небольшой self-hosted webhook-бридж, который принимает webhook от Alertmanager и пересылает сообщение ботом в нужный чат Telegram.
📄
Скачайте подробный разбор в PDF Кейсы, статистика, типовые ошибки и чек-лист самопроверки — 12 страниц
Скачать PDF

Подпишитесь на разборы ITfresh

Раз в неделю — практичные материалы по ИТ для бизнеса: без спама, только польза.