Демонизация бота в systemd
В этом туториале вы запустите бота в продакшн-режиме. Если в боте возникнет страшная ошибка, systemd
запустит его заново. И даже если весь сервер выключат и включат заново, то systemd
сам снова запустит вашего бота.
Прежде чем приступить
Этот туториал требует некоторых знаний:
- Как запускать команды из консоли.
- Как подключиться к серверу по SSH.
1. Загрузите бота на сервер
В качестве примера выступит эхобот. Это бот, который отвечает вам тем же, что вы ему написали. Зайдите на сервер с помощью ssh, перейдите в папку /opt
и скачайте заготовку бота:
# cd /opt
# git clone https://github.com/devmanorg/echobot-example
# cd echobot-example
Почему /opt?
Куда класть код — это старый и холиварный вопрос. Столько времени прошло, сколько форумов исписано, а одного простого ответа так и нет…
Вам точно не стоит класть код в домашний каталог суперпользователя /root/
. Туда вы обычно попадаете, заходя на сервер по ssh. Если положите код в /root
, то не избежите приключений с настройкой прав пользователей. Операционная система оберегает домашний каталог рута от посягательств других пользователей и запрещает им чтение файлов оттуда. Это станет проблемой при запуске Nginx, Gunicorn и других программ, часто работающих под другим пользователем.
Я советую класть код в папку /opt/{project}/
. Это место специально выделено в ОС для программ, что собираются из исходников и живут обособлено от каталогов bin
, lib
и прочих.
Затем проверьте, что файлы скачались командой ls
:
2. Запустите бота
Запустите бота по инструкциям из README. В том числе придётся создать токен от бота.
Проверьте, заработал ли ваш бот:
Бот работает! Но всё ли так гладко, как кажется?..
Теперь выйдите из терминала. Просто закройте окошко терминала или введите exit
и нажмите Enter
. Бот перестал работать
Всё потому что вы закрыли ssh-соединение, и теперь сервер остановил работу бота.
На каждое соединение сервер тратит свои мощности. Мощности у него ограниченные. Поэтому сервер всегда стремится сэкономить их, закрывая неработающие соединения. А оставлять открытые соединения небезопасно.
Нужно запустить бота в фоновом режиме работы. Тогда он продолжит работать, даже если вы выйдете с сервера.
3. Запустите бота в фоне
Программы, которые работают в фоновом режиме, называются демонами или программами в режиме демона.
Утилита systemd как раз умеет демонизировать программы, то есть запускать их в фоне. Но сначала systemd нужно настроить: сказать ему что и как демонизировать. Это делается в специальных файлах с инструкциями для systemd. Такие файлы называют юнитами.
Юниты хранятся в папке /etc/systemd/system
. Перейдите в неё и создайте новый файл с названием echobot-example.service
. Это и будет ваш юнит.
Запишите в него этот текст:
[Service]
ExecStart=python /opt/echobot-example/bot.py
Все юниты выглядят примерно вот так:
[Название секции в квадратных скобках]
имя_переменной=значение
В переменной ExecStart
юнит ждёт команду, которую systemd должен запустить.
Теперь запустите этот юнит:
# systemctl start echobot-example
Получилась ошибка:
Failed to start echobot-example.service: Unit echobot-example.service has a bad unit file setting.
See system logs and 'systemctl status echobot-example.service' for details.
Авторский перевод: Не удалось запустить файл echobot-example.service: в нём неверные настройки. Запустите команду systemctl status echobot-example.service
, чтобы узнать что случилось.
Давайте узнаем! Запустите команду systemctl status echobot-example
:
Юнит явно пишет всё о себе: Loaded: bad-setting (Reason: Unit echobot-example.service has a bad unit file setting.)
. Тут снова написано о той же ошибке, что была при запуске юнита.
Строка Active: inactive (dead)
значит что юнит неактивен/“мёртв”. То есть его либо не запускали, либо запустили, но он поломался. Чуть ниже виден трейсбек поломки:
/etc/systemd/system/echobot-example.service:2: Executable "python" not found in path "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Значит, что systemd не смог найти python
в папке /usr/bin/
.
Действительно, в этой папке есть только python3
и python3.8
.
Эту проблему легко исправить. Нужно дать программе путь до питона в вашем виртуальном окружении:
[Service]
ExecStart=/opt/echobot-example/venv/bin/python3 /opt/echobot-example/bot.py
Повторите запуск юнита:
# systemctl start echobot-example
Терминал молчит. Это хороший знак? Снова проверьте состояние файла:
# systemctl status echobot-example
Самая важная здесь строка Active: active (running)
. Это значит, что файл запущен. Обратите внимание, что и текст выделен зелёным, а в прошлый раз был красным. Это хороший знак.
Зелёный цвет - это хороший знак.
Проверьте бота в Telegram. Убедитесь, что он работает. Затем закройте терминал или наберите команду exit
и затем Enter
.
# exit
logout
Connection to 84.38.180.108 closed.
Проверьте бота в Telegram. Теперь он должен работать даже если вы закрыли терминал.
4. Добавьте в автозапуск
Бота иногда приходится перезапускать из-за обновления ПО и изменения настроек. Иногда владелец сервера, который вы арендуете, сам его выключает на пару минут, чтобы обносить софт, закрыть свежие уязвимости, и самостоятельно перезапускает сервер. Такое бывает даже на крупных хостингах, они отключают сервер всего на пару секунд и прислают письма с извинениями на почту.
Проверьте, что станет с ботом если сервер перезагрузится. Перезагрузите сервер с помощью команды:
# reboot now
Проверьте бота:
Бот снова перестал работать
Это потому что вы не сказали systemd
запускать ваш файл, если сервер включается или перезагружается. Это можно исправить:
[Service]
ExecStart=/opt/echobot-example/venv/bin/python3 /opt/echobot-example/bot.py
[Install]
WantedBy=multi-user.target
Строка [Install]
Означает блок настроек при запуске юнита. В этом блоке мы добавили одну настройку: WantedBy=multi-user.target
.
Таргет — это группа процессов, которые запустятся вместе, пачкой. multi-user.target
— это таргет, который запускается сразу при старте сервера. Получается, что настройка WantedBy=multi-user.target
добавляет ваш юнит в таргет, который запустится при рестарте сервера.
Осталась последняя команда, чтобы всё заработало. Она включит ваш юнит в список автозагрузки. Введите команду:
# systemctl enable echobot-example
Created symlink /etc/systemd/system/multi-user.target.wants/echobot-example.service → /etc/systemd/system/echobot-example.service.
Эта команда создаёт симлинк вашего юнита. Симлинк — это прямая ссылка на файл, что-то вроде ярлыка .exe
в Windows. Теперь, если вы захотите убрать ваш файл из автозагрузки, то будет достаточно написать systemctl disable echobot-example
. Это удобно, потому что не надо лезть в файл юнита и чего-то исправлять, всё отключается одной командой.
Теперь снова перезагрузите сервер с помощью команды reboot now
и проверьте бота. Он должен быть запущен, даже после перезагрузки.
5. Имитация сбоя
Плохая новость: программы иногда ломаются. Такое бывает даже с очень опытными разработчиками и даже в очень больших компаниях.
Чтобы понять, работает автозапуск или нет, нужно как-то сымитировать поломку программы. На Windows это довольно просто сделать: нужно нажать Ctrl+Shift+Esc, откроется диспетчер задач. Там выбираете любой процесс и жмёте “снять задачу”.
В Linux можно провернуть то же самое, причём из терминала. Для этого есть специальная команда kill
, но сначала нужно узнать PID вашего юнита. PID — это ProcessID, то есть id процесса. Все процессы можно получить в терминале этой командой:
# ps -aux
Выведется полотно из всех процессов, что запущены на сервере:
В таком полотне совершенно невозможно что-либо найти. Поможет поиск по ключевым словам, тоже команда linux, которая называется grep
. Вместе эти команды запускаются так:
# ps -aux | grep "ключевые слова для поиска"
Попробуйте найти PID вашего процесса по названию юнита:
# ps -aux | grep echobot-example
Вот что получилось у нас:
Нашлось 2 процесса. Первый — наш юнит, а второй — собственно процесс поиска через grep. Судя по всему, нужный PID — это 2194277.
Теперь, когда у вас есть PID вашего юнита, убейте его процесс:
# kill 2194277
Зайдите в бота. Кажется, он упал
6. Автозапуск на случай сбоя
Теперь стало понятно, что в случае сбоя ваша программа просто упадёт. Бот не будет работать, пока кто-нибудь не зайдёт на сервер и не запустит его заново. Это совсем не дело, почему он не может перезапуститься сам?..
Надо сказать юниту: “Если файл перестал работать, то файл нужно перезапустить”. Это довольно просто поправить:
[Service]
ExecStart=/opt/echobot-example/venv/bin/python3 /opt/echobot-example/bot.py
Restart=always
[Install]
WantedBy=multi-user.target
Настройка Restart=always
значит “перезапускай юнит, если он не работает”. Эта настройка хороша только для сайтов, чатботов и прочих программ, которые никогда не останавливаются. Если же вам нужно создать юнит с обычной программой, которая однажды закончится, то вам больше подойдёт Restart=on-abort
. Тогда systemd
перезапустит юнит только в том случае, если он упал с ошибкой, а если он просто завершился, программа закончилась, то systemd
оставит юнит в покое.
Теперь, когда вы поправили файл юнита, перезапустите его в systemd
:
# systemctl start echobot-example
Warning: The unit file, source configuration file or drop-ins of echobot-example.service changed on disk. Run 'systemctl daemon-reload' to reload units.
Авторский перевод: Внимание: юнит-файл echobot-example.service
был изменен. Запустите команду systemctl daemon-reload
, чтобы перезагрузить ваш файл.
Я всегда очень радуюсь, когда программы подсказывают мне команды для собственной починки.
Прим. автора
Очень мило со стороны systemd
просто подсказать вам следующую команду: systemctl daemon-reload
. Вы можете узнать что она делает на SO, но если вкратце, то это “мягкая перезагрузка”. При обычной перезагрузке systemd
полностью останавливает юнит и запускает его заново. При “мягкой” же перезагрузке он не останавливает юнит, а пытается подключить к нему изменения прямо “на ходу”. Например, чтобы добавить настройку Restart=always
, на самом деле юнит перезапускать не надо. systemd
просто сообщит процессу юнита, что если что-то случится, то стоит перезапуститься.
Мы и до этого изменяли в файл, но ни разу не запускали эту команду. В те разы мы полностью перезагружали сервер, поэтому systemd тоже перезапускался и запускал уже обновленный файл.
Запустите команду, которую просил systemd
:
# systemctl daemon-reload
Повторите запуск юнита:
# systemctl start echobot-example
Ещё раз найдите PID вашего процесса (PID поменялся!) и прервите работу файла:
# ps -aux | grep echobot-example
# kill 2194285
Проверьте бота. Он должен работать. Круто! Теперь бот работает во всех трёх случаях: после закрытия терминала, после перезагрузки сервера и даже после сбоя в демоне.
7. Что делать, если всё плохо
В этом туториале вы работали с простой программой, где и поломаться-то было почти нечему. Но всё бывает куда сложнее. Тут и там вылазят баги, которых не ждёшь. А где посмотреть трейсбек? Раньше он был в консоли, но теперь-то вашу программу запускает systemd
!
Systemd всегда пишет логи. Их можно посмотреть в утилите journalctl
. Загляните, что там происходит:
Это все логи. Прямо все-все-все. На продакшн-сервере их будет так много, что ориентироваться в них не получится. Для этого логам добавили фильтры:
journalctl -b
— записи с последнего запуска системы.journalctl -u echobot-example
— записи по юнитуechobot-example
journalctl -f -u echobot-example
— история работы по юниту в режиме реального времени. Периодически на экране будут появляться новые записи.
Попробуйте отсортированный вывод, он гораздо полезнее:
Читать дальше
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.