Comment un indicateur de service systemd qui est prêt, de sorte que d'autres services puissent attendre qu'il soit prêt avant de démarrer?

8

J'ai un tas de services (disons C0, C1C9) qui ne devraient démarrer qu'après qu'un service Sa terminé son initialisation et qu'il est entièrement opérationnel et prêt pour les autres services. Comment organiser cela avec systemd?

Dans Ordering services with path activation and target in systemd, on suppose que le service Sa un mécanisme pour écrire une sorte de fichier indicateur. Supposons ici, en revanche, que j'ai un contrôle total sur le programme Sexécuté par le service et que je puisse y ajouter des mécanismes systemd si nécessaire.

JdeBP
la source

Réponses:

7

On n'a pas nécessairement besoin de cela.

Si les Cservices doivent attendre Sd'être prêts pour pouvoir y ouvrir une connexion socket, il n'est pas nécessaire de le faire du tout. Au lieu de cela, on peut profiter de l' ouverture d' écoute précoce par les gestionnaires de services.

Plusieurs systèmes, y compris le s6 de Laurent Bercot , mon jeu d'outils NOS et systemd, ont des façons d' ouvrir une prise d'écoute dès le début, la toute première chose dans la configuration du service. Ils impliquent tous autre chose que le programme de service ouvrant les sockets d'écoute et le programme de service, lorsqu'il est appelé, recevant les sockets d'écoute en tant que descripteurs de fichiers déjà ouverts.

Avec systemd, en particulier, on crée une unité de socket qui définit la socket d'écoute. systemd ouvre l'unité de socket et la configure pour que le sous-système de mise en réseau du noyau écoute les connexions; et le transmet au service réel en tant que descripteur de fichier ouvert lorsqu'il s'agit de générer le ou les processus qui gèrent les connexions au socket. (Il peut le faire de deux manières, tout comme le inetdpourrait, mais une discussion sur les détails des services Accept=trueversus Accept=falsedépasse le cadre de cette réponse.)

Le point important est que l'on n'a pas nécessairement besoin de plus de commande que cela. Le noyau place les connexions client dans une file d'attente jusqu'à ce que le programme de service soit initialisé et prêt à les accepter et à parler aux clients.

Quand on le fait, les protocoles de préparation sont la chose.

systemd dispose d'un ensemble de protocoles de préparation qu'il comprend, service par service spécifié avec le Type=paramètre dans l'unité de service. Le protocole de préparation particulier qui nous intéresse ici est le notifyprotocole de préparation. Avec lui, systemd est invité à attendre des messages du service, et lorsque le service est prêt, il envoie un message qui signale l'état de préparation. systemd retarde l'activation des autres services jusqu'à ce que l'état de préparation soit signalé.

Utiliser cela implique deux choses:

  • Modifier le code de Spour qu'il appelle quelque chose comme la fonction de Pierre-Yves Ritschard notify_systemd()ou la fonction de Cameron T Norman notify_socket().
  • Configuration de l'unité de service pour le service avec Type=notifyet NotifyAccess=main.

La NotifyAccess=mainrestriction (qui est la valeur par défaut) est que systemd doit savoir ignorer les messages des programmes espiègles (ou tout simplement défectueux), car tout processus sur le système peut envoyer des messages au socket de notification de systemd.

On utilise de préférence le code de Pierre-Yves Ritschard ou de Cameron T Norman car cela n'exclut pas la possibilité d'avoir ce mécanisme sur UbuntuBSD, Debian FreeBSD, FreeBSD, TrueOS, OpenBSD, etc.; ce que le code fourni par les auteurs de systemd exclut.

Un piège à éviter est le systemd-notifyprogramme. Il a plusieurs problèmes majeurs, dont le moindre n'est pas que les messages envoyés avec celui-ci peuvent finir par être jetés non traités par systemd. Le problème le plus important dans ce cas est qu'il ne s'exécute pas en tant que processus "principal" du service, il faut donc ouvrir les notifications de disponibilité du service Sà chaque processus du système avec NotifyAccess=all.

Un autre piège à éviter est de penser que le forkingprotocole est plus simple. Ce n'est pas. Le faire correctement implique de ne pas bifurquer et de quitter le parent jusqu'à ce que (pour une chose) tous les threads de travail du programme soient en cours d'exécution. Cela ne correspond pas à la façon dont l'écrasante majorité des démons qui bifurquent réellement.

Lectures complémentaires

JdeBP
la source
1
Selon l' homme systemd.service(5), NotifyAccess=allaccepte les messages de tous les membres du groupe de contrôle du service , ce qui ne pas impliquent que tout processus de voyous sur le système. Ceci est suffisamment sécurisé pour la plupart des cas d'utilisation. De plus, votre préoccupation concernant la portabilité vers d'autres systèmes d'exploitation n'est pas pertinente pour OP, car nous sommes déjà sur le sujet de Systemd ici.
Amir
1

En se référant à la page de manuel pour systemd.service(5), en particulier la section sur Type = , chaque type de service a une manière différente pour Systemd de déterminer qu'il est prêt à offrir des fonctionnalités à d'autres services:

  • Si Type=simple, ses canaux de communication doivent être installés avant le démarrage du démon (par exemple les sockets configurés par systemd, via l'activation des sockets).

  • Si Type=forking, le processus parent devrait se terminer lorsque le démarrage est terminé et que tous les canaux de communication sont configurés.

  • Si Type=dbus, il est prévu que le démon acquière un nom sur le bus D-Bus, point à partir duquel systemd procédera au démarrage des unités de suivi.

  • Si Type=notify, il est prévu que le démon envoie un message de notification via sd_notify(3)ou un appel équivalent une fois le démarrage terminé. systemd poursuivra le démarrage des unités de suivi après l'envoi de ce message de notification.

Pour la dernière option (envoyer un message via sd_notify), vous pouvez utiliser l' systemd-notifyutilitaire et n'oubliez pas de lui accorder l'accès avec NotifyAccess=all.

Étant donné que vous contrôlez le service S, vous êtes libre de choisir la meilleure option pour votre cas d'utilisation, ou simplement celle qui est la plus facile à mettre en œuvre.

Amir
la source
1

comme ça:

S.service

[Unit]
Description=My main Service

[Service]
Type=notify
ExecStart=/usr/bin/myBinary

C0.service

[Unit]
Description=Dependent service number 0
PartOf=S.service

C1.service

[Unit]
Description=Dependent service number 1
PartOf=S.service

C9.service

[Unit]
Description=Dependent service number 9
PartOf=S.service

Où / usr / bin / myBinary effectue un appel sd_notify READY = 1 lorsque son initialisation est terminée.

Selon la façon dont vous souhaitez que la dépendance se comporte, vous pouvez utiliser PartOf, Requirements ou BindsTo ou autres .

code_monk
la source