Vendredi 4 janvier 2019 (Modifié le Dimanche 26 avril 2020)

Systemd, créer un service pour lancer une application au démarrage

On utilise le système d’initialisation de Linux systemd.

Chaque service généré par systemd est configuré par un fichier .service qui se trouve dans le répertoire /etc/systemd/system.

Pour notre exemple, il faut donc créer un fichier /etc/systemd/system/yclock.service avec au minimum le contenu suivant:

[Unit]
Description=Affiche l'heure sur un YoctoDisplay

[Service]
Type=simple
ExecStart=/home/pi/autostart/yclock.py


[Install]
WantedBy=multi-user.target

Le paramètre Description permet de définir une description qui est affichée lors de certaines commandes de systemctl.

Le paramètre ExecStart de la section Service définit la commande à exécuter pour démarrer l’application, dans notre cas il s’agit de notre script /home/pi/autostart/yclock.py. Le paramètre Type permet de choisir le type de service, à moins que votre application ne fasse des forks, la valeur “simple” est suffisante.

Enfin le paramètre WantedBy définit à quel moment du boot le script doit être démarré. La valeur “multi-user.target” est la valeur qui devrait correspondre à 99% des cas, c’est-à-dire juste avant l’écran de login quand le réseau et les autres services critiques ont déjà été démarrés.

L’utilitaire systemctl permet de contrôler systemd. Les principales commandes sont :

  • enable: active le service, c’est-à-dire que le service sera démarré lors des prochains boots.
  • disable: désactive le service, c’est-à-dire que le service sera ignoré lors des prochains boots.
  • status: affiche l’état courant du service.
  • start: démarre immédiatement le service.
  • stop: stope immédiatement le service.

Donc, pour que notre script démarre automatiquement lors des prochains boots, il faut exécuter la commande suivante:

sudo systemctl enable yclock.service

Il est possible de stopper et redémarrer le service sans redémarrer l’OS. Ce qui permet par exemple de modifier le script Python sur un système en production. Mais ce n’est pas tout.

La commande journalctl permet d’afficher les logs d’exécution de notre programme. En plus, ces logs ne peuvent pas remplire complètement le disque car systemd stocke de la même manière tous les logs de l’OS. Donc, en fonction de la configuration de votre distribution, systemd gardera les X logs les plus récents du service, de manière à ne pas saturer le disque.

La commande pour afficher les logs de notre application yclock.py:

sudo journalctl -u yclock.service

D’autres paramètres du fichier permettent de configurer très finement comment et quand le service doit être démarré. Par exemple, le paramètre Restart permet de spécifier si le service doit être redémarré automatiquement lorsque l’exécutable se termine avec une erreur. Les paramètres User et Group permettent de choisir avec quel utilisateur et quel groupe le script sera exécuté.

Pour reprendre notre exemple, voici le fichier complet :

[Unit]
Description=Affiche l'heure sur un YoctoDisplay

[Service]
Type=simple
ExecStart=/home/pi/autostart/yclock.py
Restart=on-failure
RestartSec=30
User=pi
Group=pi

[Install]
WantedBy=multi-user.target

Ce fichier précise en plus que le script doit être exécuté par l’utilisateur “pi” et le groupe “pi”. De plus, si le script se termine avec un code d’erreur, par exemple si aucun YoctoDisplay n’est connecté, il faut essayer de le relancer après 30 secondes.

Si vous avez besoin de configuration plus pointue, vous pouvez toujours consulter la documentation du projet à cette adresse:https://freedesktop.org/wiki/Software/systemd

Type : “simple”, “forking”, “oneshot”, etc…

Lorsque vous démarrez le service manuellement à partir de la ligne de commande (sans utiliser la commande nohup prefix ou le suffixe & pour l’exécuter en arrière-plan, ou en d’autres termes, il suffit d’exécuter la commande que vous mettriez sur la ligne ExecStart= du fichier .service), que se passe-t-il ?

  • a) Si le service démarre et continue de fonctionner, et que l'invite ne revient pas avant que vous n'ayez appuyé sur Control-C ou que vous n'ayez arrêté le service d'une autre manière : alors Type = simple est le bon choix.
  • b) Si l’invite revient mais que le service continue à fonctionner en arrière-plan (c’est-à-dire que le service se démonte de lui-même), alors Type = forking est le bon choix.
  • c) Si le service fait son travail et retourne à l'invite sans rien laisser tourner (c’est-à-dire que le service ajuste juste quelques paramètres du noyau, envoie une commande à quelque chose d’autre ou fait quelque chose de similaire), alors Type = oneshot est probablement le bon choix. Dans ce cas, ExecStart du service pourrait être la commande pour “régler” quelque chose, et ExecStop serait la commande correspondante pour le “débrancher”. Ce type bénéficie généralement de l’avantage de RemainAfterExit=true, donc systemd gardera une trace de l’“état” de ce service selon que la chose a été récemment “activée” ou “désactivée”.

Les autres valeurs de type sont des cas particuliers.

  • Par exemple, si le service utilise une connexion D-Bus, alors Type = dbus pourrait être le meilleur choix. Cela permet à systemd de prendre conscience du fait, et ensuite systemd suivra ce service (et tout ce qui en dépend) par la présence de ce service sur le D-Bus.
  • Pour utiliser Type = notify, le processus doit être capable de se connecter à la socket Unix spécifiée dans la variable d’environnement $NOTIFY_SOCKET et de signaler son état en écrivant des messages à cette socket chaque fois que cela est nécessaire. De plus, le fichier de service doit spécifier l’option NotifyAccess pour accorder l’accès à la socket de notification, le cas échéant.
    • Il existe un utilitaire en ligne de commande systemd-notify et une fonction de la bibliothèque C sd_notify(3) que vous pouvez utiliser pour envoyer ces messages, mais si aucun de ces deux éléments ne convient à vos besoins, vous pouvez simplement implémenter votre propre expéditeur de message. Les messages requis sont très simples, et ressemblent à des affectations de variables shell : par exemple, pour notifier que le service a terminé avec succès son démarrage et est prêt à répondre à toute demande entrante, le service doit envoyer à la socket la chaîne de caractères équivalente à la sortie de printf “READY=1\n”. Voir man 3 sd_notify pour plus de détails sur les messages reconnus.

Note : de nombreuses applications de service conçues pour être portables sur de nombreux systèmes de type Unix peuvent se comporter comme b) par défaut, mais peuvent être rendues fonctionnelles comme a) en ajoutant une option (généralement décrite comme “don’t fork”, “keep running in foreground”, “don’t daemonize” ou similaire). Dans ce cas, si l’option n’a pas d’autres effets secondaires, alors l’ajout de l’option et l’utilisation du comportement de type a) seraient préférables pour systemd.