Cách tạo tập lệnh Python chạy như dịch vụ hoặc trình nền trong Linux


175

Tôi đã viết một tập lệnh Python kiểm tra một địa chỉ email nhất định và chuyển e-mail mới cho một chương trình bên ngoài. Làm cách nào tôi có thể có được tập lệnh này để thực thi 24/7, chẳng hạn như biến nó thành daemon hoặc dịch vụ trong Linux. Tôi cũng sẽ cần một vòng lặp không bao giờ kết thúc trong chương trình, hoặc có thể được thực hiện bằng cách chỉ thực hiện lại mã nhiều lần?



3
"kiểm tra một địa chỉ email nhất định và chuyển e-mail mới đến một chương trình bên ngoài" Không phải đó là những gì mà sendmail làm sao? Bạn có thể xác định bí danh thư để định tuyến hộp thư đến tập lệnh. Tại sao bạn không sử dụng bí danh thư để làm điều này?
S.Lott

2
Trên một linux hiện đại, systemdbạn có thể tạo một dịch vụ systemd ở daemonchế độ như được mô tả ở đây . Xem thêm: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza

Nếu hệ thống linux hỗ trợ systemd, hãy sử dụng phương pháp được nêu ở đây .
gerardw

Câu trả lời:


96

Bạn có hai lựa chọn ở đây.

  1. Tạo một công việc định kỳ thích hợp gọi kịch bản của bạn. Cron là tên chung cho một daemon GNU / Linux, định kỳ khởi chạy các tập lệnh theo lịch bạn đặt. Bạn thêm tập lệnh của mình vào một crontab hoặc đặt một liên kết tượng trưng cho nó vào một thư mục đặc biệt và trình nền xử lý công việc khởi chạy nó trong nền. Bạn có thể đọc thêm tại Wikipedia. Có nhiều loại trình nền cron khác nhau, nhưng hệ thống GNU / Linux của bạn đã được cài đặt sẵn.

  2. Sử dụng một số cách tiếp cận python (ví dụ như một thư viện) cho tập lệnh của bạn để có thể tự tạo daemon. Có, nó sẽ yêu cầu một vòng lặp sự kiện đơn giản (trong đó các sự kiện của bạn được kích hoạt hẹn giờ, có thể, được cung cấp bởi chức năng ngủ).

Tôi không khuyên bạn nên chọn 2. vì trên thực tế, bạn sẽ lặp lại chức năng định kỳ. Mô hình hệ thống Linux là để cho nhiều công cụ đơn giản tương tác và giải quyết các vấn đề của bạn. Trừ khi có thêm lý do tại sao bạn nên tạo một daemon (ngoài việc kích hoạt định kỳ), hãy chọn cách tiếp cận khác.

Ngoài ra, nếu bạn sử dụng daemonize với một vòng lặp và một sự cố xảy ra, sẽ không có ai kiểm tra thư sau đó (như được chỉ ra bởi Ivan Nevostruev trong các bình luận cho câu trả lời này ). Trong khi nếu tập lệnh được thêm vào như một công việc định kỳ, nó sẽ chỉ kích hoạt lại.


7
+1 cho cronjob. Tôi không nghĩ câu hỏi chỉ định rằng nó đang kiểm tra tài khoản thư cục bộ, vì vậy các bộ lọc thư không được áp dụng
John La Rooy

Điều gì xảy ra không sử dụng một vòng lặp mà không chấm dứt trong một chương trình Python và sau đó đăng ký nó vào crontabdanh sách sẽ là gì? Nếu tôi thiết lập như vậy .pyhàng giờ, nó sẽ tạo ra nhiều quy trình sẽ không bao giờ bị chấm dứt? Nếu vậy, tôi nghĩ rằng điều này sẽ khá thích daemon.
Veck Hsiao

Tôi có thể thấy rằng cron là một giải pháp rõ ràng nếu bạn kiểm tra email mỗi phút một lần (đó là độ phân giải thời gian thấp nhất cho Cron). Nhưng nếu tôi muốn kiểm tra email cứ sau 10 giây thì sao? Tôi có nên viết tập lệnh Python để chạy truy vấn 60 lần không, có nghĩa là nó kết thúc sau 50 giây và sau đó để cron khởi động lại tập lệnh 10 giây sau?
Mads Skjern

Tôi chưa làm việc với daemon / dịch vụ, nhưng tôi có ấn tượng rằng nó (OS / init / init.d / upstart hoặc cái mà nó được gọi) sẽ quan tâm đến việc khởi động lại một daemon khi / nếu nó kết thúc / gặp sự cố.
Mads Skjern

@VeckHsiao có, crontab gọi một tập lệnh nên nhiều trường hợp tập lệnh python của bạn sẽ được gọi với mọi người trong vòng lặp của nó ....
Pipo

71

Đây là một lớp học hay được lấy từ đây :

