Service de démon Python et de systemd

j'ai un script Python simple avec des œuvres en tant que démon. J'essaie de créer le script systemd pour pouvoir démarrer ce script au démarrage.

Actuel systemd script:

[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 contient while True boucle.

j'essaie d'exécuter ce service par systemctl start zebra-node.service . Malheureusement service n'a jamais fini d'indiquer la séquence - je dois appuyer sur Ctrl+C. Script est en cours d'exécution, mais le statut s'active et après un certain temps il change en désactivation. Maintenant j'utilise python-daemon (mais avant j'ai essayé sans elle et les symptômes étaient similaires).

dois-je implémenter des fonctionnalités supplémentaires dans mon script ou le fichier systemd est-il incorrect?

55
demandé sur Jan Vlcinsky 2012-10-25 17:25:10

5 réponses

la raison, il ne complète pas la séquence de démarrage est, que pour le Type forking votre processus de démarrage est prévu pour bifurquer et sortir (voir $ man systemd.service de recherche de la fourche).

n'utiliser que le procédé principal, ne pas démoniser

Une option est de faire moins. Avec systemd, il n'est souvent pas nécessaire de créer des démons et vous pouvez directement exécuter le code sans démoniser.

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

This permet d'utiliser plus simple Type de service appelé simple , de sorte que votre fichier unité ressemblerait.

[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

notez que le -u en python shebang n'est pas nécessaire, mais dans le cas où vous imprimez quelque chose sur le stdout ou le stderr, le -u s'assure qu'il n'y a pas de tampon de sortie en place et les lignes imprimées seront immédiatement capturées par systemd et enregistrées dans journal. Sans elle, elle apparaîtrait avec un certain retard.

pour ce objet j'ai ajouté au fichier unitaire les lignes StandardOutput=syslog et StandardError=syslog . Si vous ne vous souciez pas de la sortie imprimée dans votre journal, ne vous souciez pas de ces lignes (elles ne doivent pas être présentes).

systemd rend de nombreux démons obsolètes

alors que le titre de votre question pose explicitement des questions sur la démonisation, je suppose, le cœur de la question Est "comment faire fonctionner mon service" et alors que en utilisant le processus principal semble beaucoup plus simple (vous n'avez pas à vous soucier des démons du tout), il pourrait être considéré réponse à votre question.

je pense que beaucoup de gens utilisent la démonisation juste parce que"tout le monde le fait". Avec systemd, les raisons de la démonisation sont souvent obsolètes. Il pourrait y avoir quelques raisons d'utiliser la démonisation, mais ce sera rare maintenant.

EDIT: fixe python -p bon python -u . merci kmftzg

79
répondu Jan Vlcinsky 2018-09-30 16:06:58

il est possible de démoniser comme Schnouki et Amit décrivent. Mais avec systemd, ce n'est pas nécessaire. Il y a deux façons plus agréables d'initialiser le démon: socket-activation et notification explicite avec sd_notify().

Socket activation fonctionne pour les démons qui veulent écouter sur un port réseau ou UNIX socket ou similaire. Systemd ouvrirait la socket, l'écouterait et lancerait le démon quand une connexion entrerait. C'est la meilleure approche, car il donne plus de flexibilité à l'administrateur. [1] et [2] donne une belle introduction, [3] décrit l'API C, tandis que [4] décrit l'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

notification explicite signifie que le démon ouvre les sockets lui-même et/ou effectue toute autre initialisation, puis notifie à init qu'il est prêt et peut servir des requêtes. Ceci peut être implémenté avec le "protocole de bifurcation", mais en fait il est plus agréable d'envoyer simplement une notification à systemd avec sd_notify(). Python l'emballage s'appelle systemd.démon.informer et aura une ligne à utiliser [5].

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

dans ce cas, le fichier unité aurait Type = notifier, et appeler systemd.démon.notifier ("READY=1") après avoir établi les sockets. Aucune fourche ou démonisation n'est nécessaire.

17
répondu zbyszek 2014-01-27 18:28:31

vous ne créez pas le fichier PID.

systemd s'attend à ce que votre programme écrive son PID dans /var/run/zebra.pid . Comme vous ne le faites pas, systemd pense probablement que votre programme est défaillant, donc le désactiver.

pour ajouter le fichier PID, installez lockfile et changez votre code à ceci:

import daemon
import daemon.pidlockfile 

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

(note rapide: une mise à jour récente de lockfile a changé son API et l'a rendu incompatible avec python-démon. Pour le corriger, éditer daemon/pidlockfile.py , supprimer LinkFileLock des importations, et ajouter from lockfile.linklockfile import LinkLockFile as LinkFileLock .)

attention à une autre chose: DaemonContext change le dir de travail de votre programme en / , ce qui rend le WorkingDirectory de votre fichier de service inutile. Si vous voulez que DaemonContext apparaisse dans un autre répertoire, utilisez DaemonContext(pidfile=pidfile, working_directory="/path/to/dir") .

13
répondu Schnouki 2012-10-26 13:46:11

en outre, vous avez très probablement besoin de définir daemon_context=True lors de la création du DaemonContext() .

c'est parce que, si python-daemon détecte que s'il fonctionne sous un système init, il ne se détache pas du processus parent. systemd s'attend à ce que le processus de démon tournant avec Type=forking le fasse. Par conséquent, vous avez besoin de cela, sinon systemd continuera d'attendre, et finalement tuer le processus.

si vous êtes curieux, dans le démon de python-daemon module, vous verrez ce code:

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

espérons que cela explique mieux.

3
répondu 2013-07-25 02:57:30

j'ai rencontré cette question en essayant de convertir certains inits de python.d services fournis à systemd dans le cadre de CentOS 7. Cela semble bien fonctionner pour moi, en plaçant ce fichier dans /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

j'ai ensuite laissé tomber mon ancien init.d fichier de service de /etc/init.d et ran sudo systemctl daemon-reload pour recharger systemd.

je voulais que mon service redémarre automatiquement, d'où les options de redémarrage. J'ai aussi trouvé plus logique d'utiliser idle pour Type que simple .

comportement de ralenti est très similaire à simple; cependant, l'exécution réelle du binaire de service est retardée jusqu'à ce que tous les emplois actifs sont expédiés. Ceci peut être utilisé pour éviter l'entrelacement de la sortie des services shell avec la sortie d'état sur la console.

plus de détails sur les options que j'ai utilisées ici .

j'ai également expérimenté avec garder le vieux service et avoir systemd resart le service, mais j'ai rencontré quelques problèmes.

[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

les problèmes que j'ai rencontrés étaient que l'init.d script de service a été utilisé à la place du service systemd si les deux ont le même nom. Si vous avez tué l'init.d processus initié, le script systemd prendrait alors la relève. Mais si vous avez couru service <service-name> stop il se référerait à l'ancien init.D service. J'ai donc trouvé le meilleur moyen de laisser tomber le vieux init.d et le service de commande visé à le service systemd à la place.

Espérons que cette aide!

0
répondu radtek 2017-02-08 14:07:07