Wie erstellt man einen Daemon in Python?

244

Bei der Suche in Google werden x2-Codefragmente angezeigt. Das erste Ergebnis ist dieses Code-Rezept, das viele Dokumentationen und Erklärungen sowie einige nützliche Diskussionen enthält.

Ein anderes Codebeispiel enthält zwar nicht so viel Dokumentation, enthält jedoch Beispielcode zum Übergeben von Befehlen wie Start, Stopp und Neustart. Außerdem wird eine PID-Datei erstellt, mit der überprüft werden kann, ob der Dämon bereits ausgeführt wird usw.

In diesen Beispielen wird erläutert, wie der Dämon erstellt wird. Gibt es zusätzliche Dinge, die berücksichtigt werden müssen? Ist eine Probe besser als die andere und warum?

DavidM
quelle
1
Ich habe immer festgestellt, dass Daemonisierungscode nicht benötigt wird. Warum nicht einfach die Shell das machen lassen?
emil.p.stanchev
17
Weil es nicht setsid oder setpgrp macht.
Bmargulies
4
Verwenden Sie Supervisord.org . Auf diese Weise müssen Sie nicht stdin / stderr fork () oder umleiten. Schreiben Sie einfach ein normales Programm.
Guettli

Antworten:

169

Aktuelle Lösung

Eine Referenzimplementierung von PEP 3143 (Standard Daemon Process Library) ist jetzt als Python-Daemon verfügbar .

Historische Antwort

Das Codebeispiel von Sander Marechal ist dem Original überlegen, das ursprünglich im Jahr 2004 veröffentlicht wurde. Ich habe einmal einen Daemonizer für Pyro beigesteuert, würde aber wahrscheinlich Sanders Code verwenden, wenn ich ihn noch einmal machen müsste.

Jeff Bauer
quelle
72
Bearbeiten: Da ich diese Antwort ursprünglich gepostet habe, ist jetzt eine Referenzimplementierung von PEP 3143 verfügbar: pypi.python.org/pypi/python-daemon
Jeff Bauer
@ JeffBauer Der ursprüngliche Link ist gestorben. Ich erinnere mich, dass er nützlich war. Sie würden keinen Live-Link dafür kennen, oder?
CrazyCasta
1
@ CrazyCasta: Sander Marechals Version ist noch auf der Wayback Machine
Jeff Bauer
1
@ JeffBauer: Sanders Code ist immer noch besser als http://pypi.python.org/pypi/python-daemon. Zuverlässiger. Nur ein Beispiel: Versuchen Sie, zweimal denselben Daemon zu starten mit python-daemon: großer hässlicher Fehler. Mit Sanders Code: ein netter Hinweis "Daemon läuft bereits."
Basj
2
Da die Dokumentation zum "Python-Daemon" -Modul noch fehlt (siehe auch viele andere SO-Fragen) und ziemlich unklar ist (wie man einen Daemon mit diesem Modul über die Befehlszeile richtig startet / stoppt?), Habe ich das Codebeispiel von Sander Marechal geändert, um es hinzuzufügen quit()Methode, die ausgeführt wird, bevor der Dämon gestoppt wird. Hier ist es.
Basj
163

Es gibt viele fummelige Dinge , um die man sich kümmern muss, wenn man ein gut erzogener Daemon-Prozess wird :

  • Verhindern von Core-Dumps (viele Daemons werden als Root ausgeführt, und Core-Dumps können vertrauliche Informationen enthalten.)

  • benimm dich korrekt in einem chrootGefängnis

  • Stellen Sie UID, GID, Arbeitsverzeichnis, umask und andere Prozessparameter entsprechend dem Anwendungsfall ein

  • verzichten erhöht suid, sgidPrivilegien

  • Schließen Sie alle geöffneten Dateideskriptoren mit Ausschlüssen je nach Anwendungsfall

  • korrekt verhalten , wenn innerhalb eines bereits abgelöst Kontext gestartet, wie init, inetdetc.

  • Richten Sie Signalhandler für ein vernünftiges Daemon-Verhalten ein, aber auch für bestimmte Handler, die vom Anwendungsfall bestimmt werden

  • die Standardströme umlenken stdin, stdout, stderrda verfügt über einen Dämon - Prozess nicht länger einen Steuerterminal

  • Behandeln Sie eine PID-Datei als kooperative Beratungssperre. Dies ist eine ganze Dose Würmer an sich mit vielen widersprüchlichen, aber gültigen Verhaltensweisen

  • Ermöglichen Sie eine ordnungsgemäße Bereinigung, wenn der Prozess beendet ist

  • tatsächlich ein Dämonprozess werden, ohne zu Zombies zu führen

