Mein Ziel war es, selbst einen Dienst einzurichten und dabei die Unterschiede zwischen wheezy (SysVinit) und Jessie (SystemD) zu lernen.
Eine gute Beschreibung für meinen Start habe ich hier gefunden. Python ist wie Perl als Grundlage im Debian System schon vorhanden, man braucht also keine weitere Installation. Das Python Script aus dem genannten Artikel ist eine gute Grundlage und ich habe es etwas modifiziert.
Alle Scripte müssen im Unix Format (nur lf als Zeilenwechsel) gespeichert werden!
Das Demo-Script
Es schreibt kontinuierlich in eine "Tages"-Logdatei im /tmp Verzeichnis und kann bei normalen Start entweder mit ctrl-c oder im Hintergrund einfach mit kill beendet werden. Das Script demonstriert ein paar nützliche Techniken, ich habe lediglich den Abbruch am Ende entfernt und das Intervall auf eine Minute heraufgesetzt. So läuft das Script "ewig". Im Script selbst ist das Wichtigste dokumentiert.#!/usr/bin/env python import logging import logging.handlers import argparse import sys import time # this is only being used as part of the example # Defaults LOG_FILENAME = "/tmp/myservice.log" LOG_LEVEL = logging.INFO # Could be e.g. "DEBUG" or "WARNING" # Define and parse command line arguments parser = argparse.ArgumentParser(description="My simple Python service") parser.add_argument("-l", "--log", help="file to write log to (default '" + LOG_FILENAME + "')") # If the log file is specified on the command line then override the default args = parser.parse_args() if args.log: LOG_FILENAME = args.log # Configure logging to log to a file, making a new file at midnight and keeping the last 3 day's data # Give the logger a unique name (good practice) logger = logging.getLogger(__name__) # Set the log level to LOG_LEVEL logger.setLevel(LOG_LEVEL) # Make a handler that writes to a file, making a new file at midnight and keeping 3 backups handler = logging.handlers.TimedRotatingFileHandler(LOG_FILENAME, when="midnight", backupCount=3) # Format each log message like this formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') # Attach the formatter to the handler handler.setFormatter(formatter) # Attach the handler to the logger logger.addHandler(handler) # Make a class we can use to capture stdout and sterr in the log class MyLogger(object): def __init__(self, logger, level): """Needs a logger and a logger level.""" self.logger = logger self.level = level def write(self, message): # Only log if there is a message (not just a new line) if message.rstrip() != "": self.logger.log(self.level, message.rstrip()) # Replace stdout with logging to file at INFO level sys.stdout = MyLogger(logger, logging.INFO) # Replace stderr with logging to file at ERROR level sys.stderr = MyLogger(logger, logging.ERROR) i = 0 # Loop forever, doing something useful hopefully: while True: logger.info("The counter is now " + str(i)) print "This is a print" i += 1 time.sleep(60)
SysVinit konfigurieren
Init-Script
Ich möchte den Dienst unter einem normalen User laufen lassen und nicht als root (default). Im Pfad /etc/init.d/ befinden sich die Scripte welche zur Dienste Steuerung verwendet werden. Dort gibt es auch ein Script Namens skeleton, welches als Template verwendet werden kann. Also zunächst mal das Steuerungs Script, wiederum aus dem eingangs erwähnten Artikel, leicht modifiziert. Wie zu sehen ist, soll der Dienst unter dem User pi ($DAEMON_USER) und im Homedirectory ($DIR) vom User laufen.#!/bin/sh ### BEGIN INIT INFO # Provides: myservice # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Put a short description of the service here # Description: Put a long description of the service here ### END INIT INFO # Change the next 3 lines to suit where you install your script and what you want to call it DIR=/home/pi DAEMON=$DIR/myservice.py DAEMON_NAME=myservice # Add any command line options for your daemon here DAEMON_OPTS="" # This next line determines what user the script runs as. # Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python. DAEMON_USER=pi # The process ID of the script when it runs is stored here: PIDFILE=/var/run/$DAEMON_NAME.pid . /lib/lsb/init-functions do_start () { log_daemon_msg "Starting system $DAEMON_NAME daemon" start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS log_end_msg $? } do_stop () { log_daemon_msg "Stopping system $DAEMON_NAME daemon" start-stop-daemon --stop --pidfile $PIDFILE --retry 10 log_end_msg $? } case "$1" in start|stop) do_${1} ;; restart|reload|force-reload) do_stop do_start ;; status) status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? ;; *) echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" exit 1 ;; esac exit 0
Scripte kopieren und einrichten
Mit winscp werden die Scripte ins Home Verzeichnis von pi kopiert. Dann mit putty ein ssh Terminal öffnen und das Init-Script an Ort und Stelle kopieren, Rechte setzen und den Dienst einrichten.sudo cp myservice.sh /etc/init.d/ chmod +x myservice.py chmod +x /etc/init.d/myservice.sh sudo update-rc.d myservice.sh defaultsJetzt kann man mit den üblichen Befehlen oder durch direkten Script Aufruf den Dienst starten, abfragen und beenden.
sudo /etc/init.d/myservice.sh start service myservice status sudo service myservice stopWill man den Dienst wieder entfernen, hilft uns update-rc.d mit den Optionen disable und remove.
SystemD
SystemD wird unter jessie mit installiert und läuft kompatibel und transparent. Man kann also sofort den Dienst auch mit systemctl <start|status|stop> myservice kontrollieren. Ich will aber nun die "alte" Einrichtung deaktivieren und den Dienst unter SystemD einrichten.Ein Dienst in SystemD wird mit sogenannten Unit Files eingerichtet.
1. Variante: einfach das init.d Script verwenden
Im FHEM Forum gibt es ein HowTo wie man den existierenden Service in einen SystemD Service umwandelt. Die folgende Datei wird als myservice.service gespeichert und mit winscp wieder nach /home/pi übertragen.Wie man sieht, wird dabei gleich das alte SysVinit Script recycelt.
[Unit] Description=Test myservice [Service] Type=forking ExecStart=/etc/init.d/myservice.sh start ExecStop=/etc/init.d/myservice.sh stop [Install] WantedBy=multi-user.target
Zunächst also den Dienst wieder entfernen, die Datei nach /etc/systemd/system kopieren und systemd über die Änderung informieren.
sudo update-rc.d myservice.sh disable sudo cp myservice.service /etc/systemd/system/ sudo systemctl --system daemon-reloadJetzt kann man zur Probe den Dienst mit systemctl <start|status|stop> myservice starten. Wenn alles funktioniert muss er noch aktiviert werden.
sudo systemctl enable myservice
2. Variante: extra Startscript verwenden
Hierauf basierend habe ich mir ein universelles Startscript erstellt. Zumindest mit meinem Pythonscript funktioniert das einwandfrei. Im Endeffekt ist es aber in der Funktion dem SysVinit adäquat. Die Funktion ist anders realisiert, ich finde es besser lesbar und der SysVinit Abschnitt am Anfang fehlt. Letztendlich könnte man sogar den Scriptaufruf als Parameter übergeben.#!/bin/bash PID="" script="python myservice.py" function get_pid { PID=`pidof $script` } function stop { get_pid if [ -z $PID ]; then echo "server is not running." exit 1 else echo -n "Stopping server.." kill -9 $PID sleep 1 echo ".. Done." fi } function start { get_pid if [ -z $PID ]; then echo "Starting server.." $script & get_pid echo "Done. PID=$PID" else echo "server is already running, PID=$PID" fi } function restart { echo "Restarting server.." get_pid if [ -z $PID ]; then start else stop sleep 5 start fi } function status { get_pid if [ -z $PID ]; then echo "Server is not running." exit 1 else echo "Server is running, PID=$PID" fi } case "$1" in start) start ;; stop) stop ;; restart) restart ;; status) status ;; *) echo "Usage: $0 {start|stop|restart|status}" esac
Praktisch gesehen ist der Unit Teil bei Systemd vom Startscript getrennt. Basierend auf dieser Dokumentation habe ich mal eine ziemlich "aufwändige" Variante erstellt. Hierbei wird start|stop|restart durch das obige Script gesteuert.
[Unit] Description=Test myservice After=network.target [Service] Type=forking User=pi WorkingDirectory=/home/pi ExecStart=/home/pi/server.sh start ExecStop=/home/pi/server.sh stop ExecReload=/home/pi/server.sh restart KillMode=none [Install] WantedBy=multi-user.target
3. Variante: Kurz und knapp einfach ein Unitfile erstellt
Hier übernimmt systemd die komplette Steuerung. Überraschenderweise funktioniert dies auch mit meinem Endlosscript. Ich habe die Anregung dazu von hier. Dort ist noch ein besseres Beispiel mit einem kleinem Webserver als Testscript dargestellt. Lässt man den User= Eintrag weg startet der Dienst als root.[Unit] Description=Test systemd myservice [Service] User=pi ExecStart=/home/pi/myservice.py StandardOutput=null [Install] WantedBy=multi-user.target
Keine Kommentare:
Kommentar veröffentlichen