Cara membuat skrip Python berjalan seperti layanan atau daemon di Linux

175

Saya telah menulis skrip Python yang memeriksa alamat email tertentu dan meneruskan email baru ke program eksternal. Bagaimana saya bisa membuat skrip ini dieksekusi 24/7, seperti mengubahnya menjadi daemon atau layanan di Linux. Apakah saya juga memerlukan perulangan yang tidak pernah berakhir di program, atau dapatkah itu dilakukan hanya dengan menjalankan kembali kode beberapa kali?

adhanlon
sumber
1
Lihat pertanyaan SO: stackoverflow.com/questions/1423345/…
mjv
3
"memeriksa alamat email tertentu dan meneruskan email baru ke program eksternal" Bukankah itu yang dilakukan oleh sendmail? Anda dapat menetapkan alias email untuk merutekan kotak surat ke skrip. Mengapa Anda tidak menggunakan alias email untuk melakukan ini?
S.Lott
2
Pada linux modern yang telah systemdAnda dapat membuat layanan systemd dalam daemonmode seperti dijelaskan di sini . Lihat juga: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza
Jika sistem linux mendukung systemd, gunakan pendekatan yang diuraikan di sini .
gerardw

Jawaban:

96

Anda memiliki dua opsi di sini.

  1. Buat pekerjaan cron yang tepat yang memanggil skrip Anda. Cron adalah nama umum untuk daemon GNU / Linux yang secara berkala meluncurkan skrip sesuai dengan jadwal yang Anda tetapkan. Anda menambahkan skrip Anda ke crontab atau menempatkan symlink ke dalamnya ke direktori khusus dan daemon menangani tugas meluncurkannya di latar belakang. Anda dapat membaca lebih lanjut di Wikipedia. Ada berbagai daemon cron yang berbeda, tetapi sistem GNU / Linux Anda seharusnya sudah diinstal.

  2. Gunakan semacam pendekatan python (pustaka, misalnya) agar skrip Anda dapat mengubah bentuknya sendiri. Ya, itu akan memerlukan loop acara sederhana (di mana acara Anda memicu timer, mungkin, disediakan oleh fungsi tidur).

Saya tidak akan merekomendasikan Anda untuk memilih 2., karena Anda akan, pada kenyataannya, mengulangi fungsionalitas cron. Paradigma sistem Linux adalah membiarkan beberapa alat sederhana berinteraksi dan menyelesaikan masalah Anda. Kecuali ada alasan tambahan mengapa Anda harus membuat daemon (selain memicu secara berkala), pilih pendekatan lain.

Juga, jika Anda menggunakan daemonize dengan loop dan crash terjadi, tidak ada yang akan memeriksa email setelah itu (seperti yang ditunjukkan oleh Ivan Nevostruev dalam komentar untuk jawaban ini ). Sementara jika skrip ditambahkan sebagai tugas cron, itu hanya akan memicu lagi.

P Shved
sumber
7
+1 ke cronjob. Saya tidak berpikir pertanyaan menentukan bahwa itu sedang memeriksa akun surat lokal, jadi filter surat tidak berlaku
John La Rooy
Apa yang terjadi tidak menggunakan loop tanpa penghentian dalam program Python dan kemudian mendaftarkannya ke dalam crontabdaftar? Jika saya mengaturnya .pyuntuk setiap jam, apakah ini akan menciptakan banyak proses yang tidak akan pernah dihentikan? Jika demikian, saya pikir ini akan seperti daemon.
Veck Hsiao
Saya dapat melihat bahwa cron adalah solusi yang jelas jika Anda memeriksa memeriksa email sekali dalam satu menit (yang merupakan resolusi waktu terendah untuk Cron). Tetapi bagaimana jika saya ingin memeriksa email setiap 10 detik? Haruskah saya menulis skrip Python untuk menjalankan kueri 60 kali, yang artinya berakhir setelah 50 detik, dan kemudian biarkan cron memulai skrip lagi 10 detik kemudian?
Mads Skjern
Saya belum bekerja dengan daemon / layanan, tetapi saya mendapat kesan bahwa itu (OS / init / init.d / pemula atau apa namanya) menangani restart daemon ketika / jika berakhir / crash.
Mads Skjern
@VeckHsiao ya, crontab memanggil sebuah skrip sehingga banyak contoh skrip python Anda akan dipanggil dengan semua orang loop-nya ....
Pipo
71

Berikut kelas bagus yang diambil dari sini :

#!/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().
                """
the_drow
sumber
1
apakah restart ketika sistem restart? karena ketika sistem me-restart proses akan mati kan?
ShivaPrasad
58

Anda harus menggunakan perpustakaan python-daemon , itu menangani semuanya.

Dari PyPI: Perpustakaan untuk menerapkan proses daemon Unix yang berperilaku baik.

Prody
sumber
3
Komentar Ditto Jorge Vargas. Setelah melihat kodenya, itu sebenarnya terlihat seperti sepotong kode yang bagus, tetapi kekurangan dokumen dan contoh membuatnya sangat sulit untuk digunakan, yang berarti sebagian besar pengembang akan mengabaikannya untuk alternatif yang lebih baik didokumentasikan.
Cerin
1
Tampaknya tidak berfungsi dengan baik di Python 3.5: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma
Tidak berfungsi seperti yang diharapkan. Akan lebih baik jika itu terjadi.
Harlin
Unix! = Linux - mungkinkah ini masalahnya?
Dana
39

Anda dapat menggunakan fork () untuk melepaskan skrip Anda dari tty dan membuatnya terus berjalan, seperti:

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

Tentu saja Anda juga perlu menerapkan loop tanpa akhir, seperti

while 1:
  do_your_check()
  sleep(5)

Semoga ini bisa Anda mulai.

jhwist
sumber
Halo, saya sudah mencoba ini dan berfungsi untuk saya. Tetapi ketika saya menutup terminal atau keluar dari sesi ssh, script juga berhenti bekerja !!
David Okwii
@ Davidvidi nohup/ disownperintah akan melepaskan proses dari konsol dan tidak akan mati. Atau Anda bisa memulainya dengan init.d
pholat
14

Anda juga dapat membuat skrip python dijalankan sebagai layanan menggunakan skrip shell. Pertama buat skrip shell untuk menjalankan skrip python seperti ini (nama arbiter scriptname)

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

sekarang buat file di /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

Sekarang Anda dapat memulai dan menghentikan skrip python Anda menggunakan perintah /etc/init.d/scriptname mulai atau berhenti.

Kishore K
sumber
Saya baru saja mencoba ini, dan ternyata ini akan memulai prosesnya, tetapi tidak akan di-daemonisasi (yaitu masih terpasang pada terminal). Mungkin akan berfungsi dengan baik jika Anda menjalankan pembaruan-rc.d dan membuatnya berjalan saat boot (saya berasumsi tidak ada terminal yang terpasang ketika skrip ini dijalankan), tetapi tidak berfungsi jika Anda memintanya secara manual. Sepertinya pengawas mungkin solusi yang lebih baik.
ryuusenshi
13

Sebuah sederhana dan didukung versi adalah Daemonize.

Instal dari Python Package Index (PyPI):

$ pip install daemonize

dan kemudian gunakan seperti:

...
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()
fcm
sumber
1
apakah restart ketika sistem restart? karena ketika sistem me-restart proses akan mati kan?
ShivaPrasad
@ ShivaPrasad Anda menemukan jawabannya?
1UC1F3R616
restart setelah sistem restart bukan fungsi iblis. gunakan cron, systemctl, kait startup atau alat lain untuk menjalankan aplikasi Anda saat startup.
fcm
1
@ Kum ya saya ingin memulai kembali setelah sistem dinyalakan kembali atau menggunakan perintah seperti saya menggunakan fungsi systemd, Jika ingin mencoba memeriksa akses ini.redhat.com/documentation/en-us/red_hat_enterprise_linux/…
ShivaPrasad
@ShivaPrasad Terima kasih bro
1UC1F3R616
12

cronjelas merupakan pilihan tepat untuk banyak tujuan. Namun itu tidak membuat layanan atau daemon seperti yang Anda minta di OP. cronhanya menjalankan pekerjaan secara berkala (artinya pekerjaan dimulai dan berhenti), dan tidak lebih dari satu kali / menit. Ada masalah dengan cron- misalnya, jika instance skrip Anda sebelumnya masih berjalan saat cronjadwal berikutnya muncul dan meluncurkan instance baru, apakah itu OK? crontidak menangani dependensi; itu hanya mencoba untuk memulai pekerjaan ketika jadwal mengatakan.

Jika Anda menemukan situasi di mana Anda benar-benar membutuhkan daemon (proses yang tidak pernah berhenti berjalan), lihatlah supervisord. Ini menyediakan cara sederhana untuk membungkus skrip atau program normal yang tidak di-daemonisasi dan membuatnya beroperasi seperti daemon. Ini adalah cara yang jauh lebih baik daripada membuat daemon Python asli.

Chris Johnson
sumber
9

bagaimana kalau menggunakan $nohup perintah di linux?

Saya menggunakannya untuk menjalankan perintah di server Bluehost saya.

Mohon saran jika saya salah.

faisal00813
sumber
Saya menggunakan itu juga, bekerja seperti pesona. "Mohon saran jika aku salah."
Alexandre Mazel
5

Jika Anda menggunakan terminal (ssh atau sesuatu) dan Anda ingin agar skrip lama berfungsi setelah Anda keluar dari terminal, Anda dapat mencoba ini:

screen

apt-get install screen

buat terminal virtual di dalam (yaitu abc): screen -dmS abc

sekarang kita terhubung ke abc: screen -r abc

Jadi, sekarang kita dapat menjalankan skrip python: python keep_sending_mails.py

mulai sekarang, Anda dapat langsung menutup terminal Anda, namun, skrip python akan tetap berjalan alih-alih ditutup

Karena keep_sending_mails.pyPID ini adalah proses anak dari layar virtual daripada terminal (ssh)

Jika Anda ingin kembali memeriksa status skrip Anda berjalan, Anda dapat menggunakannya screen -r abclagi

Mikro
sumber
2
sementara ini berfungsi, ini sangat cepat dan kotor dan harus dihindari dalam produksi
pcnate
3

Pertama, baca di email alias. Sebuah email alias akan melakukan ini di dalam sistem email tanpa Anda harus bermain-main dengan daemon atau layanan atau semacamnya.

Anda dapat menulis skrip sederhana yang akan dieksekusi oleh sendmail setiap kali pesan email dikirim ke kotak surat tertentu.

Lihat http://www.feep.net/sendmail/tutorial/intro/aliases.html

Jika Anda benar-benar ingin menulis server yang rumit dan tidak perlu, Anda dapat melakukan ini.

nohup python myscript.py &

Hanya itu yang dibutuhkan. Script Anda hanya loop dan tidur.

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
S.Lott
sumber
6
Masalahnya di sini adalah bahwa do_the_work()bisa crash script dan tidak ada yang menjalankannya lagi
Ivan Nevostruev
jika fungsi do_the_work () macet, itu akan dipanggil lagi setelah 10 menit, karena hanya satu panggilan fungsi yang menimbulkan kesalahan. Tetapi alih-alih menabrak loop hanya trybagian gagal dan except:bagian akan dipanggil sebagai gantinya (dalam hal ini tidak ada) tetapi loop akan terus dan terus mencoba memanggil fungsi.
sarbot
3

Dengan asumsi bahwa Anda benar-benar ingin loop Anda dijalankan 24/7 sebagai layanan latar belakang

Untuk solusi yang tidak melibatkan menyuntikkan kode Anda dengan pustaka, Anda bisa membuat templat layanan, karena Anda menggunakan linux:

masukkan deskripsi gambar di sini

Tempatkan file itu di folder layanan daemon Anda (biasanya /etc/systemd/system/), dan instal dengan menggunakan perintah systemctl berikut (kemungkinan akan membutuhkan hak akses sudo):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Anda kemudian dapat memeriksa apakah layanan Anda berjalan dengan menggunakan perintah:

systemctl | grep running
Heitor Castro
sumber
2

Saya akan merekomendasikan solusi ini. Anda perlu mewarisi dan mengganti metode 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()
Fomalhaut
sumber
2

untuk membuat sesuatu yang berjalan seperti layanan, Anda dapat menggunakan hal ini:

Hal pertama yang harus Anda lakukan adalah menginstal Semen kerangka: bingkai Semen kerja adalah kerangka kerja CLI bahwa Anda dapat menyebarkan aplikasi Anda di atasnya.

antarmuka baris perintah aplikasi:

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

Kelas 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

Perlu diingat bahwa aplikasi Anda harus berjalan di utas untuk menjadi daemon

Untuk menjalankan aplikasi, cukup lakukan ini di baris perintah

python interface.py --help

Manouchehr Rasouli
sumber
1

Gunakan manajer layanan apa pun yang ditawarkan sistem Anda - misalnya di bawah Ubuntu gunakan pemula . Ini akan menangani semua detail untuk Anda seperti mulai saat boot, restart saat macet, dll.

Richard
sumber