Systemd, script interattivi e timer

Systemd, script interattivi e timer

Introduzione

Nello sviluppo su linux si presentano compiti per la creazione di script interattivi, eseguiti all'accensione o allo spegnimento del sistema. In system V questo era semplice, ma con systemd necessitano alcune modifiche. Tuttavia, essa supporta anche i propri timer.

A cosa servono i target

Viene spesso scritto che i target sono l'equivalente dei runlevel in system V -init. Personalmente non sono d'accordo. Ce ne sono di più e si possono raggruppare i pacchetti in categorie, e ad esempio, avviare con un solo comando un gruppo di servizi e compiere ulteriori azioni. Inoltre, non hanno gerarchie, ma solo dipendenze.

Esempio di target all'accensione (panoramica delle funzionalità) con l'avvio di uno script interattivo

Descrizione del target stesso:

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

Questo target verrà avviato quando sarà attivato multi-user.target e invocherà installer.service. Possono esserci più di questi servizi.

cat installer.service
[Unit]
# descrizione
Description=dialogo interattivo dell’installer

[Service]
# Esegui una sola volta, quando sarà avviato il resto
Type=idle
# Comando di avvio - chiamata dello script
ExecStart=/usr/bin/installer.sh
# Interazione interattiva con l'utente tramite tty3
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes

[Install]
WantedBy=installer.target

E infine, esempio di uno script eseguito:

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

La cosa più importante è scegliere final.target - il target a cui il sistema deve arrivare all'avvio. Durante il processo di avvio, systemd seguirà le dipendenze e avvierà tutto ciò che è necessario.
Il final.target può essere scelto in vari modi, io ho utilizzato l'opzione del caricatore.

L'avvio finale appare così:

  1. Il caricatore si avvia
  2. Il caricatore inizia l'avvio del firmware, passando il parametro final.target
  3. Systemd inizia l'avvio del sistema. Procede in sequenza verso installer.target o work.target partendo da basic.target attraverso le loro dipendenze (ad esempio, multi-user.target). Questi ultimi portano il sistema a funzionare nella modalità desiderata.

Preparazione del firmware per l'avvio

Nella creazione dei firmware, sorge sempre il compito di ripristinare lo stato del sistema all'avvio e di conservarlo allo spegnimento. Per stato si intendono i file di configurazione, i dump del database, le impostazioni delle interfacce, ecc.

Systemd avvia i processi in un unico target in parallelo. Ci sono dipendenze che permettono di determinare l'ordine di esecuzione degli script.

Come funziona nel mio progetto ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. Il sistema si avvia
  2. Il servizio settings_restore.service si avvia. Controlla la presenza del file settings.txt nella sezione dati. Se non è presente, viene sostituito con il file di riferimento. Successivamente avviene il ripristino delle impostazioni di sistema:
    • password dell'amministratore
    • hostname,
    • fuso orario
    • locale
    • Determinazione se l'intero supporto è utilizzato. Per impostazione predefinita, le dimensioni dell'immagine sono ridotte, per facilitare la copia e la scrittura sul supporto. All'avvio, viene controllato se c'è ancora spazio non utilizzato. Se disponibile, il disco viene ripartizionato.
    • Generazione di machine-id dall'indirizzo MAC. Questo è importante per ottenere lo stesso indirizzo tramite DHCP
    • Impostazioni di rete
    • Viene limitata la dimensione dei log
    • Preparazione del disco esterno per il funzionamento (se è attivata l'opzione corrispondente e il disco è nuovo)
  3. Avvio di postgresq
  4. Il servizio restore si avvia. È necessario per preparare Zabbix e il suo database:
    • Controlla se esiste già un database Zabbix. Se non esiste, viene creato da dump di inizializzazione (forniti insieme a Zabbix)
    • Viene creato un elenco dei fusi orari (necessario per la loro visualizzazione nell'interfaccia web)
    • Viene trovata l'attuale IP, che viene visualizzata nel messaggio (invito per accedere alla console)
  5. Cambia il prompt — appare la frase Pronto per lavorare
  6. Il firmware è pronto per l'uso

I file di servizio sono importanti, poiché determinano l'ordine di avvio

[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

Come si può vedere, ho impostato le dipendenze affinché prima venga eseguito il mio script e solo dopo si avvii la rete e il DBMS.

E il secondo servizio (preparazione 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

Qui è un po' più complicato. Si avvia anche nel multi-user.target, ma DOPO l'avvio del DBMS PostgreSQL e del mio setting_restore. Ma PRIMA dell'avvio dei servizi Zabbix.

Servizio con timer per logrotate

Systemd può sostituire CRON. Sul serio. Inoltre, la precisione non è fino al minuto, ma fino al secondo (e magari è necessario). È possibile anche creare un timer monotono, attivato tramite timeout da un evento.
Ho creato proprio un timer monotono, che conta il tempo dall'avvio della macchina.
Per questo sono necessari 2 file
logrotateTimer.service — è la descrizione del servizio:

[Unit]
Description=run logrotate

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

È tutto semplice — è la descrizione del comando di avvio.
Il secondo file logrotateTimer.timer — è questo che imposta il funzionamento dei timer:

[Unit]
Description=Run logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Cosa c'è qui:

  • descrizione del timer
  • Tempo della prima esecuzione, a partire dal caricamento del sistema
  • periodo delle successive esecuzioni
  • Dipendenza dal servizio timer. In effetti, questa è la riga e crea il timer

Script interattivo durante lo spegnimento e il proprio target di spegnimento

In un altro sviluppo ho dovuto implementare una versione di spegnimento più complessa — attraverso un target proprio, per eseguire molte azioni. Si raccomanda di creare un servizio oneshot con l'opzione RemainAfterExit, ma questo non consente di creare uno script interattivo.

Il problema è che i comandi eseguiti con l'opzione ExecOnStop vengono eseguiti al di fuori di TTY! Puoi controllarlo facilmente — inserisci il comando tty e salva il suo output.

Quindi ho implementato lo spegnimento tramite il mio target. Non pretendo che sia al 100% corretto, ma funziona!
Come è stato fatto (in linea generale):
Ho creato il target my_shutdown.target, che non dipende da nessuno:
my_shutdown.target

[Unit]
Description=il mio spegnimento
AllowIsolate=yes
Wants=my_shutdown.service 

Quando si passa a questo target (attraverso systemctl isolate my_shutdown.target), avvia il servizio my_shutdown.service, il cui compito è semplice — eseguire lo script my_shutdown.sh:

[Unit]
Description=IL mio spegnimento

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

WantedBy=my_shutdown.target

  • All'interno di questo script eseguo le azioni necessarie. Puoi aggiungere molti script al target, per flessibilità e comodità:

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

Nota. Utilizzo di file \/tmp\/reboot e \/tmp\/shutdown. Non è possibile chiamare il target con parametri. È possibile solo il servizio.

Ma io uso il target per avere flessibilità nel lavoro e un ordine garantito nell'esecuzione delle azioni.

Tuttavia, la parte più interessante è arrivata dopo. La macchina deve essere spenta\/riavviata. E qui ci sono 2 opzioni:

  • Sostituire i comandi reboot, shutdown e altri (sono comunque collegamenti simbolici a systemctl) con il proprio script. All'interno dello script — passare a my_shutdown.target. Gli script all'interno del target poi chiamano direttamente systemctl, ad esempio, systemctl reboot
  • Un'opzione più semplice, ma che non mi piace. In tutte le interfacce non chiamare shutdown\/reboot\/altri, ma chiamare direttamente il target systemctl isolate my_shutdown.target

Ho scelto la prima opzione. In systemd reboot (come poweroff) sono collegamenti simbolici a systemd.

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

Quindi possono essere sostituiti con i propri script:
reboot

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

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster