Comment faire fonctionner un script Python comme un service ou un démon sous Linux
j'ai écrit un script Python qui vérifie une certaine adresse e-mail et passe de nouveaux e-mails à un programme externe. Comment puis-je faire exécuter ce script 24h / 24, 7j / 7, par exemple en le transformant en démon ou en service sous Linux? Est-ce que j'aurais aussi besoin d'une boucle qui ne se termine jamais dans le programme, ou est-ce que cela peut être fait en faisant simplement exécuter le code plusieurs fois?
15 réponses
vous avez deux options ici.
-
Faire un bon cron qui appelle votre script. Cron est un nom commun pour un démon GNU/Linux qui lance périodiquement des scripts selon un horaire que vous avez défini. Vous ajoutez votre script dans une crontab ou placez un lien symbolique vers lui dans un répertoire spécial et le démon gère la tâche de le lancer en arrière-plan. Vous pouvez lire la suite sur wikipedia. Y est une variété de démons cron différents, mais votre système GNU / Linux devrait déjà l'avoir installé.
-
utilisez une sorte de approche python (une bibliothèque, par exemple) pour que votre script puisse se démoniser lui-même. Oui, il vous faudra une boucle d'événements simple (où vos événements sont déclenchés par un minuteur, éventuellement fourni par la fonction de sommeil).
Je ne vous recommande pas de choisir 2. parce que vous répétez la fonctionnalité cron. Le paradigme du système Linux est de laisser plusieurs outils simples interagir et résoudre vos problèmes. À moins qu'il n'y ait d'autres raisons pour lesquelles vous devriez faire un démon (en plus de déclencher périodiquement), choisissez l'autre approche.
aussi, si vous utilisez daemonize avec une boucle et un accident se produit, personne ne vérifiera le courrier après que (comme souligné par Ivan Nevostruev dans les commentaires à cette réponse ). Alors que si le script est ajouté comme une tâche cron, il se déclenchera à nouveau.
Voici une classe agréable qui est tirée de ici :
#!/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().
"""
vous devez utiliser la bibliothèque python-daemon , elle s'occupe de tout.
De PyPI: Bibliothèque à mettre en œuvre une sage Unix processus de démon.
vous pouvez utiliser fork() pour détacher votre script du tty et le faire continuer à fonctionner, comme ceci:
import os, sys
fpid = os.fork()
if fpid!=0:
# Running as daemon now. PID is fpid
sys.exit(0)
bien sûr, vous devez également mettre en œuvre une boucle sans fin, comme
while 1:
do_your_check()
sleep(5)
J'espère que vous avez commencé.
vous pouvez également faire fonctionner le script python comme un service en utilisant un script shell. Créez d'abord un script shell pour exécuter le script python comme ceci (nom de script arbitary)
#!/bin/sh
script='/home/.. full path to script'
/usr/bin/python $script &
crée maintenant un fichier dans /etc/init.d/nomduscript
#! /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 "" 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 }'`
kill -9 $PID
log_end_msg $?
;;
force-reload|restart)
"151910920" stop
"151910920" 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
Maintenant vous pouvez démarrer et arrêter votre script python en utilisant la commande /etc/init.d/nomduscript démarrer ou arrêter.
Que diriez-vous d'utiliser la commande $nohup
sur linux?
Je l'utilise pour exécuter mes commandes sur mon serveur Bluehost.
s'il vous plaît, si je me trompe.
une version simple et pris en charge est Désamonisé Installez-le à partir de L'Index des paquets Python (PyPI):
$ pip install daemonize
et ensuite utiliser comme:
...
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()
cron
est clairement un grand choix pour de nombreuses raisons. Cependant, il ne crée pas un service ou un démon comme vous l'avez demandé dans L'OP. cron
exécute juste des travaux périodiquement (ce qui signifie que le travail commence et s'arrête), et pas plus d'une fois / minute. Il y a des problèmes avec cron
-- par exemple, si une instance antérieure de votre script est toujours en cours d'exécution la prochaine fois que le programme cron
arrive et lance une nouvelle instance, est-ce correct? cron
ne gère pas dépendances; il essaie juste de commencer un travail lorsque le calendrier dit.
si vous trouvez une situation où vous avez vraiment besoin d'un démon (un processus qui ne cesse jamais de fonctionner), jetez un oeil à supervisord
. Il fournit un moyen simple pour envelopper un script ou un programme normal, non-daemonisé et le faire fonctionner comme un daemon. C'est une bien meilleure façon que de créer un natif Python démon.
d'abord, lisez sur les alias de courrier. Un alias mail le fera à l'intérieur du système de messagerie sans que vous ayez à jouer avec des démons ou des services ou quoi que ce soit de ce genre.
vous pouvez écrire un script simple qui sera exécuté par sendmail chaque fois qu'un message est envoyé à une boîte aux lettres spécifique.
voir http://www.feep.net/sendmail/tutorial/intro/aliases.html
Si vous voulez vraiment écrire un inutilement complexe serveur, vous pouvez le faire.
nohup python myscript.py &
C'est tout ce qu'il faut. Votre script boucle et dort.
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
si vous utilisez terminal(SSH ou quelque chose du genre) et que vous voulez garder un script de longue durée qui fonctionne après que vous vous êtes déconnecté du terminal, vous pouvez essayer ceci:
screen
apt-get install screen
créer un terminal virtuel à l'intérieur( à savoir abc): screen -dmS abc
maintenant nous nous connectons à abc: screen -r abc
donc, maintenant nous pouvons exécuter le script python: python Keep_sending_mail.py
à partir de Maintenant, vous pouvez fermer directement votre terminal, cependant, le script python continuera d'exécuter plutôt que d'être arrêté
étant donné que ce PID
Keep_sending_mail.py
appartient à l'écran virtuel plutôt qu'au terminal (ssh)
si vous voulez retourner vérifier l'état d'exécution de votre script, vous pouvez utiliser screen -r abc
à nouveau
utilisez n'importe quel gestionnaire de service que votre système offre - par exemple sous Ubuntu, utilisez upstart . Cela gérera tous les détails pour vous tels que démarrer au démarrage, Redémarrer au crash, etc.
je recommande cette solution. Vous devez hériter et remplacer la méthode 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()
avait un problème similaire, le lien ci-dessous a bien fonctionné pour moi: http://werxltd.com/wp/2012/01/05/simple-init-d-script-template/#footnote_0_1077
il n'utilise rien de spécifique à un distributeur, si chkconfig utilisé, peut être lancé au démarrage du système.
pour créer quelque chose qui fonctionne comme un service, vous pouvez utiliser cette chose :
la première chose que vous devez faire est d'installer le ciment cadre: Le travail de cadre de ciment est un travail de cadre de CLI que vous pouvez déployer votre application sur elle.
interface en ligne de commande de l'application:
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 classe:
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
gardez à l'esprit que votre application doit fonctionner sur un fil pour être démon
pour lancer l'application il suffit de faire ceci en ligne de commande
python interface.py -- aide