5 services que systemd m'a déjà rendu

le 07/04/2017 par Adrien Besnard
Tags: Software Engineering

En un peu plus de 7 ans d'existence, systemd s’est peu à peu imposé comme le remplaçant par défaut du vieillissant init. On le trouve en effet installé et activé par défaut sur les distributions de Linux les plus couramment utilisées : Ubuntu depuis la 15.04, CentOS depuis la 7, et j'en passe...

systemd a fait couler beaucoup d’encre sur les forum de la communauté des utilisateurs de Linux, à tel point qu’un fork de Debian sans systemd a même été lancé à la fin de l'année 2014. Les raisons de ces débats sont multiples et parfois justifiées. Mais sans faire l'avocat du diable, je pense que pour beaucoup de détracteurs, systemd a surtout été une cause d’incompréhension : Lennart Poettering, l’initiateur du projet, a remis en cause l'un des fondements d'Unix.

L'objectif de cet article n'est aucunement tenter de convaincre le DevOps qui sommeille en chacun de nous que systemd est indispensable et qu'il serait une hérésie de s'en passer, mais plutôt de lister 5 cas d'utilisation de systemd qui peuvent être utiles lors d'un projet.

Quelques petits rappels sur la configuration systemd

La configuration de systemd est décrite à l'aide de ce qu'on appelle des units : ce sont de simples fichiers dont la syntaxe s'approche des fameux fichiers INI, qui peuvent être placés à différents endroits (comme par exemple /usr/lib/systemd/system et /etc/systemd/system, mais d'autres chemins sont possibles) en fonction des cas.

Différents types d'unit existent : on trouve par exemple les services qui correspondent plus ou moins aux fichiers que l'on trouvait dans /etc/init.d à l'époque d'init, les mounts et automounts que l'on peut assimiler à la fstab, etc.

Lorsque l'une de ces units est modifiée, il est nécessaire de notifier qu'une mise à jour doit être prise en compte par systemd à l'aide de la commande suivante : systemctl daemon-reload.

Les bases étant posées, nous sommes prêt à rentrer dans le vif du sujet... Allons-y !

systemd peut remplacer la crontab

A l’instar de cron, systemd peut programmer des tâches récurrentes. Dans le jargon de systemd, on parle alors de timer.

Pour illustrer cette fonctionnalité, nous allons faire que systemd lance toutes les dix secondes une commande qui va écrire Bip! dans un fichier /var/log/spoutnik.log. Pour cela :

  • Tout d'abord, nous allons créer un fichier spoutnik.service qui va définir le service de type oneshot qui va écrire dans notre fichier de log
  • Puis, nous allons créer un fichier spoutnik.timer qui va décrire la fréquence à laquelle notre spoutnik.service va être lancé

L’avantage majeur que systemd a par rapport à cron est dû au fait que la commande que l'on lance est wrappée par un service : il est alors possible de profiter de la myriade d’options qui permettent de contrôler finement la manière de lancer nos commandes (user, niceness, etc.).

Il est possible de lister les différents timers à l'aide de la commande systemctl list-timers (ce qui d'ailleurs de nous rendre compte que le nettoyage de /tmp est programmé à l'aide de systemd au travers du service systemd-tmpfiles-clean.service).

Notons au passage qu’il est également possible de différer l'exécution d’une commande “à la volée” à l'aide de l'utilitaire systemd-run fourni avec systemd : systemd-run --on-active=10 /bin/bash -c "echo 'Bip!' >>'/var/log/spoutnik.log'".

systemd vous permet de surcharger des déclarations en utilisant des drop-ins

systemd fournit un mécanisme assez puissant pour changer le comportement de unit déjà existantes : les drop-ins. Le principe est simple : dans /etc/systemd/system, il faut créer un dossier ayant le nom du service suffixé par .d et y placer des fichiers .conf contenant les déclarations à surcharger.

Pour l’exemple, nous allons à présent imaginer que notre service spoutnik.service provient d’un package installé à l’aide de yum ou apt-get et qu'il va lancer un script spoutnik.sh. La nuance est importante car :

  • Par convention, le fichier spoutnik.service va se trouver dans /usr/lib/systemd/system et non plus dans /etc/systemd/system
  • Notre spoutnik.service ne va plus être de type oneshot mais de type simple, ce qui signifie que systemd lui-même va se charger de daemonizer notre script (pas besoin d'utilitaire de type daemonize
  • Plus besoin de timer puisque le script se charge lui-même d'écrire à une fréquence donnée

Spoutnik Inc. qui s'est chargé du packaging a bien fait les choses puisqu’il est à présent possible de configurer le message écrit dans /var/log/spoutnik.log en utilisant la variable d’environnement SPOUTNIK_MESSAGE.

L’idée à présent va être de créer de changer le comportement de notre spoutnik.service sans pour autant alterer le fichier /usr/lib/systemd/system/spoutnik.service (ça ne serait pas une solution pérenne puisqu'étant donné que le fichier est issu d'un package, il risque d'être réécrit lors d'une mise-à-jour, par exemple). Pour cela, nous allons créer un drop-in qui va assigner une autre valeur notre variable d’environnement SPOUTNIK_MESSAGE en utilisant la directive Environment et ainsi influer sur le message écrit par spoutnik.sh.

Et voilà ! Petite astuce (qui peut éviter quelques migraines...) : si la valeur que l’on veut assigner à cette variable d’environnement contient des espaces, il est nécessaire d'utiliser l'utilitaire systemd-escape. A quelques rares excéptions, cette règle est valable pour l'ensemble des directives de systemd.

Il est aisé de se rendre compte que, couplé à un outil de provisionning, ce mécanisme peut se révéler très pratique !

systemd peut redémarrer automatiquement des services

systemd possède en effet un ensemble de directives qui lui permet de relancer les services lorsque ces derniers tombent en erreur.

Pour l’exemple, imaginons la dernière mise-à-jour du script spoutnik.sh n'est pas de la meilleure qualité, et qu'il a la facheuse tendance à s'arrêter de manière impromptue.

Voilà qui est embêtant... Pour résoudre ce problème et redémarrer automatiquement le service en cas d'erreur, nous allons créer un nouveau drop-in ha.conf qui contient la directive Restart

Notons qu’il est possible d’aller beaucoup plus loin dans le contrôle de la manière dont systemd redémarre les services (temps d’attente avant le redémarrage, nombre maximal de tentatives, etc.) et dans quels cas il doit le faire (codes de retour particuliers, signaux, etc.).

systemd peut notifier lorsqu'un service tombe en erreur

En complément du redémarrage automatique présenté ci-dessus, il est possible d’utiliser la directive OnFailure pour indiquer à systemd d'exécuter une action particulière lorsque le service tombe en erreur. A l'instar des tâches programmé à l'aide des timers, cette action est décrite sous la forme d’un service de type oneshot.

Pour notre exemple, nous allons créer un service notify-hq@.service (qui va lui-même appeler le script notify-hq.sh qui se charger d'afficher des messages sur le TTY) qui sera lancé lorsque spoutnik.service tombe en erreur, et ajouter la directive OnFailure à notre drop-in ha.conf.

Nous allons ici profiter des template units pour passer le message que l'on veut afficher comme paramètre du service.

Le fait d’afficher le nom des services qui tombent en erreur sur le TTY n’a qu’une valeur ajoutée très relative... Mais étant donné qu’il est possible d’utiliser n’importe quelle commande, on peut parfaitement imaginer un petit script Python qui va envoyer un message sur le channel #monotoring du Slack de l'équipe à l’aide de la librairie Slacker.

systemd peut lier plusieurs services

Cette fonctionnalité de systemd est souvent connue, mais parfois mal comprise. Il est en effet possible de lier le comportement de plusieurs services entre eux, ce à l’aide des directives BindTo et Wants (ou Require) et des directives After et Before.

Pour notre exemple, Spoutnik Inc. fourni à présent un utilitaire spoutnik-truncate.sh accompagné de son spoutnik-truncate.service, chargé de purger le fichier /var/log/spoutnik.log si sa taille excède un certain nombre de lignes. Afin de ne pas perdre des cycles CPU inutilement, nous allons lier les services de manière à ce que spoutnik-truncate.service démarre et s'arrête en même temps que spoutnik.service.

Il suffit pour cela de créer deux drop-ins : l'un pour spoutnik.service avec les directives Wants et Before, et l'autre pour spoutnik-truncate.service avec la directive BindTo.

Cette fonctionnalité peut par exemple être intéressante s’il on accompagne un service qui expose une API REST d'un autre service chargé de l'annoucement avec etcd et ajouter de manière quasiment transparente une couche de discovery à notre projet.

Pour aller plus loin

Ces quelques astuces ne sont qu'un infime aperçu des fonctionnalités de systemd et de l'aide qu'il peut apporter sur un projet. Il est en effet par exemple possible de :

  • Contrôler des services depuis une machine distante
  • Avoir un suivi poussé des logs à l'aide de journalctl
  • Lancer des containers à l'aide de systemd-nspawn
  • Profiter d'un monitoring assez poussé en se basant sur l'interface DBus
  • Etc.

La documentation de systemd est ici (et accessible via votre commande man préférée) et la mailing list est également une précieuse source d'information et d'inspiration. Enfin, pour les plus curieux, les sources de spoutnik.sh et de ses amis spoutnik-truncate.sh et notify-hq.sh sont ici !