- Легко настраиваемый python daemon
- Создание демона Python с использованием Systemd
- # Systemd Unit
- # Python приложение
- # Обработка сигналов завершения
- Как запустить python скрипт на Linux в виде demon
- Переносим локальный проект на сервер
- Создаем БД в PostgreSQL
- В коде должно быть подключение к PostreSQL
- Создаем демон для запуска нашего скрипта на python
- Читайте также
Легко настраиваемый python daemon
Дальше я попробую описать логику работы всех трех.
Сразу скажу, что все есть на Гитхабе. Потому как если вы легко читаете питон — читать мой весьма неумелый текст может оказаться гораздо сложнее.
Собственно в первом файлике описывать толком нечего: Это почти неизменные три класса, взятые из этой статьи. Из изменений там только то, что к самому демону был прикреплен класс обработчик сигналов, и добавление сигналов в список обрабатываемых было вкручено в собственно процедуру демонизации.
Вторая часть будет чуть интереснее. Там присутствует три класса:
1) SigFunctionsCon — содержит реакцию на сигналы. При инициализации получает экземпляр демона, чтобы уметь обращаться к его методам. Каждый метод должен соответствовать сигналу, который он обрабатывает названием. Например так:
def SIGTERM(self): sys.stderr.write("BB!\n") sys.exit(0)
Внутренние методы и поля могут быть какими угодно.
2)ReactFunctionCon — содержит реакцию на консольные команды. При инициализации так же получает демона. Каждый метод по названию должен соответствовать команде на которую он будет реагировать и может принимать аргументы (то, что собственно идет за командой в командной строке). Например:
def stmess(self,message): print message self.__ourdaemon.start()
3)StatCon — содержит всякие статические настройки демона. На данный момент выглядит так:
class StatCon: Help = "Autmation has be applied to distribution sistem feeder for a long time, aspecially as related to protection and the restoration of some parts of the feeder." def run(self): while(True): time.sleep(1) PidFile = "/tmp/daemon-naprimer.pid" Inputter = "/dev/null" Outputter = "/dev/null" Errorer = "/home/espresso/lid"
Соответственно —
Хелп строка, выводимая при неправильной передаче аргументов в какую-либо функцию (Возможно следует сделать команду хелп по умолчанию, которая выводит это сообщение?).
Метод run — собственно то, для чего все затевалось — то, что демон делает.
Адрес pid файла — для хранения процесса и все такое.
Ввод, вывод, ошибки — логгирование и прочее. По умолчанию отсылается в /dev/null
Центровой скрипт представляет интерес исключительно кодом. В общем говоря он наследует класс демона, собирает все настройки с предыдущего файла и раскладывает их по демону, и принимает команды.
Ну и собственно вопросы:
Что не так, что не очень так?
Как по вашему следует ли как-то приписывать к этому GPL, или не стоит больгеносить, и все это слишком несерьезно?
Достаточно ли адекватно я указал предыдущих авторов?
Создание демона Python с использованием Systemd
Недавно у меня возникла задача создать демон (фоновое приложение) реализованный на Python в системе Linux использующей Systemd . В поисках современного решения и родилась данная статья. Ранее для реализации демона выполнялась «демонизация» приложения Python, зачастую с помощью библиотеки python-daemon
(opens new window) . Даже была создана спецификация pep-3143
(opens new window) для реализации демонов. Но на текущий момент времени с использованием Systemd нет необходимости демонизировать наше Python приложение, достаточно корректно описать его запуск в юните. Для начала рассмотрим как работает Systemd .
Все операции указанные в данной статье выполнялись в окружении Linux Ubuntu 20, с установленным Python 3.8.
# Systemd Unit
- /usr/lib/systemd/system/ — юниты из установленных пакетов, такие как nginx, postgreee и др.
- /run/systemd/system — юниты созданные в runtime
- /etc/systemd/system — юиниты, созданные администратором, в основном пользовательские юниты должны храниться здесь.
Для создания юнита нам необходимо описать 3 секции: [Unit], [Service], [Install] Основные переменные блока [Unit]:
[Unit] Descripiton=Unit Descripion After=syslog.target After=network.target After=nginx.service After=mysql.service Requires=mysql.service Wants=redis.service
- Description — описание юнита
- After — указывает, что юнит должен быть запущен после группы указанных сервисов
- Requires — узказывает, что для запуска юнита требуется запущенный сервис mysql , запуск нашего сервиса выполняется паралллельно с требуемым ( mysql ), если требуемый не указан в After
- Wants — описательная переменная, показывающая, что для запуска сервиса желателен запущенный сервис redis
[Service] Type=simple PIDFile=/var/lib/service.pid WorkingDirectory=/var/www/myapp User=user Group=user Environment=STAGE_ENV=production OOMScoreAdjust=-100 ExecStart=/my_venv/bin/python my_app.py --start ExecStop=/my_venv/bin/python my_app.py --stop ExecReload=/my_venv/bin/python my_app.py --restart TimeoutSec=300 Restart=always
- Type — тип запуска: simple (по умолчанию) запускает службу незамедлительно при этом процесс не должен разветвляться, не подходит если другие службы зависят от очередности при запуске данной службы; forking — служба запускается однократно и процесс разветвляется с завершением родительского процесса; другие типы можно рассмотреть по ссылке ниже.
- PIDFile — позволяет задать место нахождения pid файла
- WorkingDirectory — указывает рабочий каталог приложения, если указан то ExecStart|Stop|Reload запускаются из этого каталога, т.е. my_app.py станет /var/www/myapp/my_app.py
- User, Group — соответственно пользователь и группа под которыми будет запущен сервис.
- Environment — переменные окружения
- OOOMSCoreAdjust — запрет на kill сервиса вследствие нехватки памяти и срабатывания механизма ООМ: -1000 полный запрет, -100 понижает вероятность.
- ExecStart|Stop|Reload — команды запуска, останова, перезагрузки сервиса, команда должна использовать абсолютный путь к исполняемому файлу.
- Timeout — время ожидания systemd отработки команд Start|Stop в сек.
- Restart — перезапуск сервиса если он упадет.
[Install] WantedBy=multi-user.target
- WantedBy — уровень запуска нашего сервиса, mulit-user.target или runlevel3.target соответствует runlevel=3 «Многопользовательский режим без графики»
Размещаем данный файл с указанными секциями в директории /etc/systemd/sysmtem/.service
systemctl -l status test_unit
systemctl start test_unit
При внесении изменений в наш сервис перегружаем его:
# Python приложение
Разобрав как работает Systemd можем приступить к созданию нашего сервиса. Для ознакомления создадим приложение Python которое будет выводить сообщения в log файл test_daemon.py :
import time import argparse import logging logger = logging.getLogger('test_daemon') logger.setLevel(logging.INFO) formatstr = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' formatter = logging.Formatter(formatstr) def do_something(): """ Здесь мы только лишь пишем сообщение в Log, но можем реализовать абсолютно любые задачи выполняемые в фоне. """ while True: logger.info("this is an INFO message") time.sleep(5) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Example daemon in Python") # Мы можем заменить default или запускать приложение с указанием нахождения # log файла, через параметр -l /путь_к_файлу/файл.log parser.add_argument('-l', '--log-file', default='/home/user/test_daemon.log') args = parser.parse_args() fh = logging.FileHandler(args.log_file) fh.setLevel(logging.INFO) fh.setFormatter(formatter) logger.addHandler(fh) do_something()
Проверим работоспособность нашего скрипта:
Проверим что log-файл создан и в него пишутся сообщения:
tail -f /home/user/test_daemon.log
Теперь создадим юнит Systemd для запуска демона. В директории /etc/systemd/system файл test_daemon.service с содержанием:
[Unit] Description=Test daemon After=syslog.target [Service] Type=simple User=user Group=user WorkingDirectory=/home/user/ ExecStart=/usr/bin/python3 test_daemon.py [Install] WantedBy=multi-user.target
В данном юните следует изменить пользователя и группу User , Group на вашего пользователя. Также указать в качестве рабочего каталога WorkingDirectory абсолютный путь к директории где находится файл test_daemon.py.
В случае использования venv в параметре ExecStart следует указать путь к python внутри venv , т.е. заменть /usr/bin/python3 на /venv/bin/python
Проверяем статус нашего сервиса:
systemctl -l status test_daemon
systemd enable test_daemon
systemctl start test_daemon
Проверим отображение данных в логе:
tail -f /home/user/test_daemon.log
Наш демон заработал, но осталась не решенной еще одна задача. Зачастую при остановке нашего демона требуется выполнить каие либо задачи (сохранить состояние приложения, отправить уведомление, выполнить очистку данных и т.п.), но на текущий момент при останове наш демон просто закрывается.
# Обработка сигналов завершения
В стандартной библиотеке Pyhton реализован модуль sygnal позволяющий обрабатывать сигналы UNIX-based операционной системы. Полный перечень сигналов можно посмотреть по команде:
Демон Systemd при выполнении операции останова демона отправляет изначально сигнал 15 (SIGTERM) — нормальный останов процесса. По умолчанию если в течение 30 сек. не будет получен сигнал выхода приложения будет отправлен сигнал жесткого завершения процесса 9 (SIGKILL) .
Таким образом в нашем приложении необходимо предусмотреть обработчик сигнала 15 (SIGTERM) , как результат наш скрипт test_daemon.py примет следующий вид:
import time import argparse import logging import sys import signal logger = logging.getLogger('test_daemon') logger.setLevel(logging.INFO) formatstr = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' formatter = logging.Formatter(formatstr) def terminate(signalNumber, frame): """ Здесь мы можем обработать завершение нашего приложения Главное не забыть в конце выполнить выход sys.exit() """ logger.info(f'Recieved signalNumber>') sys.exit() def do_something(): """ Здесь мы только лишь пишем сообщение в Log, но можем реализовать абсолютно любые задачи выполняемые в фоне. """ while True: logger.info("this is an INFO message") time.sleep(5) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Example daemon in Python") # Мы можем заменить default или запускать приложение с указанием нахождения # log файла, через параметр -l /путь_к_файлу/файл.log parser.add_argument('-l', '--log-file', default='/home/user/test_daemon.log') args = parser.parse_args() signal.signal(signal.SIGTERM, terminate) fh = logging.FileHandler(args.log_file) fh.setLevel(logging.INFO) fh.setFormatter(formatter) logger.addHandler(fh) do_something()
Теперь при останове нашего демона:
systemctl start test_daemon
Мы увидим в лог файле сообщение Recieved 15
2021-06-27 15:06:48,024 - test_daemon - INFO - this is an INFO message 2021-06-27 15:06:53,029 - test_daemon - INFO - this is an INFO message 2021-06-27 15:06:56,971 - test_daemon - INFO - Recieved 15
Таким образом мы создали шаблон демона который в дальнейшем может быть использован для решения реальных фоновых задач.
Как запустить python скрипт на Linux в виде demon
Admin 16.10.2022 Linux, Python
Описание процесса от создания БД и до запуска скрипта.
Соединяемся со своим сервером и дальше работаем через консоль.
Переносим локальный проект на сервер
Создаем директорию для нашего нового приложения:
Клонируем свой git-проект на наш сервер:
Создаем виртуальное окружение python:
Создаем БД в PostgreSQL
Подключаемся к PostgreSQL:
CREATE DATABASE new-application TEMPLATE=template0 ENCODING ‘UTF-8’ LC_COLLATE ‘ru_RU.UTF-8’ LC_CTYPE ‘ru_RU.UTF-8’;
Назначаем привелегии новой таблице:
Выходим из postgres окружения:
В коде должно быть подключение к PostreSQL
Выдержка из примера подключения к PostreSQL вместе с логированием.
def create_connection (
db_name = ‘new-application’ ,
db_user = ‘ploshadka’ ,
db_password = ‘12345’ ,
db_host = ‘localhost’ ,
db_port = 5432
) :
connection = None
try :
connection = psycopg2. connect (
database = db_name ,
user = db_user ,
password = db_password ,
host = db_host ,
port = db_port ,
)
connection. autocommit = True
except psycopg2. OperationalError as error:
logger. info ( f ‘Ошибка: ‘ )
return connection
Создаем демон для запуска нашего скрипта на python
Внутри этого файла вставляем:
Description=New Application
After=network.target
User=ploshadka
Group=www-data
WorkingDirectory=/home/ploshadka/new-application
Environment=»PATH=/home/ploshadka/new-application/venv/bin»
ExecStart=/home/ploshadka/new-application/venv/bin/python bot.py —start
ExecStop=/home/ploshadka/new-application/venv/bin/python bot.py —stop
ExecReload=/home/ploshadka/new-application/venv/bin/python bot.py —restart
TimeoutSec=30
Restart=always
Команды для запуска, остановки, перезагрузки службы
sudo systemctl start new-application
sudo systemctl stop new-application
sudo systemctl start new-application
sudo systemctl reload new-application
Читайте также
У сайта нет цели самоокупаться, поэтому на сайте нет рекламы. Но если вам пригодилась информация, можете лайкнуть страницу, оставить комментарий или отправить мне подарок на чашечку кофе.