Einige davon sind Standard , wie in der kanonischen Unix-Literatur beschrieben ( Advanced Programming in the UNIX Environment , von dem verstorbenen W. Richard Stevens, Addison-Wesley, 1992). Andere, wie die Stream-Umleitung und die Behandlung von PID-Dateien , sind herkömmliches Verhalten, das die meisten Daemon-Benutzer erwarten würden, das jedoch weniger standardisiert ist.

All dies wird in der PEP 3143- Spezifikation „Standard Daemon Process Library“ abgedeckt . Die Python-Daemon- Referenzimplementierung funktioniert unter Python 2.7 oder höher und Python 3.2 oder höher.

große Nase
quelle
26
"Gaol" ist richtig geschrieben, denn so hat es W. Richard Stevens geschrieben :-)
bignose
7
Gefängnis ist eine englische Sache . Das Poster ist aus Australien, also macht es Sinn.
Devin
1
Gibt es Pläne für eine py3k-freundliche Version?
Tim Tisdall
97

Hier ist mein grundlegender Python-Daemon 'Howdy World', mit dem ich beginne, wenn ich eine neue Daemon-Anwendung entwickle.

#!/usr/bin/python
import time
from daemon import runner

class App():
    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  '/tmp/foo.pid'
        self.pidfile_timeout = 5
    def run(self):
        while True:
            print("Howdy!  Gig'em!  Whoop!")
            time.sleep(10)

app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()

Beachten Sie, dass Sie die python-daemonBibliothek benötigen . Sie können es installieren durch:

pip install python-daemon

Dann starten Sie es einfach mit ./howdy.py startund beenden Sie es mit ./howdy.py stop.

Dustin Kirkland
quelle
5
Das von daemonIhnen importierte Modul ist (noch) kein Standardbestandteil von Python. Es muss mit pip install python-daemonoder gleichwertig installiert werden .
Nate
6
Ich habe Python-Daemon wie von Ihnen beschrieben installiert, aber wenn ich versuche, meine App auszuführen (genau wie Ihre letzten 3 Zeilen), erhalte ich ImportError: Name Runner kann nicht importiert werden
Nostradamnit
Können Sie überprüfen, ob es richtig installiert ist? $ dpkg -L Python-Daemon | Grep Runner /usr/share/pyshared/daemon/runner.py
Dustin Kirkland
4
Dieser Vorschlag scheint veraltet zu sein - ab September 2013 erwähnt python.org/dev/peps/pep-3143 ohnehin keinen "Läufer", der importiert werden kann. Dies würde natürlich die Beobachtung von @ Nostradamnit erklären.
offby1
2
Dies funktioniert immer noch gut für mich im September 2013 unter Ubuntu 13.04, mit Standard-Python-Paketen, Python2.7 und Python-Daemon. Bei Python3 wird jedoch der Fehler "vom Daemon-Import-Runner ImportError: Kein Modul mit dem Namen 'Daemon'" angezeigt
Dustin Kirkland,
42

Beachten Sie das Python-Daemon- Paket, das viele Probleme hinter Daemons sofort löst.

Unter anderem ermöglicht es (aus der Debian-Paketbeschreibung):

  • Trennen Sie den Prozess in eine eigene Prozessgruppe.
  • Stellen Sie die Prozessumgebung ein, die für die Ausführung innerhalb einer Chroot geeignet ist.
  • Verzichten Sie auf suid- und sgid-Privilegien.
  • Schließen Sie alle geöffneten Dateideskriptoren.
  • Ändern Sie das Arbeitsverzeichnis, die UID, die GID und die Umask.
  • Stellen Sie die entsprechenden Signalhandler ein.
  • Öffnen Sie neue Dateideskriptoren für stdin, stdout und stderr.
  • Verwalten Sie eine angegebene PID-Sperrdatei.
  • Registrieren Sie Bereinigungsfunktionen für die Verarbeitung beim Beenden.
