Comment créer un démon en Python?

244

La recherche sur Google révèle des extraits de code x2. Le premier résultat est à cette recette de code qui a beaucoup de documentation et d'explications, avec quelques discussions utiles en dessous.

Cependant, un autre exemple de code , bien qu'il ne contienne pas autant de documentation, inclut un exemple de code pour passer des commandes telles que démarrer, arrêter et redémarrer. Il crée également un fichier PID qui peut être pratique pour vérifier si le démon est déjà en cours d'exécution, etc.

Ces exemples expliquent tous deux comment créer le démon. Y a-t-il d'autres éléments à considérer? Un échantillon est-il meilleur que l'autre et pourquoi?

davidmytton
la source
1
J'ai toujours trouvé le code de démonisation inutile. Pourquoi ne pas laisser le shell le faire?
emil.p.stanchev
17
Parce qu'il ne fait pas setsid ou setpgrp.
bmargulies
4
Utilisez supervisord.org . De cette façon, vous n'avez pas besoin de fork () ou de rediriger votre stdin / stderr. Écrivez simplement un programme normal.
guettli

Réponses:

169

Solution actuelle

Une implémentation de référence de PEP 3143 (bibliothèque de processus de démon standard) est désormais disponible en tant que démon python .

Réponse historique

L' échantillon de code de Sander Marechal est supérieur à l'original, qui a été initialement publié en 2004. J'ai déjà contribué un démoniseur pour Pyro, mais j'utiliserais probablement le code de Sander si je devais le refaire.

Jeff Bauer
la source
72
Edit: depuis que j'ai initialement publié cette réponse, une implémentation de référence de PEP 3143 est désormais disponible: pypi.python.org/pypi/python-daemon
Jeff Bauer
@JeffBauer Le lien d'origine est mort, je me souviens qu'il était utile, vous ne connaîtriez pas un lien en direct pour cela, n'est-ce pas?
CrazyCasta
1
@CrazyCasta: la version de Sander Marechal est toujours disponible sur la Wayback Machine
Jeff Bauer
1
@JeffBauer: Le code de Sander est toujours meilleur que http://pypi.python.org/pypi/python-daemon. Plus fiable. Un seul exemple: essayez de démarrer deux fois le même démon avec python-daemon: big ugly error. Avec le code de Sander: une belle notice "Daemon déjà en cours d'exécution".
Basj
2
Comme la documentation du module "python-daemon" est toujours manquante (voir aussi beaucoup d'autres questions SO) et est plutôt obscure (comment démarrer / arrêter correctement un démon depuis la ligne de commande avec ce module?), J'ai modifié l'exemple de code de Sander Marechal pour l'ajouter quit()qui est exécutée avant l'arrêt du démon. C'est ici.
Basj
163

Il y a beaucoup de choses délicates à prendre en compte lorsque vous devenez un processus démon bien comporté :

  • empêcher les vidages de mémoire (de nombreux démons s'exécutent en tant que root et les vidages de mémoire peuvent contenir des informations sensibles)

  • se comporter correctement à l'intérieur d'une chrootprison

  • définir l'UID, le GID, le répertoire de travail, l'umask et d'autres paramètres de processus de manière appropriée pour le cas d'utilisation

  • renoncer élevés suid, sgidprivilèges

  • fermer tous les descripteurs de fichiers ouverts, avec des exclusions selon le cas d'utilisation

  • se comporter correctement si commencé dans un contexte déjà détaché, comme init, inetd, etc.

  • configurer des gestionnaires de signaux pour un comportement sensible des démons, mais aussi avec des gestionnaires spécifiques déterminés par le cas d'utilisation

  • rediriger les flux standards stdin, stdout, stderrdepuis un processus démon ne comporte une borne de commande

  • gérer un fichier PID comme un verrou consultatif coopératif, qui est un ensemble de vers en lui-même avec de nombreuses façons contradictoires mais valides de se comporter

  • permettre un nettoyage approprié à la fin du processus

  • devenir en fait un processus démon sans conduire à des zombies

Certains d'entre eux sont standard , comme décrit dans la littérature canonique Unix ( Advanced Programming in the UNIX Environment , par feu W. Richard Stevens, Addison-Wesley, 1992). D'autres, comme la redirection de flux et la gestion des fichiers PID , sont des comportements conventionnels auxquels la plupart des utilisateurs de démons s'attendent mais qui sont moins standardisés.

Tous ces éléments sont couverts par la spécification PEP 3143 «Standard daemon process library» . L' implémentation de référence python-daemon fonctionne sur Python 2.7 ou version ultérieure, et Python 3.2 ou version ultérieure.

gros nez
la source
26
«Gaol» est orthographié correctement, car c'est ainsi que W. Richard Stevens l'a orthographié :-)
bignose
7
La prison est une chose anglaise . L'affiche vient d'Australie, donc c'est logique.
devin
1
Avez-vous l'intention de créer une version conviviale de py3k?
Tim Tisdall
97

Voici mon démon Python «Howdy World» de base avec lequel je commence, lorsque je développe une nouvelle application démon.

#!/usr/bin/python
import time
from daemon import runner

class App():
    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  '/tmp/foo.pid'
        self.pidfile_timeout = 5
    def run(self):
        while True:
            print("Howdy!  Gig'em!  Whoop!")
            time.sleep(10)

app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()

Notez que vous aurez besoin de la python-daemonbibliothèque. Vous pouvez l'installer en:

pip install python-daemon

Il suffit ensuite de le démarrer avec ./howdy.py startet de l'arrêter avec ./howdy.py stop.

Dustin Kirkland
la source
5
Ce daemonmodule que vous importez n'est pas (encore) une partie standard de Python. Il doit être installé avec pip install python-daemonou équivalent.
Nate
6
J'ai installé python-daemon comme vous l'avez décrit, mais lorsque j'essaie d'exécuter mon application (comme vos 3 dernières lignes), j'obtiens ImportError: impossible d'importer le nom du gestionnaire
Nostradamnit
Pouvez-vous vérifier s'il est installé correctement? $ dpkg -L démon-python | grep runner /usr/share/pyshared/daemon/runner.py
Dustin Kirkland
4
Cette suggestion semble obsolète - depuis septembre 2013, de toute façon, python.org/dev/peps/pep-3143 ne fait aucune mention d'un "runner" qui peut être importé. Cela expliquerait bien sûr l'observation de @ Nostradamnit.
offby1
2
Cela fonctionne toujours bien pour moi, en septembre 2013, sur Ubuntu 13.04, avec des packages Python, python2.7 et python-daemon installés. Avec python3, cependant, je vois une erreur, "du programme d'importation de démon ImportError: Aucun module nommé 'daemon'"
Dustin Kirkland
42

Notez le démon python paquet qui résout beaucoup de problèmes derrière les démons prêts à l'emploi.

Entre autres fonctionnalités, il permet (à partir de la description du paquet Debian):

  • Détachez le processus dans son propre groupe de processus.
  • Définissez un environnement de processus approprié pour l'exécution à l'intérieur d'un chroot.
  • Renoncez aux privilèges suid et sgid.
  • Fermez tous les descripteurs de fichiers ouverts.
  • Modifiez le répertoire de travail, uid, gid et umask.
  • Définissez les gestionnaires de signaux appropriés.
  • Ouvrez de nouveaux descripteurs de fichiers pour stdin, stdout et stderr.
  • Gérez un fichier de verrouillage PID spécifié.
  • Enregistrez les fonctions de nettoyage pour le traitement à la sortie.
Viliam
la source
35

Une alternative - créez un programme Python normal, non démonisé, puis démonifiez-le en externe à l'aide de supervisord . Cela peut sauver beaucoup de maux de tête et est * nix et portable.

Chris Johnson
la source
1
Je pense que c'est la meilleure façon. Surtout si vous souhaitez exécuter plusieurs démons sur un seul système d'exploitation. Ne codez pas, ne réutilisez pas.
guettli
Cela simplifie beaucoup de problèmes. J'ai écrit de vrais démons - ils ne sont pas faciles.
Chris Johnson
1
La meilleure réponse est cachée ici :)
kawing-chiu
1
C'est de l'or. Après avoir passé des heures à essayer de parcourir python-daemon, c'est la solution prête à l'emploi qui fonctionne pour moi. Une excellente documentation et des exemples ont rendu mon démon opérationnel en quelques minutes.
Nikhil Sahu
17

Probablement pas une réponse directe à la question, mais systemd peut être utilisé pour exécuter votre application en tant que démon. Voici un exemple:

[Unit]
Description=Python daemon
After=syslog.target
After=network.target

[Service]
Type=simple
User=<run as user>
Group=<run as group group>
ExecStart=/usr/bin/python <python script home>/script.py

# Give the script some time to startup
TimeoutSec=300

[Install]
WantedBy=multi-user.target

Je préfère cette méthode car une grande partie du travail est fait pour vous, puis votre script démon se comporte de la même manière que le reste de votre système.

-Orby

Luke Dupin
la source
C'est la manière appropriée et saine. 1) Doit être enregistré dans /etc/systemd/system/control.service 2) sudo gérésystemctl start control.service
jimper
7

YapDi est un module python relativement nouveau qui est apparu dans Hacker News. Semble assez utile, peut être utilisé pour convertir un script python en mode démon depuis l'intérieur du script.

Sergey R
la source
6

puisque python-daemon n'a pas encore supporté python 3.x, et d'après ce qui peut être lu sur la liste de diffusion, il ne le sera peut-être jamais, j'ai écrit une nouvelle implémentation de PEP 3143: pep3143daemon

pep3143daemon devrait prendre en charge au moins python 2.6, 2.7 et 3.x

Il contient également une classe PidFile.

La bibliothèque ne dépend que de la bibliothèque standard et des six modules.

Il peut être utilisé en remplacement du démon python.

Voici la documentation .

stephan schultchen
la source
6

Cette fonction transformera une application en démon:

import sys
import os

def daemonize():
    try:
        pid = os.fork()
        if pid > 0:
            # exit first parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #1 failed: {0}\n'.format(err))
        sys.exit(1)
    # decouple from parent environment
    os.chdir('/')
    os.setsid()
    os.umask(0)
    # do second fork
    try:
        pid = os.fork()
        if pid > 0:
            # exit from second parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #2 failed: {0}\n'.format(err))
        sys.exit(1)
    # redirect standard file descriptors
    sys.stdout.flush()
    sys.stderr.flush()
    si = open(os.devnull, 'r')
    so = open(os.devnull, 'w')
    se = open(os.devnull, 'w')
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())
Ivan Kolesnikov
la source
5

Je crains que le module démon mentionné par @Dustin ne fonctionne pas pour moi. Au lieu de cela, j'ai installé python-daemon et utilisé le code suivant:

# filename myDaemon.py
import sys
import daemon
sys.path.append('/home/ubuntu/samplemodule') # till __init__.py
from samplemodule import moduleclass 

with daemon.DaemonContext():
    moduleclass.do_running() # I have do_running() function and whatever I was doing in __main__() in module.py I copied in it.

La course est facile

> python myDaemon.py

juste pour être complet, voici le contenu du répertoire samplemodule

>ls samplemodule
__init__.py __init__.pyc moduleclass.py

Le contenu de moduleclass.py peut être

class moduleclass():
    ...

def do_running():
    m = moduleclass()
    # do whatever daemon is required to do.
Somum
la source
2

Encore une chose à laquelle penser lors de la démonisation en python:

Si vous utilisez la journalisation python et que vous souhaitez continuer à l'utiliser après la démonisation, assurez-vous d'appelerclose() les gestionnaires (en particulier les gestionnaires de fichiers).

Si vous ne le faites pas, le gestionnaire peut toujours penser qu'il a des fichiers ouverts et vos messages disparaîtront simplement - en d'autres termes, assurez-vous que l'enregistreur sait que ses fichiers sont fermés!

Cela suppose que lorsque vous démonifiez, vous fermez TOUS les descripteurs de fichiers ouverts sans discrimination - à la place, vous pouvez essayer de fermer tous les fichiers sauf les fichiers journaux (mais il est généralement plus simple de tout fermer puis de rouvrir ceux que vous voulez).

Matthew Wilcoxson
la source
Pensez-vous qu'il est préférable d'ouvrir un nouveau gestionnaire de journalisation que de passer le gestionnaire de journalisation au démon en utilisant l'option files_preserve de DaemonContext par exemple?
HeyWatchThis
Vous fermez seulement l'enregistreur, vous n'en créez pas un nouveau (il le rouvrira juste quand il le faudra). Mais même si c'est vraiment facile à faire, il pourrait être préférable d'utiliser le DaemonContext car il fait probablement d'autres choses intelligentes (en supposant que la conservation permet toujours une démonisation appropriée).
Matthew Wilcoxson
2

Bien que vous préfériez la solution Python pure fournie par le module python-daemon, il existe une daemon(3)fonction dans libc- au moins, sur BSD et Linux - qui fera le bon choix.

L'appeler depuis python est simple:

import ctypes

ctypes.CDLL(None).daemon(0, 0) # Read the man-page for the arguments' meanings

La seule chose qui reste à faire est la création (et le verrouillage) du fichier PID. Mais que vous pouvez gérer vous-même ...

Mikhail T.
la source
1

J'ai modifié quelques lignes dans l'exemple de code de Sander Marechal (mentionné par @JeffBauer dans la réponse acceptée ) pour ajouter une quit()méthode qui est exécutée avant l'arrêt du démon. C'est parfois très utile.

C'est ici.

Remarque: je n'utilise pas le module "python-daemon" car la documentation est toujours manquante (voir aussi beaucoup d'autres questions SO) et est plutôt obscure (comment démarrer / arrêter correctement un démon depuis la ligne de commande avec ce module?)

Basj
la source
-1

Après quelques années et de nombreuses tentatives (j'ai essayé toutes les réponses données ici, mais elles avaient toutes des inconvénients mineurs à la fin), maintenant je me rends compte qu'il y a une meilleure façon que de vouloir démarrer, arrêter, redémarrer un démon directement depuis Python : utilisez plutôt les outils du système d'exploitation.

Par exemple, pour Linux, au lieu de faire python myapp startet python myapp stop, je fais cela pour démarrer l'application:

screen -S myapp python myapp.py    
CTRL+A, D to detach

ou screen -dmS myapp python myapp.pypour le démarrer et le détacher en une seule commande .

Ensuite:

screen -r myapp

à attacher à nouveau à ce terminal. Une fois dans le terminal, il est possible d'utiliser CTRL + C pour l'arrêter.

Basj
la source
-2

La façon la plus simple de créer un démon avec Python est d'utiliser le framework Twisted événementiel. Il gère tous les éléments nécessaires à la démonisation pour vous. Il utilise le modèle de réacteur pour gérer les demandes simultanées.

Travis B. Hartwell
la source
5
C'est un marteau beaucoup trop gros pour être utilisé. La plupart des gens veulent simplement exécuter un court script Python qu'ils ont écrit en tant que démon. python-daemon, comme décrit ci-dessus, est la bonne réponse.
Tom Swirly
2
Bien que cette réponse soit assez arrogante, elle était utile.
fiatjaf
-28

80% du temps, quand les gens disent "démon", ils ne veulent qu'un serveur. La question étant parfaitement floue sur ce point, il est difficile de dire quel pourrait être le domaine des réponses. Puisqu'un serveur est adéquat, commencez par là. Si un "démon" réel est réellement nécessaire (c'est rare), lisez la suite nohupcomme moyen de démoniser un serveur.

Jusqu'à ce qu'un véritable démon soit réellement requis, il suffit d'écrire un simple serveur.

Regardez également l' implémentation de référence WSGI .

Regardez également le serveur HTTP simple .

"Y a-t-il d'autres éléments à prendre en compte?" Oui. Environ un million de choses. Quel protocole? Combien de demandes? Combien de temps pour répondre à chaque demande? À quelle fréquence arriveront-ils? Allez-vous utiliser un processus dédié? Des discussions? Sous-processus? Écrire un démon est un gros travail.

S.Lott
la source
12
Aucune de ces bibliothèques ne fait même un seul fork(), et encore moins deux. Ils n'ont rien à voir avec la démonisation.
Brandon Rhodes
8
Sur les systèmes d'exploitation Unix, un processus «démon» - comme les assistants aériens que les Grecs appelaient «démons» - est un processus qui «se tient à l'écart». Au lieu de servir directement un seul utilisateur via le TTY de cet utilisateur, un démon n'appartient à aucun TTY, mais peut répondre aux demandes de nombreux utilisateurs sur le système, ou - comme crondou syslogd- fait des services d'entretien pour l'ensemble du système. Pour créer un processus démon, il faut au moins effectuer un double fork()avec tous les descripteurs de fichiers fermés, de sorte que l'on soit à l'abri des signaux de tous les terminaux de contrôle, y compris la console système. Voir la réponse de bignose.
Brandon Rhodes
5
@S Lott - «un serveur» décrit ce qu'un processus fait (écoute les demandes entrantes au lieu de lancer ses propres actions); « Daemon » décrit comment un processus pistes (sans une fenêtre ou d' un terminal de contrôle). SimpleHTTPServerest en effet un serveur, mais qui ne sait pas se démonifier nativement (vous pouvez le Ctrl-C par exemple). nohupest un utilitaire pour démoniser un processus naïf - donc votre serveur nohupped est en effet à la fois un démon et un serveur, exactement comme vous le prétendez. Cette question de débordement de pile demandait essentiellement: "Comment puis-je implémenter nohupen Python?"
Brandon Rhodes
5
Oui, mais ma compréhension de la question des OP est qu'il veut faire la démonisation depuis son programme python et sans utiliser autre chose.
Noufal Ibrahim
4
@S Lott - Vous n'avez pas besoin d'être impressionné! L'auteur de toutes les autres réponses savait ce que signifiait «démon», donc ma capacité à interpréter cette question n'est guère unique. :) Et d'où vous est venue l'idée que je voulais que l'auteur réinvente une roue? Je pense que nohupc'est un excellent outil, et je supprimerai mon vote -1 si vous déplacez simplement cette idée utile dans votre réponse réelle. En fait, si vous mentionnez supervisordet comment cela évitera également à l'auteur d'avoir à faire la journalisation, un script start-stop et à redémarrer la limitation, alors je vous attribuerai même +1. :)
Brandon Rhodes