#!/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
Có phải nó khởi động lại khi hệ thống được khởi động lại? bởi vì khi hệ thống khởi động lại, quá trình sẽ bị hủy phải không?
ShivaPrasad

58

Bạn nên sử dụng thư viện python-daemon , nó sẽ lo tất cả mọi thứ.

Từ PyPI: Thư viện để thực hiện quy trình daemon Unix hoạt động tốt.


3
Nhận xét của Ditto Jorge Vargas. Sau khi xem mã, nó thực sự trông giống như một đoạn mã đẹp, nhưng việc thiếu hoàn toàn các tài liệu và ví dụ làm cho nó rất khó sử dụng, điều đó có nghĩa là hầu hết các nhà phát triển sẽ bỏ qua nó để thay thế tài liệu tốt hơn.
Cerin

1
Có vẻ như không hoạt động đúng trong Python 3.5: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma

Không làm việc như mong đợi. Nó sẽ được tốt đẹp nếu nó đã làm mặc dù.
Harlin

Unix! = Linux - đây có thể là rắc rối không?
Dana

39

Bạn có thể sử dụng fork () để tách tập lệnh của mình khỏi tty và để nó tiếp tục chạy, như vậy:

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

Tất nhiên bạn cũng cần phải thực hiện một vòng lặp vô tận, như

while 1:
  do_your_check()
  sleep(5)

Hy vọng việc này có thể thúc đẩy bạn.


Xin chào, tôi đã thử nó và nó hoạt động với tôi. Nhưng khi tôi đóng thiết bị đầu cuối hoặc thoát khỏi phiên ssh, tập lệnh cũng ngừng hoạt động !!
David Okwii

@DavidOkwii nohup/ disowncác lệnh sẽ tách quá trình khỏi bàn điều khiển và nó sẽ không chết. Hoặc bạn có thể bắt đầu với init.d
pholat

14

Bạn cũng có thể làm cho tập lệnh python chạy như một dịch vụ bằng cách sử dụng tập lệnh shell. Trước tiên, hãy tạo một tập lệnh shell để chạy tập lệnh python như thế này (tên tập tin tên tập lệnh)

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

bây giờ tạo một tệp trong /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

Bây giờ bạn có thể bắt đầu và dừng tập lệnh python của mình bằng lệnh /etc/init.d/scriptname bắt đầu hoặc dừng.


Tôi mới thử cái này, và hóa ra cái này sẽ bắt đầu quá trình, nhưng nó sẽ không được trình bày (tức là nó vẫn được gắn vào thiết bị đầu cuối). Nó có thể hoạt động tốt nếu bạn chạy update-rc.d và làm cho nó chạy khi khởi động (tôi giả sử không có thiết bị đầu cuối nào được đính kèm khi các tập lệnh này được chạy), nhưng nó không hoạt động nếu bạn gọi nó bằng tay. Có vẻ như giám sát có thể là một giải pháp tốt hơn.
ryuusenshi

13

Một phiên bản đơn giản và được hỗ trợ là Daemonize.

Cài đặt nó từ Python Gói Index (PyPI):

$ pip install daemonize

và sau đó sử dụng như:

...
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
Có phải nó khởi động lại khi hệ thống được khởi động lại? bởi vì khi hệ thống khởi động lại, quá trình sẽ bị hủy phải không?
ShivaPrasad

@ShivaPrasad bạn tìm thấy câu trả lời cho nó?
1UC1F3R616

khởi động lại sau khi khởi động lại hệ thống không phải là chức năng của quỷ. sử dụng cron, systemctl, móc khởi động hoặc các công cụ khác để chạy ứng dụng của bạn khi khởi động.
fcm

1
@Kush có Tôi muốn khởi động lại sau khi khởi động lại hệ thống hoặc sử dụng như các lệnh tôi đã sử dụng các hàm systemd, Nếu muốn thử kiểm tra quyền truy cập này.redhat.com/documentation/en
us/red_hat_enterprise_linux/ trộm

@ShivaPrasad Cảm ơn bro
1UC1F3R616

12

cronrõ ràng là một sự lựa chọn tuyệt vời cho nhiều mục đích. Tuy nhiên, nó không tạo ra một dịch vụ hoặc trình nền như bạn yêu cầu trong OP. cronchỉ chạy các công việc định kỳ (có nghĩa là công việc bắt đầu và dừng lại), và không thường xuyên hơn một lần / phút. Có một số vấn đề với cron- ví dụ: nếu một phiên bản trước của tập lệnh của bạn vẫn chạy vào lần tiếp theo cronlịch biểu xuất hiện và khởi chạy một phiên bản mới, điều đó có ổn không? cronkhông xử lý các phụ thuộc; nó chỉ cố gắng để bắt đầu một công việc khi lịch trình nói.

