Systemd, skrypty interaktywne i timery

Systemd, skrypty interaktywne i timery

Wprowadzenie

Podczas pracy pod linuxem pojawiają się zadania związane z tworzeniem interaktywnych skryptów, które są uruchamiane przy włączeniu lub wyłączeniu systemu. W systemie V było to łatwe, ale systemd wprowadza zmiany. Z drugiej strony posiada własne timery.

Do czego służą targety

Często pisze się, że targety są odpowiednikiem runlevel w systemie V-init. Z całym szacunkiem, się z tym nie zgadzam. Ich jest więcej i można dzielić pakiety na grupy, a na przykład uruchamiać grupę usług jednym poleceniem, wykonując dodatkowe czynności. Poza tym nie mają hierarchii, tylko zależności.

Przykład targetu przy włączeniu (przegląd możliwości) z uruchomieniem interaktywnego skryptu

Opis samego targetu:

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

Ten target uruchomi się, gdy uruchomi się multi-user.target i wywoła installer.service. Może być kilka takich serwisów.

cat installer.service
[Unit]
# opis
Description=installer interactive dialog

[Service]
# Uruchomić raz, gdy reszta będzie uruchomiona
Type=idle
# Polecenie uruchamiające - wywołanie skryptu
ExecStart=\/usr\/bin\/installer.sh
# Interaktywna interakcja z użytkownikiem przez tty3
StandardInput=tty
TTYPath=\/dev\/tty3
TTYReset=yes
TTYVHangup=yes

[Install]
WantedBy=installer.target

I na koniec przykład wykonywanego skryptu:

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

Najważniejsze jest wybranie final.target - targetu, do którego system powinien dojść podczas uruchamiania. W trakcie uruchamiania systemd przejdzie przez zależności i uruchomi wszystkie potrzebne.
Wybór final.target można zrealizować na różne sposoby, użyłem do tego opcji bootloadera.

Ostateczne uruchomienie wygląda tak:

  1. Uruchamia się bootloader
  2. Bootloader rozpoczyna uruchamianie firmware'u, przekazując parametr final.target
  3. Systemd rozpoczyna uruchamianie systemu. Kolejno idzie do installer.target lub work.target z basic.target przez ich zależności (na przykład, multi-user.target). Ostatnie prowadzą system do pracy w odpowiednim trybie.

Przygotowanie firmware'u do uruchomienia

Przy tworzeniu firmware'ów zawsze pojawia się zadanie przywracania stanu systemu przy starcie i jego zachowania przy wyłączaniu. Pod stanem rozumie się pliki konfiguracyjne, zrzuty baz danych, ustawienia interfejsów itd.

Systemd uruchamia procesy w jednym targetcie równolegle. Istnieją zależności, które pozwalają określić kolejność uruchamiania skryptów.

Jak to działa u mnie w projekcie ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. System się uruchamia
  2. Uruchamiana jest usługa settings_restore.service. Sprawdza obecność pliku settings.txt w sekcji danych. Jeśli go nie ma, na jego miejsce wstawiany jest plik wzorcowy. Następnie następuje przywracanie ustawień systemowych:
    • hasło administratora
    • hostname,
    • strefa czasowa
    • lokalizacja
    • Określenie, czy całe medium jest wykorzystywane. Domyślnych rozmiar obrazu jest niewielki — dla wygody kopii i nagrywania na nośniku. Przy starcie sprawdzane jest, czy są jeszcze niewykorzystane miejsca. Jeśli tak — dysk jest przekształcany.
    • Generacja machine-id z adresu MAC. Jest to istotne dla uzyskania tego samego adresu przez DHCP
    • Ustawienia sieciowe
    • Ogranicza rozmiar logów
    • Przygotowuje się zewnętrzny dysk (jeśli włączona jest odpowiednia opcja i dysk jest nowy)
  3. Uruchamia się postgresq
  4. uruchamiana jest usługa restore. Jest potrzebna do przygotowania Zabbixa i jego bazy danych:
    • Sprawdzane jest, czy baza danych Zabbix już istnieje. Jeśli nie — tworzona jest na podstawie inicjalizujących zrzutów (dostarczane z Zabbixem)
    • tworzona jest lista stref czasowych (potrzebna do ich wyświetlania w interfejsie webowym)
    • Znajduje się bieżący adres IP, który jest wyświetlany w issue (zaproszenie do logowania się w konsoli)
  5. Zmienia się zaproszenie — pojawia się fraza Gotowy do pracy
  6. Oprogramowanie jest gotowe do pracy

