- Saved searches
- Use saved searches to filter your results more quickly
- License
- Nivratti/python-systemd
- Name already in use
- Sign In Required
- Launching GitHub Desktop
- Launching GitHub Desktop
- Launching Xcode
- Launching Visual Studio Code
- Latest commit
- Git stats
- Files
- README.md
- Создание демона Python с использованием Systemd
- # Systemd Unit
- # Python приложение
- # Обработка сигналов завершения
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
Tutorial to run Python script via systemd
License
Nivratti/python-systemd
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Tutorial to run Python script via systemd
Systemd Service file location and management
- Write Service file
- place your service files inside /etc/systemd/system/ folder
- reload services using systemctl daemon-reload
- after that you are able to perform operations such as
- systemctl start name.service
- systemctl status name.service
- systemctl stop name.service
- systemctl restart name.service
[Unit] # service description Description=**Enter Service Description** After=syslog.target [Service] Type=simple # user and group -- to run service User=**Enter username** Group=**Enter groupname** # project working directory WorkingDirectory=/path/to/working/dir/ # Command to execute when the service is started ExecStart=/usr/bin/python /path/to/python/demo_script.py # Automatically restart the service if it crashes Restart=on-failure # set Python's buffering of STDOUT and STDERR value to systemd, so that output from the # service shows up immediately in systemd's logs StandardOutput=syslog StandardError=syslog [Install] # Tell systemd to automatically start this service when the system boots # (assuming the service is enabled) WantedBy=multi-user.target
[Unit] # service description Description=Python Demo After=syslog.target [Service] Type=simple # user and group -- to run service User=nivratti Group=nivratti # project working directory WorkingDirectory=/programming/python/projects/ # Command to execute when the service is started ExecStart=/usr/bin/python /programming/python/projects/python-demo.py # Automatically restart the service if it crashes Restart=on-failure # set Python's buffering of STDOUT and STDERR value to systemd, so that output from the # service shows up immediately in systemd's logs StandardOutput=syslog StandardError=syslog [Install] # Tell systemd to automatically start this service when the system boots # (assuming the service is enabled) WantedBy=multi-user.target
Imp — Specify python interpreter
Specify Python interpreter
ExecStart=/usr/bin/python /file/path/python_demo_script.py
Service file configuration details
Создание демона 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
Таким образом мы создали шаблон демона который в дальнейшем может быть использован для решения реальных фоновых задач.