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


175

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


1
SO 질문을 참조하십시오 : stackoverflow.com/questions/1423345/…
mjv

3
"특정 전자 우편 주소를 확인하고 새 전자 우편을 외부 프로그램으로 전달합니다"sendmail이하는 일이 아닙니까? 메일 별칭을 정의하여 메일 박스를 스크립트로 라우팅 할 수 있습니다. 메일 별칭을 사용하지 않는 이유는 무엇입니까?
S.Lott

2
최신 리눅스에서는 여기에 설명 된대로 모드로 시스템 systemd서비스를 만들 수 있습니다 . 참조 : freedesktop.org/software/systemd/man/systemd.service.htmldaemon
ccpizza

Linux 시스템이 systemd를 지원하는 경우 여기에 설명 된 방법을 사용 하십시오 .
gerardw

답변:


96

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

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

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

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

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


7
cronjob에 +1 질문에 로컬 메일 계정을 확인한다고 명시되어 있지 않으므로 메일 필터가 적용되지 않습니다.
John La Rooy

파이썬 프로그램에서 종료없이 루프를 사용한 다음 crontab목록에 등록하면 어떻게됩니까? .py시간 단위로 설정하면 종료되지 않는 많은 프로세스가 생성됩니까? 그렇다면 나는 이것이 데몬과 매우 비슷하다고 생각합니다.
Veck Hsiao

분당 한 번 이메일 확인을 확인하면 Cron이 확실한 해결 책임을 알 수 있습니다 (Cron에서 가장 낮은 시간 해상도 임). 하지만 10 초마다 이메일을 확인하려면 어떻게해야합니까? 쿼리를 60 번 실행하기 위해 Python 스크립트를 작성해야합니까? 즉, 50 초 후에 끝나고 10 초 후에 cron이 스크립트를 다시 시작하게합니까?
Mads Skjern

데몬 / 서비스로 작업하지는 않았지만 (OS / init / init.d / upstart 또는 호출 된) 데몬이 종료 / 충돌 할 때 데몬을 다시 시작해야한다는 인상을 받았습니다.
Mads Skjern

@VeckHsiao 예, crontab은 스크립트를 호출하므로 많은 파이썬 스크립트 인스턴스가 모든 사람들과 함께 호출됩니다 ....
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
시스템을 다시 시작할 때 다시 시작됩니까? 시스템이 다시 시작되면 프로세스가 바로 종료되기 때문에?
ShivaPrasad

58

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

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


3
Ditto Jorge Vargas의 의견. 코드를 살펴본 후 실제로는 훌륭한 코드 조각처럼 보이지만 문서와 예제가 완전히 없기 때문에 사용하기가 매우 어려워 대부분의 개발자가 더 나은 문서화 된 대안을 위해이를 무시할 것입니다.
Cerin

1
: 파이썬 3.5에서 제대로 작동하지 않을 것 같다 gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
마틴 토마스

예상대로 작동하지 않습니다. 그래도 좋을까요?
Harlin

유닉스! = 리눅스-이것이 문제가 될 수 있습니까?
Dana

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 세션에서 나가면 스크립트도 작동하지 않습니다!
David Okwii

@DavidOkwii nohup/ disown명령은 콘솔에서 프로세스를 분리하고 죽지 않습니다. 또는 당신은 init.d를 함께 시작할 수
pholat

14

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


방금 시도한 결과 프로세스가 시작되지만 데몬 화되지는 않습니다 (예 : 여전히 터미널에 연결되어 있음). update-rc.d를 실행하고 부팅시 실행되도록 설정하면 제대로 작동하지만 (이 스크립트를 실행할 때 연결된 터미널이 없다고 가정) 수동으로 호출하면 작동하지 않습니다. supervisord가 더 나은 솔루션 인 것 같습니다.
ryuusenshi

13

간단하고 지원되는 버전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()

1
시스템을 다시 시작할 때 다시 시작됩니까? 시스템이 다시 시작되면 프로세스가 바로 종료되기 때문에?
ShivaPrasad

@ShivaPrasad u가 답을 찾았습니까?
1UC1F3R616

시스템 재시작 후 재시작은 악마 기능이 아닙니다. cron, systemctl, 시작 후크 또는 기타 도구를 사용하여 시작시 앱을 실행하십시오.
fcm

1
@Kush yes 시스템을 다시 시작한 후 다시 시작하거나 systemd 함수를 사용한 명령과 같이 사용하고 싶었습니다.이 access.redhat.com/documentation/en-us/red_hat_enterprise_linux/…를
ShivaPrasad

@ShivaPrasad 감사 형제
1UC1F3R616

12

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

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


9

$nohup리눅스에서 명령을 사용하는 것은 어떻습니까?

Bluehost 서버에서 명령을 실행하는 데 사용합니다.

내가 틀렸다면 조언하십시오.


나는 또한 그것을 사용하여 매력처럼 작동합니다. "내가 틀렸다면 조언을 부탁한다."
Alexandre Mazel

5

터미널 (ssh 또는 다른 것)을 사용하고 있고 터미널에서 로그 아웃 한 후 오랜 시간 스크립트를 작동 시키려면 다음을 시도하십시오.

screen

apt-get install screen

내부에 가상 터미널을 만듭니다 (즉 abc). screen -dmS abc

이제 abc에 연결합니다 : screen -r abc

이제 파이썬 스크립트를 실행할 수 있습니다 : python keep_sending_mails.py

이제부터 터미널을 직접 닫을 수 있지만 파이썬 스크립트는 종료되지 않고 계속 실행됩니다.

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:파트가 대신 호출되지만 (이 경우에는 아무것도 없음) 루프는 계속 진행되어 계속 함수를 호출하려고 시도합니다.
sarbot

3

루프가 백그라운드 서비스로 연중 무휴로 실행되기를 원한다고 가정합니다.

라이브러리에 코드를 주입하지 않는 솔루션의 경우 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

이 솔루션을 추천합니다. method를 상속하고 재정의해야합니다 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 프레임 워크를 설치하는 것입니다 . 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 사용) . 부팅시 시작, 충돌시 다시 시작 등과 같은 모든 세부 정보를 처리합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.