it-swarm.com.de

Python Daemon- und Systemd-Dienst

Ich habe ein einfaches Python Skript, das als Daemon arbeitet. Ich versuche, ein systemd Skript zu erstellen, um dieses Skript während des Startvorgangs starten zu können.

Aktuelles Systemd-Skript:

[Unit]
Description=Text
After=syslog.target

[Service]
Type=forking
User=node
Group=node
WorkingDirectory=/home/node/Node/
PIDFile=/var/run/zebra.pid
ExecStart=/home/node/Node/node.py

[Install]
WantedBy=multi-user.target

node.py:

if __== '__main__':
    with daemon.DaemonContext():
        check = Node()
        check.run()

run enthält while True Schleife.

Ich versuche diesen Dienst mit systemctl start zebra-node.service. Leider hat der Dienst die Angabe der Sequenz nie beendet - ich muss Strg + C drücken. Das Skript wird ausgeführt, aber der Status wird aktiviert und ändert sich nach einiger Zeit in Deaktivieren. Jetzt benutze ich Python-Daemon (aber bevor ich es ohne ausprobierte und die Symptome ähnlich waren).

Sollte ich einige zusätzliche Funktionen für mein Skript implementieren oder ist die systemd-Datei falsch?

68
pawelbial

Der Grund dafür, dass die Startsequenz nicht vollständig ist, besteht darin, dass für Typ forking erwartet wird, dass Ihr Startvorgang verzweigt und beendet wird (siehe $ man systemd.service - Suche nach Gabeln).

Einfach nur den Hauptprozess verwenden, nicht dämonisieren

Eine Möglichkeit ist, weniger zu tun. Mit systemd müssen häufig keine Dämonen erstellt werden, und Sie können den Code direkt ausführen, ohne eine Dämonisierung durchzuführen.

#!/usr/bin/python -u
from somewhere import Node
check = Node()
check.run()

Dies ermöglicht die Verwendung eines einfacheren Diensttyps mit dem Namen simple, so dass Ihre Gerätedatei wie folgt aussehen würde.

[Unit]
Description=Simplified simple zebra service
After=syslog.target

[Service]
Type=simple
User=node
Group=node
WorkingDirectory=/home/node/Node/
ExecStart=/home/node/Node/node.py
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

Notiere dass der -u in python Shebang ist nicht erforderlich, aber falls Sie etwas nach stdout oder stderr ausdrucken, wird das -u stellt sicher, dass kein Ausgabepuffer vorhanden ist und gedruckte Zeilen sofort von systemd abgefangen und im Journal aufgezeichnet werden. Ohne sie würde es mit einiger Verzögerung erscheinen.

Zu diesem Zweck habe ich die Zeilen StandardOutput=syslog und StandardError=syslog. Wenn Ihnen die gedruckte Ausgabe in Ihrem Tagebuch nicht wichtig ist, kümmern Sie sich nicht um diese Zeilen (sie müssen nicht vorhanden sein).

systemd macht die Dämonisierung überflüssig

Während der Titel Ihrer Frage explizit nach Dämonisierung fragt, ist der Kern der Frage wohl "Wie kann ich meinen Dienst zum Laufen bringen?" Und während der Hauptprozess scheint viel einfacher zu sein (Sie müssen sich nicht darum kümmern über Dämonen überhaupt) könnte es als Antwort auf Ihre Frage angesehen werden.

Ich denke, dass viele Leute Dämonisierung verwenden, nur weil "jeder es tut". Bei systemd sind die Gründe für die Dämonisierung oft veraltet. Es kann einige Gründe für die Verwendung der Dämonisierung geben, dies wird jedoch derzeit nur in Ausnahmefällen der Fall sein.

BEARBEITEN: behoben python -p zum richtigen python -u. danke kmftzg

103
Jan Vlcinsky

Es ist möglich, wie Schnouki und Amit beschreiben zu dämonisieren. Bei systemd ist dies jedoch nicht erforderlich. Es gibt zwei bessere Möglichkeiten, den Dämon zu initialisieren: Socket-Aktivierung und explizite Benachrichtigung mit sd_notify ().

Die Socket-Aktivierung funktioniert für Daemons, die einen Netzwerkport, einen UNIX-Socket oder ähnliches überwachen möchten. Systemd öffnet den Socket, überwacht ihn und erzeugt dann den Daemon, wenn eine Verbindung hergestellt wird. Dies ist der bevorzugte Ansatz, da er dem Administrator die größte Flexibilität bietet. [1] und [2] geben eine nette Einführung, [3] beschreibt die C-API, während [4] die Python API beschreibt.

[1] http://0pointer.de/blog/projects/socket-activation.html
[2] http://0pointer.de/blog/projects/socket-activation2.html
[3] http://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
[4] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.listen_fds

Explizite Benachrichtigung bedeutet, dass der Dämon die Sockets selbst öffnet und/oder eine andere Initialisierung durchführt und dann init benachrichtigt, dass er bereit ist und Anforderungen bedienen kann. Dies kann mit dem "forking protocol" implementiert werden, aber eigentlich ist es besser, mit sd_notify () eine Benachrichtigung an systemd zu senden. Python wrapper heißt systemd.daemon.notify und ist eine zu verwendende Zeile [5].

[5] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.notify

In diesem Fall hat die Unit-Datei Type = notify und ruft systemd.daemon.notify ("READY = 1") auf, nachdem die Sockets eingerichtet wurden. Es ist keine Verzweigung oder Dämonisierung erforderlich.

21
zbyszek

Sie erstellen keine PID-Datei.

systemd erwartet, dass Ihr Programm seine PID in /var/run/zebra.pid schreibt. Wenn Sie dies nicht tun, geht systemd wahrscheinlich davon aus, dass Ihr Programm fehlschlägt, und deaktiviert es daher.

Um die PID-Datei hinzuzufügen, installieren Sie Sperrdatei und ändern Sie Ihren Code wie folgt:

import daemon
import daemon.pidlockfile 

pidfile = daemon.pidlockfile.PIDLockFile("/var/run/zebra.pid")
with daemon.DaemonContext(pidfile=pidfile):
    check = Node()
    check.run()

(Kurzer Hinweis: Bei einigen kürzlichen Aktualisierungen von lockfile wurde die API geändert und inkompatibel mit Python-Daemon gemacht. Um dies zu beheben, bearbeiten Sie daemon/pidlockfile.py, Entfernen Sie LinkFileLock aus den Importen und addiere from lockfile.linklockfile import LinkLockFile as LinkFileLock.)

Beachten Sie Folgendes: DaemonContext ändert das Arbeitsverzeichnis Ihres Programms in /, Wodurch das WorkingDirectory Ihrer Servicedatei unbrauchbar wird. Wenn Sie möchten, dass DaemonContext in ein anderes Verzeichnis wechselt, verwenden Sie DaemonContext(pidfile=pidfile, working_directory="/path/to/dir").

14
Schnouki

Außerdem müssen Sie höchstwahrscheinlich daemon_context=True Einstellen, wenn Sie die Funktion DaemonContext() erstellen.

Dies liegt daran, dass python-daemon Feststellt, dass es sich nicht vom übergeordneten Prozess löst, wenn es unter einem Init-System ausgeführt wird. systemd erwartet, dass der Dämonprozess, der mit Type=forking ausgeführt wird, dies tut. Daher brauchen Sie das, sonst wartet systemd und beendet den Prozess.

Wenn Sie neugierig sind, sehen Sie im Dämonmodul von python-daemon Den folgenden Code:

def is_detach_process_context_required():
    """ Determine whether detaching process context is required.

        Return ``True`` if the process environment indicates the
        process is already detached:

        * Process was started by `init`; or

        * Process was started by `inetd`.

        """
    result = True
    if is_process_started_by_init() or is_process_started_by_superserver():
        result = False

Hoffentlich erklärt sich das besser.

3
user59634

Ich bin auf diese Frage gestoßen, als ich versucht habe, einige python= init.d-Dienste unter CentOS 7 nach systemd zu konvertieren. Dies scheint für mich großartig zu funktionieren, indem ich diese Datei in /etc/systemd/system/ Platziere:

[Unit]
Description=manages worker instances as a service
After=multi-user.target

[Service]
Type=idle
User=node
ExecStart=/usr/bin/python /path/to/your/module.py
Restart=always
TimeoutStartSec=10
RestartSec=10

[Install]
WantedBy=multi-user.target

Ich habe dann meine alte init.d-Dienstdatei von /etc/init.d Gelöscht und Sudo systemctl daemon-reload Ausgeführt, um systemd neu zu laden.

Ich wollte, dass mein Dienst automatisch neu startet, daher die Neustartoptionen. Ich fand es auch sinnvoller, idle für Type zu verwenden als simple.

Das Verhalten im Leerlauf ist sehr ähnlich zu einfach; Die tatsächliche Ausführung der Service-Binärdatei wird jedoch verzögert, bis alle aktiven Jobs abgesetzt wurden. Dies kann verwendet werden, um eine Verschachtelung der Ausgabe von Shell-Diensten mit der Statusausgabe auf der Konsole zu vermeiden.

Weitere Details zu den Optionen, die ich verwendet habe hier .

Ich habe auch versucht, den alten Dienst beizubehalten und den Dienst neu zu starten, aber ich bin auf einige Probleme gestoßen.

[Unit]
# Added this to the above
#SourcePath=/etc/init.d/old-service 

[Service]
# Replace the ExecStart from above with these
#ExecStart=/etc/init.d/old-service start
#ExecStop=/etc/init.d/old-service stop

Die Probleme, die ich hatte, waren, dass das init.d-Dienstskript anstelle des systemd-Diensts verwendet wurde, wenn beide den gleichen Namen hatten. Wenn Sie den von init.d eingeleiteten Prozess abbrechen, übernimmt das Skript systemd die Kontrolle. Wenn Sie jedoch service <service-name> stop Ausführen, wird auf den alten init.d-Dienst verwiesen. Daher fand ich die beste Möglichkeit, den alten init.d-Dienst zu löschen und stattdessen den service-Befehl auf den systemd-Dienst zu verweisen.

Hoffe das hilft!

1
radtek