|
| 1 | +--- |
| 2 | +title: Service `systemd` - Python Script |
| 3 | +author: Antoine Le Morvan |
| 4 | +contributors: Steven Spencer |
| 5 | +tested_with: 8.6, 9.0 |
| 6 | +tags: |
| 7 | + - python |
| 8 | + - systemd |
| 9 | + - cron |
| 10 | +--- |
| 11 | + |
| 12 | +# `systemd` Dienst für ein Python-Skript |
| 13 | + |
| 14 | +Wenn Sie wie viele Sysadmins ein Fan von Cron-Skripten sind, die mit `* * * * * * /I/launch/my/script.sh` gestartet werden, sollte dieser Artikel Sie an eine andere Möglichkeit erinnern, dies mit der ganzen Leichtigkeit des `systemd` zu tun. |
| 15 | + |
| 16 | +Wir schreiben ein Python-Skript, das eine Endlosschleife zur Ausführung der von Ihnen definierten Aktionen bereitstellt. |
| 17 | + |
| 18 | +Wir werden sehen, wie dieses Skript als `systemd` Dienst ausgeführt wird, wie die Protokolle in Journalctl anzeigt werden können, was passiert, wenn das Skript abstürzt. |
| 19 | + |
| 20 | +## Voraussetzungen |
| 21 | + |
| 22 | +Beginnen wir damit, einige Python-Abhängigkeiten zu installieren, die für das Skript benötigt werden, um das Kommando journalctl zu verwenden: |
| 23 | + |
| 24 | +``` |
| 25 | +shell > sudo dnf install python36-devel systemd-devel |
| 26 | +shell > sudo pip3 install systemd |
| 27 | +``` |
| 28 | + |
| 29 | +## Skript entwerfen |
| 30 | + |
| 31 | +Lass uns das folgende Skript `my_service.py` anschauen: |
| 32 | + |
| 33 | +``` |
| 34 | +""" |
| 35 | +Sample script to run as script |
| 36 | +""" |
| 37 | +import time |
| 38 | +import logging |
| 39 | +import sys |
| 40 | +from systemd.journal import JournaldLogHandler |
| 41 | +
|
| 42 | +# Get an instance of the logger |
| 43 | +LOGGER = logging.getLogger(__name__) |
| 44 | +
|
| 45 | +# Instantiate the JournaldLogHandler to hook into systemd |
| 46 | +JOURNALD_HANDLER = JournaldLogHandler() |
| 47 | +JOURNALD_HANDLER.setFormatter(logging.Formatter( |
| 48 | + '[%(levelname)s] %(message)s' |
| 49 | +)) |
| 50 | +
|
| 51 | +# Add the journald handler to the current logger |
| 52 | +LOGGER.addHandler(JOURNALD_HANDLER) |
| 53 | +LOGGER.setLevel(logging.INFO) |
| 54 | +
|
| 55 | +class Service(): # pylint: disable=too-few-public-methods |
| 56 | + """ |
| 57 | + Launch an infinite loop |
| 58 | + """ |
| 59 | + def __init__(self): |
| 60 | +
|
| 61 | + duration = 0 |
| 62 | +
|
| 63 | + while True: |
| 64 | + time.sleep(60) |
| 65 | + duration += 60 |
| 66 | + LOGGER.info("Total duration: %s", str(duration)) |
| 67 | + # will failed after 4 minutes |
| 68 | + if duration > 240: |
| 69 | + sys.exit(1) |
| 70 | +
|
| 71 | +if __name__ == '__main__': |
| 72 | +
|
| 73 | + LOGGER.info("Starting the service") |
| 74 | + Service() |
| 75 | +``` |
| 76 | + |
| 77 | +Wir beginnen damit, die notwendigen Variablen zu instanziieren, um Logs dem Journal zu senden. Das Skript startet dann eine unendliche Schleife und pausiert für 60 Sekunden (was das Minimum einer Cron-Ausführung ist, so dass wir unter diese Grenze gehen können). |
| 78 | + |
| 79 | +!!! note "Anmerkung" |
| 80 | + |
| 81 | + Der Autor benutzt dieses Skript in einer fortgeschrittenen Form, die kontinuierlich eine Datenbank abfragt und Jobs basierend auf den über die Rundeck API abgerufenen Informationen ausführt |
| 82 | + |
| 83 | +## Integration in Systemd |
| 84 | + |
| 85 | +Jetzt, da wir ein Skript haben, das als Grundlage für Ihre Phantasie dienen kann, können wir es als System-Dienst installieren. |
| 86 | + |
| 87 | +Lass uns die Datei `my_service.service` erstellen und sie nach `/etc/systemd/system/` kopieren. |
| 88 | + |
| 89 | +``` |
| 90 | +[Unit] |
| 91 | +Description=My Service |
| 92 | +After=multi-user.target |
| 93 | +
|
| 94 | +[Service] |
| 95 | +Type=simple |
| 96 | +Restart=always |
| 97 | +ExecStart=/usr/bin/python3 my_service.py |
| 98 | +WorkingDirectory=/opt/my_service/ |
| 99 | +
|
| 100 | +StandardOutput=syslog |
| 101 | +StandardError=syslog |
| 102 | +SyslogIdentifier=my_service |
| 103 | +
|
| 104 | +[Install] |
| 105 | +WantedBy=multi-user.target |
| 106 | +``` |
| 107 | + |
| 108 | +Wie Sie sehen können, wird das Skript von `/opt/my_service/` gestartet. Denken Sie daran, den Pfad an Ihr Skript und die Syslog-Kennung anzupassen. |
| 109 | + |
| 110 | +Neuen Dienst aktivieren und starten: |
| 111 | + |
| 112 | +``` |
| 113 | +shell > sudo systemctl daemon-reload |
| 114 | +shell > sudo systemctl enable my_service.service |
| 115 | +shell > sudo systemctl start my_service.service |
| 116 | +``` |
| 117 | + |
| 118 | +## Tests |
| 119 | + |
| 120 | +Wir können die Protokolle jetzt via journalctl anzeigen: |
| 121 | + |
| 122 | +``` |
| 123 | +shell > journalctl -f -u my_service |
| 124 | +oct. 14 11:07:48 rocky8 systemd[1]: Started My Service. |
| 125 | +oct. 14 11:07:49 rocky8 __main__[270267]: [INFO] Starting the service |
| 126 | +oct. 14 11:08:49 rocky8 __main__[270267]: [INFO] Total duration: 60 |
| 127 | +oct. 14 11:09:49 rocky8 __main__[270267]: [INFO] Total duration: 120 |
| 128 | +``` |
| 129 | + |
| 130 | +Lass uns nun sehen, was passiert, wenn das Skript abgestürzt ist: |
| 131 | + |
| 132 | +``` |
| 133 | +shell > ps -elf | grep my_service |
| 134 | +4 S root 270267 1 0 80 0 - 82385 - 11:07 ? 00:00:00 /usr/bin/python3 my_service.py |
| 135 | +shell > sudo kill -9 270267 |
| 136 | +``` |
| 137 | + |
| 138 | +``` |
| 139 | +shell > journalctl -f -u my_service |
| 140 | +oct. 14 11:10:49 rocky8 __main__[270267]: [INFO] Total duration: 180 |
| 141 | +oct. 14 11:11:49 rocky8 __main__[270267]: [INFO] Total duration: 240 |
| 142 | +oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Main process exited, code=killed, status=9/KILL |
| 143 | +oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Failed with result 'signal'. |
| 144 | +oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Service RestartSec=100ms expired, scheduling restart. |
| 145 | +oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Scheduled restart job, restart counter is at 1. |
| 146 | +oct. 14 11:12:19 rocky8 systemd[1]: Stopped My Service. |
| 147 | +oct. 14 11:12:19 rocky8 systemd[1]: Started My Service. |
| 148 | +oct. 14 11:12:19 rocky8 __main__[270863]: [INFO] Starting the service |
| 149 | +``` |
| 150 | + |
| 151 | +Wir können auch 5 Minuten warten, bis das Skript selbst abstürzt (entfernen Sie dies für Ihre Produktion): |
| 152 | + |
| 153 | +``` |
| 154 | +oct. 14 11:16:02 rocky8 systemd[1]: Started My Service. |
| 155 | +oct. 14 11:16:03 rocky8 __main__[271507]: [INFO] Starting the service |
| 156 | +oct. 14 11:17:03 rocky8 __main__[271507]: [INFO] Total duration: 60 |
| 157 | +oct. 14 11:18:03 rocky8 __main__[271507]: [INFO] Total duration: 120 |
| 158 | +oct. 14 11:19:03 rocky8 __main__[271507]: [INFO] Total duration: 180 |
| 159 | +oct. 14 11:20:03 rocky8 __main__[271507]: [INFO] Total duration: 240 |
| 160 | +oct. 14 11:21:03 rocky8 __main__[271507]: [INFO] Total duration: 300 |
| 161 | +oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Main process exited, code=exited, status=1/FAILURE |
| 162 | +oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Failed with result 'exit-code'. |
| 163 | +oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Service RestartSec=100ms expired, scheduling restart. |
| 164 | +oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Scheduled restart job, restart counter is at 1. |
| 165 | +oct. 14 11:21:03 rocky8 systemd[1]: Stopped My Service. |
| 166 | +oct. 14 11:21:03 rocky8 systemd[1]: Started My Service. |
| 167 | +oct. 14 11:21:03 rocky8 __main__[271993]: [INFO] Starting the service |
| 168 | +``` |
| 169 | + |
| 170 | +Wie Sie sehen können, ist die Neustartfunktion von systemd sehr nützlich. |
| 171 | + |
| 172 | +## Fazit |
| 173 | + |
| 174 | +`systemd` und `journald` stellen uns die Werkzeuge zur Verfügung, um robuste und leistungsstarke Skripte so einfach wie möglich zu machen, dass sie unsere alten zuverlässigen Crontab-Skripte ersetzen. |
| 175 | + |
| 176 | +Wir hoffen, dass dieser Vorschlag für Sie nützlich sein wird. |
0 commit comments