Viliam
quelle
35

Eine Alternative: Erstellen Sie ein normales, nicht dämonisiertes Python-Programm und dämonisieren Sie es dann extern mit Supervisord . Dies kann viele Kopfschmerzen ersparen und ist * nix- und sprachübertragbar.

Chris Johnson
quelle
1
Ich denke, das ist der beste Weg. Insbesondere, wenn Sie mehrere Daemons auf einem Betriebssystem ausführen möchten. Nicht codieren, wiederverwenden.
Guettli
Es vereinfacht viele Probleme. Ich habe echte Dämonen geschrieben - sie sind nicht einfach.
Chris Johnson
1
Die beste Antwort ist hier versteckt :)
kawing-chiu
1
Das ist Gold. Nachdem ich stundenlang versucht habe, Python-Daemon zu durchlaufen, ist dies die sofort einsatzbereite Lösung, die für mich funktioniert. Dank der großartigen Dokumentation und Beispiele war mein Daemon in wenigen Minuten betriebsbereit.
Nikhil Sahu
17

Wahrscheinlich keine direkte Antwort auf die Frage, aber systemd kann verwendet werden, um Ihre Anwendung als Daemon auszuführen. Hier ist ein Beispiel:

[Unit]
Description=Python daemon
After=syslog.target
After=network.target

[Service]
Type=simple
User=<run as user>
Group=<run as group group>
ExecStart=/usr/bin/python <python script home>/script.py

# Give the script some time to startup
TimeoutSec=300

[Install]
WantedBy=multi-user.target

Ich bevorzuge diese Methode, weil ein Großteil der Arbeit für Sie erledigt wird und sich Ihr Daemon-Skript dann ähnlich wie der Rest Ihres Systems verhält.

-Oder von

Luke Dupin
quelle
Dies ist der richtige und vernünftige Weg. 1) Muss in /etc/systemd/system/control.service gespeichert werden. 2) Managed systemctl start control.service
Sudo
7

YapDi ist ein relativ neues Python-Modul, das in Hacker News aufgetaucht ist. Sieht ziemlich nützlich aus und kann verwendet werden, um ein Python-Skript aus dem Skript heraus in den Daemon-Modus zu konvertieren.

Sergey R.
quelle
6

Da Python-Daemon Python 3.x noch nicht unterstützt hat und von dem, was auf der Mailingliste zu lesen ist, möglicherweise nie gelesen wird, habe ich eine neue Implementierung von PEP 3143 geschrieben: pep3143daemon

pep3143daemon sollte mindestens Python 2.6, 2.7 und 3.x unterstützen

Es enthält auch eine PidFile-Klasse.

Die Bibliothek hängt nur von der Standardbibliothek und den sechs Modulen ab.

Es kann als Ersatz für Python-Daemon verwendet werden.

Hier ist die Dokumentation .

stephan schultchen
quelle
6

Diese Funktion wandelt eine Anwendung in einen Daemon um:

import sys
import os

def daemonize():
    try:
        pid = os.fork()
        if pid > 0:
            # exit first parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #1 failed: {0}\n'.format(err))
        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 as err:
        sys.stderr.write('_Fork #2 failed: {0}\n'.format(err))
        sys.exit(1)
    # redirect standard file descriptors
    sys.stdout.flush()
    sys.stderr.flush()
    si = open(os.devnull, 'r')
    so = open(os.devnull, 'w')
    se = open(os.devnull, 'w')
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())
Ivan Kolesnikov
quelle
5

Ich fürchte, das von @Dustin erwähnte Daemon-Modul hat bei mir nicht funktioniert. Stattdessen habe ich python-daemon installiert und den folgenden Code verwendet:

# filename myDaemon.py
import sys
import daemon
sys.path.append('/home/ubuntu/samplemodule') # till __init__.py
from samplemodule import moduleclass 

with daemon.DaemonContext():
    moduleclass.do_running() # I have do_running() function and whatever I was doing in __main__() in module.py I copied in it.

Laufen ist einfach

> python myDaemon.py

Nur der Vollständigkeit halber ist hier der Inhalt des Samplemodul-Verzeichnisses aufgeführt

>ls samplemodule
__init__.py __init__.pyc moduleclass.py

Der Inhalt von moduleclass.py kann sein

class moduleclass():
    ...

