web-dev-qa-db-fra.com

Python et service systemd

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

Script systemd actuel:

[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 __== '__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'indiquer la séquence - je dois appuyer sur Ctrl + C. Le script est en cours d'exécution, mais le statut est activé et, au bout d'un moment, il devient désactivé. Maintenant, j'utilise python-daemon (mais avant j'ai essayé sans et les symptômes étaient similaires).

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

68
pawelbial

La raison pour laquelle la séquence de démarrage n'est pas terminée est que, pour le Type forking, votre processus de démarrage est censé s'interrompre puis se terminer (voir $ man systemd.service - recherche de forking).

Utilisez simplement uniquement le processus principal, ne démonalisez pas

Une option est de faire moins. Avec systemd, il n'est souvent pas nécessaire de créer des démons et vous pouvez exécuter le code directement 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, afin que votre fichier d'unité ressemble à ceci.

[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 in python Shebang n'est pas nécessaire, mais si vous imprimez quelque chose sur la sortie standard ou stderr, le -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, il semblerait avec un peu de retard.

À cette fin, j’ai ajouté dans le fichier d’unité les lignes StandardOutput=syslog et StandardError=syslog. Si vous ne vous souciez pas de la sortie imprimée de votre journal, ne vous souciez pas de ces lignes (elles ne doivent pas nécessairement être présentes).

systemd rend la démonisation obsolète

Bien que le titre de votre question demande explicitement à propos de la démonisation, je suppose que le cœur de la question est "comment faire fonctionner mon service" et while l'utilisation du processus principal semble beaucoup plus simple sur les démons du tout), 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 ce sera un cas rare maintenant.

EDIT: corrigé python -p au propre python -u. merci kmftzg

103
Jan Vlcinsky

Il est possible de démoniser comme décrit par 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 socket et la notification explicite avec sd_notify ().

L'activation de socket fonctionne pour les démons qui souhaitent écouter sur un port réseau, un socket UNIX ou similaire. Systemd ouvrirait le socket, l'écouterait, puis créerait le démon lorsqu'une connexion entrerait. Cette approche est privilégiée car elle offre plus de flexibilité à l'administrateur. [1] et [2] donnent une introduction de Nice, [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 eux-mêmes et/ou effectue toute autre initialisation, puis notifie à init qu'il est prêt et peut répondre aux demandes. Cela peut être implémenté avec le "protocole de forking", mais en réalité, il est préférable d'envoyer une notification à systemd avec sd_notify (). Python s’appelle systemd.daemon.notify et sera une ligne à utiliser [5].

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

Dans ce cas, le fichier d'unité aurait Type = notify et appelez systemd.daemon.notify ("READY = 1") après avoir établi les sockets. Aucun forking ou démonisation n'est nécessaire.

21
zbyszek

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 échoue, et donc le désactive.

Pour ajouter le fichier PID, installez lockfile et remplacez le code par ceci:

import daemon
import daemon.pidlockfile 

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

(Remarque rapide: une mise à jour récente de lockfile a modifié son API et l'a rendue incompatible avec python-daemon. Pour résoudre ce problème, éditez daemon/pidlockfile.py, Supprimez LinkFileLock des importations, et ajouter from lockfile.linklockfile import LinkLockFile as LinkFileLock.)

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

14
Schnouki

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

En effet, si python-daemon Détecte que s'il est exécuté sous un système init, il ne se détache pas du processus parent. systemd s'attend à ce que le processus démon exécuté avec Type=forking le fasse. Par conséquent, vous avez besoin de cela, sinon systemd continuera à attendre et finira par mettre fin au processus.

Si vous êtes curieux, dans le module démon de python-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

Espérons que cela explique mieux.

3
user59634

Je suis tombé sur cette question en essayant de convertir certains services python init.d en systemd sous CentOS 7. Cela semble très bien 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 découvert que l'utilisation de idle pour Type avait plus de sens que simple.

Le comportement du ralenti est très similaire au simple; toutefois, l'exécution réelle du binaire de service est retardée jusqu'à ce que tous les travaux actifs soient distribués. Cela 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 essayé de conserver l'ancien service et de faire redémarrer le service par systemd, 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 le script de service init.d était utilisé à la place du service systemd si les deux avaient le même nom. Si vous avez tué le processus lancé par init.d, le script systemd prend alors le relais. Mais si tu courais service <service-name> stop il ferait référence à l'ancien service init.d. J'ai donc trouvé que le meilleur moyen était de supprimer l'ancien service init.d et que la commande de service faisait plutôt référence au service systemd.

J'espère que cela t'aides!

1
radtek