Come far funzionare uno script Python come un servizio o un demone in Linux


175

Ho scritto uno script Python che controlla un determinato indirizzo e-mail e passa nuove e-mail a un programma esterno. Come posso ottenere questo script per l'esecuzione 24/7, come trasformarlo in demone o servizio in Linux. Avrei anche bisogno di un ciclo che non finisca mai nel programma, o può essere fatto semplicemente eseguendo il codice più volte?



3
"controlla un determinato indirizzo e-mail e passa le nuove e-mail a un programma esterno" Non è quello che fa sendmail? È possibile definire un alias di posta per indirizzare una cassetta postale a uno script. Perché non stai usando gli alias di posta per farlo?
S. Lott,

2
Su un Linux moderno che systemdpuoi creare un servizio systemd in daemonmodalità come descritto qui . Vedi anche: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza,

Se il sistema Linux supporta systemd, utilizzare l'approccio descritto qui .
Gerardw,

Risposte:


96

Hai due opzioni qui.

  1. Crea un cron job adeguato che chiama il tuo script. Cron è un nome comune per un demone GNU / Linux che lancia periodicamente script in base a una pianificazione impostata. Aggiungete il vostro script in un crontab o inserite un collegamento simbolico in una directory speciale e il demone gestisce il lavoro di avvio in background. Puoi leggere di più su Wikipedia. Esiste una varietà di demoni cron diversi, ma il tuo sistema GNU / Linux dovrebbe averlo già installato.

  2. Usa un qualche tipo di approccio Python (una libreria, ad esempio) affinché il tuo script sia in grado di demonizzare se stesso. Sì, richiederà un semplice ciclo di eventi (in cui i tuoi eventi attivano il timer, possibilmente, fornito dalla funzione sleep).

Non ti consiglierei di scegliere 2., perché, in effetti, ripeteresti la funzionalità cron. Il paradigma del sistema Linux è quello di consentire a più strumenti semplici di interagire e risolvere i tuoi problemi. A meno che non ci siano ulteriori motivi per cui dovresti creare un demone (oltre a innescare periodicamente), scegli l'altro approccio.

Inoltre, se usi daemonize con un loop e si verifica un arresto anomalo, nessuno controllerà la posta successiva (come sottolineato da Ivan Nevostruev nei commenti a questa risposta). Mentre se lo script viene aggiunto come cron job, si attiverà nuovamente.


7
+1 al cronjob. Non credo che la domanda specifichi che sta controllando un account di posta locale, quindi i filtri di posta non si applicano
John La Rooy,

Che cosa succede utilizza un loop senza terminazione in un programma Python e quindi registrarlo nella crontablista sarà? Se lo .pyinstallo per ogni ora, creerà molti processi che non verranno mai interrotti? Se è così, penso che questo sarebbe abbastanza demone.
Veck Hsiao,

Vedo che cron è una soluzione ovvia se controlli la presenza di e-mail una volta al minuto (che è la risoluzione temporale più bassa per Cron). E se volessi controllare la posta elettronica ogni 10 secondi? Devo scrivere lo script Python per eseguire la query 60 volte, il che significa che termina dopo 50 secondi e quindi consentire a cron di avviare nuovamente lo script 10 secondi dopo?
Mads Skjern

Non ho lavorato con demoni / servizi, ma avevo l'impressione che (OS / init / init.d / upstart o come si chiama) si occupasse di riavviare un demone quando / se termina / si arresta in modo anomalo.
Mads Skjern

@VeckHsiao sì, crontab chiama uno script così tante istanze del tuo script Python verranno chiamate con tutti i suoi loop ....
Pipo

71

Ecco una bella lezione che viene presa da qui :

#!/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().
                """

1
si riavvia al riavvio del sistema? perché quando il sistema ha riavviato il processo verrà ucciso giusto?
ShivaPrasad,

58

Dovresti usare la libreria python-daemon , si occupa di tutto.

Da PyPI: libreria per implementare un processo demone Unix ben educato.


3
Idem il commento di Jorge Vargas. Dopo aver esaminato il codice, in realtà sembra un bel pezzo di codice, ma la completa mancanza di documenti ed esempi rende molto difficile l'uso, il che significa che la maggior parte degli sviluppatori lo ignorerà giustamente per alternative meglio documentate.
Cerin,

1
Sembra non funzionare correttamente in Python 3.5: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma

Non funziona come previsto. Sarebbe bello se lo facesse però.
Harlin,

Unix! = Linux - potrebbe essere questo il problema?
Dana,

39

Puoi usare fork () per staccare il tuo script da tty e farlo continuare, in questo modo:

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

Ovviamente devi anche implementare un ciclo infinito, come

while 1:
  do_your_check()
  sleep(5)

Spero che tu abbia iniziato.


Ciao, ci ho provato e funziona per me. Ma quando chiudo il terminale o esco dalla sessione ssh, anche lo script smette di funzionare !!
David Okwii,

@DavidOkwii nohup/ disowncomandi staccerebbe il processo dalla console e non morirà. Oppure potresti avviarlo con init.d
pholat il

14

Puoi anche far funzionare lo script python come servizio usando uno script shell. Per prima cosa crea uno script shell per eseguire lo script python in questo modo (nome arbitrario nome script)

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

ora crea un file in /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

Ora puoi avviare e interrompere il tuo script Python usando il comando /etc/init.d/scriptname start o stop.


Ho appena provato questo, e si scopre che questo avvierà il processo, ma non sarà demonizzato (cioè è ancora collegato al terminale). Probabilmente funzionerebbe bene se avessi eseguito update-rc.d e l'avessi eseguito all'avvio (suppongo che non ci siano terminali collegati quando vengono eseguiti questi script), ma non funziona se lo invochi manualmente. Sembra che Supervord potrebbe essere una soluzione migliore.
Ryuusenshi,

13

Una versione semplice e supportata è Daemonize.

Installalo da Python Package Index (PyPI):

$ pip install daemonize

e quindi usare come:

...
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()

1
si riavvia al riavvio del sistema? perché quando il sistema ha riavviato il processo verrà ucciso giusto?
ShivaPrasad,

@ShivaPrasad hai trovato la risposta?
1UC1F3R616,

riavvio dopo un riavvio del sistema non è una funzione demone. usa cron, systemctl, hook di avvio o altri strumenti per eseguire la tua app all'avvio.
fcm

1
@Kush sì, ho voluto riavviare dopo il riavvio del sistema o utilizzare comandi simili che ho usato le funzioni di systemd, Se vuoi provare a controllare questo access.redhat.com/documentation/en-us/red_hat_enterprise_linux/…
ShivaPrasad

@ShivaPrasad Thanks bro
1UC1F3R616

12

cronè chiaramente un'ottima scelta per molti scopi. Tuttavia, non crea un servizio o un demone come richiesto nell'OP. cronesegue periodicamente i lavori (ovvero avvia e termina il lavoro) e non più spesso di una volta / minuto. Ci sono problemi con cron- per esempio, se un'istanza precedente del tuo script è ancora in esecuzione la prossima volta che la cronpianificazione arriva e avvia una nuova istanza, va bene? cronnon gestisce le dipendenze; cerca solo di iniziare un lavoro quando il programma dice a.

Se trovi una situazione in cui hai davvero bisogno di un demone (un processo che non si ferma mai), dai un'occhiata supervisord. Fornisce un modo semplice per racchiudere uno script o un programma normale, non daemonizzato e farlo funzionare come un demone. Questo è un modo molto migliore rispetto alla creazione di un demone nativo di Python.


9

che ne dici di usare il $nohupcomando su Linux?

Lo uso per eseguire i miei comandi sul mio server Bluehost.

Per favore, consigli se sbaglio.


Lo uso anche, funziona come un fascino. "Per favore, consigli se sbaglio."
Alexandre Mazel,

5

Se stai usando terminal (ssh o qualcosa del genere) e vuoi far funzionare uno script da molto tempo dopo esserti disconnesso dal terminale, puoi provare questo:

screen

apt-get install screen

creare un terminale virtuale all'interno (ovvero abc): screen -dmS abc

ora ci colleghiamo ad abc: screen -r abc

Quindi, ora possiamo eseguire script Python: python keep_sending_mails.py

da ora in poi, puoi chiudere direttamente il tuo terminale, tuttavia, lo script Python continuerà a funzionare invece di essere chiuso

Poiché questo keep_sending_mails.pyPID è un processo figlio dello schermo virtuale anziché del terminale (ssh)

Se vuoi tornare indietro controlla lo stato di esecuzione dello script, puoi usarlo di screen -r abcnuovo


2
mentre funziona, è molto veloce e sporco e dovrebbe essere evitato in produzione
pcnate

3

Innanzitutto, leggi gli alias di posta. Un alias di posta lo farà all'interno del sistema di posta senza che tu debba scherzare con demoni o servizi o qualcosa del genere.

Puoi scrivere un semplice script che verrà eseguito da sendmail ogni volta che un messaggio di posta viene inviato a una specifica casella di posta.

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

Se vuoi davvero scrivere un server inutilmente complesso, puoi farlo.

nohup python myscript.py &

Questo è tutto ciò che serve. La tua sceneggiatura semplicemente gira e dorme.

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

6
Il problema qui è che do_the_work()può mandare in crash la sceneggiatura e nessuno può eseguirla di nuovo
Ivan Nevostruev il

se la funzione do_the_work () si arresta in modo anomalo, verrà richiamata nuovamente dopo 10 minuti, poiché solo una chiamata di funzione genera un errore. Ma invece di interrompere il loop solo la tryparte fallisce e la except:parte verrà chiamata invece (in questo caso nulla) ma il loop continuerà e continuerà a provare a chiamare la funzione.
Sarbot

3

Supponendo che vorresti davvero che il tuo loop funzionasse 24/7 come servizio in background

Per una soluzione che non comporta l'iniezione del codice con le librerie, puoi semplicemente creare un modello di servizio, poiché stai utilizzando Linux:

inserisci qui la descrizione dell'immagine

Posiziona quel file nella cartella del servizio daemon (di solito /etc/systemd/system/) e installalo usando i seguenti comandi systemctl (probabilmente avrai bisogno dei privilegi di sudo):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

È quindi possibile verificare che il servizio sia in esecuzione utilizzando il comando:

systemctl | grep running

2

Consiglierei questa soluzione. È necessario ereditare e sovrascrivere il metodo 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()

2

per creare qualcosa che funziona come un servizio puoi usare questa cosa:

La prima cosa che devi fare è installare il framework Cement : il framework in cemento è un frame frame CLI su cui è possibile distribuire l'applicazione su di esso.

interfaccia a riga di comando dell'app:

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

Tieni presente che l'app deve essere eseguita su un thread per essere demone

Per eseguire l'app, basta farlo nella riga di comando

python interface.py --help


1

Usa qualsiasi gestore di servizi offerto dal tuo sistema, ad esempio sotto Ubuntu usa upstart . Questo gestirà tutti i dettagli per te come avvio all'avvio, riavvio in caso di arresto anomalo, ecc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.