Мониторинг уровня ОС: CPU, RAM, диск

Это восьмая статья серии «Эксплуатация VPS / DevOps-база». К концу прошлого шага (Обновления без простоя) машина актуальна: security-патчи ставятся, вы видите «патч применён?» через reboot-required/needrestart, PHP обновляется почти незаметно, есть путь отката. Но «обновляю вовремя» не отвечает на главный вопрос: а всё ли в порядке прямо сейчас? Сервер может быть полностью в актуальном состоянии и при этом лежать — потому что кончился диск, потому что OOM-killer сожрал MariaDB, потому что какой-то cron раздулся в бесконечный цикл и съел всю RAM. Вы об этом узнаете не из apt, а из того, что сайт перестал открываться.

В серии про архитектуру мы строили мониторинг приложения: метрики запросов, latency, SLO, трейсы. Здесь — про машину: какие пять цифр реально спасают самописный VPS, как за ними следить без enterprise-тяжести (node_exporter + Prometheus — или лёгкий cron-скрипт, если вы один и сайтов немного), как настроить logrotate, чтобы логи (включая slowlog PHP-FPM из Части VI) не съели диск, и как построить алерты так, чтобы они будили вас по делу, а не учили игнорировать уведомления («alert fatigue» — привычка не замечать алерты, потому что их слишком много).

Термины, которые введём один раз: метрика — число, которое замеряют во времени (сколько памяти занято, сколько запросов в секунду); дашборд — экран с графиками метрик; алерт — правило «если метрика пересекла порог — сообщить»; порог (threshold) — число, на котором алерт срабатывает. load average — «средняя длина очереди» процессов, которые хотят на CPU (внимание: на Linux учитывает и ждущих диск, см. Часть I). iowait — доля времени CPU, проведённая в ожидании диска (высокая = диск тормоз, не процессор). inode — запись в файловой системе, описывающая один файл; места на диске может быть полно, а иноды закончились → «No space left on device» при видимых гигабайтах свободно. node_exporter — крошечная программа, которая отдаёт метрики машины (CPU/RAM/диск) по HTTP в формате Prometheus. Prometheus — база time-series метрик (метрики с временны́ми метками) + движок алертов, которая «собирает» node_exporter. SLO/SLI (Service Level Objective / Indicator) — из серии архитектура: измеримая цель надёжности («99,5% времени сайт отвечает < 1 с»); здесь мы связываем машинные пороги с этой целью. Все домены/IP условные; подставляйте свои.


Часть I. Пять метрик, которые реально спасают, и как их читать

Прежде чем что-то строить, поймите, что именно спасает жизнь на одном VPS. Десяток «полезных» цифр на практике сводится к пяти вопросам. На каждый — команда, которая отвечает за секунды.

1. Загрузка: load average — очередь, а не проценты

uptime
# ... load average: 0.18, 0.21, 0.15
# три числа: среднее за 1/5/15 минут
nproc    # сколько у вас ядер CPU

Как читать: load average — это сколько работы «хочет» выполниться. Ориентир — сравните с числом ядер (nproc). На машине с 2 ядрами load 2.0 = ядра забиты под завязку; load 4.0 на 2 ядрах = очередь, запросы ждут.

⚠️ Главная ловушка load average. На Linux в load попадают и процессы, ждущие диск (iowait), а не только CPU. Поэтому высокий load при пустом CPU почти всегда означает «тормозит диск» (или база), а не «не хватает процессора». Смотрите связку: load ↑ + %usr в норме + %wa ↑ = диск-проблема. Разложим в пункте 3.

2. Память: смотри Available, не free

free -h
#               total   used   free  buff/cache   available
# Mem:          1.9Gi   1.3Gi  80Mi      500Mi         540Mi

