Recherche de site Web

Comment créer une unité de service systemd sous Linux


Bien que systemd ait fait l’objet de nombreuses controverses, au point que certaines distributions ont été forkées juste pour s’en débarrasser (voir Devuan, un fork de Debian qui, par défaut, remplace systemd par sysvinit), il est finalement devenu le système d’initialisation standard de facto dans le monde Linux.

Dans ce tutoriel, nous allons voir comment un service systemd est structuré, et nous allons apprendre comment pour en créer un.

Dans ce tutoriel, vous allez apprendre :

  • Qu’est-ce qu’une unité de service.
  • Quelles sont les sections d’une unité de service.
  • Quelles sont les options les plus courantes qui peuvent être utilisées dans chaque section.
  • Quels sont les différents types de services que l’on peut définir.

Configuration logicielle requise et conventions utilisées

Le système d’initialisation systemd

Toutes les grandes distributions, telles que Rhel, CentOS, Fedora, Ubuntu, Debian et Archlinux, ont adopté systemd comme système d’initialisation. Systemd, en fait, est plus qu’un simple système d’initialisation, et c’est l’une des raisons pour lesquelles certaines personnes sont fortement opposées à son design, qui va à l’encontre de la devise bien établie d’unix : « faire une chose et la faire bien ». Là où d’autres systèmes d’initialisation utilisent un simple script shell pour gérer les services, systemd utilise ses propres fichiers .service (unités avec le suffixe .service) : dans ce tutoriel, nous allons voir comment ils sont structurés et comment en créer et en installer un.

Anatomie d’une unité de service

Qu’est-ce qu’une unité de service ? Un fichier avec le suffixe .service contient des informations sur un processus géré par systemd. Il est composé de trois sections principales :

  • [Unité] : cette section contient des informations qui ne sont pas spécifiquement liées au type d’unité, telles que la description du service
  • [Service] : contient des informations sur le type spécifique de l’unité, un service dans ce cas
  • [Installer] : Cette section contient des informations sur l’installation de l’unité

Analysons chacun d’entre eux en détail.

La section [Unité]

La section [Unit] d’un fichier .service contient la description de l’unité elle-même, ainsi que des informations sur son comportement et ses dépendances : (pour fonctionner correctement, un service peut dépendre d’un autre). Nous abordons ici certaines des options les plus pertinentes qui peuvent être utilisées dans cette section

L’option « Description »

Tout d’abord, nous avons l’option Description. En utilisant cette option, nous pouvons fournir une description de l’unité. La description apparaîtra alors, par exemple, lors de l’appel de la commande systemctl, qui renvoie une vue d’ensemble de l’état de systemd. Voici, à titre d’exemple, comment la description du service httpd est définie sur un système Fedora :

[Unit]
Description=The Apache HTTP Server

L’option « Après »

En utilisant l’option Après, nous pouvons affirmer que notre unité doit être démarrée après les unités que nous fournissons sous la forme d’une liste séparée par des espaces. Par exemple, en observant à nouveau le fichier de service où le service web Apache est défini, on peut voir ce qui suit :

After=network.target remote-fs.target nss-lookup.target httpd-init.service

La ligne ci-dessus indique à systemd de ne démarrer l’unité de service httpd.service qu’après les cibles network, remove-fs, nss-lookup et le service httpd-init.

Spécification de dépendances matérielles avec « Requires »

Comme nous l’avons brièvement mentionné ci-dessus, une unité (un service dans notre cas) peut dépendre d’autres unités (pas nécessairement des unités « de service ») pour fonctionner correctement : de telles dépendances peuvent être déclarées en utilisant l’option Requires.

Si l’une des unités dont dépend un service ne démarre pas, l’activation du service est arrêtée : c’est pourquoi on les appelle des dépendances matérielles. Dans cette ligne, extraite du fichier de service de l’avahi-daemon, on peut voir comment il est déclaré comme dépendant de l’unité avahi-daemon.socket :

Requires=avahi-daemon.socket

Déclarer des dépendances « douces » avec « Wants »

Nous venons de voir comment déclarer les dépendances dites « dures » pour le service en utilisant l’option Requires ; nous pouvons également lister les dépendances « soft » en utilisant l’option Wants.

Quelle est la différence ? Comme nous l’avons dit ci-dessus, si une dépendance « dure » échoue, le service échouera lui-même ; L’échec d’une dépendance « douce », cependant, n’influence pas ce qui arrive à l’unité dépendante. Dans l’exemple fourni, nous pouvons voir comment l’unité docker.service a une dépendance douce sur celle de docker-storage-setup.service :

