Systemd, interaktive Skripte und Timer

Systemd, interaktive Skripte und Timer

Einführung

Bei der Entwicklung für Linux stellt sich die Aufgabe, interaktive Skripte zu erstellen, die beim Ein- oder Herunterfahren des Systems ausgeführt werden. In System V war das einfach, aber mit systemd werden Anpassungen vorgenommen. Es kann aber eigene Timer haben.

Warum brauchen wir Ziele?

Es wird oft geschrieben, dass das Ziel als Analogon zum Runlevel im System V-init dient. Ich bin grundsätzlich anderer Meinung. Es gibt mehr davon und Sie können Pakete in Gruppen einteilen und beispielsweise eine Gruppe von Diensten mit einem Befehl starten und zusätzliche Aktionen ausführen. Darüber hinaus gibt es keine Hierarchie, sondern nur Abhängigkeiten.

Beispiel eines Ziels bei Aktivierung (Funktionsübersicht) mit laufendem interaktivem Skript

Beschreibung des Ziels selbst:

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

Dieses Ziel wird gestartet, wenn multi-user.target gestartet wird und installer.service aufruft. Es kann jedoch mehrere solcher Dienste geben.

cat installer.service
[Unit]
# описание
Description=installer interactive dialog

[Service]
# Запустить один раз, когда остальное будет запущенно
Type=idle
# Команда запуска - вызов скрипта
ExecStart=/usr/bin/installer.sh
# Интерактивное взаимодействие с пользователем через tty3
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes

[Install]
WantedBy=installer.target

Und zum Schluss noch ein Beispiel für das ausgeführte Skript:

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

Das Wichtigste ist, final.target auszuwählen – das Ziel, zu dem das System beim Start gelangen soll. Während des Startvorgangs geht systemd die Abhängigkeiten durch und startet alles, was es benötigt.
Es gibt verschiedene Möglichkeiten, final.target auszuwählen. Ich habe hierfür die Loader-Option verwendet.

Der endgültige Start sieht so aus:

  1. Der Bootloader startet
  2. Der Bootloader beginnt mit dem Starten der Firmware, indem er den Parameter final.target übergibt
  3. Systemd beginnt, das System zu starten. Geht nacheinander von basic.target über ihre Abhängigkeiten zu installer.target oder work.target (z. B. multi-user.target). Letztere bringen das System in den gewünschten Modus zum Laufen

Vorbereiten der Firmware für den Start

Bei der Firmware-Erstellung stellt sich immer die Aufgabe, den Systemzustand beim Start wiederherzustellen und beim Herunterfahren zu speichern. Status bedeutet Konfigurationsdateien, Datenbank-Dumps, Schnittstelleneinstellungen usw.

Systemd führt Prozesse im selben Ziel parallel aus. Es gibt Abhängigkeiten, mit denen Sie die Startreihenfolge von Skripten bestimmen können.

Wie 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 prüft, ob die Datei „settings.txt“ im Datenabschnitt vorhanden ist. Ist sie nicht vorhanden, wird an ihrer Stelle eine Referenzdatei abgelegt. Als nächstes werden die Systemeinstellungen wiederhergestellt:
    • Administrator-Passwort
    • Hostname,
    • Zeitzone
    • Gebietsschema
    • Bestimmt, ob alle Medien verwendet werden. Standardmäßig ist die Bildgröße klein, um das Kopieren und Aufzeichnen auf Medien zu erleichtern. Beim Start prüft es, ob noch ungenutzter Speicherplatz vorhanden ist. Wenn dies der Fall ist, wird die Festplatte neu partitioniert.
    • Generieren der Maschinen-ID aus der MAC-Adresse. Dies ist wichtig, um über DHCP die gleiche Adresse zu erhalten
    • Netzwerkeinstellungen
    • Begrenzt die Größe der Protokolle
    • Das externe Laufwerk wird für den Betrieb vorbereitet (sofern die entsprechende Option aktiviert ist und das Laufwerk neu ist)
  3. Postgresq starten
  4. Der Wiederherstellungsdienst wird gestartet. Es wird benötigt, um Zabbix selbst und seine Datenbank vorzubereiten:
    • Prüft, ob bereits eine Zabbix-Datenbank vorhanden ist. Wenn nicht, wird es aus Initialisierungs-Dumps erstellt (im Lieferumfang von Zabbix enthalten).
    • Es wird eine Liste der Zeitzonen erstellt (wird zur Anzeige im Webinterface benötigt)
    • Die aktuelle IP wird gefunden, sie wird im Issue angezeigt (Einladung zur Anmeldung an der Konsole)
  5. Die Einladung ändert sich – der Satz „Bereit zur Arbeit“ erscheint
  6. Die Firmware ist einsatzbereit

