Como fazer um script Python funcionar como um serviço ou daemon no Linux

175

Eu escrevi um script Python que verifica um determinado endereço de email e passa novos emails para um programa externo. Como posso executar esse script 24/7, como transformá-lo em daemon ou serviço no Linux. Também precisaria de um loop que nunca termine no programa ou isso pode ser feito apenas com a execução do código várias vezes?

adhanlon
fonte
1
Veja a pergunta SO: stackoverflow.com/questions/1423345/…
mjv
3
"verifica um determinado endereço de email e passa novos emails para um programa externo" Não é isso que o sendmail faz? Você pode definir o alias de email para rotear uma caixa de correio para um script. Por que você não está usando aliases de email para fazer isso?
214/09 S.Lott
2
Em um linux moderno, systemdvocê pode criar um serviço systemd no daemonmodo como descrito aqui . Veja também: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza
Se o sistema linux suportar systemd, use a abordagem descrita aqui .
Gerardw # 31/18

Respostas:

96

Você tem duas opções aqui.

  1. Faça um trabalho cron apropriado que chame seu script. Cron é um nome comum para um daemon GNU / Linux que periodicamente inicia scripts de acordo com uma programação definida. Você adiciona seu script a um crontab ou coloca um link simbólico em um diretório especial e o daemon lida com o trabalho de iniciá-lo em segundo plano. Você pode ler mais na Wikipedia. Existe uma variedade de daemons cron diferentes, mas seu sistema GNU / Linux já deve ter ele instalado.

  2. Use algum tipo de abordagem python (uma biblioteca, por exemplo) para que seu script possa se daemonizar. Sim, exigirá um loop de eventos simples (onde seus eventos são acionados por timer, possivelmente, fornecidos pela função sleep).

Eu não recomendaria que você escolha 2., porque você estaria, de fato, repetindo a funcionalidade cron. O paradigma do sistema Linux é permitir que várias ferramentas simples interajam e resolvam seus problemas. A menos que haja outras razões pelas quais você deve criar um daemon (além de ser acionado periodicamente), escolha a outra abordagem.

Além disso, se você usar daemonize com um loop e ocorrer uma falha, ninguém verificará o e-mail depois disso (como apontado por Ivan Nevostruev nos comentários a esta resposta). Embora o script seja adicionado como um trabalho cron, ele será acionado novamente.

P Shved
fonte
7
+1 ao cronjob. Eu não acho que a questão especifica que é verificação de uma conta de correio local, por isso filtros de correio não se aplicam
John La Rooy
O que acontece usa um loop sem término em um programa Python e depois o registra na crontablista? Se eu configurar isso .pypara o horário, ele criará muitos processos que nunca serão encerrados? Nesse caso, acho que isso seria muito bom.
Veck Hsiao
Percebo que o cron é uma solução óbvia se você verificar a verificação de e-mails uma vez por minuto (que é a menor resolução de tempo para o Cron). Mas e se eu quiser procurar e-mails a cada 10 segundos? Devo escrever o script Python para executar a consulta 60 vezes, o que significa que termina após 50 segundos e, em seguida, deixe o cron iniciar o script novamente 10 segundos depois?
Mads Skjern
Não trabalhei com daemons / serviços, mas fiquei com a impressão de que ele (OS / init / init.d / upstart ou como é chamado) cuida de reiniciar um daemon quando / se ele terminar / trava.
Mads Skjern
@VeckHsiao sim, crontab chama um script tantas instâncias do seu script python será chamado com todos seu loop ....
Pipo
71

Aqui está uma aula agradável tirada daqui :

#!/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
fonte
1
é reiniciado quando o sistema é reiniciado? porque quando o sistema foi reiniciado, o processo será eliminado, certo?
ShivaPrasad 12/09/19
58

Você deve usar a biblioteca python-daemon , ela cuida de tudo.

Do PyPI: Library para implementar um processo de daemon Unix bem comportado.

Prody
fonte
3
O mesmo comentário de Jorge Vargas. Depois de analisar o código, ele realmente parece um bom pedaço de código, mas a completa falta de documentos e exemplos dificulta o uso, o que significa que a maioria dos desenvolvedores o ignorará por razões de alternativas melhor documentadas.
Cerin
1
Parece não funcionar corretamente no Python 3.5: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma
Não está funcionando como esperado. Seria bom se isso acontecesse.
Harlin
Unix! = Linux - poderia ser esse o problema?
Dana
39

Você pode usar o fork () para desanexar o script do tty e continuar com a execução, da seguinte maneira:

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

Claro que você também precisa implementar um loop infinito, como

while 1:
  do_your_check()
  sleep(5)

Espero que você tenha começado.

jhwist
fonte
Olá, eu tentei isso e funciona para mim. Mas quando fecho o terminal ou saio da sessão ssh, o script também para de funcionar !!
precisa saber é o seguinte
Os comandos @DavidOkwii nohup/ disowndesanexariam o processo do console e não morreriam. Ou você pode iniciá-lo com init.d
pholat 7/17
14

Você também pode fazer o script python ser executado como um serviço usando um script de shell. Primeiro, crie um shell script para executar o script python como este (scriptname arbitary name)

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

agora crie um arquivo em /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

Agora você pode iniciar e parar seu script python usando o comando /etc/init.d/scriptname start ou stop.

Kishore K
fonte
Eu apenas tentei isso, e acontece que isso iniciará o processo, mas não será daemonizado (ou seja, ainda está conectado ao terminal). Provavelmente funcionaria bem se você executasse o update-rc.d e o executasse na inicialização (presumo que não exista nenhum terminal conectado quando esses scripts forem executados), mas não funcionará se você o invocar manualmente. Parece que supervisord pode ser uma solução melhor.
Ryuusenshi 23/05
13

Uma versão simples e suportada é Daemonize.

Instale-o no PyPI Package Index (PyPI):

$ pip install daemonize

e depois use 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
fonte
1
é reiniciado quando o sistema é reiniciado? porque quando o sistema foi reiniciado, o processo será eliminado, certo?
ShivaPrasad 12/09/19
@ShivaPrasad u encontrou a resposta para isso?
1UC1F3R616 12/01
reiniciar após uma reinicialização do sistema não é uma função demoníaca. use cron, systemctl, ganchos de inicialização ou outras ferramentas para executar seu aplicativo na inicialização.
fcm 15/01
1
@Kush sim eu queria reiniciar após reinicialização do sistema ou para uso como comandos que usei funções Systemd, Se quer tentar verificar isso access.redhat.com/documentation/en-us/red_hat_enterprise_linux/...
Shivaprasad
@ShivaPrasad Thanks bro
1UC1F3R616
12

croné claramente uma ótima opção para muitos propósitos. No entanto, ele não cria um serviço ou daemon conforme solicitado no OP. cronapenas executa trabalhos periodicamente (ou seja, o trabalho inicia e para) e não mais que uma vez / minuto. Há problemas com cron- por exemplo, se uma instância anterior do seu script ainda estiver em execução na próxima vez que o cronagendamento aparecer e iniciar uma nova instância, tudo bem? cronnão lida com dependências; ele apenas tenta iniciar um trabalho quando a programação exige.

Se você encontrar uma situação em que realmente precisa de um daemon (um processo que nunca para de ser executado), dê uma olhada supervisord. Ele fornece uma maneira simples de agrupar um script ou programa normal e não daemonizado e fazê-lo operar como um daemon. Essa é uma maneira muito melhor do que criar um daemon Python nativo.

Chris Johnson
fonte
9

que tal usar o $nohupcomando no linux?

Eu o uso para executar meus comandos no meu servidor Bluehost.

Por favor, informe se estou errado.

faisal00813
fonte
Eu também uso isso, funciona como um encanto. "Por favor, avise se eu estiver errado."
Alexandre Mazel
5

Se você estiver usando o terminal (ssh ou algo assim) e quiser manter um script de longa data funcionando após o logout do terminal, tente o seguinte:

screen

apt-get install screen

crie um terminal virtual dentro (abc): screen -dmS abc

agora nos conectamos ao abc: screen -r abc

Então, agora podemos executar o script python: python keep_sending_mails.py

a partir de agora, você pode fechar diretamente seu terminal; no entanto, o script python continuará sendo executado em vez de ser desligado

Como esse keep_sending_mails.pyPID é um processo filho da tela virtual, e não do terminal (ssh)

Se você quiser voltar e verificar o status de execução do script, poderá usar screen -r abcnovamente

Microos
fonte
2
Enquanto isso funciona, é muito rápido e sujo e deve ser evitado na produção
pcnate
3

Primeiro, leia os aliases de email. Um alias de correio fará isso dentro do sistema de correio sem que você precise brincar com daemons ou serviços ou qualquer coisa desse tipo.

Você pode escrever um script simples que será executado pelo sendmail sempre que uma mensagem de email for enviada para uma caixa de correio específica.

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

Se você realmente deseja escrever um servidor desnecessariamente complexo, pode fazer isso.

nohup python myscript.py &

Isso é tudo o que preciso. Seu script simplesmente faz um loop 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
S.Lott
fonte
6
O problema aqui é que do_the_work()pode travar o script e ninguém executá-lo novamente
Ivan Nevostruev
se a função do_the_work () travar, ela será chamada novamente após 10 minutos, pois apenas uma chamada de função gera um erro. Mas, em vez de travar o loop, apenas a tryparte falha e a except:parte será chamada (nesse caso, nada), mas o loop continuará e continuará tentando chamar a função.
22418 sarbot
3

Supondo que você realmente gostaria que seu loop fosse executado 24/7 como um serviço em segundo plano

Para uma solução que não envolve injetar seu código em bibliotecas, você pode simplesmente criar um modelo de serviço, pois está usando o linux:

insira a descrição da imagem aqui

Coloque esse arquivo na pasta de serviço daemon (normalmente /etc/systemd/system/) e instale-o usando os seguintes comandos systemctl (provavelmente exigirão privilégios de sudo):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Você pode então verificar se seu serviço está sendo executado usando o comando:

systemctl | grep running
Heitor Castro
fonte
2

Eu recomendaria esta solução. Você precisa herdar e substituir o 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
fonte
2

para criar alguma coisa que está sendo executada como serviço, você pode usar esta coisa:

A primeira coisa que você deve fazer é instalar a estrutura Cement : O trabalho de estrutura de cimento é um trabalho de estrutura de CLI que você pode implementar seu aplicativo nela.

interface de linha de comando do aplicativo:

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

Lembre-se de que seu aplicativo deve ser executado em um encadeamento para ser daemon

Para executar o aplicativo, basta fazer isso na linha de comando

python interface.py --help

Manouchehr Rasouli
fonte
1

Use qualquer gerenciador de serviços que seu sistema ofereça - por exemplo, no Ubuntu, use upstart . Isso tratará de todos os detalhes para você, como iniciar na inicialização, reiniciar na falha, etc.

Richard
fonte