Подтвердить что ты не робот

Демон python и служба systemd

У меня есть простой скрипт Python, работающий в качестве демона. Я пытаюсь создать сценарий systemd, чтобы иметь возможность запустить этот сценарий во время запуска.

Текущий сценарий systemd:

[Unit]
Description=Text
After=syslog.target

[Service]
Type=forking
User=node
Group=node
WorkingDirectory=/home/node/Node/
PIDFile=/var/run/zebra.pid
ExecStart=/home/node/Node/node.py

[Install]
WantedBy=multi-user.target

node.py:

if __name__ == '__main__':
    with daemon.DaemonContext():
        check = Node()
        check.run()

run содержит while True цикл.

Я пытаюсь запустить этот сервис с помощью systemctl start zebra-node.service. К сожалению, сервис никогда не заканчивал указывать последовательность - я должен нажать Ctrl + C. Скрипт запущен, но статус активируется и через некоторое время меняется на деактивирующий. Сейчас я использую python-daemon (но прежде чем я пытался без него, и симптомы были похожи).

Должен ли я реализовать некоторые дополнительные функции в моем скрипте или файл systemd неверен?

4b9b3361

Ответ 1

Причина, по которой он не завершает последовательность запуска, заключается в том, что для forking Type ваш процесс запуска должен завершиться и завершиться (см. $ Man systemd.service - поиск разветвления).

Просто используйте только основной процесс, не демонизируйте

Один из вариантов - сделать меньше. При использовании systemd часто нет необходимости создавать демоны, и вы можете напрямую запускать код без демонизации.

#!/usr/bin/python -u
from somewhere import Node
check = Node()
check.run()

Это позволяет использовать более простой тип сервиса, называемый simple, чтобы ваш файл модуля выглядел так.

[Unit]
Description=Simplified simple zebra service
After=syslog.target

[Service]
Type=simple
User=node
Group=node
WorkingDirectory=/home/node/Node/
ExecStart=/home/node/Node/node.py
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

Обратите внимание, что -u в python shebang не является необходимым, но в случае, если вы печатаете что-то на stdout или stderr, -u гарантирует, что буферизация вывода не будет на месте, и напечатанные строки будут немедленно перехвачены systemd и записано в журнале. Без него это появилось бы с некоторой задержкой.

Для этого я добавил в файл модуля строки StandardOutput=syslog и StandardError=syslog. Если вам не нужны печатные материалы в вашем журнале, не заботьтесь об этих строках (они не обязательно должны присутствовать).

systemd делает демонизацию устаревшей

Хотя заголовок вашего вопроса явно спрашивает о демонизации, я думаю, суть вопроса заключается в том, "как заставить мой сервис работать", и хотя использование основного процесса кажется намного проще (вам вообще не нужно заботиться о демонах), оно можно считать ответом на ваш вопрос.

Я думаю, что многие люди используют демонизацию только потому, что "это делают все". При использовании systemd причины демонизации часто бывают устаревшими. Может быть несколько причин использовать демонизацию, но сейчас это будет редкий случай.

РЕДАКТИРОВАТЬ: исправлено python -p на правильный python -u. спасибо кмфтзг

Ответ 2

Можно демонизировать, как описывают Шнуки и Амита. Но с systemd это не нужно. Существует два способа инициализации демона: активация сокета и явное уведомление с помощью sd_notify().

Активация сокета работает для демонов, которые хотят прослушивать сетевой порт или UNIX-сокет или аналогичные. Systemd будет открывать сокет, слушать его, а затем запускать демона при подключении. Это предпочтительный вариант, потому что он дает большую гибкость администратору. [1] и [2] дают хорошее введение, [3] описывает C API, а [4] описывает Python API.

[1] http://0pointer.de/blog/projects/socket-activation.html
[2] http://0pointer.de/blog/projects/socket-activation2.html
[3] http://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
[4] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.listen_fds

Явное уведомление означает, что демон открывает сами сокеты и/или выполняет любую другую инициализацию, а затем уведомляет init о том, что он готов и может обслуживать запросы. Это можно реализовать с помощью протокола "forking", но на самом деле лучше всего отправить уведомление systemd с помощью sd_notify(). Пакет Python называется systemd.daemon.notify и будет одной строкой для использования [5].