Ważne są pliki usług, to one ustalają kolejność ich uruchamiania

[Unit]
Description=przywróć ustawienia systemu
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

Jak widać, ustawiłem zależności, aby najpierw wykonał mój skrypt, a dopiero później uruchamiała się sieć i startowała bazodanowa SU.

I druga usługa (przygotowanie Zabbixa)

#!/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

Tutaj jest trochę trudniej. Uruchomienie również w multi-user.target, ale PO uruchomieniu bazy danych PostgreSQL i mojego setting_restore. Ale PRZED uruchomieniem usług Zabbixa.

Usługa z timerem dla logrotate

Systemd może zastąpić CRON. Serio. Co więcej, dokładność nie co minutę, a co sekundę (a nuż się przyda). Można również stworzyć monotoniczny timer, wywoływany na podstawie zdarzenia.
To właśnie monotoniczny timer, liczący czas od uruchomienia maszyny, stworzyłem.
Do tego potrzebne będą 2 pliki
logrotateTimer.service — opis usługi:

[Unit]
Description=uruchom logrotate

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

Wszystko jest proste — opis polecenia uruchomienia.
Drugi plik logrotateTimer.timer — to on definiuje działanie timerów:

[Unit]
Description=Uruchom logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Co tutaj jest:

  • opisanie timera
  • Czas pierwszego uruchomienia, licząc od załadunku systemu
  • okres kolejnych uruchomień
  • Zależność od usługi timerów. Właściwie to jest ten wiersz, który tworzy timer

Interaktywny skrypt przy wyłączeniu i własny cel wyłączenia

W innej implementacji musiałem stworzyć bardziej złożoną wersję wyłączania maszyny — przez własny cel, aby wykonać wiele działań. Zwykle zaleca się utworzenie usługi oneshot z opcją RemainAfterExit, ale to nie daje możliwości utworzenia interaktywnego skryptu.

Chodzi o to, że komendy uruchamiane opcją ExecOnStop są wykonywane poza TTY! Można to prosto sprawdzić — wstaw komendę tty i zapisz jej wynik.

Dlatego zrealizowałem wyłączenie przez własny cel. Nie roszczę sobie pretensji o 100% poprawność, ale to działa!
Jak to było zrobione (w dużym skrócie):
Utworzyłem cel my_shutdown.target, który nie zależał od innych:
my_shutdown.target

[Unit]
Description=moje wyłączenie
AllowIsolate=yes
Wants=my_shutdown.service 

Po przejściu do tego celu (przez systemctl isolate my_shutdown.target), uruchamiał usługę my_shutdown.service, której zadaniem jest proste — wykonanie skryptu my_shutdown.sh:

[Unit]
Description=MOJE wyłączenie

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

WantedBy=my_shutdown.target

  • Wewnątrz tego skryptu wykonuję potrzebne działania. Można dodać wiele skryptów do celu, dla elastyczności i wygody:

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

Uwaga. Użycie plików \/tmp\reboot i \/tmp\shutdown. Nie można wywołać celu z parametrami. Można tylko usługę.

Ale używam celu, aby mieć elastyczność w działaniu i gwarantowaną kolejność wykonania działań.

Jednak najciekawsze było potem. Maszynę trzeba wyłączyć\/zrestartować. I tu są 2 opcje:

  • Zastąpić komendy reboot, shutdown i inne (są one zresztą linkami symbolicznymi do systemctl) własnym skryptem. Wewnątrz skryptu — przejście do my_shutdown.target. A skrypty wewnątrz celu następnie wywołują bezpośrednio systemctl, na przykład, systemctl reboot
  • Prostsza, ale mi się nie podobająca opcja. Wszystkie interfejsy wywołują nie shutdown\/reboot\/inne, a bezpośrednio wywołują cel systemctl isolate my_shutdown.target

Wybrałem pierwszą opcję. W systemd reboot (jak i poweroff) są linkami symbolicznymi do systemd.

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

Dlatego można je zastąpić swoimi skryptami:
reboot

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

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster