MAATRIX / Блог / Бот для уведомлений о событиях на сервере

Бот для уведомлений о событиях на сервере

Бот для уведомлений о событиях на сервере

MAATRIX

Узнавать о падении сервиса от клиента в чате поддержки — плохой сценарий. Полноценный Zabbix или Grafana для одного VPS с парой сервисов — избыточно: час на установку, ещё день на то, чтобы разобраться с триггерами и каналами оповещений. Между «ничего не знаю» и «поднял стек мониторинга» есть третий вариант — простой Telegram-бот на 40 строк bash, который присылает сообщение в тот момент, когда что-то пошло не так.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →

Что должен делать такой бот и чего от него не ждать

Речь не о боте с командами и диалогом — это односторонний канал оповещений. У него одна задача: заметить событие и отправить сообщение. Никакой базы данных, никакого веб-интерфейса, никаких графиков истории — для этого потом можно поставить Grafana поверх тех же данных, если понадобится.

Что реально ловит такой бот на обычном VPS:

  • диск заполнился выше порога (частая причина простоя — сервис не может писать логи или временные файлы);
  • systemd-сервис упал и не смог перезапуститься сам;
  • нагрузка CPU держится аномально высокой дольше, чем should;
  • кто-то перебирает пароли по SSH или зашёл под root, когда не должен был.

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

Минимальная отправка сообщения через Bot API

Полноценный фреймворк (aiogram, python-telegram-bot) тут не нужен — Bot API принимает обычный HTTP-запрос, и curl справляется без зависимостей:

#!/usr/bin/env bash
# /usr/local/bin/tg-alert.sh
TOKEN="123456789:AAExampleTokenHere"
CHAT_ID="987654321"
MESSAGE="$1"

curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
  -d chat_id="${CHAT_ID}" \
  -d text="${MESSAGE}" \
  -d parse_mode="HTML" > /dev/null
chmod +x /usr/local/bin/tg-alert.sh
/usr/local/bin/tg-alert.sh "Тестовое сообщение с сервера"

Токен получаете у @BotFather (/newbot), chat_id — у @userinfobot или через getUpdates после того как напишете боту первым:

curl -s "https://api.telegram.org/bot${TOKEN}/getUpdates" | grep -o '"chat":{"id":[0-9-]*'

Токен и chat_id не держите в скрипте, который лежит с правами 644 — вынесите в отдельный файл с правами 600 и подключайте через source:

# /etc/tg-alert.conf, chmod 600
TOKEN="123456789:AAExampleTokenHere"
CHAT_ID="987654321"
#!/usr/bin/env bash
source /etc/tg-alert.conf
curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
  -d chat_id="${CHAT_ID}" -d text="$1" > /dev/null

Если сервер отправляет в Telegram напрямую, а API у вас периодически недоступен из России без прокси — это отдельная головная боль, которая решается арендой сервера в локации, откуда telegram.org открывается без обхода блокировок.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Проверка места на диске через cron

Самая частая причина внезапной остановки сервиса — не хватило места на диске под логи или временные файлы. Проверка раз в 10-15 минут через cron закрывает это полностью:

#!/usr/bin/env bash
# /usr/local/bin/check-disk.sh
source /etc/tg-alert.conf
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')

if [ "$USAGE" -ge "$THRESHOLD" ]; then
  curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
    -d chat_id="${CHAT_ID}" \
    -d text="⚠️ Диск заполнен на ${USAGE}% на $(hostname)" > /dev/null
fi
chmod +x /usr/local/bin/check-disk.sh
crontab -e
*/15 * * * * /usr/local/bin/check-disk.sh

Одна деталь, которую часто упускают: без защиты от повторов бот будет слать одно и то же сообщение каждые 15 минут, пока место не освободится. Добавьте простой файл-флаг, чтобы не спамить:

FLAG="/tmp/.disk-alert-sent"
if [ "$USAGE" -ge "$THRESHOLD" ]; then
  if [ ! -f "$FLAG" ]; then
    curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
      -d chat_id="${CHAT_ID}" -d text="⚠️ Диск заполнен на ${USAGE}%" > /dev/null
    touch "$FLAG"
  fi
else
  rm -f "$FLAG"
fi

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

Уведомление о падении сервиса через systemd

Для сервисов под systemd не нужен отдельный cron-опрос — можно повесить уведомление прямо на unit-файл через OnFailure:

# /etc/systemd/system/myapp.service
[Unit]
Description=My App
OnFailure=alert-failure@%n.service

[Service]
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Отдельный шаблонный unit, который вызывает бота при срабатывании OnFailure:

# /etc/systemd/system/alert-failure@.service
[Unit]
Description=Alert on failure of %i

[Service]
Type=oneshot
ExecStart=/usr/local/bin/tg-alert.sh "🔴 Сервис %i упал на $(hostname)"
systemctl daemon-reload
systemctl restart myapp

Так бот сработает только тогда, когда systemd исчерпает попытки перезапуска (Restart=on-failure даёт сервису шанс подняться сам, и это правильно — не стоит будить себя из-за одного случайного рестарта). Если хотите отдельно знать и о самих перезапусках, добавьте логирование в ExecStartPre или смотрите на счётчик через systemctl show myapp -p NRestarts.

Частый вопрос на практике — что делать, если сервис не падает явно, а просто зависает и не отвечает. Это уже не задача OnFailure (юнит формально жив), тут нужна активная проверка порта или HTTP-эндпоинта по cron, аналогичная скрипту для диска выше, либо готовый инструмент вроде Uptime Kuma, если проверок становится много.

Нагрузка CPU: не разово, а устойчиво

Разовый всплеск CPU — это нормально, cron-задача или деплой. Смысл есть в уведомлении о нагрузке, которая держится долго:

#!/usr/bin/env bash
# /usr/local/bin/check-cpu.sh
source /etc/tg-alert.conf
THRESHOLD=90
# средняя загрузка за 1 минуту, в процентах от числа ядер
LOAD=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
CORES=$(nproc)
PERCENT=$(echo "$LOAD $CORES" | awk '{printf "%.0f", ($1/$2)*100}')

if [ "$PERCENT" -ge "$THRESHOLD" ]; then
  TOP=$(ps -eo pid,comm,%cpu --sort=-%cpu | head -4)
  curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
    -d chat_id="${CHAT_ID}" \
    -d text="🔥 CPU ${PERCENT}% на $(hostname)%0A${TOP}" > /dev/null
fi
*/5 * * * * /usr/local/bin/check-cpu.sh

Включение в сообщение топ-3 процессов по CPU (ps --sort=-%cpu) экономит следующий шаг — не придётся заходить на сервер, чтобы понять, что именно грузит систему. Если нагрузка на CPU регулярно упирается в потолок, а не разово — это повод пересчитать конфигурацию сервера, а не тюнить пороги оповещений; ориентиры по подбору ресурсов есть в статье как рассчитать конфигурацию сервера под нагрузку.

Неудачные попытки входа по SSH

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

#!/usr/bin/env bash
# /usr/local/bin/check-ssh-fails.sh
source /etc/tg-alert.conf
CURSOR_FILE="/var/lib/tg-alert-ssh-cursor"

if [ -f "$CURSOR_FILE" ]; then
  CURSOR=$(cat "$CURSOR_FILE")
  FAILS=$(journalctl -u sshd --after-cursor="$CURSOR" --show-cursor \
    | grep -i "Failed password")
else
  FAILS=$(journalctl -u sshd -n 0 --show-cursor)
fi

journalctl -u sshd -n 0 --show-cursor 2>/dev/null | grep -o 'cursor: .*' | sed 's/cursor: //' > "$CURSOR_FILE"

COUNT=$(echo "$FAILS" | grep -c "Failed password")
if [ "$COUNT" -gt 0 ]; then
  IPS=$(echo "$FAILS" | grep -oP 'from \K[0-9.]+' | sort | uniq -c | sort -rn | head -5)
  curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
    -d chat_id="${CHAT_ID}" \
    -d text="🔑 ${COUNT} неудачных входов по SSH на $(hostname)%0A${IPS}" > /dev/null
fi
*/10 * * * * /usr/local/bin/check-ssh-fails.sh

На практике сырой перебор паролей ботами из интернета — это фон, который есть почти на любом сервере с SSH на 22 порту, и присылать по нему уведомление на каждую попытку бессмысленно — вы просто перестанете читать бота. Разумнее сразу закрыть проблему на уровне доступа — переключиться на SSH-ключи вместо пароля и поставить fail2ban, а бота использовать для нечастых, но значимых событий: успешный вход под root или вход с нового IP, которого раньше не было.

Чем это отличается от полноценного мониторинга

Бот на curl и cron — это не замена Zabbix, Prometheus+Grafana или Netdata, а решение для другого масштаба задачи. Разница по существу:

Telegram-ботZabbix / Grafana
Время на настройку20-40 минутот пары часов до дня
История метрик и графикинетда
Порог срабатываниястатичный, правится рукамигибкие триггеры, зависимости, эскалация
Нагрузка на серверминимальнаятребует ресурсов под агент/базу
Сколько серверов покрываетодин-два вручнуюдесятки из единой панели
Что показываетфакт событиятренд, причину, корреляцию метрик

Если у вас один-два VPS и три-четыре сервиса — бот закрывает 80% практических потребностей за час работы. Если серверов становится больше пяти, или вам нужно видеть тренд («память подъедается третью неделю подряд», а не только «память кончилась») — это уже задача для полноценного мониторинга, разбор выбора между Zabbix и Prometheus есть в статье Zabbix или Prometheus: что выбрать для сервера. Многие в итоге держат оба слоя одновременно: полноценный мониторинг для анализа, а Telegram-бот — как быстрый канал критичных алертов поверх него, потому что Zabbix тоже умеет слать уведомления в Telegram, просто настройка каналов там сложнее, чем один curl-запрос.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Что если сервер полностью недоступен — бот же тоже на нём и не отправит сообщение?

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

Можно ли слать уведомления не одному человеку, а команде?

Да, вместо личного chat_id укажите ID группового чата или канала — бота нужно добавить туда как участника (и дать права администратора, если это канал), ID группы получаете тем же способом через getUpdates.

Как избежать спама одинаковыми уведомлениями?

Используйте файл-флаг, как в примере с диском, или ведите простой счётчик отправленных за последний час сообщений на конкретное событие и режьте повторы после 2-3 срабатываний с пометкой «алерт подавлен, проблема не устранена».

Нужно ли шифровать токен бота?

Достаточно хранить его в отдельном файле с правами 600 и владельцем root, не коммитить в git и не логировать значение переменной — токен, попавший в утечку, позволяет присвоить управление ботом, поэтому при подозрении на компрометацию перевыпустите его через /revoke у @BotFather.

Стоит ли переносить логику с bash на Python?

Если проверок больше пяти и появляется общая логика (повторные попытки, форматирование, обработка ошибок API) — да, тогда Python с requests читается и поддерживается проще, чем растущий bash-скрипт. Для двух-трёх проверок bash обычно быстрее написать и не создаёт лишней зависимости.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →