Systemd, scripts interactifs et minuteries

Systemd, scripts interactifs et minuteries

introduction

Lors du développement pour Linux, la tâche de créer des scripts interactifs qui sont exécutés lorsque le système est allumé ou arrêté se pose. Dans le système V, c'était facile, mais avec systemd, il effectue des ajustements. Mais il peut avoir ses propres minuteries.

Pourquoi avons-nous besoin d’objectifs ?

Il est souvent écrit que la cible sert d'analogue au niveau d'exécution dans le système V -init. Je suis fondamentalement en désaccord. Il y en a plus et vous pouvez diviser les packages en groupes et, par exemple, lancer un groupe de services avec une seule commande et effectuer des actions supplémentaires. De plus, ils n’ont pas de hiérarchie, seulement des dépendances.

Exemple de cible lorsqu'elle est activée (présentation des fonctionnalités) avec l'exécution d'un script interactif

Description de la cible elle-même :

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

Cette cible démarrera lorsque multi-user.target sera lancé et appellera installer.service. Cependant, il peut exister plusieurs services de ce type.

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

Et enfin, un exemple de script en cours d'exécution :

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

Le plus important est de sélectionner final.target - la cible à laquelle le système doit arriver au démarrage. Pendant le processus de démarrage, systemd parcourra les dépendances et lancera tout ce dont il a besoin.
Il existe différentes manières de sélectionner final.target, j'ai utilisé l'option de chargement pour cela.

Le lancement final ressemble à ceci :

  1. Le chargeur de démarrage démarre
  2. Le bootloader commence à lancer le firmware en passant le paramètre final.target
  3. Systemd commence à démarrer le système. Va séquentiellement vers installer.target ou work.target depuis basic.target via leurs dépendances (par exemple, multi-user.target). Ces derniers font fonctionner le système dans le mode souhaité

Préparation du firmware pour le lancement

Lors de la création d'un micrologiciel, la tâche consiste toujours à restaurer l'état du système au démarrage et à le sauvegarder lors de l'arrêt. L'état signifie les fichiers de configuration, les sauvegardes de base de données, les paramètres d'interface, etc.

Systemd exécute les processus dans la même cible en parallèle. Il existe des dépendances qui vous permettent de déterminer la séquence de démarrage des scripts.

Comment ça marche dans mon projet ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. Le système démarre
  2. Le service settings_restore.service est lancé, il vérifie la présence du fichier settings.txt dans la section données. S'il n'y est pas, alors un fichier de référence est placé à sa place. Ensuite, les paramètres système sont restaurés :
    • mot de passe administrateur
    • nom d'hôte,
    • fuseau horaire
    • lieu
    • Détermine si tous les médias sont utilisés. Par défaut, la taille de l'image est petite pour faciliter la copie et l'enregistrement sur un support. Au démarrage, il vérifie s'il reste de l'espace inutilisé. Si tel est le cas, le disque est partitionné.
    • Génération d'un identifiant de machine à partir de l'adresse MAC. Ceci est important pour obtenir la même adresse via DHCP
    • Paramètres réseau
    • Limite la taille des journaux
    • Le disque externe est en cours de préparation au travail (si l'option correspondante est activée et que le disque est neuf)
  3. Démarrer PostgreSQL
  4. Le service de restauration démarre. Il est nécessaire pour préparer zabbix lui-même et sa base de données :
    • Vérifie s'il existe déjà une base de données zabbix. Sinon, il est créé à partir des dumps d'initialisation (inclus avec zabbix)
    • une liste de fuseaux horaires est créée (nécessaire pour les afficher dans l'interface web)
    • L'IP actuelle est trouvée, elle est affichée en issue (invitation à se connecter à la console)
  5. L'invitation change - la phrase Prêt à travailler apparaît
  6. Le firmware est prêt à l'emploi

Les fichiers de service sont importants, ce sont eux qui fixent la séquence de leur lancement

[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

Comme vous pouvez le voir, j'ai installé des dépendances pour que mon script fonctionne d'abord, et ensuite seulement le réseau monterait et le SGBD démarrerait.

Et le deuxième service (préparation 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

C'est un peu plus compliqué ici, le lancement se fait aussi dans multi-user.target, mais APRES le démarrage du SGBD postgresql et de mon settings_restore. Mais AVANT de démarrer les services zabbix.

Service de minuterie pour logrotate

Systemd peut remplacer CRON. Sérieusement. De plus, la précision n'est pas à la minute près, mais à la seconde (et si cela est nécessaire) ou vous pouvez créer une minuterie monotone, appelée par un délai d'attente d'un événement.
C'était le minuteur monotone qui compte le temps depuis le démarrage de la machine que j'ai créé.
Cela nécessitera 2 fichiers
logrotateTimer.service - la description réelle du service :

[Unit]
Description=run logrotate

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

C'est simple - description de la commande de lancement.
Le deuxième fichier logrotateTimer.timer est l'endroit où fonctionnent les minuteries :

[Unit]
Description=Run logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Qu'est ce qu'il y a ici:

  • description de la minuterie
  • Heure du premier démarrage, à partir du démarrage du système
  • période de lancements ultérieurs
  • Dépendance au service timer. En fait, c'est la chaîne qui fait que le timer

Script interactif lors de l'arrêt et votre objectif d'arrêt

Dans un autre développement, j'ai dû réaliser une version plus complexe consistant à éteindre la machine - via ma propre cible, afin d'effectuer de nombreuses actions. Il est généralement recommandé de créer un service oneshot avec l'option RemainAfterExit, mais cela vous empêche de créer un script interactif.

Mais le fait est que les commandes lancées par l'option ExecOnStop sont exécutées en dehors du TTY ! C'est facile à vérifier : collez la commande tty et enregistrez sa sortie.

Par conséquent, j'ai implémenté l'arrêt via ma cible. Je ne prétends pas avoir raison à 100%, mais ça marche !
Comment cela a été fait (en termes généraux) :
J'ai créé une cible my_shutdown.target, qui ne dépendait de personne :
mon_shutdown.target

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

En accédant à cette cible (via systemctl isolate my_shutdwn.target), il a lancé le service my_shutdown.service, dont la tâche est simple : exécuter le script 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

  • Dans ce script, j'effectue les actions nécessaires. Vous pouvez ajouter de nombreux scripts à la cible pour plus de flexibilité et de commodité :

mon_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

Note. Utilisation des fichiers /tmp/reboot et /tmp/shutdown. Vous ne pouvez pas appeler target avec des paramètres. Seul le service est possible.

Mais j'utilise Target pour avoir une flexibilité de travail et un ordre d'actions garanti.

Mais le plus intéressant est venu plus tard. La machine doit être éteinte/redémarrée. Et il y a 2 options :

  • Remplacez les commandes de redémarrage, d'arrêt et autres (ce sont toujours des liens symboliques vers systemctl) par votre script. À l'intérieur du script, accédez à my_shutdown.target. Et les scripts à l'intérieur de la cible appellent alors directement systemctl, par exemple, systemctl reboot
  • Une option plus simple, mais je ne l'aime pas. Dans toutes les interfaces, n'appelez pas shutdown/reboot/other, mais appelez directement la cible systemctl isolate my_shutdown.target

J'ai choisi la première option. Dans systemd, le redémarrage (comme poweroff) sont des liens symboliques vers systemd.

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

Par conséquent, vous pouvez les remplacer par vos propres scripts :
reboot

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

Source: habr.com

Ajouter un commentaire