Как заставить скрипт Python запускаться как сервис или демон в Linux


175

Я написал скрипт Python, который проверяет определенный адрес электронной почты и передает новые сообщения во внешнюю программу. Как я могу заставить этот скрипт выполняться 24/7, например, превратив его в демон или службу в Linux. Нужен ли мне цикл, который никогда не заканчивается в программе, или это можно сделать, просто повторяя код несколько раз?


1
См. ТАК вопрос: stackoverflow.com/questions/1423345/…
mjv

3
«проверяет определенный адрес электронной почты и передает новые сообщения во внешнюю программу». Разве это не то, что делает sendmail? Вы можете определить псевдоним почты для маршрутизации почтового ящика в скрипт. Почему вы не используете псевдонимы почты для этого?
S.Lott

2
На современном linux systemdвы можете создать сервис systemd в daemonрежиме, как описано здесь . См. Также: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza,

Если система Linux поддерживает systemd, используйте подход, описанный здесь .
Gerardw

Ответы:


96

У вас есть два варианта здесь.

  1. Сделайте правильную работу cron, которая вызывает ваш скрипт. Cron - это общее имя для демона GNU / Linux, который периодически запускает скрипты в соответствии с заданным вами расписанием. Вы добавляете свой скрипт в crontab или помещаете символическую ссылку на него в специальный каталог, и демон выполняет его запуск в фоновом режиме. Вы можете прочитать больше в Википедии. Существует множество различных демонов cron, но в вашей системе GNU / Linux она должна быть уже установлена.

  2. Используйте некоторый подход к Python (например, библиотеку), чтобы ваш сценарий мог демонизировать себя. Да, это потребует простой петли событий (где ваши события запускаются по таймеру, возможно, обеспечивается функцией сна).

Я бы не рекомендовал вам выбирать 2., потому что вы на самом деле повторяли бы функциональность cron. Системная парадигма Linux - позволить нескольким простым инструментам взаимодействовать и решать ваши проблемы. Если нет дополнительных причин, по которым вы должны создать демон (в дополнение к периодическому запуску), выберите другой подход.

Также, если вы используете daemonize с циклом и происходит сбой, никто не будет проверять почту после этого (как указал Иван Невоструев в комментариях к этому ответу). Хотя, если скрипт добавлен как задание cron, он просто сработает снова.


7
+1 к кроне. Я не думаю, что в вопросе указано, что он проверяет учетную запись локальной почты, поэтому почтовые фильтры не применяются
John La Rooy

Что произойдет, если в программе на Python использовать цикл без завершения, а затем зарегистрировать его в crontabсписке? Если я настрою такое .pyна почасовую работу, создаст ли оно много процессов, которые никогда не будут прекращены? Если это так, я думаю, что это очень похоже на демона.
Век Сяо

Я вижу, что cron является очевидным решением, если вы проверяете проверку писем раз в минуту (что является самым низким разрешением времени для Cron). Но что, если я хочу проверять электронную почту каждые 10 секунд? Должен ли я написать скрипт Python для выполнения запроса 60 раз, что означает, что он заканчивается через 50 секунд, а затем позволить cron запустить скрипт снова через 10 секунд?
Мэдс Скьерн

Я не работал с демонами / сервисами, но у меня сложилось впечатление, что он (OS / init / init.d / upstart или как он называется) позаботится о перезапуске демона, когда / если он заканчивается / падает.
Мадс Скьерн

@VeckHsiao да, crontab вызывает скрипт, так что все экземпляры вашего скрипта на python будут вызываться для каждого его цикла ....
Pipo

71

Вот хороший класс, который взят отсюда :

#!/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
это перезагрузка, когда система перезагружается? потому что когда система перезагрузится, процесс будет убит, верно?
ШиваПрасад

58

Вы должны использовать библиотеку python-daemon , она обо всем позаботится.

Из PyPI: библиотека для реализации хорошо работающего процесса демона Unix.


3
То же самое замечает Хорхе Варгас. Посмотрев на код, он на самом деле выглядит довольно неплохим фрагментом кода, но полное отсутствие документов и примеров делает его очень сложным в использовании, а это означает, что большинство разработчиков по праву игнорируют его для более документированных альтернатив.
Cerin

1
Кажется, не работает должным образом в Python 3.5: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Мартин Тома

Не работает, как ожидалось. Было бы неплохо, если бы это было так.
Харлин

Unix! = Linux - это может быть проблемой?
Дана

39

Вы можете использовать fork () для отсоединения вашего скрипта от tty и продолжения его работы, вот так:

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

Конечно, вам также нужно реализовать бесконечный цикл, как

while 1:
  do_your_check()
  sleep(5)

Надеюсь, ты начал.


Здравствуйте, я попробовал это, и это работает для меня. Но когда я закрываю терминал или выхожу из сеанса ssh, скрипт также перестает работать !!
Дэвид Окви

Команды @DavidOkwii nohup/ disownмогут отделить процесс от консоли, и он не умрет. Или вы можете начать его с
init.d

14

Вы также можете запустить скрипт Python как сервис, используя скрипт оболочки. Сначала создайте сценарий оболочки для запуска сценария Python, как этот (имя сценария произвольное имя)

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

теперь создайте файл в /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

Теперь вы можете запускать и останавливать ваш скрипт на python, используя команду /etc/init.d/scriptname start или stop.


Я только что попробовал это, и оказалось, что это запустит процесс, но он не будет демонизирован (т.е. он все еще подключен к терминалу). Вероятно, он будет работать нормально, если вы запустите update-rc.d и заставите его работать при загрузке (я предполагаю, что при запуске этих сценариев нет подключенного терминала), но он не будет работать, если вы вызываете его вручную. Похоже, надзор может быть лучшим решением.
Рюусенши

13

Простая и поддерживаются версией является Daemonize.

Установите его из Python Package Index (PyPI):

$ pip install daemonize

а затем использовать как:

...
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
это перезагрузка, когда система перезагружается? потому что когда система перезагрузится, процесс будет убит, верно?
ШиваПрасад

@ShivaPrasad ты нашел ответ на это?
1UC1F3R616

перезапуск после перезагрузки системы не является функцией демона. используйте cron, systemctl, ловушки запуска или другие инструменты для запуска приложения при запуске.
FCM

1
@Kush да я хотел перезапускать после перезагрузки системы или использования , как команды , я использовал Systemd функции, если хотите попробовать проверить эту access.redhat.com/documentation/en-us/red_hat_enterprise_linux/...
ShivaPrasad

@ShivaPrasad Спасибо, брат
1UC1F3R616

12

cronявно отличный выбор для многих целей. Однако он не создает службу или демон, как вы запросили в OP. cronпросто запускает задания периодически (то есть задание запускается и останавливается), и не чаще, чем раз в минуту. Есть проблемы с cron- например, если предыдущий экземпляр вашего скрипта все еще работает в следующий раз, когда cronрасписание приходит и запускает новый экземпляр, это нормально? cronне обрабатывает зависимости; он просто пытается начать работу, когда расписание говорит.

Если вы обнаружите ситуацию, когда вам действительно нужен демон (процесс, который никогда не останавливается), взгляните на него supervisord. Он предоставляет простой способ обернуть обычный, недемонизированный скрипт или программу и заставить его работать как демон. Это гораздо лучший способ, чем создание нативного демона Python.


9

как насчет использования $nohupкоманды в Linux?

Я использую его для запуска своих команд на моем сервере Bluehost.

Пожалуйста, совет, если я ошибаюсь.


Я также использую это, работает как шарм. «Пожалуйста, совет, если я ошибаюсь».
Александр Мазель

5

Если вы используете терминал (ssh или что-то еще) и хотите, чтобы после выхода из терминала долгое время работал скрипт, вы можете попробовать это:

screen

apt-get install screen

создать виртуальный терминал внутри (а именно abc): screen -dmS abc

Теперь мы подключаемся к ABC: screen -r abc

Итак, теперь мы можем запустить скрипт Python: python keep_sending_mails.py

отныне вы можете напрямую закрыть свой терминал, однако скрипт python будет продолжать работать, а не выключаться

Поскольку этот keep_sending_mails.pyPID является дочерним процессом виртуального экрана, а не терминала (ssh)

Если вы хотите вернуться и проверить состояние запущенного скрипта, вы можете использовать screen -r abcснова


2
хотя это работает, оно очень быстрое и грязное, и его следует избегать при производстве
pcnate

3

Сначала прочтите почтовые псевдонимы. Почтовый псевдоним будет делать это внутри почтовой системы без необходимости дурачиться с демонами или сервисами или чем-то в этом роде.

Вы можете написать простой скрипт, который будет выполняться sendmail каждый раз, когда почтовое сообщение отправляется на определенный почтовый ящик.

См. Http://www.feep.net/sendmail/tutorial/intro/aliases.html.

Если вы действительно хотите написать ненужно сложный сервер, вы можете сделать это.

nohup python myscript.py &

Это все, что нужно. Ваш сценарий просто зацикливается и спит.

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
Проблема в том, do_the_work()что сценарий может разбиться, и никто не сможет его запустить
Иван Невоструев,

в случае сбоя функции do_the_work () она будет вызвана снова через 10 минут, поскольку только один вызов функции вызывает ошибку. Но вместо сбоя цикла просто происходит tryсбой except:части, и вместо этого будет вызвана часть (в данном случае ничего), но цикл продолжится и будет пытаться вызвать функцию.
Сарбот

3

Предполагая, что вы действительно хотите, чтобы ваш цикл работал 24/7 в качестве фоновой службы

Для решения, которое не включает внедрение кода в библиотеки, вы можете просто создать шаблон службы, поскольку вы используете Linux:

введите описание изображения здесь

Поместите этот файл в папку службы демона (обычно /etc/systemd/system/) и установите его с помощью следующих команд systemctl (вероятно, потребуются права sudo):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Затем вы можете проверить, работает ли ваш сервис, с помощью команды:

systemctl | grep running

2

Я бы порекомендовал это решение. Вам нужно наследовать и переопределять метод 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

чтобы создать какую-то вещь, которая работает как сервис, вы можете использовать эту вещь:

Первое, что вы должны сделать, это установить инфраструктуру Cement : работа с цементом - это работа с CLI, в которой вы можете развернуть на ней свое приложение.

интерфейс командной строки приложения:

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

Класс 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

Имейте в виду, что ваше приложение должно работать в потоке, чтобы быть демоном

Чтобы запустить приложение, просто сделайте это в командной строке

python interface.py --help


1

Используйте любой сервисный менеджер, который предлагает ваша система - например, в Ubuntu используйте upstart . Это будет обрабатывать все детали для вас, такие как запуск при загрузке, перезапуск при сбое и т. Д.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.