Die Servicedateien sind wichtig, sie legen die Reihenfolge ihres Starts fest

[Unit]
Description=restore system settings
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 Sie sehen, habe ich Abhängigkeiten installiert, damit mein Skript zuerst funktioniert und erst dann das Netzwerk hochfährt und das DBMS startet.

Und der zweite Gottesdienst (Zabbix-Vorbereitung)

#!/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 in multi-user.target, aber NACH dem Start des Postgresql-DBMS und meiner Einstellung_restore. Aber BEVOR Sie mit den Zabbix-Diensten beginnen.

Timer-Dienst für Logrotate

Systemd kann CRON ersetzen. Ernsthaft. Darüber hinaus ist die Genauigkeit nicht auf die Minute, sondern auf die Sekunde genau (was ist, wenn es nötig ist). Oder Sie können einen monotonen Timer erstellen, der durch eine Zeitüberschreitung eines Ereignisses aufgerufen wird.
Es war der monotone Timer, der die Zeit vom Start der von mir erstellten Maschine zählt.
Dazu sind 2 Dateien erforderlich
logrotateTimer.service – die tatsächliche Beschreibung des Dienstes:

[Unit]
Description=run logrotate

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

Es ist ganz einfach: Beschreibung des Startbefehls.
In der zweiten Datei logrotateTimer.timer funktionieren die Timer:

[Unit]
Description=Run logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Was gibt es hier:

  • Timer-Beschreibung
  • Erster Startzeitpunkt, beginnend mit dem Systemstart
  • Zeitraum weiterer Markteinführungen
  • Abhängigkeit vom Timer-Dienst. Tatsächlich ist dies die Zeichenfolge, die den Timer erstellt

Interaktives Skript beim Herunterfahren und Ihr Abschaltziel

In einer anderen Entwicklung musste ich eine komplexere Version des Ausschaltens der Maschine durchführen – über mein eigenes Ziel, um viele Aktionen ausführen zu können. Normalerweise wird empfohlen, einen Oneshot-Dienst mit der Option „RemainAfterExit“ zu erstellen. Dies verhindert jedoch, dass Sie ein interaktives Skript erstellen können.

Tatsache ist jedoch, dass die von der Option ExecOnStop gestarteten Befehle außerhalb des TTY ausgeführt werden! Das lässt sich leicht überprüfen: Fügen Sie den Befehl tty ein und speichern Sie die Ausgabe.

Daher habe ich die Abschaltung über mein Ziel implementiert. Ich behaupte nicht, 100 % richtig zu sein, aber es funktioniert!
Wie es gemacht wurde (allgemein):
Ich habe ein Ziel my_shutdown.target erstellt, das von niemandem abhängig ist:
my_shutdown.target

[Unit]
Description=my shutdown
AllowIsolate=yes
Wants=my_shutdown.service 

Beim Aufrufen dieses Ziels (über systemctl isolate my_shutdwn.target) wurde der Dienst my_shutdown.service gestartet, dessen Aufgabe einfach ist: das Ausführen des Skripts my_shutdown.sh:

[Unit]
Description=MY shutdown

[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 notwendigen Aktionen aus. Aus Gründen der Flexibilität und Bequemlichkeit können Sie dem Ziel viele Skripte hinzufügen:

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

Notiz. Verwenden der Dateien /tmp/reboot und /tmp/shutdown. Sie können das Ziel nicht mit Parametern aufrufen. Es ist nur Service möglich.

Aber ich nutze Target, um Flexibilität bei der Arbeit und eine garantierte Reihenfolge der Aktionen zu haben.

Das Interessanteste kam jedoch später. Die Maschine muss ausgeschaltet/neu gestartet werden. Und es gibt 2 Möglichkeiten:

  • Ersetzen Sie die Befehle „reboot“, „shutdown“ und andere Befehle (sie sind immer noch symbolische Links zu systemctl) durch Ihr Skript. Gehen Sie im Skript zu „my_shutdown.target“. Und die Skripte im Ziel rufen dann systemctl direkt auf, zum Beispiel systemctl reboot
  • Eine einfachere Option, aber sie gefällt mir nicht. Rufen Sie in allen Schnittstellen nicht Shutdown/Reboot/Other auf, sondern rufen Sie direkt das Zielsystemctl Isolate my_shutdown.target auf

Ich habe mich für die erste Option entschieden. In systemd sind reboot (wie poweroff) symbolische Links zu systemd.

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

Daher können Sie sie durch Ihre eigenen Skripte ersetzen:
rebooten

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

Source: habr.com

Kommentar hinzufügen