def do_running():
    m = moduleclass()
    # do whatever daemon is required to do.
Somum
quelle
2

Noch etwas zu beachten, wenn Sie in Python dämonisieren:

Wenn Sie die Python- Protokollierung verwenden und diese nach der Dämonisierung weiter verwenden möchten, müssen Sie close()die Handler (insbesondere die Datei-Handler) aufrufen .

Wenn Sie dies nicht tun, kann der Handler immer noch glauben, dass Dateien geöffnet sind, und Ihre Nachrichten verschwinden einfach - mit anderen Worten, stellen Sie sicher, dass der Logger weiß, dass seine Dateien geschlossen sind!

Dies setzt voraus, dass Sie beim Dämonisieren ALLE offenen Dateideskriptoren wahllos schließen. Stattdessen können Sie versuchen, alle außer den Protokolldateien zu schließen (in der Regel ist es jedoch einfacher, alle zu schließen und die gewünschten Dateien erneut zu öffnen).

Matthew Wilcoxson
quelle
Denken Sie, dass das Öffnen eines neuen Protokollierungshandlers besser ist, als den Protokollierungshandler beispielsweise mit der Option files_preserve des DaemonContext an den Dämon weiterzuleiten?
HeyWatchThis
Sie schließen nur den Logger, Sie erstellen keinen neuen (er wird nur wieder geöffnet, wenn dies erforderlich ist). Aber obwohl es wirklich einfach ist, dies zu tun, ist es möglicherweise besser, den DaemonContext zu verwenden, da er wahrscheinlich einige andere clevere Dinge tut (vorausgesetzt, das Beibehalten ermöglicht immer noch eine ordnungsgemäße Dämonisierung).
Matthew Wilcoxson
2

Obwohl Sie vielleicht die reine Python-Lösung bevorzugen, die vom Python-Daemon-Modul bereitgestellt wird, gibt es daemon(3)in libc- zumindest unter BSD und Linux - eine Funktion , die das Richtige tut.

Es ist einfach, es von Python aus aufzurufen:

import ctypes

ctypes.CDLL(None).daemon(0, 0) # Read the man-page for the arguments' meanings

Das einzige, was noch zu tun ist, ist das Erstellen (und Sperren) der PID-Datei. Aber dass du damit umgehen kannst ...

Mikhail T.
quelle
1

Ich habe einige Zeilen im Codebeispiel von Sander Marechal (von @JeffBauer in der akzeptierten Antwort erwähnt ) geändert , um eine quit()Methode hinzuzufügen , die ausgeführt wird, bevor der Dämon gestoppt wird. Dies ist manchmal sehr nützlich.

Hier ist es.

Hinweis: Ich verwende das Modul "Python-Daemon" nicht, da die Dokumentation noch fehlt (siehe auch viele andere SO-Fragen) und ziemlich unklar ist (wie man einen Daemon mit diesem Modul ordnungsgemäß über die Befehlszeile startet / stoppt?).

Basj
quelle
-1

Nach ein paar Jahren und vielen Versuchen (ich habe alle hier gegebenen Antworten ausprobiert, aber alle hatten am Ende kleinere Nachteile), ist mir jetzt klar, dass es einen besseren Weg gibt, als einen Daemon direkt von Python aus zu starten, zu stoppen und neu zu starten : Verwenden Sie stattdessen die Betriebssystem-Tools.

Zum Beispiel mache ich für Linux anstelle von python myapp startund python myapp stopFolgendes, um die App zu starten:

screen -S myapp python myapp.py    
CTRL+A, D to detach

oder um es in einem Befehlscreen -dmS myapp python myapp.py zu starten und zu trennen .

Dann:

screen -r myapp

wieder an dieses Terminal anschließen. Sobald Sie sich im Terminal befinden, können Sie es mit STRG + C stoppen.

Basj
quelle
-2

Der einfachste Weg, einen Daemon mit Python zu erstellen, ist die Verwendung des ereignisgesteuerten Twisted -Frameworks. Es erledigt alle für die Dämonisierung erforderlichen Dinge für Sie. Es verwendet das Reaktormuster , um gleichzeitige Anforderungen zu verarbeiten.

