Демон 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 неверен?
Ответы
Ответ 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.
Надеюсь, это поможет!