[Unit]
Wants=docker-storage-setup.service

La section [Service]

Dans la section [Service] d’une unité de service, nous pouvons spécifier des éléments tels que la commande à exécuter au démarrage du service ou le type du service lui-même. Jetons un coup d’œil à quelques-uns d’entre eux.

Démarrage, arrêt et rechargement d’un service

Un service peut être démarré, arrêté, redémarré ou rechargé. Les commandes à exécuter lors de l’exécution de chacune de ces actions peuvent être spécifiées à l’aide des options associées dans la section [Service].

La commande à exécuter au démarrage d’un service est déclarée à l’aide de l’option ExecStart. L’argument passé à l’option peut également être le chemin d’accès à un script. Optionnellement, nous pouvons déclarer des commandes à exécuter avant et après le démarrage du service, en utilisant respectivement les options ExecStartPre et ExecStartPost. Voici la commande utilisée pour démarrer le service NetworkManager :

[Service]
ExecStart=/usr/sbin/NetworkManager --no-daemon

De la même manière, nous pouvons spécifier la commande à exécuter lorsqu’un service est rechargé ou arrêté, en utilisant les options ExecStop et ExecReload. De la même manière que ce qui se passe avec ExecStartPost, une ou plusieurs commandes à lancer après l’arrêt d’un processus peuvent être spécifiées avec l’option ExecStopPost.

Le type de service

Systemd définit et distingue différents types de services en fonction de leur comportement attendu. Le type d’un service peut être défini à l’aide de l’option Type, en fournissant l’une des valeurs suivantes :

  • simple
  • Bifurquer
  • coup unique
  • dbus
  • notifier

Le type par défaut d’un service, si les options Type et Busname ne sont pas définies, mais qu’une commande est fournie via l’option ExecStart, est simple. Lorsque ce type de service est défini, la commande déclarée dans ExecStart est considérée comme le processus/service principal.

Le type de forking fonctionne différemment : la commande fournie avec ExecStart est censée dupliquer et lancer un processus enfant, qui deviendra le processus/service principal. Le processus parent il est censé mourir une fois le processus de démarrage terminé.

Le type oneshot est utilisé par défaut si les options Type et ExecStart ne sont pas définies. Cela fonctionne à peu près comme si c’était simple : la différence est que le processus est censé terminer son travail avant que les autres unités ne soient lancées. L’unité, cependant, est toujours considérée comme « active » même après la fermeture de la commande, si l’option RemainAfterExit est définie sur « yes » (la valeur par défaut est « no »).

Le type de service suivant est dbus. Si ce type de service est utilisé, le démon doit obtenir un nom de Dbus, comme spécifié dans l’option BusName, qui dans ce cas, devient obligatoire. Pour le reste, il fonctionne comme le type simple. Les unités suivantes, cependant, ne sont lancées qu’après l’acquisition du nom DBus.

Un autre processus fonctionne de la même manière que simple, et il s’agit de notify : la différence est que le démon est censé envoyer une notification via la fonction sd_notify. Ce n’est qu’une fois cette notification envoyée que les unités suivantes sont lancées.

Définir les délais d’expiration du processus

À l’aide d’options spécifiques, il est possible de définir des délais d’expiration pour le service. Commençons par RestartSec : en utilisant cette option, nous pouvons configurer le temps (par défaut en secondes) que systemd doit attendre avant de redémarrer un service. Une plage horaire peut également être utilisée comme valeur pour cette option, comme « 5min 20s ». La valeur par défaut est de 100 ms.

Les options TimeoutStartSec et TimeoutStopSec peuvent être utilisées pour spécifier, respectivement, le délai d’expiration du démarrage et de l’arrêt d’un service, en secondes. Dans le premier cas, si, après le délai d’expiration spécifié, le processus de démarrage du démon n’est pas terminé, il sera considéré comme ayant échoué.

Dans le second cas, si un service doit être arrêté mais n’est pas terminé après le délai d’expiration spécifié, un signal SIGTERM est d’abord envoyé, puis, après le même laps de temps, un signal SIGKILL. Les deux options acceptent également un intervalle de temps comme valeur et peuvent être configurées en une seule fois, avec un raccourci : TimeoutSec. Si infinity est fourni en tant que valeur, les délais d’expiration sont désactivés.

Enfin, nous pouvons définir la limite de temps d’exécution d’un service à l’aide de RuntimeMaxSec. Si un service dépasse ce délai d’expiration, il est arrêté et considéré comme ayant échoué.

La section [Installer]

Dans la section [installer], nous pouvons utiliser les options liées à l’installation du service. Par exemple, en utilisant l’option Alias, nous pouvons spécifier une liste d’alias séparés par des espaces à utiliser pour le service lors de l’utilisation des commandes systemctl (sauf enable).

De la même manière que cela se passe avec les options Requires et Wants dans la section [Unit], pour établir des dépendances, dans la section [install], nous pouvons utiliser RequiredBy et WantedBy. Dans les deux cas, nous déclarons une liste d’unités qui dépendent de celle que nous configurons : avec la première option, elles en dépendront durement, avec la seconde, elles seront considérées uniquement comme faiblement dépendantes. Par exemple:

[Install]
WantedBy=multi-user.target

Avec la ligne ci-dessus, nous avons déclaré que la cible multi-utilisateurs a une dépendance douce de notre unité. Dans la terminologie systemd, les unités se terminant par le suffixe .target peuvent être associées à ce que l’on appelait des runtimes dans d’autres systèmes d’initialisation comme Sysvinit. Dans notre cas, la cible multi-utilisateurs, lorsqu’elle est atteinte, doit inclure notre service.

Création et installation d’une unité de service

Il y a essentiellement deux endroits dans le système de fichiers où les unités de service systemd sont installées : /usr/lib/systemd/system et /etc/systemd/system. Le premier chemin est utilisé pour les services fournis par les paquets installés, tandis que le second peut être utilisé par l’administrateur système pour ses propres services qui peuvent remplacer ceux par défaut.

Créons un exemple de service personnalisé. Supposons que nous voulions créer un service qui désactive la fonction de réveil sur un réseau local spécifique (ens5f5 dans notre cas) lorsqu’elle est démarrée, et la réactive lorsqu’elle est arrêtée. Nous pouvons utiliser la commande ethtool pour accomplir la tâche principale. Voici à quoi pourrait ressembler notre dossier de service :

[Unit]
Description=Force ens5f5 ethernet interface to 100Mbps
Requires=Network.target
After=Network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ethtool -s ens5f5 wol d
ExecStop=/usr/sbin/ethtool -s ens5f5 wol g

[Install]
WantedBy=multi-user.target

Nous avons défini une description d’unité simple et déclaré que le service dépend de l’unité network.target et doit être lancé une fois qu’elle est atteinte. Dans la section [Service], nous avons défini le type de service comme oneshot, et avons demandé à systemd de considérer le service comme actif après l’exécution de la commande, en utilisant l’option RemainAfterExit. Nous avons également défini les commandes à exécuter lorsque le service est démarré et arrêté. Enfin, dans la section [Installer], nous avons essentiellement déclaré que notre service devait être inclus dans la cible multi-utilisateurs.

Pour installer le service, nous allons copier le fichier dans le répertoire /etc/systemd/system en tant que wol.service, puis nous le démarrerons :

sudo cp wol.service /etc/systemd/system && sudo systemctl start wol.service

Nous pouvons vérifier que le service est actif, avec la commande suivante :

systemctl is-active wol.service
active

La sortie de la commande, comme prévu, est active. Maintenant, pour vérifier que « wake on lan » a été défini sur d, et qu’il est donc maintenant désactivé, nous pouvons exécuter :

sudo ethtool ens5f5|grep Wake-on
Supports Wake-on: pg
Wake-on: d

Maintenant, l’arrêt du service devrait produire le résultat inverse, et réactiver wol :

sudo systemctl stop wol.service && sudo ethtool ens5f5|grep Wake-on
Supports Wake-on: pg
Wake-on: g

Conclusions

Dans ce tutoriel, nous avons vu comment un fichier de service systemd est composé, quelles sont ses sections et certaines des options qui peuvent être utilisées dans chacun d’eux. Nous avons appris à configurer une description de service, à définir ses dépendances et à déclarer les commandes qui doivent être exécutées lorsqu’il est démarré, arrêté ou rechargé.

Puisque systemd, qu’on le veuille ou non, est devenu le système d’initialisation standard dans le monde Linux, il est important de se familiariser avec sa façon de faire les choses. La documentation officielle des services systemd se trouve sur le site web freedesktop. Vous pourriez également être intéressé par la lecture de notre article sur la gestion des services avec systemd.

Articles connexes: