[python] Linux에서 Python 스크립트를 서비스 또는 데몬처럼 실행되게하는 방법

특정 전자 메일 주소를 확인하고 새 전자 메일을 외부 프로그램으로 전달하는 Python 스크립트를 작성했습니다. Linux에서 데몬이나 서비스로 전환하는 것과 같이이 스크립트를 24/7로 실행하려면 어떻게해야합니까? 프로그램에서 끝나지 않는 루프가 필요합니까? 아니면 코드를 여러 번 다시 실행하여 수행 할 수 있습니까?



답변

여기에는 두 가지 옵션이 있습니다.

  1. 스크립트를 호출 하는 적절한 크론 작업 을 수행하십시오. Cron은 설정 한 일정에 따라 정기적으로 스크립트를 시작하는 GNU / Linux 데몬의 일반적인 이름입니다. 스크립트를 crontab에 추가하거나 해당 링크를 특수 디렉토리에 배치하면 데몬이 백그라운드에서 스크립트를 실행하는 작업을 처리합니다. Wikipedia에서 더 많은 내용읽을 수 있습니다 . 다양한 크론 데몬이 있지만 GNU / Linux 시스템에 이미 설치되어 있어야합니다.

  2. 스크립트가 자신을 데몬화할 수 있도록 일종의 파이썬 접근법 (예 : 라이브러리)을 사용하십시오. 그렇습니다. 간단한 이벤트 루프가 필요합니다 (이벤트가 타이머 트리거되고 슬립 기능이 제공하는 경우).

실제로 cron 기능을 반복하기 때문에 2를 선택하지 않는 것이 좋습니다. Linux 시스템 패러다임은 여러 간단한 도구가 상호 작용하고 문제를 해결하도록하는 것입니다. 주기적으로 트리거하는 것 외에도 데몬을 만들어야하는 다른 이유가 없다면 다른 방법을 선택하십시오.

또한 루프와 함께 daemonize를 사용하고 충돌이 발생하면 아무도이 메일을 확인하지 않습니다 ( Ivan Nevostruev 가이 답변에 대한 의견에서 지적한 것처럼 ). 스크립트가 크론 작업으로 추가되면 다시 트리거됩니다.


답변

여기 좋은 수업이 있습니다 :

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


답변

python-daemon 라이브러리를 사용해야하며 모든 것을 처리합니다.

PyPI : 라이브러리가 올바르게 작동하는 Unix 데몬 프로세스를 구현합니다.


답변

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)

이것이 시작되기를 바랍니다.


답변

파이썬 스크립트를 쉘 스크립트를 사용하여 서비스로 실행하도록 만들 수도 있습니다. 먼저 다음과 같은 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

이제 /etc/init.d/scriptname start 또는 stop 명령을 사용하여 Python 스크립트를 시작하고 중지 할 수 있습니다.


답변

간단하고 지원되는 버전Daemonize입니다.

Python 패키지 색인 (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()


답변

cron많은 목적을위한 훌륭한 선택입니다. 그러나 OP에서 요청한대로 서비스 나 데몬을 만들지 않습니다. cron주기적으로 작업을 실행하고 (작업 시작 및 중지를 의미), 1 회 / 분을 초과하지 않습니다. 문제가 있습니다 cron– 예를 들어, 스크립트의 이전 인스턴스가 여전히 다음 시간 실행중인 경우 cron일정 주위에 와서 출시 새로운 인스턴스를, 그 확인은? cron종속성을 처리하지 않습니다. 일정에 따라 작업을 시작하려고합니다.

실제로 데몬이 필요한 상황 (실행을 멈추지 않는 프로세스)을 찾으면를 살펴보십시오 supervisord. 일반적인 비 데몬 스크립트 또는 프로그램을 래퍼하고 데몬처럼 작동하게하는 간단한 방법을 제공합니다. 이것은 네이티브 파이썬 데몬을 만드는 것보다 훨씬 더 좋은 방법입니다.