Python-Daemon und systemd-Dienst

78

Ich habe ein einfaches Python-Skript, das als Daemon arbeitet. Ich versuche, ein systemd-Skript zu erstellen, um dieses Skript beim Start 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 __name__ == '__main__':
    with daemon.DaemonContext():
        check = Node()
        check.run()

runenthält while TrueSchleife.

Ich versuche diesen Dienst mit auszuführen systemctl start zebra-node.service. Leider hat der Dienst die Sequenz nie beendet - ich muss Strg + C drücken. Das Skript wird ausgeführt, aber der Status wird aktiviert und nach einer Weile wird er deaktiviert. Jetzt benutze ich Python-Daemon (aber bevor ich es ohne versuchte und die Symptome waren ähnlich).

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

pawelbial
quelle
Hat die Antwort Ihr Problem gelöst? Wenn nicht, setzen Sie beim Erstellen von DaemonContext () daemon_context = True. Es kann funktionieren.
1
@pawelbial Es ist schade, dass Ihr Python-Codebeispiel nicht vollständig ist (fehlender Import von daemonund nicht klar, woher das Nodekommt), so dass es nicht einfach / möglich ist, Ihre Situation zu reproduzieren.
Jan Vlcinsky
@pawelbial Dies bezog sich indirekt auf die Frage, könnte Ihnen aber helfen: unix.stackexchange.com/a/226853/33386
Jonathan Komar

Antworten:

116

Der Grund dafür, dass die Startsequenz nicht abgeschlossen wird, ist, dass für Type forkingerwartet wird, dass Ihr Startprozess verzweigt und beendet wird (siehe $ man systemd.service - Suche nach Gabelung).

Verwenden Sie einfach nur den Hauptprozess, dämonisieren Sie nicht

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 ihn zu dämonisieren.

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

Dies ermöglicht die Verwendung eines einfacheren aufgerufenen simpleDiensttyps, sodass Ihre Gerätedatei so 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

Beachten Sie, dass der -uIn-Python-Shebang nicht erforderlich ist. Wenn Sie jedoch etwas auf stdout oder stderr ausdrucken, -uwird sichergestellt, dass keine Ausgabepufferung 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=syslogund in die Einheitendatei eingefügt StandardError=syslog. Wenn Sie sich nicht für die gedruckte Ausgabe in Ihrem Tagebuch interessieren, interessieren Sie sich nicht für diese Zeilen (sie müssen nicht vorhanden sein).

systemd macht die Dämonisierung überflüssig

Während der Titel Ihrer Frage explizit nach Daemonisierung fragt, ist der Kern der Frage, wie ich meinen Dienst zum Laufen bringen kann, und während die Verwendung des Hauptprozesses viel einfacher erscheint (Sie müssen sich überhaupt nicht um Daemons kümmern), ist dies der Fall könnte als Antwort auf Ihre Frage angesehen werden.

Ich denke, dass viele Leute Daemonisierung verwenden, nur weil "jeder es tut". Mit systemd sind die Gründe für die Dämonisierung oft überholt. Es kann einige Gründe geben, die Dämonisierung zu verwenden, aber dies wird jetzt selten der Fall sein.

EDIT: python -prichtig eingestellt python -u. danke kmftzg

Jan Vlcinsky
quelle
4
Der Grund für die Dämonisierung besteht darin, andere Plattformen zu unterstützen, die systemd nicht verwenden. Die Notwendigkeit, einen separaten Codepfad für systemd zu erstellen, ist eine weitere Möglichkeit, mit der systemd die Portabilität verhindert.
Nick Bastin
2
@NickBastin und dies ist ein weiterer Weg, wie Portabilität Fortschritt und Vereinfachung hemmt.
Intelfx
2
@NickBastin systemd unterstützt Forking-Anwendungen, sodass kein separater Codepfad für systemd erforderlich ist, wenn Sie dies nicht möchten. Das Nichtlesen der Dokumentation beeinträchtigt die praktischen Fähigkeiten und kann Kommentare unbegründet machen.
Jan Vlcinsky
4
@ NickBastin Das OP spricht über "einfaches Python-Skript" und die Verwendung von "systemd". Keine Anfrage für die Portabilität von Plattformen ohne Systemd. Eine solche Anfrage besteht nur in Ihren Reaktionen, die systemd beschuldigen.
Jan Vlcinsky
1
Es gibt eindeutig andere Prozessmanagement-Tools wie Supervisord oder pm2. Ich denke, "nicht zu dämonisieren" ist eindeutig die Unix-Methode, um Dinge zu tun.
ospider
23

Es ist möglich zu dämonisieren, wie Schnouki und Amit es beschreiben. 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 oder einen UNIX-Socket oder ähnliches abhören möchten. Systemd würde den Socket öffnen, ihn abhören und dann den Dämon erzeugen, wenn eine Verbindung eingeht. Dies ist der bevorzugte Ansatz, da er dem Administrator die größte Flexibilität bietet. [1] und [2] geben eine schöne 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 init dann benachrichtigt, dass er bereit ist und Anforderungen bedienen kann. Dies kann mit dem "Forking-Protokoll" implementiert werden, aber eigentlich ist es besser, einfach eine Benachrichtigung mit sd_notify () an systemd zu senden. Der 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 hätte die Unit-Datei Type = notify und rufe systemd.daemon.notify ("READY = 1") auf, nachdem sie die Sockets eingerichtet hat. Es ist kein Forking oder Daemonization erforderlich.

zbyszek
quelle
Sieht gut aus. Wie installiere ich diese Python-Bibliothek, die systemd.daemonvia Pip bereitstellt ?
Guettli
Die offiziellen Installationsanweisungen unter github.com/systemd/python-systemd#installation zeigen, wie mit pip installiert wird. Wenn sie für Sie nicht funktionieren, melden Sie bitte ein Problem unter github.com/systemd/python-systemd/issues .
Zbyszek
15

Sie erstellen die PID-Datei nicht.

systemd erwartet, dass Ihr Programm seine PID schreibt /var/run/zebra.pid. Da Sie dies nicht tun, glaubt systemd wahrscheinlich, dass Ihr Programm fehlschlägt, und deaktiviert es daher.

Um die PID-Datei hinzuzufügen, installieren Sie die 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: Einige kürzlich durchgeführte Aktualisierungen haben lockfiledie API geändert und sie mit python-daemon inkompatibel gemacht. Um dies zu beheben, bearbeiten Sie sie daemon/pidlockfile.py, entfernen Sie sie LinkFileLockaus den Importen und fügen Sie sie hinzu from lockfile.linklockfile import LinkLockFile as LinkFileLock.)

Achten Sie auf eine andere Sache: DaemonContext Ändert das Arbeitsverzeichnis Ihres Programms in /und macht die WorkingDirectoryIhrer Servicedatei unbrauchbar. Wenn Sie DaemonContextin ein anderes Verzeichnis chdir möchten , verwenden Sie DaemonContext(pidfile=pidfile, working_directory="/path/to/dir").

Schnouki
quelle
2
Der letzte Absatz darüber, wie DaemonContextÄnderungen im Arbeitsverzeichnis des Programms gerade meine Dämonisierungsprobleme gelöst haben
DJG
2
Da weniger Code mehr ist, bevorzuge ich die Antwort "Verwenden Sie einfach nur den Hauptprozess, dämonisieren Sie nicht".
Guettli
idk wenn sich die API in Python3 geändert hat. aber da muss es sein import daemon.pidfileund nichtimport daemon.pidlockfile
reox
Wenn die Sperre
aufgehoben wird,
4

Außerdem müssen Sie höchstwahrscheinlich daemon_context=Truebeim Erstellen des festlegenDaemonContext() .

Dies liegt daran, python-daemondass, wenn festgestellt wird, dass es unter einem Init-System ausgeführt wird, es sich nicht vom übergeordneten Prozess löst. systemderwartet, dass der Daemon-Prozess, mit dem ausgeführt Type=forkingwird, dies tut. Daher brauchen Sie das sonstsystemd wird weiter warten und schließlich den Prozess beenden.

Wenn Sie neugierig sind, sehen Sie im python-daemonDaemon-Modul diesen 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 dies besser.


quelle
2
Ich könnte mich irren, aber ich denke, dieses Flag heißt "attach_process", nicht und nicht "daemon_context"
Peter Turner
4

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

[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-Servicedatei aus gelöscht /etc/init.dund ausgeführt sudo systemctl daemon-reload, um systemd neu zu laden.

Ich wollte, dass mein Dienst automatisch neu gestartet wird, daher die Neustartoptionen. Ich fand auch Verwendung idlefür Typeals wettgemacht mehr Sinn simple.

Das Verhalten im Leerlauf ist dem einfachen sehr ähnlich. Die tatsächliche Ausführung der Service-Binärdatei wird jedoch verzögert, bis alle aktiven Jobs ausgelöst 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 hier verwendet habe .

Ich habe auch experimentiert, um den alten Dienst beizubehalten und systemd den Dienst neu starten zu lassen, 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 Dienstskript init.d anstelle des Dienstes systemd verwendet wurde, wenn beide den gleichen Namen hatten. Wenn Sie den von init.d initiierten Prozess beendet haben, übernimmt das systemd-Skript. Wenn Sie service <service-name> stopes jedoch ausführen, verweist es auf den alten Dienst init.d. Daher fand ich, dass der beste Weg darin bestand, den alten Dienst init.d zu löschen, und der Befehl service verwies stattdessen auf den Dienst systemd.

Hoffe das hilft!

radtek
quelle