Travis B. Hartwell
quelle
5
Das ist ein viel zu großer Hammer, um ihn zu benutzen. Die meisten Leute möchten nur ein kurzes Python-Skript ausführen, das sie als Daemon geschrieben haben. Python-Daemon, wie oben beschrieben, ist die richtige Antwort.
Tom Swirly
2
Obwohl diese Antwort ziemlich arrogant war, war sie nützlich.
Fiatjaf
-28

In 80% der Fälle, wenn Leute "Daemon" sagen, wollen sie nur einen Server. Da die Frage in diesem Punkt völlig unklar ist, ist es schwer zu sagen, welche Antwortdomäne möglich sein könnte. Da ein Server ausreichend ist, starten Sie dort. Wenn tatsächlich ein tatsächlicher "Daemon" benötigt wird (dies ist selten), lesen Sie weiter nohup, um einen Server zu dämonisieren.

Bis ein tatsächlicher Daemon tatsächlich benötigt wird, schreiben Sie einfach einen einfachen Server.

Schauen Sie sich auch die WSGI-Referenzimplementierung an .

Schauen Sie sich auch den Simple HTTP Server an .

„Gibt es zusätzliche Dinge, die berücksichtigt werden müssen?“ Ja. Über eine Million Dinge. Welches Protokoll? Wie viele Anfragen? Wie lange kann jede Anfrage bearbeitet werden? Wie oft werden sie ankommen? Verwenden Sie einen dedizierten Prozess? Themen? Unterprozesse? Einen Daemon zu schreiben ist eine große Aufgabe.

S.Lott
quelle
12
Keine dieser Bibliotheken macht eine einzige fork(), geschweige denn zwei. Sie haben nichts mit Dämonisierung zu tun.
Brandon Rhodes
8
Unter Unix-Betriebssystemen steht ein "Daemon" -Prozess - wie die Flugbegleiter, die die Griechen "Daemons" nannten - "zur Seite". Anstatt einen einzelnen Benutzer direkt über das TTY dieses Benutzers zu bedienen, gehört ein Daemon zu keinem TTY, kann jedoch Anforderungen von vielen Benutzern im System beantworten oder - wie crondoder syslogd- Housekeeping-Dienste für das gesamte System ausführen. Um einen Daemon-Prozess zu erstellen, muss mindestens ein Double ausgeführt werden, fork()wobei alle Dateideskriptoren geschlossen sind, damit man gegen Signale von allen steuernden Terminals, einschließlich der Systemkonsole, immun ist. Siehe die Antwort von Bignose.
Brandon Rhodes
5
@S Lott - "ein Server" beschreibt, was ein Prozess tut ( wartet auf eingehende Anforderungen, anstatt seine eigenen Aktionen zu initiieren); „A - Daemon“ beschreibt , wie ein Prozess läuft (ohne Fenster oder ein Steueranschluss). SimpleHTTPServerist in der Tat ein Server, aber einer, der nicht von Haus aus weiß, wie man sich selbst dämonisiert (Sie können ihn beispielsweise mit Strg-C drücken). nohupist ein Dienstprogramm zum Dämonisieren eines naiven Prozesses. Ihr Nohupped-Server ist also in der Tat sowohl ein Dämon als auch ein Server, genau wie Sie behaupten. Diese Frage zum Stapelüberlauf lautete im Wesentlichen: "Wie kann ich nohupin Python implementieren ?"
Brandon Rhodes
5
Ja, aber mein Verständnis der OP-Frage ist, dass er die Dämonisierung aus seinem Python-Programm heraus und ohne etwas anderes durchführen möchte.
Noufal Ibrahim
4
@ S Lott - Sie müssen nicht beeindruckt sein! Der Autor jeder anderen Antwort wusste, was „Daemon“ bedeutet, daher ist meine Fähigkeit, diese Frage zu interpretieren, kaum einzigartig. :) Und woher kam die Idee, dass der Autor ein Rad neu erfinden soll? Ich denke, es nohupist ein gutes Werkzeug, und ich werde meine -1-Stimme entfernen, wenn Sie diese nützliche Idee einfach in Ihre eigentliche Antwort übernehmen. In der Tat, wenn Sie erwähnen supervisordund wie es den Autor auch davon abhält, Protokollierung, ein Start-Stopp-Skript und eine Neustartdrosselung durchzuführen, dann werde ich Sie sogar +1. :)
Brandon Rhodes