Как читать: available — сколько памяти реально можно отдать новому процессу (ядро умеет быстро отдать buff/cache под новые нужды). Колонка free (80 Mi) — это «просто не занята прямо сейчас», она на живой машине почти всегда маленькая, и пугаться «free мало» — ошибка. Смотрите available; если он падает к нулю — следующий шаг OOM-killer (см. Часть V про swap как подстраховку и финальную статью про диагностику).

Память «утекает» проверяется историей: free -h раз в час через cron-лог (Часть II) покажет монотонно растущий used — признак утечки (и отсюда же берёт смысл pm.max_requests из Части II — пересоздавать воркеров, не давая утечке накопиться).

3. Диск по времени отклика: iowait

# vmstat 1 3  — среднее за 3 секунды
vmstat 1 3
# ...  r  b   swpd   free  ...   us   sy   id   wa
#   ... 2 ...                         5    2   5   88  ← wa=88% это очень плохо

Как читать: колонка wa (iowait) — сколько времени CPU простаивал, ждя диск. Здоровая NVMe-машина на WordPress: wa в единицы процентов. Если wa стабильно высокий (20–50%+) — диск не успевает: вероятные причины — не хватает innodb_buffer_pool_size (база свопит страницы с диска, см. Часть III), кончился кэш, «кто-то» пишет большие файлы, или диск/сетап медленный (дёшевый HDD-backed VPS). Это опережающий сигнал «через минуту будет load average ↑ и сайт затормозит».

4. Диск по месту и по инодам: df -h и df -i

df -h /        # место: Filesystem  Size  Used Avail Use% Mounted
df -i /        # иноды: тот же взгляд, но по «числу файлов»
# где жрут:
sudo du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -n 15

Как читать: df -h — привычка «диск заполнен на 91%» → почти неизбежно кончится (логи, tmp, бэкапы). Но df -h может показать 50% свободно при закончившихся инодах — тогда создание любого файла падает с No space left on device. Это классика «свалки из миллиона крошечных файлов» (сессии PHP в /var/lib/php/sessions, кэш-фрагменты, раздутые логи в тысячи мелких ротаций). Отсюда обязательный второй взгляд — df -i (IUse% ≈ 100% = закончились иноды). Профилактика обоих — logrotate (Часть III) и чистка.

5. Соединения и «что упало/перезапустилось»

# сколько открытых соединений по состояниям
ss -s
# соединения к базе и к 443
ss -tan state established | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
# кто из юнитов systemd падал/перезапускался
systemctl --failed --no-pager
journalctl -u nginx -u php8.2-fpm -u mariadb -p err --since "1 hour ago" --no-pager | tail -n 30

Как читать: «слишком много соединений» (Too many connections, из Части VI) видно здесь по раздутому числу established к 3306. А рестарт-шторм юнита («PHP-FPM перезапустился 8 раз за минуту») виден по systemctl status (N restarts в юните) и по journalctl (цепочка Started/Stopped). Это прямая связь с тем, как вы уже читаете systemd-логи из Части V. Когда строк в журнале много и их надо фильтровать по содержимому — помогает journalctl -p err | grep -E и умение собрать нужный паттерн; regex для этого — в Регулярные выражения на практике.

6. «Одна команда на всё» — быстрый снэпшот

Когда вас разбудили «что-то не так», за 10 секунд прочитайте пять цифр разом:

echo "== load ==" && uptime
echo "== mem  ==" && free -h | awk '/^Mem/{print $2,$3,$6,$7}'
echo "== disk ==" && df -h / | tail -1 && df -i / | tail -1
echo "== iowait ==" && vmstat 1 2 | tail -1 | awk '{print "wa="$NF}'
echo "== units failing ==" && systemctl --failed --no-legend | head

Этот набор — то, что в Часть II вы будете гонять по cron и шить в алерт: не «сорок метрик», а пять сигналов, которые спасают.

Чек-лист части

  • Паникуют при малом `free` в `free -h`, не глядя на `available` — это нормальное состояние живой Linux (память занята кэшем).
  • Видят высокий `load average` и сразу «не хватает CPU» → а по факту высокий `wa` = тормозит диск/база.
  • Смотрят `df -h`, не смотрят `df -i` → однажды `No space left on device` при 30% занятого диска (кончились иноды).
  • Не проверяют `systemctl —failed` после обновлений (из [статьи 7](obnovleniya-bez-prostoya-i-neobsluzhivaemye-patchi.md)) → упавший юнит тихо «не работает», а мониторинг приложений молчит.

Часть II. Два уровня стека: cron-скрипт против node_exporter + Prometheus

Минимальный стек на одной машине выбирают по числу сайтов/машин и по тому, нужны ли графики. Начинать с Grafana/облачных APM — оверинжиниринг для VPS на 2 ГБ RAM (сам стек мониторинга съест память, которую вы мониторите).

Уровень «достаточно»: cron-скрипт + алерт

Самая дешёвая рабочая схема: раз в N минут гонять чек из Части I, сравнивать с порогами, при пересечении — слать уведомление; плюс писать строку-снимок в лог, чтобы видеть историю.

#!/usr/bin/env bash
# /usr/local/bin/os-check.sh — минимальный watchdog одной машины
set -euo pipefail

THRESH_DISK=85       # % занятого диска
THRESH_INODE=85
THRESH_MEM_AVAIL_PCT=10   # available < 10% от total → тревога
THRESH_LOAD_PER_CPU=2.0   # load1 > ядра*2 → тревога
LOG=/var/log/os-check.log

load1=$(awk '{print $1}' /proc/loadavg)
nproc=$(nproc)
disk=$(df -P / | awk 'NR==2{gsub("%","",$5);print $5}')
inode=$(df -Pi / | awk 'NR==2{gsub("%","",$5);print $5}')
mem_avail=$(awk '/MemAvailable/{print $2}' /proc/meminfo)
mem_total=$(awk '/MemTotal/{print $2}' /proc/meminfo)
mem_pct=$(( mem_avail * 100 / mem_total ))
load_limit=$(awk -v n="$nproc" -v m="$THRESH_LOAD_PER_CPU" 'BEGIN{print n*m}')

ts=$(date -u +%FT%TZ)
echo "$ts load=$load1/$nproc disk=$disk% inode=$inode% mem_avail=$mem_pct%" >> "$LOG"

fail=""
[ "$disk"  -ge "$THRESH_DISK"  ] && fail="$fail disk:${disk}%"
[ "$inode" -ge "$THRESH_INODE" ] && fail="$fail inode:${inode}%"
[ "$mem_pct" -lt "$THRESH_MEM_AVAIL_PCT" ] && fail="$fail memavail:${mem_pct}%"
awk -v l="$load1" -v m="$load_limit" 'BEGIN{exit !(l>m)}' && fail="$fail load:${load1}"

if [ -n "$fail" ]; then
  # канал алерта — см. Часть IV (webhook/email). Здесь заглушка:
  echo "ALERT host=$(hostname) -$fail" >&2
fi

Подвешиваем на cron (механика планировки — та же, что в Части IV про warm-up и в бэкапах из Части IV):

# /etc/cron.d/os-check  — каждые 5 минут
*/5 * * * * root /usr/local/bin/os-check.sh >/dev/null 2>&1

Плюсы: ноль памяти на сам монитор, всё видно через tail /var/log/os-check.log, легко читать/править, нет внешних зависимостей. Минусы: нет графиков, порог «в лоб» (не видно тренда), каждый чек — это отдельный запуск. Для одной машины этого часто достаточно, а os-check.log потом легко скормить в Prometheus через textfile-collector, если решите расти.

Уровень «растём»: node_exporter + Prometheus

Когда хочется графики, тренды и алерты по истории (или машин/сайтов стало несколько) — стандартный лёгкий стек:

node_exporter (на вашей машине, метрики ОС) ──┐
                                              ├── Prometheus (сбор + алерты) ── Alertmanager / уведомление
node_exporter (другие машины) ────────────────┘
  • node_exporter — один бинарь, слушает :9100, отдаёт /metrics (CPU, RAM, диск, сеть, иноды, …). Ставится на каждую машину.
  • Prometheus — раз в scrape_interval (например, 15s) «скрейпит» /metrics, хранит ряды, умеет язык запросов PromQL и алерты.
# node_exporter через apt (Ubuntu/Debian) или бинарем
sudo apt install -y prometheus-node-exporter
sudo systemctl status prometheus-node-exporter --no-pager | head -n 5
curl -s http://127.0.0.1:9100/metrics | head   # сырые метрики

Примеры PromQL под пять метрик Части I:

# свободная память, доля
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes

# занято диска на "/" (срабатывает и на иноды отдельно: node_filesystem_inodes_free / _total)
1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})

# load1 на ядро
node_load1 / count(node_cpu_seconds_total{mode="idle"}) without cpu

# iowait: доля idle-времени, ушедшая в ожидание диска, за 5 минут
rate(node_cpu_seconds_total{mode="iowait"}[5m])

Где ставить Prometheus: на другую машину или VPS-контейнер, а не на ту же, что вы мониторите, — иначе при полном падении вашей машины алерт тоже умрёт (это ровно та причина, по которой нужен heartbeat из Части IV). Если Prometheus на той же машине — закройте ему порт :9090 наружу firewall-ом (Часть II: bind 127.0.0.1 или allow только под ваш IP), иначе дашборд метрик торчит в интернет.

Честная граница: node_exporter + Prometheus «закрыли машину». Для прикладных метрик (latency сайта, ошибки PHP, «не открывается страница») нужен либо blackbox-пробник (Prometheus blackbox_exporter: ходит по HTTPS и меряет код/время), либо отдельный uptime-сервис (Часть IV). Разделение «OS-метрики vs app-метрики» — та же мысль, что в Метрики и мониторинг: машина и приложение падают независимо.

Чек-лист части

  • Ставят Prometheus+Grafana+Alertmanager «сразу как в докладе» на VPS 2 ГБ, где мониторинг жрёт 40% RAM, нужной сайту. Для одной машины — cron-скрипт.
  • node_exporter/Prometheus торчит наружу (`:9100`/`:9090` открыты миру) → утечка метрик, а `:9100` без firewall — ещё и вектор. Закрывать так же, как в [Части II](firewall-i-ssh-hardning-zakryvaem-perimetr-mashiny.md).
  • Prometheus на той же машине, что мониторит → «монитор умер вместе с сервером, и алерта не было».
  • Метрики ОС есть, а ответа на «открывается ли сайт» нет → не хватает blackbox/uptime-пробы (Часть IV).

Часть III. logrotate: чтобы логи и slowlog не съели диск

Прямая линия к самому частому способу «уронить» живой VPS: диск кончился от логов. У вас теперь логов больше, чем было в начале серии: nginx access/error (из Части III), PHP-FPM slowlog (из Части VI), MariaDB error-лог и slow-лог, journal systemd (из Части V), бэкапы mysqldump (из Части IV). Ни один из них не сжимается и не удаляется сам — всем управляет logrotate.

1. Что делает logrotate и почему без него кончается диск

logrotate запускается по cron/systemd-таймеру (обычно ежедневно) и для каждого файла, перечисленного в /etc/logrotate.d/, применяет правило: «раз в срок или по достижении размера переименовать текущий лог (app.log → app.log.1), сжать, удалить самые старые (держать N штук)». Без него access.log на оживлённом сайте наедает гигабайты за неделю.

Две независимые ловушки диска: df -h (закончились байты — раздутые логи/бэкапы) и df -i (закончились иноды — свалка из миллионов мелких файлов: раздутая /var/lib/php/sessions, тысячи сжатых ротаций). Иноды не лечатся logrotate, лечатся чисткой и (для сессий) systemd-tmpfiles/своим session.save_path на tmpfs (см. Часть III про пулы).

2. Конфиг и разбор ключевых директив

Свой файл в /etc/logrotate.d/nginx-wordpress (override-принцип, как везде в серии — не правим чужие дефолты):

/var/log/nginx/*.log /var/www/example.test/wp-content/debug.log {
    daily                 # или size 50M — ротируем и по размеру тоже
    rotate 14             # держать 14 старых копий, дальше удалять
    compress              # gzip старых (.1 → .1.gz)
    delaycompress         # не сжимать самый свежий поворот (демон ещё может дописывать)
    missingok             # файла нет — не ошибка, молча пропускаем
    notifempty            # пустой файл не ротируем
    create 0640 www-data adm   # после ротации создать новый лог с нужными правами/владельцем
    sharedscripts         # postrotate-скрипт один раз на весь блок, а не на каждый файл
    postrotate
        # сказать демону «закрой старый дескриптор и пиши в новый» — КРИТИЧНО
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid) 2>/dev/null || true
    endscript
}

Ключевые директивы и зачем:

  • daily vs size 50M — daily ротит по календарю; size 50M — как только превышен размер (полезно для «взрывных» access-логов). Можно совмещать с maxsize («daily, но если больше 50M — хоть сейчас»).
  • rotate N — глубина хранения: 14 = ~2 недели. Это и ваш «бюджет места на логи»; на 16 ГБ диске не стоит держать rotate 365.
  • compress + delaycompress — gzip экономит 80–95% места; delaycompress нужен, потому что демон ещё держит открытый старый файл — жать его «на лету» нельзя, ждём один цикл.
  • create … owner group — без этого после ротации новый лог может создаться с правами logrotate (root), и демон его не запишет → «логи пропали» (тихий отказ писать).
  • sharedscripts + postrotate — самое важное и самое пропускаемое.

3. Почему без postrotate лог «зависает в старом файле»

Лог — это открытый файловый дескриптор демона. logrotate переименовывает error.log → error.log.1, создаёт новый пустой error.log, но демон (nginx/php-fpm) держит дескриптор старого inode и продолжает писать в error.log.1. Пока вы не дадите сигнал, новый файл останется пустым, а место жрёт «ротированный». Отсюда — postrotate:

  • nginx — сигнал USR1 (kill -USR1): перечислить логи (не рвёт соединения, из механики reload Части III).
  • php-fpm — тоже USR2/USR1 (kill -USR2 $(cat /run/php/php8.2-fpm.pid)) или systemctl reload php8.2-fpm.
  • rsyslog (syslog-логи) — свой postrotate (systemctl kill -s HUP rsyslog).

Отдельно journal (systemd) живёт не через logrotate, а сам по себе: ограничить через /etc/systemd/journald.conf → SystemMaxUse=200M и systemctl restart systemd-journald (иначе journal silently разрастается до ~10% раздела). Это тот же журнал, что читаете journalctl из Части V.

4. Проверка без ожидания «завтра cron сам»

# сухим ходом: что logrotate СДЕЛАЕТ и где ошибки синтаксиса конфига
sudo logrotate -d /etc/logrotate.conf
# реально прогнать один блок (тестово «забить» и проверить, что демон принял сигнал)
sudo logrotate -f /etc/logrotate.d/nginx-wordpress
# после — убедиться, что .1 появился, новый пишется, права верные
ls -l /var/log/nginx/ | head

Чек-лист части

  • Забыли `postrotate`/`kill -USR1` → после ротации nginx пишет в старый `.1`, новый `error.log` пуст, а диск кончается «из-под одного файла».
  • `rotate 365` на маленьком диске → логи сожрали место; ставьте глубину по бюджету диска.
  • Нет лимита journal (`SystemMaxUse`) → `/var/log/journal` тихо наелает гигабайты, logrotate его не чистит.
  • Не проверяют `df -i`/не чистят сессии → иноды кончились при 30% занятого места.
  • Не делают `logrotate -d` перед `deploy` конфига → синтаксическая ошибка в `.d/` ломает ротацию всех логов сразу.

Часть IV. Алерты: threshold и heartbeat — чтобы монитор не был «слепым»

Метрики в дашборде бесполезны, пока вы на них не смотрите. Чтобы вас будило само, нужны алерты — двух типов, и второй важнее.

1. Type-1: пороговый алерт (threshold)

«Метрика пересекла границу → сообщение». На cron-скрипте — это if fail → послать; на Prometheus — правило в *.rules.yml:

# alert-rules.yml — фрагмент
- alert: DiskAlmostFull
  expr: 1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) > 0.85
  for: 10m            # «для:10m» — алерт горит только если держится 10 минут (гистерезис, против дёрганья)
  annotations:
    summary: "{{ $labels.instance }}: диск > 85% 10 минут"

Ключевое — for (гистерезис). Без него алерт орёт на каждое кратковременное касание порога (диск подскочил до 86% на минуту при mysqldump — и вас разбудили). for: 10m значит «только если держится».

2. Type-2: heartbeat / dead man’s switch — ловит «монитор умер»

Пороговый алерт немой, когда монитор упал (упал node_exporter, упал Prometheus, кончился cron, упала сама машина — алерта нет, и вы думаете «всё ок»). Лечится dead man’s switch («курок мёртвого человека», из сигнальных систем): не «сообщи, если плохо», а наоборот — «сообщи, если НЕ прилетел сигнал что всё хорошо».

Механика: машина регулярно «пинкует» внешний приёмник («я жив»); приёмник алертует, если пинга нет в окне.

# в тот же cron (каждые 5 мин) — сообщить внешнему health-эндпоинту «я жив»
curl -sf --max-time 8 https://hc-ping.com/Ваш-токен >/dev/null || true

Приёмник (healthchecks.io, «ping» от своего же мониторинга, Telegram-бот на чужом хосте) алертует, если >15 мин тишины. Важное следствие: приёмник обязан жить на другой машине, иначе при падении VPS умрёт и он. Это ответ на «Prometheus на той же машине» из Части II.

Heartbeat ловит «упало всё, включая мониторинг»; threshold ловит «монитор жив, но метрика плохая». Нужны оба — иначе слепые зоны.

3. Канал доставки: email или webhook

  • Email — простой и «в карман». Отправка с машины — через лёгкий MTA (msmtp/ssmtp к вашему SMTP), потому что на чистом VPS нет sendmail (настроить полноценный почтовый сервер Postfix/Dovecot — отдельная тема вне этой серии). Alertmanager имеет email_config из коробки.
  • Webhook (Telegram-бот, Slack, healthchecks) — быстрее и «в телефон»; Alertmanager отдаёт POST сам. Про «фоновые процессы и приёмники» — перекликается с Сервис-воркеры и офлайн-режим.

Канал алерта не должен зависеть от того же, что алертует (алерт «база легла», который шлёт сама база через SMTP на том же хосте = алерт, который не уйдёт при падении). heartbeat + приёмник на стороне — это страховка и от «SMTP-тупик».

4. Alert fatigue — почему «орать на всё» хуже молчания

Если алерт срабатывает по пустякам, вы начинаете его игнорировать, и однажды игнорируете настоящий. Правила «не орём в пустоту»:

  • Пороги ставьте на реальную границу боли, а не «1% отклонения» (CPU 95% на 20 секунд не будит; диск 90% на 30 минут — будит).
  • for-гистерезис везде (часть 1).
  • Разделяйте critical (буди немедленно: диск кончился, сайт не отвечает) и warning (посмотри днём: диск 75%, патч не применён, load слегка выше нормы). Будить ночью — только critical.
  • Каждый алерт — с действием. «Диск > 85%» → «удалить старые бэкапы / почистить логи (Часть III)». Если к алерту нет первого действия — он информационный шум, понижайте его до warning/дашборда.

Чек-лист части

  • Только threshold, без heartbeat → «монитор лёг вместе с сервером», алертов нет, узнали от пользователя через сутки.
  • Heartbeat-приёмник на том же VPS → не спасает при падении машины (смысл потерян).
  • Алерты без `for` → ложные срабатывания на касание порога, вы привыкли их закрывать.
  • Всё помечено critical → «мальчик, который кричал волк», настоящий инцидент утонул в шуме.
  • Алерт «SMTP с той же машины» и база упала/почта не уходит → уведомление не доставлено.

Часть V. Пороги и SLO: машинный алерт как опережающий индикатор

Финальный скрепляющий смысл: зачем пороги именно такие. В серии про архитектуру вы задали SLO («99,5% времени страница отвечает < 1 с», error-budget). Машинный мониторинг — не про «посмотреть красивые цифры», а про то, чтобы upstream-метрика упала раньше, чем это почувствует SLO. Установите связь «машинный порог → какая SLO-граница за ним».

1. Цепочка: машина → приложение → пользователь

диск/иноды → RAM/available → iowait → load → [PHP-FPM 502, slow запросы] → [latency/ошибки сайта] → SLO

Машинный алерт — первый в цепочке: он бьёт до того, как пользователь заметил. Примеры связей (пороги условные, калибруйте под свою машину):

Машинная метрикаЧто за ней следует для приложенияСмысл порога
df / > 85% (warn) / > 92% (crit)через часы/дни кончится → БД не пишет, сессии падают, wp-admin не работаетwarn даёт вам сутки на расчистку логов (Часть III)
df -i > 90%создание любого файла падает → запись сессий/кэша/дампакрит, будит: «иноды» — это внезапный отказ «без нехватки байт»
available < 10%следующий спайк → OOM-killer съест MariaDB/PHP-FPM (Часть V)warn заранее, пока swap ещё держит
iowait стабильно > 20%база свопит страницы, latency сайта ползёт вверх → ближе к SLO-нарушениюпредупреждает «диск-узкое место», чинят innodb_buffer_pool (Часть III)
load1 > ядра×2 на 10мочередь, воркеры PHP ждут CPU → 502/таймаутыкрит, будит: это уже «тормозим для пользователя»
юнит в --failed / рестарт-штормсервис лежит/перезапускается (Часть II)крит, будит: сайт может быть уже недоступен

Заметьте: пороги не «из воздуха» — каждый привязан к тому, как это ударит по SLO и что делать (чистить логи, добавить swap, поднять buffer pool, снять зависший коннект из Части VI). Это и есть отличие зрелого мониторинга от «орём на всё».

2. Что алертить, а что — нет (граница «машина vs приложение»)

Эта статья держит машину (CPU/RAM/диск/иноды/юниты/iowait). Приложение (латентность, ошибки PHP, «главная не отдаётся», битые страницы) — это Метрики и мониторинг из архитектуры плюс blackbox-проба («раз в минуту GET https://example.test, ждём 200 и время < X») и uptime-алерт (Часть IV heartbeat). Не дублируйте: машинный алерт не должен дублировать app-алерт, но должен быть раньше него по времени. Правильная картина при деградации: сначала молча горит warn «available падает», крит «iowait 30%», и только потом app-алерт «latency P95 > 2s» и «SLO burning». Если вы увидели первым app-алерт, а машинных warn не было — пороги Части I–V надо пересмотреть.

3. Минимальный «аварийный» набор для одной машины (сборка воедино)

Если вы всё-таки один и не хотите ни Prometheus, ни Grafana, вот рабочий floor из трёх кусков (собирается из Частей I–IV):

  1. os-check.sh (Часть II) — порога по 5 метрикам, log-история + крит-вывод.
  2. в него же — heartbeat curl hc-ping (Часть IV), приёмник на другом хосте.
  3. logrotate.d/nginx-wordpress + лимит journal (Часть III) — чтобы диск не кончился сам по себе, плюс blackbox-проба «главная = 200» (из app-слоя).

Этого хватает, чтобы не узнать о падении от пользователя. Всё остальное (графики, тренды, P95-дэшборды) — приятный рост, когда машины/сайты стали числами.

Чек-лист части

  • Пороги скопированы «с чужого VPS на 32 ГБ» без привязки к своему дисковому/памятному бюджету → молчат вхолостую или орут без причины.
  • Нет warn, только crit → узнаёте о проблеме ровно в момент падения (сутки запаса на действия потеряны).
  • Машинные алерты дублируют app-алерты (оба «сайт лежит») → шум; машинный должен быть раньше.
  • Алерт без «первого действия» → реакция каждый раз с нуля и в панике; к каждому криту держите ссылку на чек-лист.

Инструмент под задачу

Опросник метрик и алерт-скрипты живут по расписанию: соберите корректный crontab (частота, ловля вывода, ротация), не путая минуты и PATH. Попробуйте инструмент прямо здесь, на своих данных:

AI Cron Generator
Гость
Токены: — / 50 000
Гостевой лимит — 50 000. Зарегистрируйтесь, для пользователей лимит 200 000 токенов.
0 / 5000

Чек-лист граблей всей статьи

  • Смотрят `free`, не `available` → паника на здоровой машине, занявшей память кэшем.
  • `load average` высокий → автоматически «не хватает CPU»; а на деле высокий `wa` = диск/база.
  • Смотрят только `df -h`, не `df -i` → `No space left on device` при 30% занятого места (иноды).
  • Нет `logrotate`/лимита journal → логи медленнно, верно съедают диск, однажды — «сайт лежит из-за места».
  • `logrotate` без `postrotate` (`kill -USR1` nginx / reload php-fpm) → новый лог пуст, демон пишет в ротированный `.1`.
  • Только threshold, без heartbeat → «монитор умер вместе с сервером», алертов нет.
  • Heartbeat-приёмник/Prometheus на том же VPS, что мониторит → не спасает при падении.
  • Пороги без `for`-гистерезиса и всё помечено critical → alert fatigue, настоящий инцидент проигнорирован.
  • Пороги «из воздуха», не привязаны к «что ударит по SLO и что делать» → к алерту нет реакции.
  • Алерт шлёт SMTP с той же машины, что упала → уведомление не уходит.

Материал написан простым языком; любой англоязычный термин (метрика, дашборд, алерт, threshold, load average, iowait, inode, swap, OOM-killer, node_exporter, Prometheus, PromQL, time-series, scrape, Alertmanager, heartbeat/dead man’s switch, alert fatigue, hysteresis/for, logrotate, journal/journald, SLO/SLI, blackbox-probe) объясняется при первом употреблении. Конфиги — для Ubuntu/Debian; имена пакетов/юнитов (prometheus-node-exporter/node_exporter, php8.2-fpm/8.3, пути логов, nproc), поведение postinst apt и наличие msmtp/SMTP сознательно условны — проверяйте фактические через systemctl status, logrotate -d, df. Домены/IP/номера портов/пороги условные (example.test, 203.0.113.x — TEST-NET как в №1, 85%/20%/10m — ориентиры, калибруйте под свой бюджет диска/RAM). Правило серии действует: приёмник алертов и сам монитор — не на той же машине, что вы сторожите; порог с привязкой к действию, а будит ночью только critical. Тонкие механизмы (log-based метрики через Loki, трейсинг, distributed-мониторинг на флот, auto-healing, P95-дашборды Grafana) упомянуты лишь где нужно — это следующий уровень или отдельная разовая тема, когда базовый контур «пять метрик + logrotate + порог и heartbeat» уже стоит на машине и не даёт узнать о падении от пользователя.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.