Comment faire exécuter un script Python comme un service ou un démon sous Linux

175

J'ai écrit un script Python qui vérifie une certaine adresse e-mail et transmet les nouveaux e-mails à un programme externe. Comment puis-je faire exécuter ce script 24h / 24 et 7j / 7, par exemple en le transformant en démon ou en service sous Linux. Aurais-je également besoin d'une boucle qui ne se termine jamais dans le programme, ou cela peut-il être fait en faisant simplement ré-exécuter le code plusieurs fois?

Adhanlon
la source
1
Voir la question SO: stackoverflow.com/questions/1423345/…
mjv
3
"vérifie une certaine adresse e-mail et transmet les nouveaux e-mails à un programme externe" N'est-ce pas ce que fait sendmail? Vous pouvez définir un alias de messagerie pour acheminer une boîte aux lettres vers un script. Pourquoi n'utilisez-vous pas d'alias de messagerie pour cela?
S.Lott
2
Sur un Linux moderne qui a, systemdvous pouvez créer un service systemd en daemonmode comme décrit ici . Voir aussi: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza
Si le système Linux prend en charge systemd, utilisez l'approche décrite ici .
gerardw

Réponses:

96

Vous avez deux options ici.

  1. Faites un travail cron approprié qui appelle votre script. Cron est un nom commun pour un démon GNU / Linux qui lance périodiquement des scripts selon une planification que vous avez définie. Vous ajoutez votre script dans une crontab ou placez un lien symbolique vers celui-ci dans un répertoire spécial et le démon se charge de le lancer en arrière-plan. Vous pouvez en savoir plus sur Wikipedia. Il existe une variété de démons cron différents, mais votre système GNU / Linux devrait l'avoir déjà installé.

  2. Utilisez une sorte d' approche python (une bibliothèque, par exemple) pour que votre script puisse se démoniser. Oui, cela nécessitera une simple boucle d'événements (où vos événements sont déclenchés par la minuterie, éventuellement, fournis par la fonction de veille).

Je ne vous recommanderais pas de choisir 2., car vous répéteriez en fait la fonctionnalité cron. Le paradigme du système Linux est de laisser plusieurs outils simples interagir et résoudre vos problèmes. Sauf s'il existe des raisons supplémentaires pour lesquelles vous devriez créer un démon (en plus de le déclencher périodiquement), choisissez l'autre approche.

De plus, si vous utilisez daemonize avec une boucle et qu'un crash se produit, personne ne vérifiera le courrier après cela (comme l'a souligné Ivan Nevostruev dans les commentaires de cette réponse). Alors que si le script est ajouté en tant que tâche cron, il se déclenchera à nouveau.

P Shved
la source
7
+1 au cronjob. Je ne pense pas que la question spécifie qu'elle vérifie un compte de messagerie local, donc les filtres de messagerie ne s'appliquent pas
John La Rooy
Que se passe-t-il utilise une boucle sans terminaison dans un programme Python, puis l'enregistre dans la crontabliste? Si je le configure .pytoutes les heures, cela créera-t-il de nombreux processus qui ne seront jamais terminés? Si c'est le cas, je pense que cela ressemblerait à un démon.
Veck Hsiao le
Je peux voir que cron est une solution évidente si vous vérifiez les e-mails une fois par minute (qui est la résolution de temps la plus basse pour Cron). Mais que faire si je veux vérifier les e-mails toutes les 10 secondes? Dois-je écrire le script Python pour exécuter la requête 60 fois, ce qui signifie qu'elle se termine après 50 secondes, puis laisser cron redémarrer le script 10 secondes plus tard?
Mads Skjern
Je n'ai pas travaillé avec des démons / services, mais j'avais l'impression qu'il (OS / init / init.d / upstart ou comment on l'appelle) s'occupe de redémarrer un démon quand / s'il se termine / plante.
Mads Skjern
@VeckHsiao oui, crontab appelle un script tant d'instances de votre script python seront appelées avec tout le monde sa boucle ....
Pipo
71

Voici une belle classe tirée d' ici :

#!/usr/bin/env python

import sys, os, time, atexit
from signal import SIGTERM

class Daemon:
        """
        A generic daemon class.

        Usage: subclass the Daemon class and override the run() method
        """
        def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
                self.stdin = stdin
                self.stdout = stdout
                self.stderr = stderr
                self.pidfile = pidfile

        def daemonize(self):
                """
                do the UNIX double-fork magic, see Stevens' "Advanced
                Programming in the UNIX Environment" for details (ISBN 0201563177)
                http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
                """
                try:
                        pid = os.fork()
                        if pid > 0:
                                # exit first parent
                                sys.exit(0)
                except OSError, e:
                        sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
                        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, e:
                        sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
                        sys.exit(1)

                # redirect standard file descriptors
                sys.stdout.flush()
                sys.stderr.flush()
                si = file(self.stdin, 'r')
                so = file(self.stdout, 'a+')
                se = file(self.stderr, 'a+', 0)
                os.dup2(si.fileno(), sys.stdin.fileno())
                os.dup2(so.fileno(), sys.stdout.fileno())
                os.dup2(se.fileno(), sys.stderr.fileno())

                # write pidfile
                atexit.register(self.delpid)
                pid = str(os.getpid())
                file(self.pidfile,'w+').write("%s\n" % pid)

        def delpid(self):
                os.remove(self.pidfile)

        def start(self):
                """
                Start the daemon
                """
                # Check for a pidfile to see if the daemon already runs
                try:
                        pf = file(self.pidfile,'r')
                        pid = int(pf.read().strip())
                        pf.close()
                except IOError:
                        pid = None

                if pid:
                        message = "pidfile %s already exist. Daemon already running?\n"
                        sys.stderr.write(message % self.pidfile)
                        sys.exit(1)

                # Start the daemon
                self.daemonize()
                self.run()

        def stop(self):
                """
                Stop the daemon
                """
                # Get the pid from the pidfile
                try:
                        pf = file(self.pidfile,'r')
                        pid = int(pf.read().strip())
                        pf.close()
                except IOError:
                        pid = None

                if not pid:
                        message = "pidfile %s does not exist. Daemon not running?\n"
                        sys.stderr.write(message % self.pidfile)
                        return # not an error in a restart

                # Try killing the daemon process       
                try:
                        while 1:
                                os.kill(pid, SIGTERM)
                                time.sleep(0.1)
                except OSError, err:
                        err = str(err)
                        if err.find("No such process") > 0:
                                if os.path.exists(self.pidfile):
                                        os.remove(self.pidfile)
                        else:
                                print str(err)
                                sys.exit(1)

        def restart(self):
                """
                Restart the daemon
                """
                self.stop()
                self.start()

        def run(self):
                """
                You should override this method when you subclass Daemon. It will be called after the process has been
                daemonized by start() or restart().
                """
la_drow
la source
1
est-il redémarré lorsque le système est redémarré? parce que lorsque le système redémarrera, le processus sera tué, non?
ShivaPrasad le
58

Vous devriez utiliser la bibliothèque python-daemon , elle s'occupe de tout.

De PyPI: Library pour implémenter un processus démon Unix bien comporté.

Prody
la source
3
Commentaire de Ditto Jorge Vargas. Après avoir regardé le code, cela ressemble en fait à un bon morceau de code, mais le manque complet de documents et d'exemples le rend très difficile à utiliser, ce qui signifie que la plupart des développeurs l'ignoreront à juste titre pour des alternatives mieux documentées.
Cerin
1
Semble ne pas fonctionner correctement dans Python 3.5: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma
Ne fonctionne pas comme prévu. Serait-ce bien si c'était le cas.
Harlin
Unix! = Linux - cela pourrait-il être le problème?
Dana
39

Vous pouvez utiliser fork () pour détacher votre script du tty et le faire continuer à s'exécuter, comme ceci:

import os, sys
fpid = os.fork()
if fpid!=0:
  # Running as daemon now. PID is fpid
  sys.exit(0)

Bien sûr, vous devez également implémenter une boucle sans fin, comme

while 1:
  do_your_check()
  sleep(5)

J'espère que vous avez commencé.

jhwist
la source
Bonjour, j'ai essayé cela et cela fonctionne pour moi. Mais quand je ferme le terminal ou que je sors de la session ssh, le script cesse également de fonctionner !!
David Okwii
Les commandes @DavidOkwii nohup/ disowndétacheraient le processus de la console et il ne mourrait pas. Ou vous pouvez le démarrer avec init.d
pholat
14

Vous pouvez également exécuter le script python en tant que service à l'aide d'un script shell. Commencez par créer un script shell pour exécuter le script python comme celui-ci (nom du script nom arbitraire)

#!/bin/sh
script='/home/.. full path to script'
/usr/bin/python $script &

créez maintenant un fichier dans /etc/init.d/scriptname

#! /bin/sh

PATH=/bin:/usr/bin:/sbin:/usr/sbin
DAEMON=/home/.. path to shell script scriptname created to run python script
PIDFILE=/var/run/scriptname.pid

test -x $DAEMON || exit 0

. /lib/lsb/init-functions

case "$1" in
  start)
     log_daemon_msg "Starting feedparser"
     start_daemon -p $PIDFILE $DAEMON
     log_end_msg $?
   ;;
  stop)
     log_daemon_msg "Stopping feedparser"
     killproc -p $PIDFILE $DAEMON
     PID=`ps x |grep feed | head -1 | awk '{print $1}'`
     kill -9 $PID       
     log_end_msg $?
   ;;
  force-reload|restart)
     $0 stop
     $0 start
   ;;
  status)
     status_of_proc -p $PIDFILE $DAEMON atd && exit 0 || exit $?
   ;;
 *)
   echo "Usage: /etc/init.d/atd {start|stop|restart|force-reload|status}"
   exit 1
  ;;
esac

exit 0

Vous pouvez maintenant démarrer et arrêter votre script python en utilisant la commande /etc/init.d/scriptname start ou stop.

Kishore K
la source
J'ai juste essayé ceci, et il s'avère que cela lancera le processus, mais il ne sera pas démonisé (c'est-à-dire qu'il est toujours attaché au terminal). Cela fonctionnerait probablement bien si vous exécutiez update-rc.d et le faisiez fonctionner au démarrage (je suppose qu'il n'y a pas de terminal attaché lorsque ces scripts sont exécutés), mais cela ne fonctionne pas si vous l'appelez manuellement. On dirait que supervisord pourrait être une meilleure solution.
ryuusenshi
13

Une version simple et prise en charge est Daemonize.

Installez-le à partir de Python Package Index (PyPI):

$ pip install daemonize

puis utilisez comme:

...
import os, sys
from daemonize import Daemonize
...
def main()
      # your code here

if __name__ == '__main__':
        myname=os.path.basename(sys.argv[0])
        pidfile='/tmp/%s' % myname       # any name
        daemon = Daemonize(app=myname,pid=pidfile, action=main)
        daemon.start()
fcm
la source
1
est-il redémarré lorsque le système est redémarré? parce que lorsque le système redémarrera, le processus sera tué, non?
ShivaPrasad
@ShivaPrasad vous avez trouvé la réponse?
1UC1F3R616
le redémarrage après un redémarrage du système n'est pas une fonction démon. utilisez cron, systemctl, hooks de démarrage ou d'autres outils pour exécuter votre application au démarrage.
fcm
1
@Kush oui, je voulais redémarrer après le redémarrage du système ou utiliser des commandes similaires.J'ai utilisé des fonctions systemd, si vous voulez essayer, vérifiez ceci access.redhat.com/documentation/en-us/red_hat_enterprise_linux/…
ShivaPrasad
@ShivaPrasad Merci frère
1UC1F3R616
12

cronest clairement un excellent choix à de nombreuses fins. Cependant, il ne crée pas de service ou de démon comme vous l'avez demandé dans l'OP. cronexécute uniquement les travaux périodiquement (ce qui signifie que le travail démarre et s'arrête), et pas plus d'une fois / minute. Il y a des problèmes avec cron- par exemple, si une instance précédente de votre script est toujours en cours d'exécution la prochaine fois que le croncalendrier arrive et lance une nouvelle instance, est-ce OK? cronne gère pas les dépendances; il essaie simplement de démarrer un travail lorsque le calendrier le demande.

Si vous trouvez une situation où vous avez vraiment besoin d'un démon (un processus qui ne s'arrête jamais), jetez un œil à supervisord. Il fournit un moyen simple d'encapsuler un script ou un programme normal et non démonisé et de le faire fonctionner comme un démon. C'est un bien meilleur moyen que de créer un démon Python natif.

Chris Johnson
la source
9

que diriez-vous d'utiliser la $nohupcommande sur linux?

Je l'utilise pour exécuter mes commandes sur mon serveur Bluehost.

S'il vous plaît aviser si je me trompe.

faisal00813
la source
J'utilise ça aussi, ça marche comme un charme. "S'il vous plaît, conseil si je me trompe."
Alexandre Mazel le
5

Si vous utilisez un terminal (ssh ou quelque chose du genre) et que vous souhaitez conserver un script de longue durée après vous être déconnecté du terminal, vous pouvez essayer ceci:

screen

apt-get install screen

créer un terminal virtuel à l'intérieur (à savoir abc): screen -dmS abc

maintenant nous nous connectons à abc: screen -r abc

Donc, maintenant, nous pouvons exécuter un script python: python keep_sending_mails.py

à partir de maintenant, vous pouvez fermer directement votre terminal, cependant, le script python continuera à s'exécuter au lieu d'être arrêté

Puisque ce keep_sending_mails.pyPID est un processus enfant de l 'écran virtuel plutôt que du terminal (ssh)

Si vous souhaitez revenir vérifier l'état d'exécution de votre script, vous pouvez utiliser à screen -r abcnouveau

Microos
la source
2
bien que cela fonctionne, il est très rapide et sale et devrait être évité en production
pcnate
3

Tout d'abord, lisez les alias de messagerie. Un alias de messagerie le fera dans le système de messagerie sans que vous ayez à vous amuser avec des démons ou des services ou quoi que ce soit de ce genre.

Vous pouvez écrire un script simple qui sera exécuté par sendmail chaque fois qu'un message électronique est envoyé à une boîte aux lettres spécifique.

Voir http://www.feep.net/sendmail/tutorial/intro/aliases.html

Si vous voulez vraiment écrire un serveur inutilement complexe, vous pouvez le faire.

nohup python myscript.py &

C'est tout ce qu'il faut. Votre script boucle et s'endort simplement.

import time
def do_the_work():
    # one round of polling -- checking email, whatever.
while True:
    time.sleep( 600 ) # 10 min.
    try:
        do_the_work()
    except:
        pass
S.Lott
la source
6
Le problème ici est que do_the_work()le script peut planter et que personne ne l'exécute à nouveau
Ivan Nevostruev
si la fonction do_the_work () tombe en panne, elle sera à nouveau appelée après 10 minutes, car seul l'appel d'une fonction déclenche une erreur. Mais au lieu de planter la boucle, juste la trypartie échoue et la except:partie sera appelée à la place (dans ce cas rien) mais la boucle continuera et continuera d'essayer d'appeler la fonction.
sarbot
3

En supposant que vous souhaitiez vraiment que votre boucle fonctionne 24h / 24 et 7j / 7 en tant que service d'arrière-plan

Pour une solution qui n'implique pas d'injecter votre code avec des bibliothèques, vous pouvez simplement créer un modèle de service, puisque vous utilisez Linux:

entrez la description de l'image ici

Placez ce fichier dans votre dossier de service démon (généralement /etc/systemd/system/) et installez-le à l'aide des commandes systemctl suivantes (cela nécessitera probablement les privilèges sudo):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Vous pouvez ensuite vérifier que votre service est en cours d'exécution à l'aide de la commande:

systemctl | grep running
Heitor Castro
la source
2

Je recommanderais cette solution. Vous devez hériter et remplacer la méthode run.

import sys
import os
from signal import SIGTERM
from abc import ABCMeta, abstractmethod



class Daemon(object):
    __metaclass__ = ABCMeta


    def __init__(self, pidfile):
        self._pidfile = pidfile


    @abstractmethod
    def run(self):
        pass


    def _daemonize(self):
        # decouple threads
        pid = os.fork()

        # stop first thread
        if pid > 0:
            sys.exit(0)

        # write pid into a pidfile
        with open(self._pidfile, 'w') as f:
            print >> f, os.getpid()


    def start(self):
        # if daemon is started throw an error
        if os.path.exists(self._pidfile):
            raise Exception("Daemon is already started")

        # create and switch to daemon thread
        self._daemonize()

        # run the body of the daemon
        self.run()


    def stop(self):
        # check the pidfile existing
        if os.path.exists(self._pidfile):
            # read pid from the file
            with open(self._pidfile, 'r') as f:
                pid = int(f.read().strip())

            # remove the pidfile
            os.remove(self._pidfile)

            # kill daemon
            os.kill(pid, SIGTERM)

        else:
            raise Exception("Daemon is not started")


    def restart(self):
        self.stop()
        self.start()
Fomalhaut
la source
2

pour créer quelque chose qui fonctionne comme un service, vous pouvez utiliser cette chose:

La première chose que vous devez faire est d'installer le framework Cement : le cadre de travail en ciment est un travail de cadre CLI sur lequel vous pouvez déployer votre application.

interface de ligne de commande de l'application:

interface.py

 from cement.core.foundation import CementApp
 from cement.core.controller import CementBaseController, expose
 from YourApp import yourApp

 class Meta:
    label = 'base'
    description = "your application description"
    arguments = [
        (['-r' , '--run'],
          dict(action='store_true', help='Run your application')),
        (['-v', '--version'],
          dict(action='version', version="Your app version")),
        ]
        (['-s', '--stop'],
          dict(action='store_true', help="Stop your application")),
        ]

    @expose(hide=True)
    def default(self):
        if self.app.pargs.run:
            #Start to running the your app from there !
            YourApp.yourApp()
        if self.app.pargs.stop:
            #Stop your application
            YourApp.yourApp.stop()

 class App(CementApp):
       class Meta:
       label = 'Uptime'
       base_controller = 'base'
       handlers = [MyBaseController]

 with App() as app:
       app.run()

Classe YourApp.py:

 import threading

 class yourApp:
     def __init__:
        self.loger = log_exception.exception_loger()
        thread = threading.Thread(target=self.start, args=())
        thread.daemon = True
        thread.start()

     def start(self):
        #Do every thing you want
        pass
     def stop(self):
        #Do some things to stop your application

Gardez à l'esprit que votre application doit s'exécuter sur un thread pour être un démon

Pour exécuter l'application, faites-le simplement en ligne de commande

python interface.py --help

Manouchehr Rasouli
la source
1

Utilisez n'importe quel gestionnaire de services proposé par votre système - par exemple sous Ubuntu, utilisez upstart . Cela gérera tous les détails pour vous tels que le démarrage au démarrage, le redémarrage en cas de panne, etc.

Richard
la source