Обзор
Инструменты из диагностики производительности и мониторинга и отладки отвечают на вопрос «что происходит прямо сейчас». Они интерактивны и требуют, чтобы вы сидели за терминалом в момент, когда проблема есть. Вопрос «что происходило в три часа ночи» они не закрывают вообще: top не хранит историю, а perf не был запущен.
Постоянный сбор решает другую задачу — он превращает состояние системы в ряд данных, который можно посмотреть задним числом, сравнить с прошлой неделей и повесить на него условие срабатывания. Это не замена разовой диагностике, а другой слой: мониторинг говорит, что и когда пошло не так, а разбираться в причине вы всё равно будете perf и strace.
Статья про минимально осмысленную конфигурацию для одного-двух серверов, а не про инфраструктуру наблюдаемости предприятия. Разница существенная: половина сложности Prometheus-стека связана с федерацией, шардированием и высокой доступностью, и на одиночной машине этого всего не нужно.
Версии-якоря: Prometheus 3.14.0, node_exporter 1.12.1, Alertmanager 0.34.0, Grafana 13.2.1, Loki 3.7.7, blackbox_exporter 0.28.0.
Важная поправка к привычным рецептам. Promtail — агент, который во всех руководствах прошлых лет ставился рядом с Loki, — объявлен end of life 2 марта 2026 года. Разработка перенесена в Grafana Alloy, и документация Loki прямо требует мигрировать. Инструкции с promtail в интернете по-прежнему многочисленны и по-прежнему работают, но ставить его сегодня на новую машину не нужно.
Что вообще стоит собирать
Соблазн при первой настройке — включить всё. Это ошибка, которая обходится дороже, чем кажется: каждая лишняя метрика занимает место, а каждый лишний дашборд отнимает внимание. Полезный минимум для одиночного сервера невелик и укладывается в USE-метод из мониторинга и отладки — для каждого ресурса утилизация, насыщение, ошибки:
| Ресурс | Что смотреть | Откуда |
|---|---|---|
| CPU | загрузка по режимам, длина очереди | node_cpu_seconds_total, node_load1 |
| Память | доступно, своп, OOM | node_memory_MemAvailable_bytes, node_vmstat_oom_kill |
| Диск: место | свободно и скорость расходования | node_filesystem_avail_bytes |
| Диск: I/O | время в операциях, очередь | node_disk_io_time_seconds_total |
| Сеть | байты, ошибки, дропы | node_network_*_total |
| Система | время работы, перезагрузки, состояние юнитов | node_boot_time_seconds, node_systemd_unit_state |
| Сертификаты | срок до истечения | probe_ssl_earliest_cert_expiry |
| Бэкапы | возраст последнего успешного | своя метрика, см. резервное копирование |
Последние две строки — не про производительность, и именно поэтому их обычно забывают. При этом истёкший сертификат и молча не работающий месяц бэкап наносят больше ущерба, чем любая из строк выше.
Отдельно стоит сказать про место на диске: полезен не столько порог «занято 90%», сколько прогноз. predict_linear(node_filesystem_avail_bytes[6h], 4*3600) отвечает на вопрос «кончится ли место в ближайшие четыре часа при нынешней скорости», и это предупреждение приходит тогда, когда ещё есть время что-то сделать.
Prometheus: модель данных
Pull вместо push
Prometheus сам ходит за метриками по HTTP — цели не отправляют ему ничего. Решение спорное на первый взгляд и очень удобное на практике: состояние «цель недоступна» получается бесплатно (метрика up), конфигурация целей лежит в одном месте, а любой экспортер можно проверить руками через curl, увидев ровно то, что увидит Prometheus.
Ограничение обратной стороной: короткоживущие задания опросить не успевают. Для них существует Pushgateway, но пользоваться им нужно с осторожностью — это не «push-режим Prometheus», а костыль для конкретного случая: он держит последнее значение вечно, и упавшее задание выглядит как успешное.
Строго говоря, приёмники входящих данных у Prometheus всё-таки есть: remote write (--web.enable-remote-write-receiver, путь /api/v1/write) и OTLP (--web.enable-otlp-receiver, путь /api/v1/otlp/v1/metrics, в 3.0 получивший обычный флаг вместо feature flag). Оба по умолчанию выключены, и документация называет их неэффективным способом приёма образцов, пригодным для отдельных низкообъёмных случаев, а не заменой сбору опросом.
Ряды, лейблы и кардинальность
Метрика — это имя плюс набор пар «лейбл-значение». Каждая уникальная комбинация лейблов создаёт отдельный временной ряд со своим хранилищем и своей памятью.
Отсюда единственная по-настоящему опасная ошибка при работе с Prometheus: лейбл с неограниченным множеством значений. Число рядов равно произведению числа значений всех лейблов, и рост тут не линейный.
http_requests_total{method="GET", status="200", path="/api/users"}
Три лейбла: пять методов, десять статусов, полсотни путей — полторы тысячи рядов, нормально. Заменим path на полный URL с параметрами — и каждый уникальный запрос порождает новый ряд навсегда. Идентификатор пользователя, идентификатор сессии, временная метка, IP клиента, полный текст сообщения об ошибке в лейбле — всё это способы за сутки превратить рабочую систему в неподнимающуюся.
Практическое правило: лейбл допустим, если вы можете назвать верхнюю границу числа его значений и она не превышает нескольких сотен. Всё остальное — данные для логов, а не для метрик; в Loki высокая кардинальность содержимого нормальна, потому что там индексируются только лейблы потока, а не сам текст.
Диагностика уже случившегося взрыва:
topk(10, count by (__name__)({__name__=~".+"}))
Retention и место
По умолчанию Prometheus хранит данные 15 дней. Настраивается флагами --storage.tsdb.retention.time и --storage.tsdb.retention.size, причём второй полезнее: он даёт гарантию, что база не съест диск, чего срок по времени не обещает.
Порядок величин для прикидки: один ряд с интервалом сбора 15 секунд занимает считаные байты на образец после сжатия. Тысяча рядов — это десятки мегабайт в месяц, то есть на одиночном сервере с node_exporter и парой сервисов год истории умещается в единицы гигабайт. Проблемы начинаются не от длины истории, а от кардинальности.
Remote write и внешние хранилища вроде VictoriaMetrics нужны, когда данные должны пережить машину, объединиться с других узлов или храниться годами. На одном сервере это лишний слой; ставить его «на вырост» — типичная преждевременная сложность.
PromQL: то, на чём спотыкаются
rate и irate
rate() считает среднюю скорость роста за окно и умеет обрабатывать сброс счётчика (перезапуск процесса). Применять его можно только к счётчикам — монотонно растущим величинам. rate() от gauge (температура, объём памяти) даёт бессмыслицу без каких-либо предупреждений: функция увидит любое уменьшение значения как сброс счётчика и «досчитает» его до положительного.
irate() берёт только две последних точки. Документация Prometheus формулирует область применения жёстко: irate уместен только при построении графиков волатильных, быстро меняющихся счётчиков; для алертов и медленных счётчиков нужен rate, потому что кратковременные изменения скорости сбрасывают отсчёт клаузы for. То есть алерт на irate может не сработать никогда — он будет то и дело «выздоравливать» на одной точке.
Окно у rate() должно вмещать минимум две-три точки сбора. При scrape_interval: 15s брать rate(...[15s]) бессмысленно — обычный минимум [1m], а практичное значение [5m].
Гистограммы и квантили
Классическая гистограмма — набор счётчиков-корзин с лейблом le. Квантиль считается так:
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
Две ошибки здесь встречаются постоянно.
Первая — забыть rate() и взять histogram_quantile от сырых _bucket. Получится квантиль за всё время жизни процесса, а не за окно; на графике это будет плоская линия, медленно ползущая и не реагирующая на инцидент.
Вторая, серьёзнее — усреднение квантилей. avg(quantile_95) по нескольким инстансам не является 95-м перцентилем ничего: квантиль не аддитивен, среднее от перцентилей не имеет статистического смысла. Складывать нужно корзины (sum by (le)), и уже от суммы считать квантиль — как в примере выше. Ровно поэтому гистограммы предпочтительнее summary, у которых квантили считаются на стороне клиента и агрегировать их между инстансами нельзя вообще.
Начиная с Prometheus 3.8 (ноябрь 2025) нативные гистограммы перестали быть экспериментом: весь набор корзин лежит в одном ряду вместо ряда на каждую границу le, то есть исчезает и главный источник кардинальности у классических гистограмм. Включаются они явно — scrape_native_histograms в глобальной секции или в отдельном scrape_config, по умолчанию false, — и требуют поддержки на стороне клиентской библиотеки. Запрос при этом упрощается до histogram_quantile(0.95, sum by (job) (rate(http_request_duration_seconds[5m]))): без суффикса _bucket и без le в группировке.
Ловушки Prometheus 3
В версии 3.0 интервалы диапазона и lookback delta стали полуоткрытыми: образец, попавший ровно на нижнюю границу окна, теперь исключается, а не включается. На практике это редко заметно, но объясняет расхождение результатов при сравнении со старыми графиками — если после обновления числа «поехали» на доли процента, причина, скорее всего, здесь, а не в данных.
Там же нормализовались значения лейбла le у классических гистограмм и quantile у summary: теперь они всегда приводятся к записи с плавающей точкой независимо от протокола сбора, то есть le="1" превращается в le="1.0". Правила и дашборды, которые ссылались на границу целым числом, после обновления молча перестают что-либо выбирать.
Алерты
Механика
Правила живут в Prometheus, доставка — в Alertmanager. Ключевой параметр правила — for: сколько времени условие должно держаться, прежде чем алерт станет активным. Без него любой одиночный выброс порождает уведомление.
groups:
- name: node
rules:
- alert: DiskWillFillIn4Hours
expr: predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|ramfs"}[6h], 4*3600) < 0
for: 30m
labels:
severity: warning
annotations:
summary: "На {{ $labels.instance }} закончится место на {{ $labels.mountpoint }}"
В Alertmanager настраивается группировка (group_by), задержка перед первой отправкой (group_wait), интервал повторов (repeat_interval) и маршрутизация по лейблам. Смысл группировки прикладной: когда падает хост, отдельные алерты приходят по каждому его сервису, и группировка по instance превращает двадцать сообщений в одно.
Шум
Алерт, на который не реагируют, хуже отсутствующего: он не только не помогает, но и приучает игнорировать всю категорию сообщений, включая настоящие. Это самая частая причина, по которой мониторинг перестаёт работать, — не поломка, а привыкание.
Дисциплина простая и трудная: у каждого алерта должно быть действие. Если на срабатывание нечего сделать — это не алерт, а метрика для дашборда. Три практических следствия:
- Порог ставится там, где нужна реакция, а не там, где «уже некрасиво». Загрузка CPU 90% сама по себе не проблема — это может быть нормальный режим работы.
- Симптомы важнее причин. «Сервис не отвечает» — алерт; «CPU высокий» — обычно нет, потому что второе интересно только когда вызывает первое.
- Если алерт срабатывает регулярно и каждый раз разрешается сам, порог или
forподобраны неверно. Чинить нужно правило, а не отключать уведомления.
Полезный пример: срок сертификата
Проверяется blackbox_exporter, который выставляет probe_ssl_earliest_cert_expiry — время истечения ближайшего сертификата в цепочке:
- alert: CertificateExpiringSoon
expr: (probe_ssl_earliest_cert_expiry - time()) / 86400 < 10
for: 1h
Порог здесь заслуживает отдельного разговора, потому что старые рецепты рекомендуют 30 дней, а это значение устарело вместе с предпосылкой. Как разобрано в обратном прокси и TLS, Let's Encrypt сокращает сроки: профиль tlsserver уже 45 дней, shortlived — около шести. Порог в 30 дней для 45-дневного сертификата означает алерт, который горит треть жизни каждого сертификата и потому будет проигнорирован; для шестидневного он не сработает никогда, потому что сертификат столько не живёт.
Правильная логика — привязываться не к абсолютному числу дней, а к запасу относительно нормального цикла продления: тревога поднимается, когда сертификат прожил заметно дольше, чем должен был бы до автоматического обновления. Для 90-дневного classic с продлением на 60-м дне разумный порог — около 10 дней, для 45-дневного — около 5. И главное: этот алерт ловит не «сертификат истекает», а «автопродление сломалось», — то есть ровно тот отказ, который иначе обнаруживается пользователями.
Grafana и логи
Дашборды
Grafana — только визуализация; данные остаются в Prometheus и Loki. Практическая рекомендация одна и она о сдержанности: дашборд, на который смотрят, — тот, что помещается на экран без прокрутки и отвечает на один вопрос. «Обзорная панель со всеми метриками сервера» не используется никогда, потому что при инциденте на ней невозможно ничего найти.
Переменные ($instance, $job) избавляют от копирования одинаковых дашбордов под каждый хост — один дашборд с выпадающим списком вместо десяти.
Loki
Loki устроен принципиально иначе, чем Elasticsearch: он не индексирует содержимое строк. Индексируются только лейблы потока (хост, юнит, окружение), а сам текст лежит сжатыми чанками. Отсюда и профиль: хранение дёшево, ресурсов нужно немного, поиск по произвольной подстроке — это последовательное чтение отобранных чанков, а не обращение к индексу.
Практический вывод: запрос обязан сначала сузиться лейблами, а потом фильтровать текст.
{unit="nginx.service"} |= "500" | json | status >= 500
Запрос вида {job=~".+"} |= "error" формально допустим и означает «прочитать все логи за период», что на любом заметном объёме бесполезно. При этом кардинальность лейблов Loki так же чувствительна, как у Prometheus: класть идентификатор запроса в лейбл потока нельзя, а вот искать по нему в тексте — ровно то, для чего Loki и нужен.
Главная ценность связки — переход от метрики к логу: увидев всплеск ошибок на графике, вы выделяете интервал и получаете строки логов за него, не подбирая временные рамки руками.
Агент сбора логов
Здесь и находится расхождение с большинством существующих руководств. Promtail мёртв — end of life с 2 марта 2026 года, разработка перенесена в Grafana Alloy, и документация Loki требует перейти на Alloy или другой поддерживаемый клиент. Для существующих установок Grafana предоставляет конвертер конфигурации; для новой установки правильный выбор — сразу Alloy.
На машине с systemd основной источник — journald, и агент читает его напрямую, без промежуточных файлов. Альтернатива без агента вообще: systemd-journal-upload и приёмник на стороне сервера — годится, когда логи нужны просто в одном месте, но связки с метриками в Grafana это не даёт.
Развёртывание
Откуда брать пакеты
Три источника с разными компромиссами:
| Источник | Плюсы | Минусы |
|---|---|---|
| Пакеты дистрибутива | юниты, пользователи и обновления безопасности сделаны за вас | версии сильно отстают |
| Бинарники upstream | свежие версии, единственный вариант для многих экспортеров | юниты и обновления — ваша забота |
| Контейнеры | версия зафиксирована, изоляция, простое обновление | ещё один слой; хранилище всё равно на хосте |
Отставание здесь больше обычного: Prometheus дошёл до 3.x, Grafana — до 13.x, и в стабильных дистрибутивах лежат заметно более ранние ветки. Мажорные версии Prometheus меняли поведение (см. выше про полуоткрытые интервалы), так что вопрос «какая версия стоит» здесь не риторический.
Разумный компромисс для одиночного сервера: Prometheus и Grafana из пакетов, если версия устраивает, а экспортеры — бинарниками upstream, потому что в дистрибутивах их обычно просто нет. При выборе upstream помните цену, о которой сказано и в обратном прокси: пакет выпадает из-под обновлений безопасности дистрибутива, и следить за ним теперь вам.
Юниты и изоляция
Экспортер, поставленный бинарником, требует своего юнита. Пользуйтесь директивами изоляции из статьи про systemd — эти сервисы смотрят наружу и не должны работать от root:
[Service]
User=node_exporter
ExecStart=/usr/local/bin/node_exporter --web.listen-address=127.0.0.1:9100
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
NoNewPrivileges=yes
Слушать экспортеры должны на 127.0.0.1 или в VPN-подсети. Открытый наружу node_exporter — это подробная карта вашей машины, отдаваемая без аутентификации: версия ядра, файловые системы, сетевые интерфейсы, список смонтированных путей.
TSDB Prometheus кладётся на диск с запасом и, желательно, не на тот же раздел, что корень: заполнение базой корневой файловой системы превращает мониторинг в причину аварии вместо средства её обнаружения.
Публикация Grafana
Наружу выставляется только Grafana, и только через обратный прокси с TLS — конфигурация разобрана в обратном прокси и TLS. Prometheus и Alertmanager наружу не выставляются вовсе: встроенная аутентификация у них сводится к базовой через --web.config.file и до сих пор описана в документации как экспериментальная, авторизации нет никакой, а Prometheus вдобавок умеет по HTTP выключать сам себя, если оставить включёнными административные эндпоинты (--web.enable-lifecycle, --web.enable-admin-api).
В самой Grafana первым делом отключается анонимный доступ и меняется пароль администратора по умолчанию. Правильный вариант для одиночного сервера — вообще не публиковать её наружу, а ходить через VPN или SSH-туннель (см. SSH углублённо): интерфейс мониторинга нужен вам, а не публике.
Источники
- Prometheus documentation — модель данных, конфигурация, retention
- PromQL functions —
rateтолько для счётчиков;irateтолько для графиков волатильных счётчиков, для алертов —rate - Prometheus CHANGELOG — изменения 3.0, включая полуоткрытые интервалы диапазона; нативные гистограммы как стабильная возможность в 3.8
- Prometheus HTTP API — приёмники remote write и OTLP, административные эндпоинты и флаги к ним
- node_exporter — состав метрик и коллекторы
- blackbox_exporter —
probe_ssl_earliest_cert_expiry - Alertmanager — группировка, маршрутизация, подавление
- Loki: Promtail — уведомление о конце жизни 2 марта 2026 и требование перейти на Alloy
- Grafana Alloy — агент, заменивший Promtail
Связанные заметки
- Linux — мониторинг и отладка — разовая диагностика и USE-метод, на который ложится набор собираемых метрик
- Linux — средства диагностики производительности — чем разбираться в причине после того, как мониторинг показал следствие
- Linux — systemd: init, сервисы, таймеры — юниты экспортеров и директивы изоляции для них
- Обратный прокси и TLS — публикация Grafana наружу и сроки сертификатов, под которые калибруется алерт
- Резервное копирование: стратегия и проверка восстановления — возраст последнего успешного бэкапа как метрика и алерт по ней
- Эксплуатация локальных ИИ-сервисов — метрики видеокарты и движка инференса и почему утилизация GPU здесь бесполезна
Из библиотеки института уносят только знания. Читателей здесь не считают: посещение не регистрируется, отметок в издании не остаётся, счётчиков нет ни у одного издания фонда.
Издание пополняется и пересверяется. Текст доступен на условиях CC BY-SA 4.0 — унесённое можно передавать дальше на тех же условиях.