Systemd, interaktive Skripte und Timer

Systemd, interaktive Skripte und Timer

Einführung

Bei der Entwicklung unter Linux treten die Herausforderungen auf, interaktive Skripte zu erstellen, die beim Starten oder Herunterfahren des Systems ausgeführt werden. In System V war das einfach, aber mit systemd gibt es Änderungen. Zusätzlich kann es eigene Timer verwalten.

Wozu sind Targets gut?

Es wird oft gesagt, dass Targets das Äquivalent zu Runlevels in System V -init sind. Damit bin ich grundlegend nicht einverstanden. Es gibt mehr von ihnen und man kann Pakete in Gruppen unterteilen und beispielsweise mit einem Befehl eine Gruppe von Diensten starten oder zusätzliche Aktionen ausführen. Außerdem haben sie keine Hierarchie, nur Abhängigkeiten.

Beispiel eines Targets beim Start (Übersicht der Möglichkeiten) mit dem Start eines interaktiven Skripts

Beschreibung des Targets:

cat installer.target
[Unit]
Description=Mein Installer
Requires=multi-user.target
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target
AllowIsolate=yes
Wants=installer.service

Dieses Target wird gestartet, wenn multi-user.target aktiviert wird und ruft installer.service auf. Dabei kann es mehrere solcher Dienste geben.

cat installer.service
[Unit]
# Beschreibung
Description=installer interaktiver Dialog

[Service]
# Einmal starten, wenn alles andere gestartet ist
Type=idle
# Startbefehl - Aufruf des Skripts
ExecStart=/usr/bin/installer.sh
# Interaktive Benutzerinteraktion über tty3
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes

[Install]
WantedBy=installer.target

Und schließlich, ein Beispiel für ein ausführbares Skript:

#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answer

Das Wichtigste ist, den final.target auszuwählen – das Ziel, das das System beim Start erreichen soll. Während des Systemstarts durchläuft systemd die Abhängigkeiten und startet alles Notwendige.
Den final.target kann man auf verschiedene Weisen auswählen, ich habe dazu die Option des Bootloaders verwendet.

Der endgültige Start sieht so aus:

  1. Der Bootloader startet
  2. Der Bootloader beginnt mit dem Start der Firmware und übergibt den Parameter final.target
  3. Systemd beginnt mit dem Start des Systems. Es geht der Reihe nach zu installer.target oder work.target von basic.target durch ihre Abhängigkeiten (zum Beispiel multi-user.target). Letztere bringen das System in den gewünschten Betriebsmodus.

Vorbereitung der Firmware für den Start

Bei der Erstellung von Firmware steht immer die Aufgabe im Vordergrund, den Zustand des Systems beim Start wiederherzustellen und ihn beim Herunterfahren zu speichern. Unter Zustand versteht man Konfigurationsdateien, Datenbank-Dumps, Schnittstelleneinstellungen usw.

Systemd startet Prozesse in einem Ziel parallel. Es gibt Abhängigkeiten, die die Reihenfolge der Skriptausführung festlegen.

So funktioniert es in meinem Projekt ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. Das System startet
  2. Der Dienst settings_restore.service wird gestartet. Er überprüft, ob die Datei settings.txt im Datenverzeichnis vorhanden ist. Falls nicht, wird eine Standarddatei an ihre Stelle gelegt. Anschließend erfolgt die Wiederherstellung der Systemeinstellungen:
    • Admin-Passwort
    • hostname,
    • Zeitzone
    • Sprache
    • Bestimmung, ob das gesamte Medium genutzt wird. Standardmäßig ist die Imagegröße klein – zur Erleichterung des Kopierens und Schreibens auf das Medium. Beim Start wird überprüft, ob noch ungenutzter Speicher vorhanden ist. Falls ja, wird die Festplatte neu partitioniert.
    • Generierung der machine-id aus der MAC-Adresse. Dies ist wichtig, um die gleiche Adresse über DHCP zu erhalten.
    • Netzwerkeinstellungen
    • Die Größe der Protokolle wird begrenzt.
    • Es wird eine externe Festplatte vorbereitet (wenn die entsprechende Option aktiviert ist und die Festplatte neu ist)
  3. postgreSQL wird gestartet
  4. Der Wiederherstellungsdienst wird gestartet. Er dient zur Vorbereitung von Zabbix und seiner Datenbank:
    • Es wird geprüft, ob bereits eine Zabbix-Datenbank existiert. Wenn nicht, wird sie aus den initialen Dumps (die in der Zabbix-Lieferung enthalten sind) erstellt.
    • Eine Liste der Zeitzonen wird erstellt (notwendig für die Anzeige im Web-Interface)
    • Die aktuelle IP wird ermittelt und im Prompt (Einladung zum Eingeben in der Konsole) angezeigt.
  5. Das Prompt wird geändert – die Phrase 'Bereit zum Arbeiten' erscheint
  6. Die Firmware ist bereit zur Arbeit

Die Dateien der Dienste sind wichtig, da sie die Reihenfolge ihres Starts festlegen

[Unit]
Description=Systemeinstellungen wiederherstellen
Before=network.service prepare.service postgresql.service systemd-networkd.service systemd-resolved.service

[Service]
Type=oneshot
ExecStart=/usr/bin/settings_restore.sh

[Install]
WantedBy=multi-user.target

Wie zu sehen ist, habe ich Abhängigkeiten festgelegt, damit mein Skript zuerst ausgeführt wird, bevor das Netzwerk hochfährt und die DBMS gestartet wird.

Und der zweite Dienst (Vorbereitung von Zabbix)

#!/bin/sh
[Unit]
Description=monitor prepare system
After=postgresql.service settings_restore.service
Before=zabbix-server.service zabbix-agent.service

[Service]
Type=oneshot
ExecStart=/usr/bin/prepare.sh

[Install]
WantedBy=multi-user.target

Hier ist es etwas komplizierter. Der Start erfolgt ebenfalls im multi-user.target, aber NACH dem Start der DBMS PostgreSQL und meines setting_restore. Aber VOR dem Start der Zabbix-Dienste.

Dienst mit Timer für logrotate

Systemd kann CRON ersetzen. Ernsthaft. Dabei liegt die Genauigkeit nicht bei Minuten, sondern bei Sekunden (man weiß ja nie). Außerdem kann man einen monotone Timer erstellen, der nach einem Timeout vom Ereignis ausgelöst wird.
Genau so einen monotone Timer, der die Zeit ab Maschinenstart zählt, habe ich erstellt.
Für dies werden 2 Dateien benötigt.
logrotateTimer.service — die Beschreibung des Dienstes:

[Unit]
Description=logrotate ausführen

[Service]
ExecStart=logrotate /etc/logrotate.conf
TimeoutSec=300

Ganz einfach — die Beschreibung des Startbefehls.
Die zweite Datei logrotateTimer.timer — die definiert die Arbeit des Timers:

[Unit]
Description=logrotate ausführen

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Was haben wir hier:

  • eine Beschreibung des Timers
  • die Zeit bis zum ersten Start, beginnend mit dem Systemstart
  • der Zeitraum für zukünftige Starts
  • die Abhängigkeit vom Timer-Service. Tatsächlich ist diese Zeile verantwortlich für die Timerfunktion

ein interaktives Skript beim Herunterfahren und sein Ziel beim Herunterfahren

In einem anderen Projekt musste ich eine komplexere Variante zum Herunterfahren der Maschine erstellen — über ein eigenes Ziel, um eine Vielzahl von Aktionen durchzuführen. Normalerweise empfiehlt es sich, einen Oneshoot-Dienst mit der Option RemainAfterExit zu erstellen, aber das ermöglicht nicht die Erstellung eines interaktiven Skripts.

Das Problem ist, dass die durch die Option ExecOnStop ausgeführten Befehle außerhalb von TTY ausgeführt werden! Es ist ganz einfach zu überprüfen — geben Sie den Befehl tty ein und speichern Sie die Ausgabe.

Deshalb habe ich das Herunterfahren über mein Ziel implementiert. Ich erhebe keinen Anspruch auf 100%ige Richtigkeit, aber es funktioniert!
So wurde es gemacht (übersichtlich):
Ich habe das Ziel my_shutdown.target erstellt, das von niemandem abhängig war:
my_shutdown.target

[Unit]
Description=Mein Herunterfahren
AllowIsolate=yes
Wants=my_shutdown.service 

Beim Wechsel zu diesem Ziel (über systemctl isolate my_shutdown.target) startete es den Dienst my_shutdown.service, dessen Aufgabe einfach ist — das Skript my_shutdown.sh auszuführen:

[Unit]
Description=MEIN Herunterfahren

[Service]
Type=oneshot
ExecStart=/usr/bin/my_shutdown.sh
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes

WantedBy=my_shutdown.target

  • Innerhalb dieses Skripts führe ich die benötigten Aktionen aus. Man kann viele Skripte zum Ziel hinzufügen, um Flexibilität und Komfort zu gewährleisten:

my_shutdown.sh

#!/bin/bash --login
if [ -f /tmp/reboot ];then
    command="systemctl reboot"
elif [ -f /tmp/shutdown ]; then
    command="systemctl poweroff"
fi
#Вот здесь нужные команды
#Например, cp /home/user/data.txt /storage/user/
    $command

Hinweis. Die Verwendung der Dateien /tmp/reboot und /tmp/shutdown. Man kann das Ziel nicht mit Parametern aufrufen. Nur der Dienst kann gestartet werden.

Ich verwende jedoch das Ziel, um Flexibilität in der Ausführung zu haben und die Reihenfolge der Aktionen zu garantieren.

Das Interessanteste kam jedoch danach. Man muss die Maschine schließlich herunterfahren/neu starten. Und hier gibt es 2 Möglichkeiten:

  • Ersetzen Sie die Befehle reboot, shutdown und andere (sie sind sowieso alle Symlinks zu systemctl) durch Ihr Skript. Innerhalb des Skripts erfolgt der Übergang zu my_shutdown.target. Die Skripte innerhalb des Ziels rufen dann direkt systemctl auf, zum Beispiel systemctl reboot.
  • Eine einfachere, aber mir nicht zusagende Variante. In allen Schnittstellen direkt nicht shutdown/reboot/andere aufrufen, sondern direkt das Ziel systemctl isolate my_shutdown.target ansteuern.

Ich habe die erste Option gewählt. In systemd sind reboot (wie auch poweroff) Symlinks zu systemd.

ls -l /sbin/poweroff 
lrwxrwxrwx 1 root root 14 Sep 30 18:23 /sbin/poweroff -> /bin/systemctl

Deshalb können sie durch eigene Skripte ersetzt werden:
reboot

#!/bin/sh
    touch /tmp/reboot
    sudo systemctl isolate my_shutdown.target
fi

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster