web-dev-qa-db-fra.com

Comment configurer un démon avec python-daemon?

Je suis nouveau dans les démons, alors excuses-moi s'il s'agit d'une question pour débutant.

Dans plusieurs autres réponses (par exemple, cette question ), des personnes ont suggéré que le paquet python-daemon était la solution, car il implémentait pleinement le PEP 3143 standard. 

Malheureusement, python-daemon est un peu léger sur la documentation (ou plutôt je suis un peu léger sur la connaissance/expérience ...;)), et je pense que je manque probablement quelque chose de vraiment fondamental. Voici ce que je fais:

J'ai le suivant:

import daemon

logfile = open('daemon.log', 'w')

context = daemon.DaemonContext(stdout = logfile, stderr = logfile)

context.open()

with context:
    do_something_1()
    do_something_2()

Question: Comment configurer un démon avec python-daemon, comment puis-je le démarrer et l'arrêter?


Notes de côté:

En gros, je ne sais pas trop comment/si la méthode .open() devrait être utilisée ici - les documents n'étaient pas vraiment clairs sur ce point. La même chose semble arriver, que je l'inclue ou non.

Alors maintenant que dois-je faire? Lorsque j'essaie d'exécuter ce fichier, par exemple:

python startConsumerDaemons.py

il semble exécuter do_something_1(), mais pas le second. Et, il semble laisser le programme attaché à la fenêtre du terminal. IE, stdout n'est pas redirigé et lorsque je ferme la fenêtre du terminal, le processus est tué. Donc, je suis presque sûr que je fais quelque chose de mal ici ... que devrais-je faire différemment?

Et enfin, une fois le démon en marche, comment puis-je l'arrêter/le redémarrer (par exemple, si je modifie le code sous-jacent)?

28
CQP

Voici ce que j'ai, ça marche pour moi. Il a également un script d'initialisation sysv. Repo est à GitHub , et j'ai aussi un bref article de blog avec des liens vers d'autres solutions possibles que j'ai trouvées.

Il ne peut y avoir qu'un seul processus en cours d'exécution: il est géré par le fichier de verrouillage PID, comme la plupart des autres démons Linux. Pour l'arrêter, faites

kill `cat /var/run/eg_daemon.pid`

Pour voir s'il est en cours d'exécution:

ps -elf | grep `cat /var/run/eg_daemon.pid`

À l’aide du sous-module pidfile, le fichier PID est géré automatiquement. Lorsque le démon est arrêté, le pidfile est effacé. Veuillez consulter le référentiel lié GitHub pour le script init.

Voici le code du démon Python:

#!/usr/bin/env python3.5
import sys
import os
import time
import argparse
import logging
import daemon
from daemon import pidfile

debug_p = False

def do_something(logf):
    ### This does the "work" of the daemon

    logger = logging.getLogger('eg_daemon')
    logger.setLevel(logging.INFO)

    fh = logging.FileHandler(logf)
    fh.setLevel(logging.INFO)

    formatstr = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(formatstr)

    fh.setFormatter(formatter)

    logger.addHandler(fh)

    while True:
        logger.debug("this is a DEBUG message")
        logger.info("this is an INFO message")
        logger.error("this is an ERROR message")
        time.sleep(5)


def start_daemon(pidf, logf):
    ### This launches the daemon in its context

    ### XXX pidfile is a context
    with daemon.DaemonContext(
        working_directory='/var/lib/eg_daemon',
        umask=0o002,
        pidfile=pidfile.TimeoutPIDLockFile(pidf),
        ) as context:
        do_something(logf)


if __== "__main__":
    parser = argparse.ArgumentParser(description="Example daemon in Python")
    parser.add_argument('-p', '--pid-file', default='/var/run/eg_daemon.pid')
    parser.add_argument('-l', '--log-file', default='/var/log/eg_daemon.log')

    args = parser.parse_args()

    start_daemon(pidf=args.pid_file, logf=args.log_file)

Par souci d'exhaustivité, voici le script init. Notez que "kill" n’est en réalité qu’une méthode pour envoyer un signal POSIX - voir la page de manuel de signal pour signal (7) pour un aperçu. Le contexte python-daemon intercepte le signal, met fin au processus en fermant proprement les descripteurs de fichier et supprime automatiquement le fichier PID. Donc, c'est vraiment une fin propre.

Vous pouvez écrire votre code pour intercepter SIGUSR1 ou quelque chose de similaire, afin de recharger le démon config. Il n’ya aucun avantage à écrire en Python si vous arrêtez le démon.

#!/bin/bash
#
# eg_daemon      Startup script for eg_daemon
#
# chkconfig: - 87 12
# description: eg_daemon is a dummy Python-based daemon
# config: /etc/eg_daemon/eg_daemon.conf
# config: /etc/sysconfig/eg_daemon
# pidfile: /var/run/eg_daemon.pid
#
### BEGIN INIT INFO
# Provides: eg_daemon
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Short-Description: start and stop eg_daemon server
# Description: eg_daemon is a dummy Python-based daemon
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

if [ -f /etc/sysconfig/eg_daemon ]; then
        . /etc/sysconfig/eg_daemon
fi

eg_daemon=/var/lib/eg_daemon/eg_daemon.py
prog=eg_daemon
pidfile=${PIDFILE-/var/run/eg_daemon.pid}
logfile=${LOGFILE-/var/log/eg_daemon.log}
RETVAL=0

OPTIONS=""

start() {
        echo -n $"Starting $prog: "

        if [[ -f ${pidfile} ]] ; then
            pid=$( cat $pidfile  )
            isrunning=$( ps -elf | grep  $pid | grep $prog | grep -v grep )

            if [[ -n ${isrunning} ]] ; then
                echo $"$prog already running"
                return 0
            fi
        fi
        $eg_daemon -p $pidfile -l $logfile $OPTIONS
        RETVAL=$?
        [ $RETVAL = 0 ] && success || failure
        echo
        return $RETVAL
}

stop() {
    if [[ -f ${pidfile} ]] ; then
        pid=$( cat $pidfile )
        isrunning=$( ps -elf | grep $pid | grep $prog | grep -v grep | awk '{print $4}' )

        if [[ ${isrunning} -eq ${pid} ]] ; then
            echo -n $"Stopping $prog: "
            kill $pid
        else
            echo -n $"Stopping $prog: "
            success
        fi
        RETVAL=$?
    fi
    echo
    return $RETVAL
}

reload() {
    echo -n $"Reloading $prog: "
    echo
}

# See how we were called.
case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
    status -p $pidfile $eg_daemon
    RETVAL=$?
    ;;
  restart)
    stop
    start
    ;;
  force-reload|reload)
    reload
    ;;
  *)
    echo $"Usage: $prog {start|stop|restart|force-reload|reload|status}"
    RETVAL=2
esac

exit $RETVAL
9
phzx_munki

Un exemple complet est disponible ici .

Vous devriez être capable de mieux comprendre le fonctionnement interne de python-daemon.

De plus, le code fourni donne également un exemple de script init pour simplement démarrer/arrêter le démon. Cependant, vous pouvez le démarrer/l'arrêter simplement en appelant à nouveau la fonction d'origine avec l'argument stop:

python original_func.py stop
5
gromain

Comme vous pouvez le voir dans le «avec» documentation de déclaration , cette déclaration effectue un peu de «magie», qui est liée à notre objectif. Plus précisément:

L'exécution de l'instruction with avec un «élément» se poursuit sous la forme suit:

  1. L'expression de contexte (l'expression donnée dans le paramètre with_item) est évaluée pour obtenir un gestionnaire de contexte.

  2. __exit__() du gestionnaire de contexte est chargé pour une utilisation ultérieure.

  3. la méthode __enter__() du gestionnaire de contexte est appelée.

  4. Si une cible était incluse dans l'instruction with, la valeur de retour de __enter__() lui est affectée.

  5. La suite est exécutée.

  6. La méthode __exit__() du gestionnaire de contexte est appelée. Si une exception entraîne la sortie de la suite, son type, sa valeur et traceback sont passés en arguments à __exit__(). Sinon, trois Aucune les arguments sont fournis.

Qu'est-ce que ça veut dire? Si vous regardez de près le le PEP en question , qui sert également de documentation python-daemon (et qui pourrait en réalité être beaucoup améliorée), vous verrez qu'il implémente __enter__() et __exit__():

La classe implémente également le protocole du gestionnaire de contexte via __enter__ et __exit__ méthodes.

__enter__()

Appelez la méthode open () de l'instance, puis renvoyez-la.

__exit__(exc_type, exc_value, exc_traceback)

Appelez la méthode close () de l'instance, puis retournez True si l'exception a été gérée ou False si elle ne l'a pas été.

En d'autres termes, open () n'est pas nécessaire, l'exemple donné dans le PEP (bien que non expliqué correctement) fonctionne tel quel. Bien que l'instruction with veuille dire quelque chose, elle ne conserve aucune boucle. Une fois que la fin de son étendue est atteinte, elle appelle exit (), ce qui en démon-python signifie close (). Par conséquent, vous devez y placer un moment True ou la boucle infinie que vous envisagez. 

Si votre deuxième script ne fonctionne pas, je ne peux pas vous le dire, je suis surpris que le premier fonctionne déjà. Si votre démon est en train de s'arrêter, il y a certainement un problème avec vos scripts, vous pouvez vérifier votre consumerDaemonLogFile. (en note de bas de page, vous avez une faute de frappe 'sderr' -> 'stderr')

En outre, vous pouvez voir dans le PEP que, si elle n'est pas spécifiée, la propriété du répertoire de travail est définie par défaut sur '/'. cela pourrait être la source de votre problème si vous utilisez des chemins relatifs dans vos scripts.

Enfin, à propos de la dernière question, vous pouvez facilement tuer votre démon en trouvant son PID:

ps ax | grep startConsumerDaemons.py

et en lui envoyant un SIGTERM:

kill <pid>

La réponse fournie par gromain fournit un moyen plus pratique de le démarrer et de l'arrêter, avec 'daemon.runner ()', mais sa configuration est bien plus compliquée. 

3
ArnauOrriols

Le constructeur daemon.DaemonContext accepte une option lockfile. Utilisez une bibliothèque lockfile qui enregistrera le PID du processus.

La bibliothèque recommandait à l'origine la classe lockfile.PIDLockFile, mais cette bibliothèque est maintenant obsolète sans être remplacée correctement. Mais vous pouvez implémenter un autre objet avec la même sémantique.

Ensuite, le PID du processus est trouvé simplement en lisant le contenu du fichier PID nommé. Utilisez ce PID pour envoyer des signaux à votre démon en cours d'exécution.

0
bignose

Il manque encore une documentation utile pour le module "python-daemon". J'ai personnellement renoncé à l'utiliser, et maintenant j'utilise avec succès le code démon de Sander Marechal - référencé dans cette réponse .

Je l'ai légèrement modifié pour pouvoir faire certaines choses lorsque vous appelez python testdaemon.py stop.

Voici le code .


Exemple d'utilisation:

import sys, daemon, time

class testdaemon(daemon.Daemon):
    def run(self):
        self.i = 0
        with open('test1.txt', 'w') as f:
            f.write(str(self.i))
        while True:
            self.i += 1
            time.sleep(1)

    def quit(self):
        with open('test2.txt', 'w') as f:
            f.write(str(self.i))

daemon = testdaemon()

if 'start' == sys.argv[1]: 
    daemon.start()
Elif 'stop' == sys.argv[1]: 
    daemon.stop()
Elif 'restart' == sys.argv[1]: 
    daemon.restart()
0
Basj

Sous Linux, vous pouvez arrêter le démon en lançant:

$ ps -x

et trouvez le PID qui correspond à votre démon, puis tuez le processus.

0
Joseph Feeney