[5] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.notify

В этом случае файл unit будет иметь Type = notify и вызвать systemd.daemon.notify( "READY = 1" ) после того, как он установил сокеты. Не требуется разветвления или демонализации.

Ответ 3

Вы не создаете файл PID.

systemd ожидает, что ваша программа будет писать свой PID в /var/run/zebra.pid. Поскольку вы этого не делаете, systemd, вероятно, думает, что ваша программа терпит неудачу, отсюда ее деактивировать.

Чтобы добавить файл PID, установите lockfile и измените код на это:

import daemon
import daemon.pidlockfile 

pidfile = daemon.pidlockfile.PIDLockFile("/var/run/zebra.pid")
with daemon.DaemonContext(pidfile=pidfile):
    check = Node()
    check.run()

(Быстрое примечание: недавнее обновление lockfile изменило его API и сделало его несовместимым с python-daemon. Чтобы исправить его, отредактируйте daemon/pidlockfile.py, удалите LinkFileLock из импорта и добавьте from lockfile.linklockfile import LinkLockFile as LinkFileLock.)

Остерегайтесь еще одной вещи: DaemonContext изменяет рабочий каталог вашей программы на /, делая WorkingDirectory вашего служебного файла бесполезным. Если вы хотите, чтобы DaemonContext в chdir в другой каталог, используйте DaemonContext(pidfile=pidfile, working_directory="/path/to/dir").

Ответ 4

Кроме того, при создании DaemonContext() вам, скорее всего, нужно будет установить daemon_context=True.

Это связано с тем, что если python-daemon обнаруживает, что если он запущен в системе init, он не отсоединяется от родительского процесса. systemd ожидает, что процесс демона, работающий с Type=forking, сделает это. Следовательно, вам это нужно, иначе systemd будет продолжать ждать и, наконец, убить процесс.

Если вам интересно, в модуле daemon python-daemon вы увидите этот код:

def is_detach_process_context_required():
    """ Determine whether detaching process context is required.

        Return ``True`` if the process environment indicates the
        process is already detached:

        * Process was started by `init`; or

        * Process was started by `inetd`.

        """
    result = True
    if is_process_started_by_init() or is_process_started_by_superserver():
        result = False

Надеюсь, это объясняет лучше.

Ответ 5

Я столкнулся с этим вопросом при попытке конвертировать некоторые службы init.d python в systemd под CentOS 7. Это, похоже, отлично работает для меня, разместив этот файл в /etc/systemd/system/:

[Unit]
Description=manages worker instances as a service
After=multi-user.target

[Service]
Type=idle
User=node
ExecStart=/usr/bin/python /path/to/your/module.py
Restart=always
TimeoutStartSec=10
RestartSec=10

[Install]
WantedBy=multi-user.target

Затем я удалил свой старый файл службы init.d из /etc/init.d и запустил sudo systemctl daemon-reload, чтобы перезагрузить systemd.

Я хотел, чтобы моя служба автоматически перезагружалась, поэтому параметры перезапуска. Я также нашел, что использование idle для Type имеет больше смысла, чем simple.

Поведение простоя очень похоже на простое; однако фактическое исполнение служебного двоичного файла откладывается до тех пор, пока не будут отправлены все активные задания. Это можно использовать, чтобы избежать чередования вывода служб оболочки с выходом состояния на консоли.

Подробнее о параметрах, которые я использовал здесь.

Я также экспериментировал с сохранением старой службы и возможностью переустановки службы systemd, но я столкнулся с некоторыми проблемами.

[Unit]
# Added this to the above
#SourcePath=/etc/init.d/old-service 

[Service]
# Replace the ExecStart from above with these
#ExecStart=/etc/init.d/old-service start
#ExecStop=/etc/init.d/old-service stop

Проблемы, с которыми я столкнулся, заключались в том, что вместо службы systemd была использована служба init.d script, если оба они были названы одинаковыми. Если вы убили инициированный init.d процесс, systemd script затем перейдет в прошлое. Но если вы запустили service <service-name> stop, это будет ссылаться на старую службу init.d. Поэтому я нашел, что лучший способ - отказаться от старой службы init.d и команды службы, упомянутой вместо службы systemd.

Надеюсь, это поможет!