82 votes

Démon Python et service systemd

J'ai un simple script Python script qui fonctionne comme un démon. J'essaie de créer systemd script pour pouvoir lancer ce script au démarrage.

Script script actuel de 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 contient while True boucle.

J'essaie de faire fonctionner ce service avec systemctl start zebra-node.service . Malheureusement, le service n'a jamais fini d'énoncer la séquence - je dois appuyer sur Ctrl+C. Le script est en cours d'exécution, mais le statut est activant et, après un certain temps, il devient désactivant. J'utilise maintenant python-daemon (mais avant j'ai essayé sans et les symptômes étaient similaires).

Dois-je ajouter des fonctionnalités supplémentaires à mon script ou le fichier systemd est-il incorrect ?

123voto

Jan Vlcinsky Points 7932

La raison pour laquelle il ne termine pas la séquence de démarrage est que, pour le type forking votre processus de démarrage est censé s'emboîter et se terminer (voir $ man systemd.service - search forking).

Il suffit d'utiliser le processus principal, sans le démoniser.

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

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

Cela permet d'utiliser un type de service plus simple appelé simple Ainsi, votre fichier d'unité ressemblerait à ce qui suit

[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

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

À cette fin, j'ai ajouté les lignes suivantes dans le fichier de l'unité StandardOutput=syslog y StandardError=syslog . Si vous ne vous souciez pas de l'impression de votre journal, ne vous souciez pas de ces lignes (il n'est pas nécessaire qu'elles soient présentes).

systemd rend la démonisation obsolète

Bien que le titre de votre question porte explicitement sur la démonisation, je suppose que le cœur de la question est "comment faire fonctionner mon service" et bien que l'utilisation du processus principal semble beaucoup plus simple (vous n'avez pas besoin de vous préoccuper des démons), cela pourrait être considéré comme une réponse à votre question.

Je pense que beaucoup de gens utilisent la démonisation simplement parce que "tout le monde le fait". Avec systemd, les raisons de la démonisation sont souvent obsolètes. Il peut y avoir des raisons d'utiliser la démonisation, mais c'est un cas rare maintenant.

EDIT : corrigé python -p à la bonne python -u . merci kmftzg

22voto

zbyszek Points 1181

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

L'activation de sockets fonctionne pour les démons qui veulent écouter sur un port réseau ou un socket UNIX ou similaire. Systemd ouvrirait la socket, l'écouterait, puis lancerait le démon lorsqu'une connexion serait établie. C'est l'approche préférée car elle donne le plus de flexibilité à l'administrateur. [1] et [2] donnent une bonne 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

La notification explicite signifie que le démon ouvre lui-même les sockets et/ou effectue toute autre initialisation, puis notifie à init qu'il est prêt et qu'il peut répondre aux demandes. Ceci peut être implémenté avec le "forking protocol", mais en fait il est plus agréable d'envoyer une notification à systemd avec sd_notify(). Le wrapper Python s'appelle systemd.daemon.notify et n'est qu'une ligne à utiliser [5].

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

Dans ce cas, le fichier de l'unité aurait pour nom Type=notify, et appellerait systemd.daemon.notify("READY=1") après avoir établi les sockets. Aucun forking ou daemonisation n'est nécessaire.

15voto

Schnouki Points 2872

Vous ne créez pas le fichier PID.

systemd s'attend à ce que votre programme écrive son PID dans le champ /var/run/zebra.pid . Comme vous ne le faites pas, systemd pense probablement que votre programme échoue et le désactive.

Pour ajouter le fichier PID, installez fichier de verrouillage et modifiez votre code comme suit :

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 récente mise à jour de lockfile a modifié son API et l'a rendu incompatible avec python-daemon. Pour le corriger, éditez daemon/pidlockfile.py , supprimer LinkFileLock à partir des importations, et ajoutez from lockfile.linklockfile import LinkLockFile as LinkFileLock .)

Attention à une autre chose : DaemonContext modifie le répertoire de travail de votre programme en / , ce qui fait que le WorkingDirectory de votre dossier de service inutile. Si vous souhaitez DaemonContext pour chdir dans un autre répertoire, utilisez DaemonContext(pidfile=pidfile, working_directory="/path/to/dir") .

4voto

En outre, vous devrez probablement définir daemon_context=True lors de la création du DaemonContext() .

En effet, si python-daemon détecte que s'il tourne sous un système init, il ne se détache pas du processus parent. systemd s'attend à ce que le processus démon s'exécutant sous le nom de Type=forking le fera. C'est donc de cela qu'il s'agit, sinon systemd continuera d'attendre et finira par tuer le processus.

Si vous êtes curieux, en python-daemon du module daemon, 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

J'espère que cela explique mieux la situation.

4voto

radtek Points 506

Je suis tombé sur cette question en essayant de convertir quelques services python init.d à systemd sous CentOS 7. Cela semble très 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 abandonné mon ancien fichier de service init.d de /etc/init.d et a couru sudo systemctl daemon-reload pour recharger systemd.

Je voulais que mon service redémarre automatiquement, d'où les options de redémarrage. J'ai également trouvé que l'utilisation de idle para Type a plus de sens que simple .

Le comportement de l'inactivité est très similaire à celui de la simplicité ; cependant, l'exécution réelle du binaire de service est retardée jusqu'à ce que tous les travaux actifs soient distribués. Ceci peut être utilisé pour éviter l'entrelacement des sorties des services shell. avec la sortie d'état sur la console.

Plus de détails sur les options que j'ai utilisées aquí .

J'ai également essayé de conserver l'ancien service et de demander à systemd de le redémarrer, 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

Le problème que j'ai rencontré est que le service init.d script était utilisé à la place du service systemd si les deux étaient nommés de la même manière. Si vous tuiez le processus initié par init.d, le service systemd script prenait le relais. Mais si vous exécutez service <service-name> stop il se réfère à l'ancien service init.d. J'ai donc découvert que la meilleure solution était de supprimer l'ancien service init.d et que la commande service faisait référence au service systemd à la place.

J'espère que cela vous aidera !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X