Python демон и системный сервис

У меня есть простой скрипт на 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 (но прежде чем пытался без него, и симптомы были похожи).

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

5 ответов

Причина, по которой он не завершает последовательность запуска, заключается в том, что для типа forking ожидается, что ваш процесс запуска завершится и завершится (см. $ 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, спасибо кмфтзг

Можно демонизировать, как описывают Шнуки и Амит. Но с 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

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

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

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

Вы не создаете файл 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 чтобы перейти в другой каталог, используйте DaemonContext(pidfile=pidfile, working_directory="/path/to/dir"),

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

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

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

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

Если вам интересно, в 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

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

Другие вопросы по тегам