Bir Python betiğinin Linux'ta bir hizmet veya arka plan programı gibi çalışmasını sağlama


175

Belirli bir e-posta adresini kontrol eden ve harici bir programa yeni e-postalar ileten bir Python betiği yazdım. Bu betiğin Linux'ta daemon veya hizmete dönüştürülmesi gibi 7/24 çalışmasını nasıl sağlayabilirim. Ayrıca programda hiç bitmeyen bir döngüye ihtiyacım var mı, yoksa sadece kodun birden çok kez çalıştırılmasıyla yapılabilir mi?



3
"belirli bir e-posta adresini kontrol eder ve harici bir programa yeni e-postalar iletir" Sendmail'in yaptığı bu değil mi? Bir posta kutusunu bir komut dosyasına yönlendirmek için posta takma adı tanımlayabilirsiniz. Bunu yapmak için neden posta takma adları kullanmıyorsunuz?
S.Lott

2
Sahip olduğunuz modern bir linux üzerinde, burada açıklandığı gibi modda systemdbir systemd hizmeti oluşturabilirsiniz . Ayrıca bakınız: freedesktop.org/software/systemd/man/systemd.service.htmldaemon
ccpizza

Linux sistemi systemd'yi destekliyorsa, burada belirtilen yaklaşımı kullanın .
gerardw

Yanıtlar:


96

Burada iki seçeneğiniz var.

  1. Senaryonuzu çağıran uygun bir cron işi yapın. Cron, belirlediğiniz bir programa göre komut dosyalarını periyodik olarak başlatan bir GNU / Linux arka plan programı için ortak bir addır. Komut dosyanızı bir crontab'a eklersiniz veya özel bir dizine bir sembol bağlantısı yerleştirirsiniz ve arka plan programı bunu arka planda başlatma işini yürütür. Wikipedia'da daha fazla bilgi edinebilirsiniz . Çeşitli cron cinleri var, ancak GNU / Linux sisteminizde zaten kurulu olmalıdır.

  2. Betiğinizin kendini arka plana çevirebilmesi için bir tür python yaklaşımı kullanın (örneğin bir kütüphane). Evet, basit bir olay döngüsü gerektirecektir (etkinliklerinizin muhtemelen uyku işlevi tarafından sağlanan zamanlayıcı tetiklemesi vardır).

2.'yi seçmenizi tavsiye etmem, çünkü aslında cron işlevselliğini tekrar edersiniz. Linux sistem paradigması, birden fazla basit aracın etkileşime girmesine ve sorunlarınızı çözmesine izin vermektir. Bir daemon (periyodik olarak tetiklemeye ek olarak) yapmanın başka nedenleri olmadıkça, diğer yaklaşımı seçin.

Ayrıca, bir döngü ile arka plan programı kullanırsanız ve bir kilitlenme meydana gelirse, kimse bundan sonra postayı kontrol etmez ( Ivan Nevostruev tarafından bu cevaba yapılan yorumlarda belirtildiği gibi ). Komut dosyası bir cron işi olarak eklenirse, sadece yeniden tetiklenir.


7
Cronjob için +1. Sorunun yerel bir posta hesabını kontrol ettiğini belirttiğini sanmıyorum, bu yüzden posta filtreleri geçerli değil
John La Rooy

Python programında sonlandırılmadan bir döngü kullanır ve sonra crontablisteye kaydettirir mi? .pySaatlik böyle kurarsam, asla sonlandırılmayacak birçok süreç yaratacak mı? Eğer öyleyse, bence bu daemon gibi olurdu.
Veck Hsiao

Dakikada bir kez e-postaları kontrol ederseniz cron'un bariz bir çözüm olduğunu görebiliyorum (Cron için en düşük zaman çözünürlüğüdür). Ancak, her 10 saniyede bir e-postaları kontrol etmek istersem ne olur? Sorguyu 60 kez çalıştırmak için Python betiğini yazmalı mıyım, bu da 50 saniye sonra sona erecek ve cron'un betiği 10 saniye sonra yeniden başlatmasına izin vermeli mi?
Mads Skjern

Ben daemons / hizmetleri ile çalışmadım, ancak (OS / init / init.d / upstart veya ne denir) bir daemon'u sona erdiğinde / çöktüğünde yeniden başlatmaya özen gösterdiği izlenimi altındaydım.
Mads Skjern

@VeckHsiao evet, crontab bir komut dosyası çağırıyor, bu yüzden python komut dosyanızın birçok örneği herkesin döngüsü ile çağrılır ....
Pipo

71

İşte alınır güzel sınıfı var burada :

#!/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
yeniden başlatıldığında yeniden başlatılıyor mu? çünkü sistem yeniden başlatıldığında süreç öldürülecek değil mi?
ShivaPrasad

58

Python-daemon kütüphanesini kullanmalısınız, her şeyle ilgilenir.

PyPI'den: Kütüphane, iyi çalışan bir Unix arka plan programı uygulamak için.


3
Ditto Jorge Vargas'ın yorumu. Koda baktıktan sonra, aslında oldukça güzel bir kod parçası gibi görünüyor, ancak belgelerin ve örneklerin eksik olması, kullanımı çok zorlaştırıyor, bu da çoğu geliştiricinin daha iyi belgelenmiş alternatifler için haklı olarak görmezden geleceği anlamına geliyor.
Cerin

1
Python 3.5'te
Martin Thoma

Beklendiği gibi çalışmıyor. Olsa da güzel olurdu.
Harlin

Unix! = Linux - bu sorun olabilir mi?
Dana

39

Senaryonuzu tty'den ayırmak için fork () kullanabilirsiniz ve şöyle devam etmesini sağlayabilirsiniz:

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

Tabii ki sonsuz bir döngü de uygulamanız gerekiyor.

while 1:
  do_your_check()
  sleep(5)

Umarım bu işe başlarsınız.


Merhaba, bunu denedim ve benim için çalışıyor. Ama terminali kapattığımda veya ssh oturumundan çıktığımda, komut dosyası da çalışmayı durduruyor !!
David Okwii

@DavidOkwii nohup/ disownkomutları işlemi konsoldan ayırır ve ölmez. Veya bunu init.d ile başlatabilirsiniz
pholat

14

Ayrıca, bir kabuk betiği kullanarak python betiğinin hizmet olarak çalışmasını sağlayabilirsiniz. Önce python betiğini bu şekilde çalıştırmak için bir kabuk betiği oluşturun (scriptname arbitary name)

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

şimdi /etc/init.d/scriptname içinde bir dosya oluşturun

#! /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

Artık /etc/init.d/scriptname start veya stop komutunu kullanarak python komut dosyanızı başlatabilir ve durdurabilirsiniz.


Ben sadece denedim, ve bu süreci başlatacak ortaya çıkıyor, ama o daemonized olmayacak (yani hala terminale bağlı). Update-rc.d dosyasını çalıştırırsanız ve önyükleme üzerinde çalıştırırsanız iyi çalışır (bu komut dosyaları çalıştırıldığında bağlı bir terminal olmadığını varsayalım), ancak el ile çağırırsanız çalışmaz. Süpervizör daha iyi bir çözüm olabilir gibi görünüyor.
ryuusenshi

13

Basit ve desteklenen versiyonu olduğunu Daemonize.

Python Paket İndeksinden (PyPI) yükleyin:

$ pip install daemonize

ve sonra şöyle kullanın:

...
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
yeniden başlatıldığında yeniden başlatılıyor mu? çünkü sistem yeniden başlatıldığında süreç öldürülecek değil mi?
ShivaPrasad

@ShivaPrasad u cevap buldunuz mu?
1UC1F3R616

sistemin yeniden başlatılmasından sonra yeniden başlatma bir iblis işlevi değildir. uygulamanızı başlangıçta çalıştırmak için cron, systemctl, başlangıç ​​kancaları veya diğer araçları kullanın.
fcm

1
@ Evet evet Sistem yeniden başlatıldıktan sonra yeniden başlatmak istedim veya benzer komutlar kullandım. Sistemd işlevlerini kullandım, Denemek isterseniz bu erişimi
ShivaPrasad

@ShivaPrasad Teşekkürler kardeşim
1UC1F3R616

12

cronbirçok amaç için mükemmel bir seçimdir. Ancak, OP'de istediğiniz gibi bir hizmet veya arka plan programı oluşturmaz. cronişleri düzenli aralıklarla çalıştırır (yani iş başlar ve durur) ve en fazla bir kez / dakika. Sorunlar var cron- örneğin, komut dosyanızın önceki bir örneği, cronprogram bir sonraki sefer başlatıldığında ve yeni bir örneği başlattığında hala çalışıyorsa, sorun değil mi? cronbağımlılıkları ele almaz; sadece programın söylediği bir işe başlamaya çalışır.

Gerçekten bir arka plan programına ihtiyacınız olan bir durum bulursanız (asla durmayan bir işlem), bir göz atın supervisord. Normal, daemonlaştırılmamış bir komut dosyasını veya programı sarmak ve bir daemon gibi çalışmasını sağlamak için basit bir yol sağlar. Bu, yerel bir Python arka plan programı oluşturmaktan çok daha iyi bir yoldur.


9

kullanmaya ne dersin $nohupLinux üzerinde komut ?

Komutlarımı Bluehost sunucumda çalıştırmak için kullanıyorum.

Yanılıyorsam lütfen tavsiye.


Bunu da kullanıyorum, bir cazibe gibi çalışıyor. "Eğer yanılıyorsam, lütfen tavsiye."
Alexandre Mazel

5

Terminal (ssh vb.) Kullanıyorsanız ve terminalden çıkış yaptıktan sonra uzun süre çalışan bir komut dosyasının çalışmasını istiyorsanız, bunu deneyebilirsiniz:

screen

apt-get install screen

içinde sanal bir terminal oluşturun (yani abc): screen -dmS abc

şimdi abc'ye bağlanıyoruz: screen -r abc

Şimdi, python betiğini çalıştırabiliriz: python keep_sending_mails.py

bundan sonra doğrudan terminalinizi kapatabilirsiniz, ancak python betiği kapatılmak yerine çalışmaya devam eder

Bunun keep_sending_mails.pyPID'si terminal (ssh) yerine sanal ekranın alt süreci olduğu için

Geri dönmek isterseniz komut dosyanızın çalışma durumunu kontrol edin, screen -r abctekrar kullanabilirsiniz


2
Bu çalışırken, çok hızlı ve kirli ve üretimde kaçınılmalıdır
pcnate

3

İlk olarak, posta takma adlarını okuyun. Bir posta takma adı, bunu cinayetler veya hizmetler veya herhangi bir şeyle uğraşmak zorunda kalmadan posta sistemi içinde yapar.

Belirli bir posta kutusuna her mesaj gönderildiğinde sendmail tarafından yürütülecek basit bir komut dosyası yazabilirsiniz.

Görmek Http://www.feep.net/sendmail/tutorial/intro/aliases.html

Gerçekten gereksiz yere karmaşık bir sunucu yazmak istiyorsanız, bunu yapabilirsiniz.

nohup python myscript.py &

Tüm gereken bu. Senaryonuz sadece döngüler ve uyur.

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
Buradaki sorun do_the_work()senaryoyu
çökertebilir

do_the_work () işlevi çökerse, yalnızca bir işlev çağrısı hata verdiğinden, 10 dakika sonra yeniden çağrılır. Ancak, döngü çökmek yerine sadece tryparça başarısız olur ve except:bunun yerine parça çağrılır (bu durumda hiçbir şey), ancak döngü devam eder ve işlevi çağırmaya devam eder.
sarbot

3

Döngünüzün arka plan hizmeti olarak 7/24 çalışmasını gerçekten istediğinizi varsayarsak

Kodunuzu kütüphanelerle enjekte etmeyi içermeyen bir çözüm için, linux kullandığınızdan basitçe bir hizmet şablonu oluşturabilirsiniz:

resim açıklamasını buraya girin

Bu dosyayı daemon service klasörünüze yerleştirin (genellikle /etc/systemd/system/) ve aşağıdaki systemctl komutlarını kullanarak yükleyin (büyük olasılıkla sudo ayrıcalıkları gerektirecektir):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Ardından, şu komutu kullanarak hizmetinizin çalışıp çalışmadığını kontrol edebilirsiniz:

systemctl | grep running

2

Bu çözümü tavsiye ederim. Yöntemi devralmanız ve geçersiz kılmanız gerekir 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

hizmet gibi çalışan bir şey oluşturmak için bu şeyi kullanabilirsiniz:

Yapmanız gereken ilk şey, Çimento çerçevesini kurmaktır: Çimento çerçeve çalışması, uygulamanızı üzerine uygulayabileceğiniz bir CLI çerçeve çalışmasıdır.

uygulamanın komut satırı arayüzü:

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

App.py sınıfınız:

 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

Uygulamanızın daemon olması için bir iş parçacığında çalışması gerektiğini unutmayın

Uygulamayı çalıştırmak için bunu komut satırında yapın

python interface.py --help


1

Sisteminizin sunduğu herhangi bir servis yöneticisini kullanın - örneğin Ubuntu altında upstart kullanın . Bu, önyüklemede başlatma, çökmede yeniden başlatma vb.Gibi tüm ayrıntıları işleyecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.