Cómo hacer que un script Python se ejecute como un servicio o demonio en Linux

175

Escribí un script de Python que verifica una determinada dirección de correo electrónico y pasa nuevos correos electrónicos a un programa externo. ¿Cómo puedo hacer que este script se ejecute 24/7, como convertirlo en demonio o servicio en Linux? ¿También necesitaría un bucle que nunca termina en el programa, o puede hacerse simplemente haciendo que el código se vuelva a ejecutar varias veces?

adhanlon
fuente
1
Vea la pregunta SO: stackoverflow.com/questions/1423345/…
mjv
3
"comprueba una determinada dirección de correo electrónico y pasa nuevos correos electrónicos a un programa externo" ¿No es eso lo que hace sendmail? Puede definir un alias de correo para enrutar un buzón a un script. ¿Por qué no usas alias de correo para hacer esto?
S.Lott
2
En un Linux moderno que tenga systemd, puede crear un servicio systemd en daemonmodo como se describe aquí . Ver también: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza
Si el sistema Linux es compatible con systemd, utilice el enfoque descrito aquí .
gerardw

Respuestas:

96

Tienes dos opciones aquí.

  1. Realice un trabajo cron adecuado que llame a su script. Cron es un nombre común para un demonio GNU / Linux que lanza scripts periódicamente de acuerdo con un cronograma establecido. Agregue su script a un crontab o coloque un enlace simbólico en un directorio especial y el demonio se encargará de iniciarlo en segundo plano. Puedes leer más en Wikipedia. Hay una variedad de demonios cron diferentes, pero su sistema GNU / Linux ya debería tenerlo instalado.

  2. Use algún tipo de enfoque de Python (una biblioteca, por ejemplo) para que su script pueda demonizarse a sí mismo. Sí, requerirá un bucle de eventos simple (donde sus eventos son disparadores de temporizador, posiblemente, proporcionados por la función de suspensión).

No recomendaría que elijas 2. porque estarías, de hecho, repitiendo la funcionalidad cron. El paradigma del sistema Linux es permitir que múltiples herramientas simples interactúen y resuelvan sus problemas. A menos que haya razones adicionales por las que debería hacer un demonio (además de disparar periódicamente), elija el otro enfoque.

Además, si usa daemonize con un bucle y ocurre un bloqueo, nadie revisará el correo después de eso (como lo señaló Ivan Nevostruev en los comentarios a esta respuesta). Mientras que si el script se agrega como un trabajo cron, simplemente se activará nuevamente.

P Shved
fuente
77
+1 al cronjob. No creo que la pregunta especifique que está revisando una cuenta de correo local, por lo que no se aplican los filtros de correo
John La Rooy
¿Qué sucede si usa un bucle sin terminación en un programa de Python y luego lo registrará en la crontablista? Si configuro tal .pypor hora, ¿creará muchos procesos que nunca terminarán? Si es así, creo que a este le gustaría un demonio.
Veck Hsiao
Puedo ver que cron es una solución obvia si verifica la verificación de correos electrónicos una vez por minuto (que es la resolución de tiempo más baja para Cron). Pero, ¿qué pasa si quiero verificar los correos electrónicos cada 10 segundos? ¿Debo escribir el script Python para ejecutar la consulta 60 veces, lo que significa que termina después de 50 segundos, y luego dejar que cron inicie el script nuevamente 10 segundos después?
Mads Skjern
No he trabajado con daemons / services, pero tenía la impresión de que (OS / init / init.d / upstart o cómo se llama) se encarga de reiniciar un daemon cuando / si termina / falla.
Mads Skjern
@VeckHsiao sí, crontab llama a un script, por lo que muchas instancias de su script de Python se llamarán con todos su ciclo ...
Pipo
71

Aquí hay una buena clase que se toma desde aquí :

#!/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().
                """
the_drow
fuente
1
¿Se reinicia cuando se reinicia el sistema? porque cuando el sistema se reinicie, el proceso se cancelará, ¿verdad?
ShivaPrasad
58

Deberías usar la biblioteca python-daemon , se encarga de todo.

Desde PyPI: Biblioteca para implementar un proceso de demonio Unix con buen comportamiento.

Prody
fuente
3
Comentario de Jorge Vargas. Después de mirar el código, en realidad parece un código bastante agradable, pero la falta total de documentos y ejemplos hace que sea muy difícil de usar, lo que significa que la mayoría de los desarrolladores lo ignorarán legítimamente para obtener alternativas mejor documentadas.
Cerin
1
Parece que no funciona correctamente en Python 3.5: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma
No funciona como se esperaba. Sería bueno si así fuera.
Harlin
Unix! = Linux: ¿podría ser este el problema?
Dana
39

Puede usar fork () para separar su script del tty y hacer que continúe ejecutándose, así:

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

Por supuesto, también debe implementar un bucle sin fin, como

while 1:
  do_your_check()
  sleep(5)

Espero que esto te ayude a comenzar.

jhwist
fuente
Hola, he probado esto y me funciona. ¡Pero cuando cierro el terminal o salgo de la sesión ssh, el script también deja de funcionar!
David Okwii el
@DavidOkwii nohup/ command disownsepararía el proceso de la consola y no morirá. O puede comenzar con init.d
pholat
14

También puede hacer que el script de Python se ejecute como un servicio utilizando un script de shell. Primero cree una secuencia de comandos de shell para ejecutar la secuencia de comandos de Python de esta manera (nombre de archivo nombre arbitrario)

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

ahora cree un archivo en /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

Ahora puede iniciar y detener su secuencia de comandos de Python usando el comando /etc/init.d/scriptname start o stop.

Kishore K
fuente
Acabo de intentar esto, y resulta que esto iniciará el proceso, pero no se demonizará (es decir, todavía está conectado al terminal). Probablemente funcionaría bien si ejecutó update-rc.d y lo hizo ejecutarse en el arranque (supongo que no hay un terminal conectado cuando se ejecutan estos scripts), pero no funciona si lo invoca manualmente. Parece que la supervisión podría ser una mejor solución.
ryuusenshi
13

Una versión simple y compatible esDaemonize .

Instálelo desde Python Package Index (PyPI):

$ pip install daemonize

y luego usar como:

...
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
fuente
1
¿Se reinicia cuando se reinicia el sistema? porque cuando el sistema se reinicie, el proceso se cancelará, ¿verdad?
ShivaPrasad
@ShivaPrasad, ¿has encontrado la respuesta?
1UC1F3R616
reiniciar después de reiniciar el sistema no es una función demoníaca. use cron, systemctl, enlaces de inicio u otras herramientas para ejecutar su aplicación en el inicio.
fcm
1
@Kush sí, quería reiniciar después de reiniciar el sistema o usar comandos similares. Utilicé las funciones de systemd. Si quiero probar, verifique esto access.redhat.com/documentation/en-us/red_hat_enterprise_linux/…
ShivaPrasad
@ShivaPrasad Gracias hermano
1UC1F3R616
12

cronEs claramente una gran opción para muchos propósitos. Sin embargo, no crea un servicio o demonio como lo solicitó en el OP. cronsolo ejecuta trabajos periódicamente (lo que significa que el trabajo comienza y se detiene), y no más de una vez / minuto. Hay problemas con cron, por ejemplo, si una instancia anterior de su secuencia de comandos todavía se está ejecutando la próxima vez que se cronproduce el cronograma y se inicia una nueva instancia, ¿está bien? cronno maneja dependencias; solo intenta comenzar un trabajo cuando el horario lo indica.

Si encuentra una situación en la que realmente necesita un demonio (un proceso que nunca deja de ejecutarse), eche un vistazo supervisord. Proporciona una manera simple de envolver un script o programa normal no demonizado y hacerlo funcionar como un demonio. Esta es una forma mucho mejor que crear un demonio nativo de Python.

Chris Johnson
fuente
9

¿ $nohupQué hay de usar el comando en Linux?

Lo uso para ejecutar mis comandos en mi servidor Bluehost.

Por favor, consejo si estoy equivocado.

faisal00813
fuente
Yo también uso eso, funciona como un encanto. "Por favor, consejo si estoy equivocado".
Alexandre Mazel
5

Si está utilizando la terminal (ssh o algo) y desea mantener un script de larga duración después de cerrar sesión en la terminal, puede intentar esto:

screen

apt-get install screen

crear una terminal virtual dentro (es decir, abc): screen -dmS abc

ahora nos conectamos a abc: screen -r abc

Entonces, ahora podemos ejecutar el script python: python keep_sending_mails.py

a partir de ahora, puede cerrar directamente su terminal, sin embargo, el script de Python seguirá ejecutándose en lugar de cerrarse

Dado que este keep_sending_mails.pyPID es un proceso secundario de la pantalla virtual en lugar de la terminal (ssh)

Si desea volver a comprobar el estado de ejecución de su script, puede screen -r abcvolver a usar

Microos
fuente
2
Si bien esto funciona, es muy rápido y sucio y debe evitarse en la producción
pcnate
3

Primero, lea sobre alias de correo. Un alias de correo hará esto dentro del sistema de correo sin que tenga que perder el tiempo con demonios o servicios ni nada por el estilo.

Puede escribir un script simple que se ejecutará mediante sendmail cada vez que se envíe un mensaje de correo a un buzón específico.

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

Si realmente desea escribir un servidor innecesariamente complejo, puede hacerlo.

nohup python myscript.py &

Eso es todo lo que se necesita. Tu guión simplemente gira y duerme.

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
fuente
66
El problema aquí es que do_the_work()puede bloquear el script y que nadie vuelva a ejecutarlo
Ivan Nevostruev
si la función do_the_work () falla, se volverá a llamar después de 10 minutos, ya que solo la llamada a una función genera un error. Pero en lugar de bloquear el bucle, solo la tryparte falla y la except:parte se llamará en su lugar (en este caso nada), pero el bucle continuará y seguirá intentando llamar a la función.
sarbot
3

Suponiendo que realmente desea que su bucle se ejecute 24/7 como un servicio en segundo plano

Para una solución que no implica inyectar su código con bibliotecas, simplemente puede crear una plantilla de servicio, ya que está utilizando Linux:

ingrese la descripción de la imagen aquí

Coloque ese archivo en su carpeta de servicio de daemon (generalmente /etc/systemd/system/) e instálelo utilizando los siguientes comandos systemctl (probablemente requerirá privilegios de sudo):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Luego puede verificar que su servicio se esté ejecutando mediante el comando:

systemctl | grep running
Heitor Castro
fuente
2

Yo recomendaría esta solución. Necesita heredar y anular el método 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
fuente
2

para crear algo que se ejecuta como servicio, puede usar esto:

Lo primero que debe hacer es instalar el cemento marco de : el marco de cemento es un marco de CLI que puede implementar su aplicación en él.

interfaz de línea de comando de la aplicación:

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

Clase 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

Tenga en cuenta que su aplicación debe ejecutarse en un hilo para ser demonio

Para ejecutar la aplicación solo haz esto en la línea de comando

python interface.py --help

Manouchehr Rasouli
fuente
1

Use cualquier administrador de servicios que ofrezca su sistema, por ejemplo, en Ubuntu use upstart . Esto manejará todos los detalles para usted, como comenzar en el arranque, reiniciar en caso de accidente, etc.

Ricardo
fuente