Представим: после перезагрузки сервера Telegram-бот падает при первом подключении к PostgreSQL. Через минуту запускается вручную без ошибок.
В unit-файле уже есть:
[Unit]
After=postgresql.service
Кажется, что systemd должен был дождаться базы. Но эта строка задаёт порядок запуска юнитов, если оба участвуют в запуске. Самостоятельно запускать PostgreSQL она не заставляет.
Для подключения зависимости используют Wants= или Requires=. У Requires= связь строже: в сочетании с After= неудачный запуск зависимости может заблокировать запуск самого бота. Поэтому выбирать настройку нужно по требуемому поведению, а не добавлять обе на всякий случай. Документация systemd.unit
Даже если оба юнита запускаются, остаётся вопрос: что systemd считает завершением запуска PostgreSQL?
Это зависит от Type=. Для Type=simple systemd не ждёт окончания инициализации приложения. Для Type=notify сервис должен отправить READY=1. Такой сигнал полезен, если приложение действительно отправляет его после подготовки к работе. Если у юнита есть ExecStartPost=, его выполнение тоже учитывается при упорядочивании через After=. Документация systemd.service
Поэтому сначала смотрим журнал и фактическую конфигурацию:
sudo journalctl -b -u bot.service -u postgresql.service --no-pager
systemctl cat postgresql.service
systemctl show postgresql.service -p Type -p ExecStart
bot.service и postgresql.service здесь примерные имена. Нужно проверить, какой юнит запускает нужный экземпляр PostgreSQL: за общим именем может скрываться обёртка. По одному симптому «позже запускается» нельзя установить причину — в журнале может оказаться ошибка конфигурации, прав или подключения.
Если база находится на другом сервере, порядок локальных юнитов её готовность не определяет.
Следующий шаг — проверить адрес, к которому подключается бот. Для команд ниже нужны клиентские утилиты PostgreSQL:
pg_isready -h 127.0.0.1 -p 5432 -t 3
Успешный pg_isready означает, что сервер принимает подключения. Он не подтверждает, что бот сможет войти под своей ролью: для получения состояния сервера утилите необязательно передавать правильные имя пользователя, пароль и имя базы. Документация pg_isready
Проверяем подключение с параметрами приложения:
PGCONNECT_TIMEOUT=3 psql -X \
-h 127.0.0.1 -p 5432 -U bot -d bot \
-c 'SELECT 1'
Адрес, порт, роль и имя базы замените своими. Используйте те же параметры подключения, включая TLS, что и у приложения. Пароль не нужно вставлять в команду: для ручной проверки можно воспользоваться запросом пароля от psql.
PGCONNECT_TIMEOUT ограничивает ожидание подключения. Успешный SELECT 1 подтверждает возможность выполнить простой запрос, но не наличие нужных таблиц, прав на них и применённых миграций. Это проверяется отдельно. Переменные окружения libpq · Документация psql
При временной недоступности базы приложению нужны повторные подключения: таймаут отдельной попытки, пауза и общий срок ожидания. Ошибку пароля или отсутствие таблицы нужно диагностировать отдельно — ожидание само их не исправит.
Если после исчерпания попыток бот завершается с ненулевым кодом, можно настроить перезапуск процесса. Для уже существующего системного сервиса:
sudo systemctl edit bot.service
В drop-in добавляем:
[Unit]
StartLimitIntervalSec=120
StartLimitBurst=12
[Service]
Restart=on-failure
RestartSec=5s
Это пример политики: пауза перед перезапуском — пять секунд, лимит — 12 запусков за 120 секунд. Это не 12 попыток за всё время работы. Если запуски достаточно редкие, лимит может вообще не сработать.
При достижении лимита автоматические перезапуски прекращаются. Само истечение 120 секунд новую попытку не создаёт. После устранения причины можно сбросить счётчик и запустить сервис вручную. Политика перезапуска · Ограничение частоты запусков
После сохранения drop-in применяем конфигурацию и перезапускаем бота:
sudo systemctl daemon-reload
sudo systemctl restart bot.service
sudo journalctl -u bot.service -n 50 --no-pager
Перезапуск прервёт текущий процесс. Если сервис уже упёрся в лимит запусков, после устранения причины:
sudo systemctl reset-failed bot.service
sudo systemctl start bot.service
Эта политика помогает при завершении процесса с ошибкой. Если бот вообще не был запущен из-за неудачного запуска обязательной зависимости, одного Restart=on-failure недостаточно, чтобы гарантировать новую попытку после её восстановления.
Фиксированный sleep 30 перед запуском опирается на предположение, что базе хватит тридцати секунд. Проверка подключения опирается на её фактическое состояние, но и она не гарантирует, что соединение не оборвётся следующим запросом.
Поэтому на стенде стоит проверить два отдельных сценария: база недоступна при старте бота и база перезапускается уже во время его работы. Во втором случае нужны восстановление соединения и отдельное решение о том, какие операции безопасно повторять.
Комментарии 0