Гайды

Мониторинг личного сервиса: что действительно стоит проверять

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

Мониторинг личного сервиса обычно устроен так: пользователь пишет «у тебя сайт не работает», вы идёте смотреть. Это работает ровно до того момента, когда сбой оказывается тихим — сервис отвечает, страница открывается, а делает он не то.

Задача здесь не в полноте метрик. Дашборд с сорока графиками, на который никто не смотрит, стоит ровно ноль. Задача в том, чтобы вы вообще узнали о сбое, и узнали по каналу, который в момент сбоя работает. Ниже — минимальный набор из семи проверок, каждая с порогом и с объяснением, какой конкретно отказ она ловит.

#Что понадобится

  • Ручка живости в приложении, которая отдаёт версию сборки. Без версии она бесполезна, и ниже видно почему.
  • Планировщик, который умеет запускать скрипт по расписанию (cron, таймер systemd, задача расписания Windows).
  • Канал оповещения, до которого машина реально дотягивается. Это отдельная и неочевидная проблема — раздел про неё ниже.

#Шаг 1. Признать, что порт не доказывает ничего

Самая распространённая проверка — «отвечает ли порт». Она ловит ровно один сценарий: процесс умер и никто не занял сокет. Всё остальное она пропускает.

Реальный случай из эксплуатации этого проекта. После обновления кода деплой рапортовал успех, порт был занят, сайт открывался — и отдавал версию недельной давности. Причина: старый процесс работал с повышенными правами в служебной сессии, обычный терминал его не убил, а taskkill при этом напечатал SUCCESS и вернул нулевой код. Скрипт деплоя добросовестно поверил коду возврата.

Вывод, который стоит записать в регламент: проверка живости обязана отдавать версию, а деплой обязан её сверять. У Persona для этого две независимые проверки, и обе должны сойтись с версией в исходниках:

  • /healthz — версия процесса, который реально слушает порт;
  • параметр ?v= в HTML страницы — то, что уедет в браузер вместе со статикой.

Первая ловит осиротевший процесс. Вторая ловит ситуацию, когда процесс новый, а браузеру и сервис-воркеру достаётся закэшированная старая статика.

#Шаг 2. Семь проверок с порогами

Порядок здесь — по стоимости отказа, а не по сложности реализации.

# Проверка Порог тревоги Какой отказ ловит
1 HTTP 200 на ручке живости 3 неудачи подряд процесс умер или завис
2 Версия в ответе == версия сборки любое расхождение тихий стухший деплой
3 Свободное место на диске данных ниже 10% или 5 ГБ база перестанет писать
4 Срок сертификата меньше 14 дней автопродление сломалось
5 Время ответа на дешёвый запрос выше 2× обычного блокировки базы, своп, деградация
6 Возраст последней успешной фоновой задачи больше двух периодов воркер тихо умер
7 Свежесть резервной копии старше суток бэкап перестал делаться месяц назад

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

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

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

#Шаг 3. Собрать это без внешнего сервиса

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

#!/usr/bin/env sh
# /opt/persona/ops/check.sh — запускается по расписанию раз в 5 минут
set -eu

URL=http://127.0.0.1:8000/healthz
WANT=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' /opt/persona/app/__init__.py)
STATE=/var/lib/persona/check.state

fail() { echo "$(date -Iseconds) ALERT: $1" >> /var/lib/persona/alerts.log; notify "$1"; }

BODY=$(curl -fsS --max-time 5 "$URL" 2>/dev/null) || { fail "healthz недоступен"; exit 1; }
GOT=$(printf '%s' "$BODY" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
[ "$GOT" = "$WANT" ] || fail "версия расходится: отдаётся $GOT, ожидается $WANT"

FREE=$(df -P /var/lib/persona | awk 'NR==2 {print $4}')
[ "$FREE" -gt 5242880 ] || fail "меньше 5 ГБ на диске данных"

echo "$(date -Iseconds) ok $GOT" > "$STATE"

Ключевая деталь — не сам скрипт, а функция notify. Она должна пользоваться каналом, который в момент сбоя работает.

#Шаг 4. Выбрать канал оповещения, который действительно доставляет

Здесь нас поймали, и история стоит пересказа целиком.

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

Два следствия, оба неприятные:

  1. Любая операция, ждавшая отправки письма, висела шестнадцать секунд. Отправку пришлось делать фоновой задачей с собственным потолком в десять секунд, чтобы посетитель не ждал на ней.
  2. Механика проверки почты «настроено» отвечала утвердительно, потому что галочка стояла и хост был указан. Пришлось переделать проверку так, чтобы «можем ли мы отправить» и «отправка на самом деле» решались одним и тем же кодом — иначе эти два ответа неизбежно расходятся.

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

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

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

  1. Остановите приложение. Ожидание: тревога по первой проверке за время, равное периоду, умноженному на порог.
  2. Запустите заведомо старую версию. Ожидание: тревога по второй проверке, даже если сайт полностью работоспособен.
  3. Займите диск большим файлом до порога. Ожидание: тревога по третьей.
  4. Остановите фонового воркера, не трогая веб. Ожидание: тревога по шестой через два его периода.

Если хотя бы один из четырёх сценариев прошёл незамеченным, у вас не мониторинг, а его имитация. Пункт 2 проваливают чаще всего, потому что версия в ручке живости либо не отдаётся, либо не сверяется.

#Что может пойти не так

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

Проверка живости ходит в базу. Тогда медленная база выглядит как мёртвое приложение, и watchdog начнёт перезапускать здоровый процесс. Ручка живости должна быть дешёвой и не трогать ни базу, ни воркеров; состояние зависимостей — это отдельная, более редкая проверка. Почему база вообще становится медленной и что с блокировками — в разборе WAL и конкурентного доступа в SQLite.

Пороги выставлены «на всякий случай» узко. Пять ложных тревог — и вы перестаёте их читать. Лучше меньше проверок с честными порогами, чем много с шумными.

Тревоги приходят, но по ним нечего делать. Каждая проверка должна иметь заранее записанное действие: перезапустить, освободить место, продлить сертификат. Если действия нет, проверка не нужна.

Первый запрос после простоя всегда медленный. Это не отказ, а холодный старт, и лечится он не порогами — приёмы разобраны в гайде про холодный старт приложения.

Тревога есть, а причины в логах не найти. Значит логи не структурированы: расследование по текстовому журналу занимает часы вместо минут. Минимальный набор обязательных полей — в статье про структурированные логи.

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

#Сколько проверок нужно личному сервису на самом деле?

Семь из таблицы выше покрывают подавляющее большинство отказов, которые случаются в первый год. Расширять список стоит только по факту: случился отказ, которого проверки не увидели, — добавляется проверка на него. Расширять «впрок» бессмысленно.

#Как часто запускать проверки?

Раз в минуту для живости, раз в пять минут для остальных, раз в сутки для сертификата и резервной копии. Более частый опрос не даёт выигрыша, но создаёт нагрузку и увеличивает шанс ложной тревоги под пиком.

#Нужен ли отдельный сервер для мониторинга?

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

#Что делать, если сервис падает раз в неделю без видимой причины?

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

#Обязательно ли перезапускать сервис автоматически?

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

#Как узнать, что упал именно внешний провайдер модели, а не мой сервис?

По разделению проверок: живость приложения и доступность провайдера — это две разные строки в таблице. Что делать, когда провайдер лежит, и как заранее подготовить запасной вариант, разобрано в статье про доступность и сбои провайдеров.

Общий маршрут, в котором мониторинг — шестой и последний этап, описан в статье о том, как поднять свой ИИ-сервис.

Хотите ИИ, который помнит вас?

Persona держит контекст между чатами и работает на вашем железе — если развернуть её у себя и подключить локальную модель.

Создать аккаунт