Nếu bạn tìm thấy một tình huống mà bạn thực sự cần một daemon (một quá trình không bao giờ ngừng chạy), hãy xem xét supervisord. Nó cung cấp một cách đơn giản để bao bọc một tập lệnh hoặc chương trình bình thường, không có daemon và làm cho nó hoạt động như một daemon. Đây là một cách tốt hơn nhiều so với việc tạo một daemon Python bản địa.


9

Làm thế nào về việc sử dụng $nohuplệnh trên linux?

Tôi sử dụng nó để chạy các lệnh của tôi trên máy chủ Bluehost của tôi.

Xin tư vấn nếu tôi sai.


Tôi cũng sử dụng nó, hoạt động như một nét duyên dáng. "Xin hãy tư vấn nếu tôi sai."
Alexandre Mazel

5

Nếu bạn đang sử dụng thiết bị đầu cuối (ssh hoặc một cái gì đó) và bạn muốn giữ một tập lệnh thời gian dài hoạt động sau khi bạn đăng xuất khỏi thiết bị đầu cuối, bạn có thể thử điều này:

screen

apt-get install screen

tạo một thiết bị đầu cuối ảo bên trong (cụ thể là abc): screen -dmS abc

bây giờ chúng tôi kết nối với abc: screen -r abc

Vì vậy, bây giờ chúng ta có thể chạy kịch bản python: python keep_sending_mails.py

từ giờ trở đi, bạn có thể trực tiếp đóng thiết bị đầu cuối của mình, tuy nhiên, tập lệnh python sẽ tiếp tục chạy thay vì bị tắt

keep_sending_mails.pyPID này là một quá trình con của màn hình ảo chứ không phải là thiết bị đầu cuối (ssh)

Nếu bạn muốn quay lại kiểm tra trạng thái chạy tập lệnh của mình, bạn có thể sử dụng screen -r abclại


2
trong khi điều này hoạt động, nó rất nhanh và bẩn và nên tránh trong sản xuất
pcnate 16/08/17

3

Đầu tiên, đọc lên bí danh thư. Một bí danh thư sẽ thực hiện việc này trong hệ thống thư mà không cần bạn phải loay hoay với trình nền hoặc dịch vụ hoặc bất cứ thứ gì thuộc loại này.

Bạn có thể viết một tập lệnh đơn giản sẽ được thực thi bằng sendmail mỗi khi một tin nhắn thư được gửi đến một hộp thư cụ thể.

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

Nếu bạn thực sự muốn viết một máy chủ phức tạp không cần thiết, bạn có thể làm điều này.

nohup python myscript.py &

Đó là tất cả những gì nó cần. Kịch bản của bạn chỉ đơn giản là vòng lặp và ngủ.

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
Vấn đề ở đây là do_the_work()có thể làm hỏng kịch bản và không ai chạy lại nó
Ivan Nevostruev

nếu hàm do_the_work () gặp sự cố, nó sẽ được gọi lại sau 10 phút, vì chỉ một lệnh gọi hàm phát sinh lỗi. Nhưng thay vì làm hỏng vòng lặp, chỉ có tryphần bị lỗi và except:phần đó sẽ được gọi thay thế (trong trường hợp này không có gì) nhưng vòng lặp sẽ tiếp tục và tiếp tục cố gắng gọi hàm.
sarbot

2

Tôi muốn giới thiệu giải pháp này. Bạn cần kế thừa và ghi đè phương thức 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

để tạo một số thứ đang chạy như dịch vụ, bạn có thể sử dụng thứ này:

Điều đầu tiên mà bạn phải làm là cài đặt các xi măng khung: khung làm việc Xi măng là một tác phẩm khung CLI mà bạn có thể triển khai ứng dụng của bạn trên đó.

giao diện dòng lệnh của ứng dụng:

giao diện

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

Lớp 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

Hãy nhớ rằng ứng dụng của bạn phải chạy trên một chủ đề để là daemon

Để chạy ứng dụng, chỉ cần làm điều này trong dòng lệnh

giao diện python - trợ giúp


2

Giả sử rằng bạn thực sự muốn vòng lặp của mình chạy 24/7 như một dịch vụ nền

Đối với một giải pháp không liên quan đến việc tiêm mã của bạn vào các thư viện, bạn chỉ cần tạo một mẫu dịch vụ, vì bạn đang sử dụng linux:

nhập mô tả hình ảnh ở đây

Đặt tệp đó vào thư mục dịch vụ daemon của bạn (thường /etc/systemd/system/) và cài đặt nó bằng các lệnh systemctl sau (có thể sẽ yêu cầu đặc quyền sudo):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Sau đó, bạn có thể kiểm tra xem dịch vụ của bạn có đang chạy hay không bằng cách sử dụng lệnh:

systemctl | grep running

1

Sử dụng bất kỳ trình quản lý dịch vụ nào mà hệ thống của bạn cung cấp - ví dụ như trong Ubuntu sử dụng mới bắt đầu . Điều này sẽ xử lý tất cả các chi tiết cho bạn như bắt đầu khởi động, khởi động lại khi gặp sự cố, v.v.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.