Ein Python-Script mit Systemd als Daemon (Systemd tut garnicht weh… :-) )

Einen Python-Server dauerhaft laufen zu lassen (z.B. auf einem Debian/Ubuntu Server oder einem Raspberry Pi) kann man mit Screen realisieren, oder -fast genauso einfach- mit Systemd…

Zuallererst brauchen wir ein Stück Python(3) Programmcode. In die Shebang-Zeile schreiben wir das ‚-u‘-Flag für Python3, damit die Scriptausgabe nicht gepuffert wird (wir wollen jede Ausgabe sofort im Log sehen):

#!/usr/bin/python3 -u
import socket

TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 20  # Normally 1024, but we want fast response

while 1:
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   s.bind((TCP_IP, TCP_PORT))
   s.listen(1)
   
   conn, addr = s.accept()
   print('Connection address:', addr)
   while 1:
      data = conn.recv(BUFFER_SIZE)
      if not data: break
      print("received data:", data)
      conn.send(data)  # echo
   conn.close()

Wir speichern das Script unter ‚/etc/pyserver/pyserver.py‘ (Ordner vorher anlegen) und machen es mit ‚chmod +x /etc/pyserver/pyserver.py‘ ausführbar.

Das Script gibt alles aus was es empfangen hat, und schickt es unverändert zurück. Wird die Connection beendet so wartet es auf eine neue. Da das script nicht forkt/multithreaded ist kann dieser Server natürlich immer nur eine gleichzeitige Connection bedienen!

Nun benötigen wir einen User unter dem der Server läuft, dieser bekommt auch den Ordner:

useradd -r -s /bin/false pyserveruser
chown -R pyserveruser:pyserveruser /etc/pyserver

Jetzt brauchen wir die sog. Unit-Datei. Sie erklärt systemd was wir für ein Dienst sind:

[Unit]
Description=My Python Server
After=syslog.target

[Service]
Type=simple
User=pyserveruser
Group=pyserveruser
WorkingDirectory=/etc/pyserver
ExecStart=/etc/pyserver/pyserver.py
SyslogIdentifier=pyserver
StandardOutput=syslog
StandardError=syslog
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Diese Datei bitte als ‚/etc/systemd/system/pyserver.service‘ speichern.
(Falls man die Datei ändert muss man dies systemd mittels ’systemctl daemon-reload‘ mitteilen)

Und schon können wir unseren Server starten:

systemctl enable pyserver
systemctl start pyserver

Mittels ’systemctl status pyserver‘ sollten wir nun sehen dass er läuft:

Active: active (running)

Jetzt testen wir ihn mal, indem wir uns mittels netcat zu unserem localhost auf den Port 5005 verbinden:

nc 127.0.0.1 5005

Wenn er alles zurückschickt was man ihm schreibt funktioniert alles.
Mittels Strg+C beendet man Netcat.

Übrigens überlebt unser Dienst dank den Restart-Zeilen in seinem Unitfile auch einen ‚kill -9‘ auf seinen Prozess. Systemd startet ihn einfach 3 Sekunden später neu.

Steuern kann man den Server nun mit den normalen systemd Kommandos:

systemctl status pyserver
systemctl start pyserver
systemctl stop pyserver
systemctl restart pyserver

Seine Logausgaben sieht man so:

journalctl -u pyserver

Die Logausgaben landen übrigens in der Datei ‚/var/log/syslog‘.
Da das noch nicht ganz so optimal ist korrigieren wir das noch!

In der Datei ‚/etc/rsyslog.d/50-default.conf‘ teilen wir unserem Syslog-Deamon mit dass das Programm ‚pyserver‘ bitte nach ‚/var/log/pyserver.log‘ geschrieben werden soll:

:programname,isequal,"pyserver"         /var/log/pyserver.log
& ~

Neustart des Rsyslog-Daemons mittels

systemctl restart rsyslog

nicht vergessen.

So, und nun sorgen wir als letztes noch dafür dass Logrotate unsere Logs täglich zippt und maximal 5 Logs aufbewahrt, Datei ‚/etc/logrotate.d/pysever‘ erstellen, folgender Inhalt:

/var/log/pyserver.log { 
    su root syslog
    daily
    rotate 5
    compress
    delaycompress
    missingok
    postrotate
        systemctl restart rsyslog > /dev/null
    endscript    
}

Und nun testen wir das wegrotieren erstmal trocken:

logrotate -d /etc/logrotate.d/pysever

Und wenn es keine Fehler gab können wir ihn zwingen zu rotieren, obwohl der Tag noch nicht abgelaufen ist:

logrotate --force /etc/logrotate.d/pysever

Und so einfach haben wir unseren eigenen kleinen Daemon erschaffen! (an dieser Stelle bitte Bösewicht-Gelächter vorstellen!)

Die Anleitung sollte auch auf Debian gehen, da muss nur die Gruppe in der Logrotate-Datei von ’syslog‘ auf ‚adm‘ geändert werden.
Wie immer, bitte in den Kommentaren melden wenn ich irgendeinen schrecklichen Patzer gemacht hab und meine Anleitung den Server nach ein paar Stunden abbrennen lässt – danke 🙂

5 Antworten auf „Ein Python-Script mit Systemd als Daemon (Systemd tut garnicht weh… :-) )“

  1. Hallo Thomas
    ich habe auch ein Anliegen, denn ich möchte ein Server, welcher die SPI Schnittstelle vom Raspi ersetzt betreiben.
    Grund: Raspi ist mit einem Controller verbunden und soll den Datentaustausch zwischen raspi (python) und Controller (C) realisieren. Das Daten senden vom Raspi an den Controller funktioniert prima. Sendet der Controller Daten an den Raspi bleibt der ganze Laden (raspi und Controller) stehen.
    Da habe ich zur Not versucht die SPI Schnittstelle von Python als Master zu betreiben hatte auch nicht zum Erfolg geführt. Nun will ich die GPIO-PINS vom Raspi direkt anschalten und den Datenaustausch (abrufen der Daten vom Controller) auf diesem Wege zu realisieren.
    Dieses ist jetzt eine normale Python Datei. Diese Datei wollte ich dann als Service im Hintergrund laufen lassen, welche die Daten bei Bedarf vom Controller abruft.
    So nun ist es denn möglich eine Normale Datei mit while loop als Server laufen zu lassen?
    Ja mit den UNITS und systemd habe ich schon herumgearbeitet, aber ich habe keine Ahnung ob es mit einer normalen Datei mit wihle Loop auch funktionieren kann.
    Währe nett, wenn Du mir ein kleinen Hinweis geben könntest, wie ich diese Datei als Service laufen lassen kann

  2. Hallo Wilhelm,

    klar, wenn du ein Python Script mittels ‚crontab -e‘ in die Crontab-Datei einträgst und als Zeitpunkt einfach ‚@reboot‘ schreibst dann startet das direkt nach dem reboot im Hintergrund und kann auch eine Endlosschleife beinhalten.

    Problem ist dabei dann halt dass diese Endlosschleife tatsächlich einen Prozessorcore fast auf 100% auslastet. Damit verbraucht dein Pi mehr Strom und wird wärmer.

    Wenns dir drum geht ein Pin Togglen abzufangen so wäre ein GPIO Interrupt (die RasPi GPIO Library kann das) sinnvoller.
    Alternativ könnte man mal debuggen warum „der ganze Laden stehen bleibt“ wenn du normales SPI verwendest. Stehenbleiben sollte da eigentlich gar nichts.

    Wenn du mir die Programmcodes gibst kann ich das evtl. nachstellen – vorausgesetzt ich habe den richtigen Controller zur Hand. Nen Pi kann ich bieten 🙂

    Thomas

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.