Простой мониторинг сервера штатными средствами системы

Опубликовано 18 мин. чтения

Скрипт проверки, systemd-таймер и проверенный канал уведомлений закрывают потребности одиночного root-сервера. Руководство показывает, что стоит мониторить, как проверить каждый шаг и с какого момента окупается большой инструментарий.

Сервер не сообщает о себе сам, когда что-то идёт не так. Он работает, пока не перестаёт работать, а первая обратная связь приходит от клиента или от вас, если вы случайно туда посмотрели. Это руководство собирает минимальный мониторинг, который кладёт этому конец: скрипт проверки, systemd-таймер, канал уведомлений. Без базы данных временных рядов, без панели мониторинга, без единого дополнительного открытого порта.

Все сведения относятся к Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS и Ubuntu 22.04 LTS. Там, где эти четыре системы расходятся, об этом сказано отдельно. Все команды написаны для работы от имени root; обычному пользователю нужно ставить перед каждой командой sudo. Это руководство подробно разбирает шаг 8 из чек-листа по настройке нового root-сервера.

Что вообще стоит мониторить

Самая частая ошибка на первом мониторинге не в том, что измеряют слишком мало, а в том, что измеряют слишком много. Тот, кто собирает 40 показателей, не смотрит ни на один из них. Смысл есть только в том, что способно остановить работу и на что вы можете отреагировать. Остаётся семь пунктов.

ПоказательПочему он в спискеОткуда берётся значениеРазумный порог
Место на накопителеСамая частая причина отказа, которую никто не видит заранееdf --output=pcent,targetот 85%
InodesМесто как будто есть, и всё равно «No space left on device»df --output=ipcent,targetот 85%
Свободная оперативная памятьOOM-killer редко выбирает тот процесс, которым вы готовы пожертвоватьMemAvailable в /proc/meminfoменьше 200 МБ
Средняя нагрузкаПоказывает затор независимо от того, виноват CPU или накопительтретье поле в /proc/loadavgсреднее за 15 минут выше удвоенного числа ядер
Упавшие службыСлужба, умершая ночью, иначе пролежит мёртвой до утраsystemctl is-system-runningвсё, кроме running
Истечение сертификатаРазом отсекает каждого посетителя, а не частьopenssl x509 -checkendменьше 21 дня до конца срока
Доступность снаружиЕдинственная проверка, отвечающая на вопрос, жив ли сервервторой хост, curlдва сбоя подряд

В списке нет загрузки CPU в процентах: сервер, который выдаёт 100%, потому что на нём работает видеокодировщик, делает ровно то, для чего его поставили. По той же причине нет пропускной способности сети и количества процессов. И то и другое помогает искать причину, но не годится в качестве оповещения, потому что не существует значения, начиная с которого вы обязаны вмешаться.

Путь назад, ещё до первого файла

Мониторинг только читает и в обычном случае ничего сломать не может. Три вещи всё же могут.

Скрипт, который чинит вместо того, чтобы сообщать. Мысль соблазнительная: если nginx умер, пусть скрипт его просто перезапустит. Получается служба, которая стартует каждые десять минут, работает полсекунды и прячет настоящую причину. А скрипт, который при заполненном накопителе удаляет файлы самостоятельно, рано или поздно удалит что-то нужное. Первая версия только читает и не вызывает ни systemctl restart, ни rm, ни kill.

Открытые сетевые интерфейсы. Экспортёры метрик, которые слушают на всех адресах, — одна из самых частых непреднамеренных публикаций данных на одиночных серверах. Описанный здесь способ не открывает ни одного порта и не требует правила файрвола.

Сам канал оповещения. Мониторинг, уведомление которого ни разу не проверяли, — это не мониторинг, а приятное чувство уверенности. Проверка описана ниже, и она обязательна.

Как попасть на сервер без SSH

У KVM root-серверов и выделенных серверов нет ни IPMI, ни iDRAC. Если SSH перестал отвечать, вход остаётся один: VNC-консоль в личном кабинете. Она не завязана на сетевой стек гостевой системы, поэтому ни правило файрвола, ни перегруженная служба SSH её не заблокируют. Зайдите туда один раз заранее и убедитесь, что знаете пароль root.

Аварийный выключатель

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

systemctl disable --now kh-monitor.timer
systemctl mask kh-monitor.service

Первая немедленно останавливает таймер и не даёт ему вернуться при следующей загрузке. Вторая — это стоп-кран: замаскированную службу не получится случайно запустить и вручную, а отменяется маскировка командой systemctl unmask kh-monitor.service. Способ малорискованный прежде всего потому, что появляются исключительно новые файлы; откат сводится к удалению этих файлов. И всё же держите открытой вторую SSH-сессию, пока работаете с системой.

Сначала проверьте показатели вручную

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

Место на накопителе и inodes

df -h
df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay
df -i

Исключения здесь необходимы. В Ubuntu 22.04 и 24.04 snap подключает свои пакеты как squashfs-образы только для чтения, и они постоянно показывают 100%. Без -x squashfs ваш мониторинг начиная с первого же запуска будет сообщать о заполненном накопителе, каждый день, бесконечно. То же самое относится к overlay-точкам монтирования Docker.

Стоит знать две особенности. df не принимает -P и --output вместе и завершается сообщением о взаимоисключающих параметрах. А на ext4 по умолчанию пять процентов зарезервированы для root, поэтому df показывает уже 100%, пока root ещё может писать. Что делать после оповещения, описано в статье Диск заполнен в Linux.

Проверка inodes не второстепенная мелочь. Каталог с миллионами крошечных сессионных или кэш-файлов способен израсходовать все inodes, пока df -h показывает вдоволь свободного места. Запись тогда падает с ошибкой No space left on device, и самое очевидное объяснение оказывается неверным.

Оперативная память

free -m
awk '/^MemAvailable:/ { printf "%d MB\n", $2 / 1024 }' /proc/meminfo

Столбец free на здоровой Linux-системе почти всегда маленький, потому что ядро использует незанятую память под файловый кэш. Доверять можно только столбцу available, то есть значению MemAvailable: это объём памяти, который может получить новое приложение без вытеснения чего-либо в swap. Оповещайте по этому значению, а не по free.

Было ли уже туго раньше, покажет журнал ядра:

journalctl -k -b --grep "Out of memory"

Каждое совпадение — это процесс, который ядро завершило из-за нехватки памяти. Как на это реагировать, не создавая swap вслепую, описано в статье Out of Memory и правильная настройка swap.

Средняя нагрузка

nproc
cat /proc/loadavg
uptime

Первые три поля в /proc/loadavg — это средние значения за одну, пять и пятнадцать минут. Два момента понимают неправильно постоянно. Во-первых, нагрузка в Linux не является чисто процессорной величиной: процессы, ожидающие обращения к накопителю, тоже учитываются. Нагрузка 20 на четырёх ядрах может означать и что CPU горит, и что подтормаживает накопитель. Во-вторых, значение за одну минуту не годится для оповещений, потому что любой прогон резервного копирования ненадолго подбрасывает его вверх. Берите среднее за 15 минут, а порог задавайте относительно числа ядер.

Если ваше ядро приносит с собой статистику заторов, она информативнее, потому что показывает CPU, ввод-вывод и память по отдельности. Есть она не везде:

test -d /proc/pressure && cat /proc/pressure/io || echo "в этом ядре нет статистики заторов"

Службы

systemctl is-system-running
systemctl --failed --no-pager
systemctl is-active nginx

systemctl is-system-running — самая короткая осмысленная общая проверка. Она выводит running, если ни один юнит не находится в состоянии ошибки, и degraded, как только такой юнит появляется. Код возврата соответственно 0 или отличный от нуля.

Две ловушки. Состояние degraded сохраняется до тех пор, пока вы после ремонта не сбросите его командой systemctl reset-failed; иначе один раз упавшая задача держит это сообщение неделями. А при проверке отдельных служб системы расходятся: в Ubuntu 24.04 SSH запускается через активацию по сокету, и ssh.service в состоянии покоя там inactive, хотя SSH прекрасно доступен. Кто мониторит там ssh.service, получает постоянный ложный сигнал. В Ubuntu 24.04 наблюдать нужно за ssh.socket, а в Debian 12, Debian 13 и Ubuntu 22.04, наоборот, за ssh.service.

Истечение сертификата

openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem
openssl x509 -checkend 1814400 -noout -in /etc/letsencrypt/live/example.com/fullchain.pem

Интереснее вторая команда. -checkend ожидает количество секунд, 1814400 — это 21 день. Если сертификат истекает в этот срок, команда выводит Certificate will expire и возвращает код 1, иначе Certificate will not expire и 0. Каталоги /etc/letsencrypt/live и /etc/letsencrypt/archive доступны на чтение только пользователю root.

У этой проверки есть пробел, о котором многие руководства умалчивают: она проверяет файл на диске, а не сертификат, который отдаёт ваш веб-сервер. Если продление прошло, но перезагрузка конфигурации веб-сервера сорвалась, файл новый, а ключ отдаётся старый. Проверка файла молчит, а посетители уже видят предупреждение о сертификате. Такой случай ловит только взгляд снаружи:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate

-servername перестаёт быть необязательным, как только на одном IP-адресе лежит несколько сертификатов. Без этого параметра вы получите сертификат сервера по умолчанию и проверите не тот домен.

Скрипт проверки

Все проверки вместе складываются в скрипт, который только читает, сравнивает и сообщает, когда что-то не так. Ему нужны curl и, для отправки через webhook, jq. В минимальных установках Debian нет ни того, ни другого:

apt update
apt install -y curl jq
cat > /usr/local/sbin/kh-monitor <<'EOF'
#!/bin/bash
set -u

DISK_WARN="${DISK_WARN:-85}"
INODE_WARN="${INODE_WARN:-85}"
MEM_MIN_MB="${MEM_MIN_MB:-200}"
LOAD_FACTOR="${LOAD_FACTOR:-2}"
CERT_DAYS="${CERT_DAYS:-21}"
UNITS="${UNITS:-ssh nginx}"
STATE_DIR="${STATE_DIRECTORY:-/var/lib/kh-monitor}"

problems=""
add() { problems+="- ${1}"$'\n'; }

notify() {
  printf '%s | %s\n' "$1" "$(printf '%s' "$2" | tr '\n' ' ')"
  if [ -n "${WEBHOOK_URL:-}" ]; then
    printf '%s\n%s' "$1" "$2" | jq -Rs '{text: .}' \
      | curl -fsS -m 10 -o /dev/null -H 'Content-Type: application/json' \
             --data-binary @- "$WEBHOOK_URL"
  fi
  if [ -n "${MAILTO:-}" ]; then
    printf '%s\n' "$2" | mail -s "$1" "$MAILTO"
  fi
}

while read -r pcent target; do
  pcent="${pcent%\%}"
  case "$pcent" in ''|*[!0-9]*) continue ;; esac
  [ "$pcent" -ge "$DISK_WARN" ] && add "Накопитель ${target} заполнен на ${pcent}%"
done < <(df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay | tail -n +2)

while read -r ipcent target; do
  ipcent="${ipcent%\%}"
  case "$ipcent" in ''|*[!0-9]*) continue ;; esac
  [ "$ipcent" -ge "$INODE_WARN" ] && add "Inodes на ${target} заняты на ${ipcent}%"
done < <(df --output=ipcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay | tail -n +2)

mem_avail=$(awk '/^MemAvailable:/ { printf "%d", $2 / 1024 }' /proc/meminfo)
[ "${mem_avail:-0}" -lt "$MEM_MIN_MB" ] && add "Доступно всего ${mem_avail} МБ оперативной памяти"

cores=$(nproc)
load15=$(awk '{ print $3 }' /proc/loadavg)
awk -v l="$load15" -v c="$cores" -v f="$LOAD_FACTOR" 'BEGIN { exit !(l > c * f) }' \
  && add "Средняя нагрузка за 15 минут ${load15} при ${cores} ядрах"

sysstate=$(systemctl is-system-running)
[ "$sysstate" = "running" ] || add "systemd сообщает о состоянии ${sysstate}"

for unit in $UNITS; do
  systemctl is-active --quiet "$unit" || add "Служба ${unit} в состоянии $(systemctl is-active "$unit")"
done

for cert in /etc/letsencrypt/live/*/fullchain.pem; do
  [ -r "$cert" ] || continue
  openssl x509 -checkend $(( CERT_DAYS * 86400 )) -noout -in "$cert" >/dev/null 2>&1 \
    || add "Сертификат ${cert} истекает менее чем через ${CERT_DAYS} дней"
done

mkdir -p "$STATE_DIR"
now=$(printf '%s' "$problems" | sha256sum | cut -d' ' -f1)
before=$(cat "${STATE_DIR}/last" 2>/dev/null || true)
printf '%s' "$now" > "${STATE_DIR}/last"

if [ -z "$problems" ]; then
  [ -n "${HEARTBEAT_URL:-}" ] && curl -fsS -m 10 -o /dev/null "$HEARTBEAT_URL"
  [ -n "$before" ] && [ "$now" != "$before" ] \
    && notify "Отбой тревоги $(hostname -s)" "Все проверки снова без замечаний."
  exit 0
fi

[ "$now" = "$before" ] && exit 0
notify "Предупреждение $(hostname -s)" "$problems"
EOF

Четыре места заслуживают пояснения.

  • Циклы читают из < <( ... ), а не из конвейера. Конвейер переносит цикл в subshell, и собранные там сообщения пропали бы после done. Ошибка коварная, потому что скрипт отрабатывает без сбоев и просто никогда ни о чём не сообщает.
  • Пороги читаются из окружения. Любой предел можно переопределить для одного отдельного вызова. На этом и построена проверка оповещения ниже.
  • Состояние сохраняется в виде контрольной суммы. Сообщение уходит только тогда, когда список проблем изменился. Иначе заполненный накопитель будет каждые десять минут присылать вам одно и то же, и через два дня вы выключите мониторинг.
  • Цикл по сертификатам уходит вхолостую, если каталога Let's Encrypt нет. Шаблон остаётся нераскрытым, [ -r "$cert" ] не срабатывает, и итерация пропускается.

Проверьте сейчас, пока в игру не вступил systemd:

chmod 700 /usr/local/sbin/kh-monitor
bash -n /usr/local/sbin/kh-monitor && echo "Синтаксис в порядке"
/usr/local/sbin/kh-monitor; echo "Код возврата $?"

На здоровом сервере третья команда не выводит ничего, кроме Код возврата 0. Если вы видите сообщение, значит либо что-то действительно не в порядке, либо порог подобран неудачно, например потому, что в UNITS указана служба, которой на этой машине нет.

systemd-таймер

Cron-задание тоже подошло бы. Но у таймера четыре конкретных преимущества: он не запускает второй экземпляр, пока работает первый, он навёрстывает пропущенный запуск после перезагрузки, его вывод попадает в журнал, и отключается он одной командой. Юниту службы не нужен раздел [Install], потому что активирует его не запуск системы, а таймер.

cat > /etc/systemd/system/kh-monitor.service <<'EOF'
[Unit]
Description=Короткая проверка состояния сервера
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/kh-monitor
EnvironmentFile=-/etc/default/kh-monitor
StateDirectory=kh-monitor
SyslogIdentifier=kh-monitor
Nice=10
IOSchedulingClass=idle
EOF
cat > /etc/systemd/system/kh-monitor.timer <<'EOF'
[Unit]
Description=Регулярно запускает kh-monitor

[Timer]
OnCalendar=*:0/10
RandomizedDelaySec=60
Persistent=true

[Install]
WantedBy=timers.target
EOF

StateDirectory=kh-monitor создаёт /var/lib/kh-monitor с подходящими правами и задаёт переменную STATE_DIRECTORY, к которой обращается скрипт. Знак минуса перед путём в EnvironmentFile=-/etc/default/kh-monitor означает, что отсутствующий файл не считается ошибкой. RandomizedDelaySec=60 разбрасывает момент старта, а Persistent=true при загрузке навёрстывает запуск, пропущенный во время выключения.

Таймер автоматически активирует одноимённую службу, строка Unit= не нужна:

systemd-analyze verify /etc/systemd/system/kh-monitor.service
systemd-analyze calendar '*:0/10'
systemctl daemon-reload
systemctl start kh-monitor.service
systemctl enable --now kh-monitor.timer

Проверка результата:

systemctl list-timers kh-monitor.timer --no-pager
journalctl -u kh-monitor.service -n 20 --no-pager

Список обязан показать строку со временем следующего запуска. Если он пуст, таймер не активен. systemd-analyze calendar находит опечатки во временном выражении: он приводит его к нормальной форме и называет ближайший срок. Подробности о юнит-файлах и их типичных неисправностях описаны в статье Создание systemd-службы.

Канал уведомлений

Здесь и разваливается большинство самодельных систем мониторинга. Сообщающая часть работает на том самом сервере, за которым следит, а значит падает вместе с ним. В итоге она надёжно сообщает обо всём, кроме единственного случая, который действительно важен.

Ответ на это — сигнал жизни, то есть heartbeat: сервер после каждого удачного прогона отмечается на внешней стороне, а если отметка не приходит, внешняя сторона поднимает тревогу. Скрипт выше делает именно это, когда задана переменная HEARTBEAT_URL. Обрыв сети, зависшая файловая система и рухнувшая система выглядят для внешней стороны одинаково, и знать вы хотите про все три случая.

Учётные данные должны лежать в отдельном файле, а не в скрипте. Адрес webhook — это секрет: у кого он есть, тот может отправлять сообщения от вашего имени:

cat > /etc/default/kh-monitor <<'EOF'
WEBHOOK_URL=https://example.example/hooks/xxxxxxxx
HEARTBEAT_URL=https://example.example/heartbeat/xxxxxxxx
UNITS="ssh nginx"
EOF
chmod 600 /etc/default/kh-monitor

Кавычки вокруг UNITS важны: systemd обошёлся бы и без них, но при тестовом прогоне ниже файл читает shell, и там nginx без кавычек был бы воспринят как команда. Вписывайте только те юниты, которые на этой системе есть, то есть в Ubuntu 24.04 ssh.socket вместо ssh.

Кому больше нравится электронная почта, тому нужен способ отправки. Полноценный почтовый сервер здесь избыточен, достаточно простого пересыльщика:

apt install -y msmtp msmtp-mta bsd-mailx

Настраивается это в /etc/msmtprc с учётными данными существующего почтового ящика. Файл содержит пароль, поэтому ему полагается chmod 600, иначе msmtp откажется работать и сошлётся на права доступа. Два честных ограничения: письмо с только что выданного IP-адреса сервера часто попадает в спам или отклоняется, а сообщение, которое вы увидите только при следующем заходе в почту, при отказе приходит слишком поздно. Для оповещений push-доставка практичнее.

Вызовите оповещение один раз вручную

Этот шаг обязателен. Вызовите сообщение принудительно, задав для одного отдельного вызова заведомо бессмысленный порог:

set -a; . /etc/default/kh-monitor; set +a
DISK_WARN=0 /usr/local/sbin/kh-monitor
rm -f /var/lib/kh-monitor/last

Сообщение теперь должно действительно дойти до вас, а не просто оказаться в журнале. Третья строка удаляет сохранённое состояние, чтобы тестовое оповещение не подавило следующий настоящий прогон. Один побочный эффект задуман нарочно: если доставка сорвётся, kh-monitor.service завершится с ошибкой и попадёт в systemctl --failed. Молчащий канал уведомлений был бы худшей из мыслимых неисправностей в системе мониторинга.

Взгляд снаружи

Сигнал жизни говорит вам, что сервер жив. Он не говорит, что ваш сайт отвечает. Для этого нужна проверка с другой площадки, по той же схеме из скрипта и таймера, только на втором хосте:

curl -fsS -m 10 -o /dev/null -w '%{http_code} %{time_total}\n' https://example.com/

Ценность этой проверки определяют четыре момента. Первое: проверяйте настоящую службу, а не только ICMP. Сервер, который отвечает на ping, пока веб-сервер висит в бесконечном цикле, при проверке пингом считается здоровым. Второе: -f заставляет curl завершаться с ошибкой при кодах ошибок HTTP, без этого ключа страница ошибки тоже засчитывается как успех. Третье: поднимайте тревогу только после двух неудач подряд, иначе о простое отчитается любая короткая сетевая помеха. Четвёртое: вызов раз в минуту с одного и того же адреса может упереться в ограничение частоты запросов или быть расценен блокирующим программным обеспечением как атака; внесите адрес проверяющего хоста в исключения.

Если второго сервера нет, остаётся внешний сервис проверки. Бесплатные предложения проверяют обычно раз в пять минут, о простое вы узнаете с соответствующей задержкой. Для одного сервера этого хватает, и это лучше, чем альтернатива: не узнать вовсе.

Частые ошибки и решения

Failed to start kh-monitor.service: Unit kh-monitor.service not found.: после создания или изменения юнита не хватает systemctl daemon-reload. Вторая по частоте причина — неверный каталог; собственные юниты кладутся в /etc/systemd/system/.

The unit files have no installation config: вы вызвали systemctl enable kh-monitor.service вместо kh-monitor.timer. У службы намеренно нет раздела [Install], включать нужно таймер.

code=exited, status=203/EXEC: systemd не смог выполнить файл. Либо неверен путь в ExecStart, либо не сделан chmod 700, либо файл правили в Windows и он несёт концы строк с возвратом каретки. Против этого помогает sed -i 's/\r$//' /usr/local/sbin/kh-monitor.

Syntax error: redirection unexpected: скрипт запустили через sh, а не через bash. В Debian и Ubuntu /bin/sh — это оболочка dash, а она не знает ни < <( ... ), ни += для строк. Первая строка обязана быть #!/bin/bash.

bash: mail: command not found: почтовая программа не установлена. Команда mail в зависимости от системы приходит из bsd-mailx или mailutils, и наборы ключей у них различаются. Держитесь -s для темы письма.

curl: (22) The requested URL returned error: 404: адрес webhook неверен или удалён на принимающей стороне. Без -f curl проглотил бы эту ошибку молча.

curl: (60) SSL certificate problem: certificate has expired: при проверке снаружи это не сбой инструмента, а именно тот результат, который искали. При обращении к собственному webhook то же сообщение указывает на неверное время на проверяющем сервере.

Certificate will expire: штатный вывод openssl x509 -checkend с кодом возврата 1. Проверьте, работает ли ещё продление и перезагружается ли после него веб-сервер.

Failed to parse calendar specification: выражение после OnCalendar= недопустимо. Проверьте его отдельно командой systemd-analyze calendar, прежде чем переносить в юнит.

Warning: Stopping kh-monitor.service, but it can still be activated by: kh-monitor.timer: вы остановили службу вместо таймера. Служба и так работает лишь секунды, отключать нужно таймер.

Оповещение при каждом прогоне, хотя ничего не изменилось: состояние не сохраняется. Проверьте, есть ли в юните строка StateDirectory=kh-monitor и существует ли /var/lib/kh-monitor/last с правом на запись.

Оповещения нет, хотя что-то явно сломано: вызовите принудительное сообщение из раздела выше. Если и оно не приходит, дело в канале доставки, а не в проверках.

Различия между четырьмя системами

СистемаКакой SSH-юнит мониторитьТочки монтирования squashfsКуда попадают сообщения
Debian 13 (trixie)ssh.service, отдельные образы используют ssh.socketкак правило, неттолько журнал, в минимальных установках rsyslog отсутствует
Debian 12 (bookworm)ssh.serviceкак правило, нетжурнал, rsyslog в зависимости от варианта установки
Ubuntu 24.04 LTSssh.socketобычно есть, нужно исключениежурнал и rsyslog
Ubuntu 22.04 LTSssh.serviceобычно есть, нужно исключениежурнал и rsyslog

На всех четырёх системах одинаково: systemd-analyze, StateDirectory= и RandomizedDelaySec= есть везде, скрипт и юнит-файлы работают без изменений.

Когда окупается большой инструментарий

У того, что у вас теперь есть, чёткие границы. История не сохраняется, поэтому нельзя посмотреть, растёт ли потребление памяти последние три недели. Корреляции между несколькими хостами тоже нет. И нет ни эскалации, ни правил дежурства, ни возможности заглушить известное оповещение на два часа.

Именно эти пункты и отвечают на вопрос о переходе. Связка сбора метрик и панели мониторинга окупается, как только выполняется хотя бы одно из условий:

  • Вы держите больше горстки серверов и хотите видеть их рядом друг с другом.
  • Вам нужны история и тренды, например для планирования мощностей или чтобы ответить цифрами на жалобу о медленной работе.
  • Дежурство делят несколько человек, а значит нужны уровни эскалации и заглушение оповещений.
  • Вам нужно подтверждать доступность перед третьей стороной.

Если ничего из этого не подходит, для одиночного сервера такая конструкция обычно невыгодна. Сборщик, база данных, панель мониторинга и экспортёр вместе легко занимают несколько сотен мегабайт оперативной памяти на той самой машине, свободную память которой они должны наблюдать. К этому добавляются ещё один открытый порт и второе программное обеспечение, которое требует обновлений. Решающий момент остаётся прежним: если сборщик работает на том же сервере, о его отказе он сообщит ровно так же плохо, как скрипт. При переходе сборщик уносят на другой хост, а экспортёр привязывают к 127.0.0.1 или ограничивают файрволом до его адреса.

Средний путь работает хорошо: таймер и скрипт остаются, потому что закрывают случай тревоги, а сбор метрик добавляется тогда, когда понадобится история. Одно другому не мешает.

Откат

Если захотите избавиться от всей этой конструкции, понадобится пять строк:

systemctl disable --now kh-monitor.timer
rm -f /etc/systemd/system/kh-monitor.timer /etc/systemd/system/kh-monitor.service
rm -f /usr/local/sbin/kh-monitor /etc/default/kh-monitor
rm -rf /var/lib/kh-monitor
systemctl daemon-reload

Итоговая проверка

Шесть проверок, которые показывают действительное состояние, а не то, на которое вы надеетесь:

  1. systemctl list-timers kh-monitor.timer --no-pager называет время следующего запуска.
  2. journalctl -u kh-monitor.service --since "1 hour ago" --no-pager показывает прогоны с ожидаемым интервалом.
  3. Принудительное оповещение через DISK_WARN=0 доходит до вас, а не остаётся только в журнале.
  4. После reboot таймер продолжает работать без каких-либо действий с вашей стороны.
  5. Сигнал жизни срабатывает: остановите таймер и подождите, поднимет ли внешняя сторона тревогу.
  6. systemctl --failed --no-pager не перечисляет ничего, и прежде всего не kh-monitor.service.

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

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

Действительно ли для одиночного root-сервера нужен сервер метрик с панелью мониторинга?
В большинстве случаев нет. Сборщик, база данных, панель мониторинга и экспортёр вместе легко занимают несколько сотен мегабайт оперативной памяти на той самой машине, свободную память которой они должны наблюдать, и приносят с собой ещё один открытый порт плюс второе программное обеспечение, которое надо обновлять. Скрипт проверки с systemd-таймером полностью закрывает случай тревоги. Большая конструкция становится осмысленной, как только вы захотите видеть несколько серверов рядом друг с другом, вам понадобится история для планирования мощностей, дежурство разделят несколько человек или доступность придётся подтверждать перед третьей стороной.
Почему systemd-таймер, а не cron-задание?
Работает и то и другое. У таймера четыре практических преимущества: он не запускает второй экземпляр, пока ещё работает первый, с Persistent=true он навёрстывает при загрузке запуск, пропущенный во время выключения, его вывод благодаря SyslogIdentifier автоматически попадает в журнал, и отключается он одной командой systemctl disable --now kh-monitor.timer. Включать нужно всегда таймер, а не службу: у юнита службы намеренно нет раздела [Install].
Мониторинг постоянно сообщает о заполненном накопителе, хотя места достаточно. В чём дело?
Почти всегда в точках монтирования snap. В Ubuntu 22.04 и 24.04 пакеты snap подключаются как squashfs-образы только для чтения, и они по своей природе постоянно показывают 100%. Исключите их, то есть df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay. То же самое относится к overlay-точкам монтирования Docker. Вторая возможная причина: на ext4 по умолчанию пять процентов зарезервированы для root, поэтому df может показывать уже 100%, пока root ещё способен писать.
Как я узнаю, что сервер отказал полностью?
Не через скрипт на этом же сервере, потому что он падает вместе с ним. Для этого есть два дополняющих друг друга пути. Сигнал жизни: сервер после каждого удачного прогона отмечается на внешней стороне, и если отметка не приходит, внешняя сторона поднимает тревогу. И проверка снаружи: второй хост или внешний сервис проверки, который обращается к настоящей службе, а не только к ICMP. Бесплатные сервисы проверки работают обычно раз в пять минут, поэтому об отказе вы узнаете с соответствующей задержкой.
За каким SSH-юнитом нужно следить?
Это зависит от дистрибутива. В Ubuntu 24.04 SSH запускается через активацию по сокету, и ssh.service в состоянии покоя там inactive, хотя SSH прекрасно доступен. Кто мониторит там ssh.service, получает постоянный ложный сигнал; следить нужно за ssh.socket. В Debian 12, Debian 13 и Ubuntu 22.04 всё наоборот, там действует ssh.service. Впрочем, у отдельных образов Debian 13 тоже активен ssh.socket, проверьте это командой systemctl is-enabled ssh.socket.
Как избавиться от одного и того же предупреждения каждые десять минут?
Через файл состояния. Скрипт считает контрольную сумму по списку найденных проблем, кладёт её в /var/lib/kh-monitor/last и сообщает только тогда, когда эта сумма изменилась по сравнению с прошлым прогоном. Когда проблема исчезает, один раз приходит отбой тревоги. Если сообщение всё равно приходит при каждом прогоне, значит в юните нет строки StateDirectory=kh-monitor или файл недоступен для записи.
Сертификат на диске действителен, а посетители всё равно видят предупреждение. Как так получается?
Потому что проверка файла и отдаваемый сертификат — две разные вещи. Если автоматическое продление прошло, но перезагрузка конфигурации веб-сервера сорвалась, файл новый, а ключ отдаётся старый. openssl x509 -checkend в этом случае молчит. Поэтому проверяйте дополнительно снаружи командой openssl s_client -connect example.com:443 -servername example.com. Параметр -servername перестаёт быть необязательным, как только на одном IP-адресе лежит несколько сертификатов.
Как быстро отключить мониторинг, если он мешает?
Командой systemctl disable --now kh-monitor.timer: она немедленно останавливает таймер и не даёт ему вернуться при следующей загрузке. В качестве стоп-крана systemctl mask kh-monitor.service дополнительно закрывает любой ручной запуск, а отменяется это через systemctl unmask. Полностью конструкция убирается удалением обоих юнит-файлов, скрипта из /usr/local/sbin/, файла /etc/default/kh-monitor и каталога /var/lib/kh-monitor, а следом идёт systemctl daemon-reload. Существующая конфигурация при этом не затрагивается, потому что создавались исключительно новые файлы.

Мониторинг systemd Linux Debian Ubuntu root-сервер Мониторинг сервера Bash