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


175

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


1
SOの質問を参照してください:stackoverflow.com/questions/1423345/...
MJV

3
「特定の電子メールアドレスをチェックし、新しい電子メールを外部プログラムに渡す」sendmailが行うことではありませんか?メールエイリアスを定義して、メールボックスをスクリプトにルーティングできます。これを行うためにメールエイリアスを使用しないのはなぜですか?
S.Lott、2009年

2
持っている現代のLinux上でsystemdあなたがsystemdにサービスを作成することができますdaemon説明したようにモードここに。次も参照してください:freedesktop.org/software/systemd/man/systemd.service.html
ccpizza '11 / 09/18

Linuxシステムがsystemdをサポートしている場合は、ここで説明する方法使用してください
gerardw 2018年

回答:


96

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

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

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

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

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


7
cronjobに+1。質問ではローカルメールアカウントをチェックすることを指定しているとは思わないので、メールフィルターは適用されません
John La Rooy

Pythonプログラムで終了せずにループを使用し、それをcrontabリストに登録するとどうなりますか?.py時間単位で設定した場合、終了しないプロセスが多数作成されますか?もしそうなら、私はこれがかなりデーモンに似ていると思います。
Veck Hsiao

1分に1回メールのチェックをチェックすれば、cronが明白な解決策であることがわかります(これはCronの最小時間分解能です)。しかし、10秒ごとにメールをチェックしたい場合はどうなりますか?クエリを60回実行するためのPythonスクリプトを作成する必要があります。つまり、50秒後に終了し、cronに10秒後にスクリプトを再度開始させますか?
Mads Skjern 2016年

私はデーモン/サービスを扱っていませんが、デーモン(OS / init / init.d / upstartまたはそれが何と呼ばれるか)が、デーモンが終了/クラッシュしたときにデーモンの再起動を処理するという印象を受けました。
Mads Skjern 2016年

@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
システムが再起動したときに再起動しますか?システムが再起動したときにプロセスが正しく終了するからですか?
ShivaPrasad

58

あなたはpython-daemonライブラリを使用するべきです、それはすべてを処理します。

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


3
Ditto Jorge Vargasのコメント。コードを見ると、実際には非常に優れたコードのように見えますが、ドキュメントとサンプルが完全に不足しているため、使用が非常に難しくなっています。
セリン2012年

1
Python 3.5では正しく機能しないようです:gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma

期待どおりに動作していません。もしそうならそれはいいのでしょうか。
ハーリン

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セッションから抜けると、スクリプトも機能しなくなります。
David Okwii、2016年

@DavidOkwii nohup/ disownコマンドはプロセスをコンソールから切り離し、死にません。または、init.dで開始することもできます
pholat

14

シェルスクリプトを使用して、Pythonスクリプトをサービスとして実行することもできます。最初に、次のようなpythonスクリプトを実行するシェルスクリプトを作成します(scriptname任意の名前)

#!/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を実行してブート時に実行すると問題なく動作します(これらのスクリプトの実行時に端末が接続されていないと思います)が、手動で起動すると機能しません。スーパーバイザの方がより良いソリューションかもしれません。
リュウセンシ2014年

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

@ShivaPrasadあなたはそれに答えを見つけましたか?
1UC1F3R616

システムの再起動後の再起動は、デーモン機能ではありません。cron、systemctl、スタートアップフック、またはその他のツールを使用して、起動時にアプリを実行します。
fcm

1
@Kushはい、システムの再起動後に再起動するか、systemd関数を使用したようなコマンドを使用したい場合は、このアクセスを
documentation /

@ShivaPrasadありがとうbro
1UC1F3R616

12

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

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


9

$nohupLinuxでコマンドを使用するのはどうですか?

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.pyのPIDは、端末ではなく仮想画面の子プロセスであるため(ssh)

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


2
これは機能しますが、非常に速くて汚れているため、本番
環境で

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()、スクリプトをクラッシュさせて再度実行することができないことです
Ivan Nevostruev '21

関数do_the_work()がクラッシュした場合、1回の関数呼び出しだけでエラーが発生するため、10分後に再び呼び出されます。しかし、ループをクラッシュさせるのではなく、try一部だけが失敗し、except:代わりにその部分が呼び出されます(この場合は何も行われません)が、ループは継続し、関数の呼び出しを試み続けます。
sarbot

3

ループをバックグラウンドサービスとして24時間年中無休で実行したい場合

ライブラリをコードに挿入する必要がないソリューションの場合、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

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

あなたがしなければならない最初のことはセメントをインストールすることですフレームワークの。セメントフレームワークは、アプリケーションをデプロイできる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.