Демон Python та служба systemd


78

У мене є простий сценарій 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 неправильний?


Чи відповідь вирішила вашу проблему? Якщо ні, спробуйте встановити daemon_context = True під час створення DaemonContext (). Це може спрацювати.

1
@pawelbial Шкода, що ваш приклад коду на Python не є повним (відсутні імпорт daemonі незрозуміло, звідки Nodeпоходить), тому відтворити вашу ситуацію непросто / можливо.
Ян Вльчинський

@pawelbial Це опосередковано пов'язано з питанням, але може допомогти вам: unix.stackexchange.com/a/226853/33386
Джонатан Комар,

Відповіді:


116

Причина в тому, що він не завершує послідовність запуску, полягає в тому, що для типу 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 причини демонізації часто застарілі. Можливо, є деякі причини використовувати демонізацію, але це буде рідкісним випадком зараз.

EDIT: фіксований python -pдо власне python -u. дякую kmftzg


4
Причиною демонізації є підтримка інших платформ, які не використовують systemd. Потрібно зробити окремий шлях коду для systemd - це ще один спосіб, який systemd гальмує портативність.
Нік Бастін,

2
@NickBastin, і це ще один спосіб, яким портативність стримує прогрес і спрощення.
intelfx

2
@NickBastin systemd підтримує розгалуження додатків, тому немає необхідності в окремому шляху коду для systemd, якщо ви цього не хочете. Не читання документації заважає практичним навичкам і може зробити коментарі безпідставними.
Ян Влчинський

4
@NickBastin OP розповідає про "простий сценарій python" та використання "systemd". Жодного запиту на перенесення несистемних платформ, такий запит існує лише у ваших реакціях, які звинувачують systemd.
Ян Влчинський,

1
Очевидно, існують інші інструменти управління процесами, такі як супервізор або pm2. Я думаю, що "не демонізувати" - це явно єдиний спосіб робити щось.
ospider

23

Це можна демонізувати, як описують Шноукі та Аміт. Але з systemd це не потрібно. Існує два приємніших способи ініціалізації демона: активація сокета та явне сповіщення за допомогою sd_notify ().

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

[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 про те, що він готовий і може обслуговувати запити. Це може бути реалізовано за допомогою "протоколу форкінгу", але насправді приємніше просто надіслати повідомлення systemd за допомогою sd_notify (). Обгортка Python називається systemd.daemon.notify і буде одним рядком для використання [5].

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

У цьому випадку файловий модуль має мати Type = notify і викликати systemd.daemon.notify ("ГОТОВ = 1") після встановлення сокетів. Не потрібне розгалуження та демонізація.


Виглядає добре. Як встановити цю бібліотеку python, яка надає systemd.daemonчерез pip?
guettli

Офіційні вказівки щодо встановлення на github.com/systemd/python-systemd#installation показують, як встановити за допомогою pip. Якщо вони не працюють для вас, надішліть проблему за адресою github.com/systemd/python-systemd/issues .
zbyszek

15

Ви не створюєте файл PID.

systemd очікує, що ваша програма напише свій PID /var/run/zebra.pid. Оскільки ви цього не робите, systemd, мабуть, вважає, що ваша програма не працює, отже, деактивує її.

Щоб додати файл PID, встановіть файл блокування та змініть свій код на такий:

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").


2
Останній абзац про те, як DaemonContextзміни робочого каталогу програми щойно вирішили мої проблеми демонізації
DJG

2
Оскільки менше коду більше, я віддаю перевагу відповіді "Просто використовуйте лише основний процес, не демонізуйте".
guettli

idk, якщо API змінився в Python3. але там повинно бути, import daemon.pidfileа ніimport daemon.pidlockfile
reox

коли замок знято, він завжди залишається заблокованим, навіть якщо демон завершений
alper

4

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

Це тому, що якщо python-daemonвиявляє, що, якщо вона працює в системі init, вона не від'єднується від батьківського процесу. 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

Сподіваємось, це пояснює краще.


2
Я можу помилитися, але я думаю, що цей прапор називається "detach_process", а не "daemon_context"
Пітер Тернер,

4

Я зіткнувся з цим питанням при спробі перетворити деякі служби 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.

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

Детальніше про варіанти, якими я користувався тут .

Я також експериментував із збереженням старої служби та систематичним перезапуском служби, але зіткнувся з деякими проблемами.

[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, а команда служби замість неї посилалася на службу systemd.

Сподіваюся, це допомагає!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.