web-dev-qa-db-ja.com

LinuxでPythonスクリプトをサービスまたはデーモンのように実行させる方法

特定の電子メールアドレスをチェックし、新しい電子メールを外部プログラムに渡すPythonスクリプトを作成しました。 Linuxでデーモンまたはサービスに変換するなど、このスクリプトを24時間365日実行するにはどうすればよいですか。また、プログラムで終わらないループが必要ですか、それともコードを複数回再実行するだけでできますか?

157
adhanlon

ここには2つのオプションがあります。

  1. スクリプトを呼び出す適切なcronジョブを作成します。 Cronは、設定したスケジュールに従って定期的にスクリプトを起動するGNU/Linuxデーモンの一般名です。スクリプトをcrontabに追加するか、スクリプトへのシンボリックリンクを特別なディレクトリに配置すると、デーモンがスクリプトをバックグラウンドで起動するジョブを処理します。 詳細 ウィキペディアでできます。さまざまなcronデーモンがありますが、GNU/Linuxシステムには既にインストールされている必要があります。

  2. スクリプトが自分自身をデーモン化できるようにするために、ある種のpythonアプローチ(たとえば、ライブラリ)を使用します。はい、単純なイベントループが必要になります(イベントは、おそらくスリープ機能によって提供されるタイマートリガーです)。

実際にはcron機能を繰り返しているため、2を選択することはお勧めしません。 Linuxシステムのパラダイムは、複数のシンプルなツールが相互作用して問題を解決できるようにすることです。 (定期的にトリガーすることに加えて)デーモンを作成する追加の理由がない限り、他のアプローチを選択します。

また、ループでデーモン化を使用してクラッシュが発生した場合、その後誰もメールをチェックしません( this answerへのコメントで Ivan Nevostruev で指摘されています)。一方、スクリプトがcronジョブとして追加された場合、再びトリガーされます。

90
P Shved

here から取得したNiceクラスを次に示します。

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

python-daemon ライブラリを使用する必要があります。すべての面倒を見てくれます。

PyPIから:適切に動作するUnixデーモンプロセスを実装するためのライブラリ。

50
Prody

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)

これがあなたが始めたことを願っています。

37
jhwist

シェルスクリプトを使用して、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

コマンド/etc/init.d/scriptname startまたはstopを使用して、pythonスクリプトを開始および停止できるようになりました。

13
Kishore K

cronは、明らかに多くの目的に最適です。ただし、OPで要求したサービスまたはデーモンは作成されません。 cronは、定期的に(ジョブの開始と停止を意味する)ジョブを実行するだけで、1分間に1回以下です。 cronには問題があります。たとえば、cronスケジュールが次回実行されて新しいインスタンスを起動するときに、スクリプトの前のインスタンスがまだ実行されている場合、それは大丈夫ですか? cronは依存関係を処理しません。スケジュールが指示したときにジョブを開始しようとします。

デーモン(実行を停止しないプロセス)が本当に必要な状況を見つけた場合は、supervisordを見てください。デーモン化されていない通常のスクリプトまたはプログラムをラップし、デーモンのように動作させる簡単な方法を提供します。これは、ネイティブPythonデーモンを作成するよりもはるかに優れた方法です。

11
Chris Johnson

シンプルで サポートされている バージョンは、Pythonパッケージインデックス(PyPI)からDeamonize Installです:

$ pip install daemonize

そして、次のように使用します:

...
import os, sys
from daemonize import Daemonize
...
def main()
      # your code here

if __== '__main__':
        myname=os.path.basename(sys.argv[0])
        pidfile='/tmp/%s' % myname       # any name
        daemon = Daemonize(app=myname,pid=pidfile, action=main)
        daemon.start()
9
fcm

linuxで$Nohupコマンドを使用してはどうですか?

Bluehostサーバーでコマンドを実行するために使用します。

私が間違っている場合はアドバイスしてください。

8
faisal00813

まず、メールエイリアスについて調べます。メールエイリアスはメールシステム内でこれを行います。デーモンやサービスなどをだます必要はありません。

メールメッセージが特定のメールボックスに送信されるたびに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
3
S.Lott

ターミナル(sshなど)を使用していて、ターミナルからログアウトした後も長時間のスクリプトを機能させたい場合は、これを試すことができます。

screen

apt-get install screen

内部に仮想端末を作成します(つまりabc):screen -dmS abc

ここでabcに接続します:screen -r abc

これで、pythonスクリプトを実行できるようになりました:python Keep_sending_mail.py

これからは、ターミナルを直接閉じることができますが、pythonスクリプトはシャットダウンされるのではなく実行され続けます

このKeep_sending_mail.pyのPIDは端末(ssh)ではなく仮想スクリーンに属しているため

戻ってスクリプトの実行ステータスを確認したい場合は、screen -r abcを再度使用できます

3
Microos

システムが提供するサービスマネージャーを使用します。たとえば、Ubuntuではpstartを使用します。これにより、起動時の起動、クラッシュ時の再起動など、すべての詳細が処理されます。

1
Richard

このソリューションをお勧めします。メソッド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()
1
Fomalhaut

サービスのように実行されているものを作成するには、このものを使用できます:

最初に行う必要があるのは、 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

0