Задать вопрос
@oneLEAM

Почему тг бот запущенный в контейнере podman самостоятельно завершается получая SIGTERM?

Боты работают исправно, ошибок не наблюдаются. Без контейнера могут работать месяцами, но вот в контейнере podman они просто перестают работать без ошибок выводя в логи:
WARNING:aiogram.dispatcher:Received SIGTERM signal
INFO:aiogram.dispatcher:Polling stopped for bot ...

Я их сам никаким образом не останавливаю, останавливаются они в те моменты когда я и впомине не лазяю в терминале сервера.
  • Вопрос задан
  • 26 просмотров
Подписаться 1 Простой 1 комментарий
Пригласить эксперта
Ответы на вопрос 1
@Ferritin
Здравствуйте! Самопроизвольное завершение Telegram-бота в контейнере **Podman** с получением сигнала **SIGTERM** — это распространенная проблема, которая может быть вызвана несколькими факторами, связанными с тем, как система управляет контейнерами и процессами. Поскольку бот работает стабильно вне контейнера, проблема, скорее всего, кроется в конфигурации окружения контейнера или его взаимодействии с хост-системой.

Логи `WARNING:aiogram.dispatcher:Received SIGTERM signal` и `INFO:aiogram.dispatcher:Polling stopped for bot ...` однозначно указывают, что приложение корректно получает и обрабатывает сигнал на завершение работы. Вопрос в том, что именно отправляет этот сигнал без вашего ведома.

### Возможные причины отправки SIGTERM

Наиболее вероятные причины неожиданного завершения контейнера связаны с внешними управляющими процессами, такими как **systemd**, или особенностями работы rootless-контейнеров.

#### 1. Управление контейнером через systemd

Если вы запускаете контейнер как сервис **systemd**, то именно **systemd** может быть источником сигнала `SIGTERM`.

* **Завершение пользовательской сессии:** Если вы используете rootless-контейнер и запускаете его от имени обычного пользователя, **systemd** может автоматически завершать все процессы этого пользователя при его выходе из системы (например, при закрытии SSH-сессии)[1]. Это стандартное поведение для предотвращения "осиротевших" процессов.
* **Конфигурация сервисного файла:** Сервисный файл, сгенерированный с помощью `podman generate systemd`, содержит директиву `ExecStop=/usr/bin/podman stop ...`[2]. При определенных условиях (например, при перезапуске системы или самого сервиса) **systemd** выполнит эту команду, которая, в свою очередь, отправит `SIGTERM` вашему контейнеру. По умолчанию `podman stop` ожидает 10 секунд, а затем отправляет `SIGKILL`, если процесс не завершился[3][4].

#### 2. Особенности работы rootless-контейнеров

Запуск контейнеров без прав `root` (rootless) является более безопасным и предпочтительным методом, но имеет свои нюансы[5].

* **Проблема 15 минут:** Некоторые пользователи сталкивались с тем, что rootless-контейнеры без видимых причин получают `SIGTERM` ровно через 15 минут после запуска[6]. Это может быть связано с системными таймерами или политиками, управляющими пользовательскими службами.
* **Завершение при выходе:** Как уже упоминалось, процессы, запущенные в сессии пользователя, могут быть автоматически остановлены при ее завершении[1].

#### 3. Неправильный основной процесс в контейнере (PID 1)

Среда выполнения контейнеров (Podman, Docker) отправляет сигналы процессу с идентификатором **PID 1** внутри контейнера[7][8].

* **Запуск через shell-скрипт:** Если в вашем `Dockerfile` используется инструкция вида `CMD ./start.sh`, то главным процессом (PID 1) становится оболочка `/bin/sh`, а ваш Python-скрипт запускается как дочерний процесс. Стандартные оболочки не всегда корректно перенаправляют сигналы дочерним процессам. В результате, при получении `SIGTERM`, завершается только оболочка, а Podman считает, что контейнер можно останавливать.
* Судя по вашим логам, библиотека `aiogram` *получает* сигнал, так что эта причина менее вероятна, но является важной для корректной настройки.

#### 4. Внешние системные процессы

* **OOM Killer (Out of Memory Killer):** Хотя обычно OOM Killer отправляет сигнал `SIGKILL` (безусловное завершение), возможно, на вашей системе настроены скрипты, которые при нехватке памяти сначала пытаются корректно остановить процессы с помощью `SIGTERM`[9].
* **Сторонние скрипты или службы:** Администратор системы мог настроить скрипты для очистки процессов или управления ресурсами, которые отправляют `SIGTERM` контейнерам.

### Решения и рекомендации

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

#### 1. Правильный запуск и управление контейнером

* **Используйте политику перезапуска:** Запускайте контейнер с флагом `--restart=always`. Это заставит Podman автоматически перезапускать контейнер в случае его остановки по любой причине.
```bash
podman run -d --restart=always --name my-tg-bot my-image
```
* **Убедитесь, что бот является PID 1:** Чтобы ваш бот был основным процессом, используйте в `Dockerfile` формат `exec`:
* *JSON-синтаксис (рекомендуется):*
```dockerfile
CMD ["python", "main.py"]
```
* *Использование `exec` в shell-скрипте:*
```sh
#!/bin/sh
# Другие команды
exec python main.py
```
* **Запуск в фоновом режиме:** Всегда используйте флаг `-d` (`--detach`) для запуска контейнера в фоновом режиме, чтобы он не был привязан к вашей текущей сессии терминала[10].

#### 2. Настройка для работы с systemd

Если вы хотите, чтобы контейнер запускался при старте системы и работал постоянно, используйте **systemd**.

* **Включите "linger" для пользователя:** Чтобы разрешить сервисам пользователя работать даже после его выхода из системы, выполните команду (заменив `` на ваше имя пользователя):
```bash
loginctl enable-linger
```
* **Создайте и настройте systemd-сервис:**
1. Создайте контейнер с именем: `podman create --name my-tg-bot my-image`.
2. Сгенерируйте сервисный файл: `podman generate systemd --name my-tg-bot --files`.
3. Переместите сгенерированный файл `container-my-tg-bot.service` в `~/.config/systemd/user/`.
4. Перезагрузите демона **systemd**: `systemctl --user daemon-reload`.
5. Включите и запустите сервис:
```bash
systemctl --user enable --now container-my-tg-bot.service
```

#### 3. Обработка SIGTERM в коде бота

Хотя это не решает причину отправки сигнала, вы можете модифицировать код бота для более "гибкой" реакции на `SIGTERM`[11]. Например, вы можете не завершать работу сразу, а сначала логировать получение сигнала и пытаться продолжить работу (хотя это может быть нежелательно, если система действительно пытается остановить процесс по веской причине).

Пример обработки сигнала в Python:
```python
import signal
import asyncio
import logging

# Настройка логирования
logging.basicConfig(level=logging.INFO)

# Флаг для корректного завершения
shutdown_flag = asyncio.Event()

def handle_sigterm(signum, frame):
logging.warning("Получен сигнал SIGTERM. Инициирую корректное завершение.")
# Устанавливаем событие, чтобы основной цикл мог завершиться
shutdown_flag.set()

async def main():
# Здесь ваш основной код инициализации бота (aiogram)
# bot = Bot(...)
# dp = Dispatcher(...)

# Устанавливаем обработчик сигнала
signal.signal(signal.SIGTERM, handle_sigterm)

logging.info("Бот запущен и готов к работе.")

try:
# Ваш основной цикл работы бота
# await dp.start_polling(bot)
# Вместо бесконечного цикла, ждем события о завершении
await shutdown_flag.wait()
finally:
# Корректное завершение
logging.info("Останавливаю бота...")
# await bot.session.close()

logging.info("Бот остановлен.")

if __name__ == '__main__':
try:
asyncio.run(main())
except (KeyboardInterrupt, SystemExit):
logging.info("Процесс прерван.")
```
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы