Comprendre les Unités Systemd : Une Analyse Approfondie de la Configuration des Services
Apprenez comment fonctionnent les unités de service systemd, y compris les sections Unit, Service, Install, les surcharges, les redémarrages et les journaux.
Comprendre les Unités Systemd : Une Analyse Approfondie de la Configuration des Services
Les fichiers d'unité systemd sont de petits fichiers texte qui déterminent comment les services démarrent, de quoi ils dépendent, sous quel utilisateur ils s'exécutent et ce qui se passe en cas d'échec. Si vous vous êtes déjà demandé pourquoi systemctl restart myapp.service fonctionne pour une application mais pas pour une autre, la réponse se trouve généralement dans le fichier d'unité.
Ce guide se concentre sur les unités .service car ce sont celles que les administrateurs et les développeurs modifient le plus souvent. Le même système gère également les sockets, les minuteries, les montages, les périphériques, les chemins et les cibles, mais les fichiers de service sont ceux où la plupart des erreurs opérationnelles apparaissent.
Que sont les fichiers d'unité Systemd ?
Les fichiers d'unité systemd sont de simples fichiers texte contenant des directives de configuration pour une unité spécifique. Une unité représente une ressource gérée par systemd. Le type le plus courant est l'unité de service, qui définit comment démarrer, arrêter, redémarrer et gérer un processus en arrière-plan ou une application.
Les fichiers d'unité sont organisés en sections, chacune désignée par des crochets ([]). Les sections les plus importantes pour les unités de service sont :
[Unit]: Contient des métadonnées sur l'unité, les dépendances et l'ordre.[Service]: Définit le comportement du service lui-même, y compris la façon de l'exécuter.[Install]: Spécifie comment l'unité doit être activée ou désactivée, généralement en la liant à des unités cibles.
Systemd recherche les fichiers d'unité dans plusieurs répertoires standard, les plus courants étant :
/etc/systemd/system/: Pour les unités configurées localement, remplaçant celles par défaut./usr/lib/systemd/system/: Pour les unités installées par les paquets sur de nombreuses distributions./lib/systemd/system/: Utilisé par certains systèmes de la famille Debian pour les unités fournies par les paquets.
Lorsque vous devez inspecter une unité, évitez de deviner le chemin. Utilisez :
systemctl cat nginx.service
systemctl show -p FragmentPath nginx.service
systemctl cat est particulièrement utile car il affiche l'unité de base ainsi que les éventuelles surcharges (drop-in). C'est la version que systemd utilise réellement.
Anatomie d'un fichier d'unité .service
Décomposons un fichier d'unité .service typique pour comprendre ses composants.
La section [Unit]
Cette section fournit des informations descriptives et définit les relations entre les unités.
Description=: Une description lisible par l'homme du service.Documentation=: URLs ou chemins vers la documentation du service.After=: Spécifie que cette unité doit démarrer après que les unités listées aient fini de démarrer.Requires=: Similaire àAfter=, mais rend également les unités listées obligatoires. Si une unité requise échoue à démarrer, cette unité échouera également.Wants=: Une forme plus faible de dépendance. Cette unité tentera de démarrer ses unités souhaitées, mais leur échec n'empêchera pas cette unité de démarrer.Conflicts=: Spécifie les unités qui ne peuvent pas fonctionner simultanément avec cette unité.
Exemple de section [Unit] :
[Unit]
Description=Mon Serveur Web Personnalisé
Documentation=https://example.com/docs/mon-serveur-web
After=network.target
Cela indique que notre serveur web personnalisé doit démarrer après que le réseau soit disponible.
Un piège courant : After= contrôle l'ordre, pas l'exigence. Si vous écrivez After=postgresql.service, systemd démarre votre service après PostgreSQL lorsque les deux font partie de la transaction, mais il ne tire pas automatiquement PostgreSQL. Si votre application a vraiment besoin que PostgreSQL soit démarré par la même transaction, utilisez également Wants=postgresql.service ou, pour une dépendance forte, Requires=postgresql.service.
Même dans ce cas, les dépendances ne sont pas des vérifications de santé. After=network.target ne garantit pas que le DNS fonctionne, qu'une API distante est accessible ou qu'une base de données accepte les connexions. Votre application a toujours besoin d'un comportement de nouvelle tentative raisonnable.
La section [Service]
C'est ici que réside la logique principale pour l'exécution du service.
Type=: Définit le type de démarrage du processus. Les types courants incluent :simple(par défaut) : Le processus principal est celui démarré parExecStart=. Systemd considère le service comme démarré immédiatement après le fork du processusExecStart=.forking: Utilisé pour les démons traditionnels qui forkent un processus enfant et se terminent. Systemd attend que le processus parent se termine.oneshot: Pour les tâches qui exécutent une seule commande puis se terminent.notify: Le service envoie une notification à systemd lorsqu'il a fini de démarrer.dbus: Pour les services qui acquièrent un nom D-Bus.
ExecStart=: La commande à exécuter pour démarrer le service.ExecStop=: La commande à exécuter pour arrêter le service.ExecReload=: La commande à exécuter pour recharger la configuration du service sans le redémarrer.Restart=: Définit quand le service doit être redémarré. Les options incluentno(par défaut),on-success,on-failure,on-abnormal,on-watchdog,on-abortetalways.RestartSec=: Le temps d'attente avant de redémarrer le service.User=/Group=: L'utilisateur et le groupe sous lesquels le service doit s'exécuter.WorkingDirectory=: Le répertoire de travail pour les processus exécutés.Environment=/EnvironmentFile=: Définit les variables d'environnement pour le service.
Exemple de section [Service] :
[Service]
Type=simple
ExecStart=/usr/local/bin/mon-serveur-web --config /etc/mon-serveur-web.conf
User=www-data
Group=www-data
Restart=on-failure
RestartSec=5
Cette configuration démarre notre serveur web, l'exécute sous l'utilisateur et le groupe www-data, et le redémarre automatiquement en cas d'échec, avec un délai de 5 secondes.
Type= mérite une attention particulière. De nombreuses unités défectueuses utilisent Type=forking parce qu'un ancien script init utilisait le mode démon. Pour une application moderne qui reste au premier plan, Type=simple est généralement correct. Si votre processus fork en arrière-plan mais que systemd n'est pas informé de la façon d'identifier le véritable processus principal, les rapports d'état et les redémarrages peuvent devenir trompeurs.
Pour un travail ponctuel, utilisez Type=oneshot et souvent RemainAfterExit=yes si l'action terminée doit être considérée comme active. Par exemple, une unité qui prépare une règle de pare-feu ou monte une ressource spéciale peut se terminer avec succès mais représente toujours un état qui vous importe.
La section [Install]
Cette section est utilisée lors de l'activation ou de la désactivation d'une unité. Elle définit comment l'unité s'intègre avec les unités cibles de systemd.
WantedBy=: Spécifie la ou les cibles qui devraient "vouloir" cette unité lorsqu'elle est activée. Pour les services qui doivent démarrer au démarrage,multi-user.targetest couramment utilisé.
Exemple de section [Install] :
[Install]
WantedBy=multi-user.target
Lorsque vous exécutez systemctl enable mon-service-personnalise.service, systemd crée un lien symbolique de /etc/systemd/system/multi-user.target.wants/ vers votre fichier de service, garantissant qu'il démarre lorsque le système atteint le niveau d'exécution multi-utilisateur.
Si une unité n'a pas de section [Install], elle peut toujours être parfaitement valide. Elle ne peut simplement pas être activée directement avec systemctl enable à moins qu'un autre mécanisme d'installation n'existe. Certaines unités sont destinées à être appelées par des dépendances, des sockets, des minuteries ou des cibles plutôt que d'être activées manuellement.
Création et Gestion d'Unités de Service Personnalisées
Parcourons le processus de création d'une unité de service personnalisée.
Étape 1 : Créer le fichier d'unité
Créez un nouveau fichier dans /etc/systemd/system/ avec une extension .service. Pour notre exemple, créons /etc/systemd/system/mon-app.service.
[Unit]
Description=Mon Service d'Application Personnalisé
After=network.target
[Service]
Type=simple
ExecStart=/opt/mon-app/bin/lancer-app --port 8080
User=appuser
Group=appgroup
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Considérations importantes :
- Assurez-vous que la commande
ExecStartpointe vers un script ou un binaire exécutable accessible et disposant des permissions d'exécution. - Créez l'
Useret leGroupspécifiés s'ils n'existent pas (sudo useradd -r -s /bin/false appuser,sudo groupadd appgroup,sudo usermod -a -G appgroup appuser). - Assurez-vous que l'application peut être démarrée et arrêtée correctement en utilisant les commandes spécifiées.
Avant de mettre une commande dans ExecStart=, exécutez-la manuellement en tant que même utilisateur si possible :
sudo -u appuser /opt/mon-app/bin/lancer-app --port 8080
Cela permet de détecter les bits d'exécution manquants, les répertoires manquants, les chemins relatifs incorrects et les problèmes de permissions avant que systemd ne soit impliqué. Une fois que cela fonctionne manuellement, déplacez-le dans l'unité et laissez systemd gérer la supervision.
Étape 2 : Recharger la configuration Systemd
Après avoir créé ou modifié un fichier d'unité, vous devez dire à systemd de recharger sa configuration.
sudo systemctl daemon-reload
Cette commande recherche les fichiers d'unité nouveaux ou modifiés et met à jour l'état interne de systemd.
Étape 3 : Activer et Démarrer le Service
Pour démarrer le service immédiatement et le configurer pour qu'il démarre au démarrage :
sudo systemctl enable mon-app.service # Crée des liens symboliques pour le démarrage
sudo systemctl start mon-app.service # Démarre le service maintenant
Étape 4 : Gérer le Service
Utilisez les commandes systemctl pour gérer votre service :
Vérifier l'état :
sudo systemctl status mon-app.serviceCela affichera si le service est actif, son ID de processus, les entrées de journal récentes, etc.
Arrêter le service :
sudo systemctl stop mon-app.serviceRedémarrer le service :
sudo systemctl restart mon-app.serviceRecharger le service (si
ExecReload=est défini) :sudo systemctl reload mon-app.serviceDésactiver le service (empêcher le démarrage au boot) :
sudo systemctl disable mon-app.service
Étape 5 : Voir les Journaux avec journalctl
Systemd s'intègre étroitement avec journald pour la journalisation. Vous pouvez voir les journaux de votre service en utilisant journalctl :
Voir les journaux pour un service spécifique :
sudo journalctl -u mon-app.serviceSuivre les journaux en temps réel :
sudo journalctl -f -u mon-app.serviceVoir les journaux depuis le dernier démarrage :
sudo journalctl -b -u mon-app.service
Meilleures Pratiques et Conseils
- Utilisez
Type=notifypour les applications modernes : Si votre application le supporte,Type=notifyoffre une meilleure intégration avec systemd, permettant de suivre avec précision l'état de préparation du service. - Exécutez les services en tant qu'utilisateurs non-root : Spécifiez toujours
User=etGroup=dans la section[Service]pour minimiser les risques de sécurité. - Définissez les dépendances avec soin : Utilisez
After=,Requires=etWants=pour garantir que les services démarrent dans le bon ordre et que les dépendances critiques sont satisfaites. - Tirez parti de
Restart=: Configurez des politiques de redémarrage appropriées pour garantir la disponibilité du service. - Gardez les fichiers d'unité simples : Pour les séquences de démarrage complexes, envisagez d'utiliser des scripts wrapper invoqués par
ExecStart=plutôt que des commandes complexes directement dans le fichier d'unité. - Utilisez
systemctl cat <unité>: Pour voir le contenu complet d'un fichier d'unité tel que systemd le voit, y compris les éventuelles surcharges. - Utilisez
systemctl edit <unité>: Cette commande ouvre un éditeur pour créer un fichier de surcharge pour une unité existante, ce qui est une manière plus propre de modifier les fichiers d'unité par défaut que de les éditer directement.
Modifier les Unités Existantes en Toute Sécurité
Ne modifiez pas les unités appartenant aux paquets dans /usr/lib/systemd/system/ ou /lib/systemd/system/ sauf si vous déboguez une machine jetable. Les mises à jour de paquets peuvent remplacer ces fichiers. Utilisez plutôt une surcharge :
sudo systemctl edit nginx.service
Cela crée un fichier drop-in sous /etc/systemd/system/nginx.service.d/. Par exemple, pour ajouter une politique de redémarrage :
[Service]
Restart=on-failure
RestartSec=5s
Certaines directives peuvent être spécifiées plusieurs fois. D'autres doivent être effacées avant d'être remplacées. ExecStart= est l'exemple classique :
[Service]
ExecStart=
ExecStart=/usr/local/bin/mon-wrapper-nginx
La ligne ExecStart= vide réinitialise la valeur précédente. Sans cela, systemd pourrait rejeter l'unité ou conserver plus de commandes que prévu.
Après toute modification d'unité ou de drop-in, utilisez la même boucle de vérification :
sudo systemctl daemon-reload
systemctl cat mon-app.service
sudo systemctl restart mon-app.service
journalctl -u mon-app.service -n 50 --no-pager
Les fichiers d'unité ne sont pas difficiles une fois que vous séparez les trois tâches : [Unit] décrit les relations, [Service] décrit le comportement du processus et [Install] décrit l'activation. La plupart des débogages dans le monde réel consistent simplement à trouver laquelle de ces tâches a été configurée avec la mauvaise hypothèse.
Exemple de Fichier de Service Réaliste
Voici un petit fichier de service réaliste pour une application web Python :
[Unit]
Description=API d'Inventaire
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=inventory
Group=inventory
WorkingDirectory=/srv/inventory-api
EnvironmentFile=/etc/inventory-api/env
ExecStart=/srv/inventory-api/.venv/bin/gunicorn app:app --bind 127.0.0.1:9000
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Il y a plusieurs décisions implicites dans ce fichier. Le service s'exécute en tant que inventory, pas root. La commande utilise un chemin absolu vers gunicorn de l'environnement virtuel, donc elle ne dépend pas du PATH d'un shell interactif. L'application se lie à localhost car un proxy inverse l'exposera publiquement. Le fichier d'environnement vit en dehors de l'unité afin que le déploiement puisse mettre à jour la configuration sans réécrire les métadonnées de service appartenant au paquet.
Les lignes de dépendance sont intentionnellement modestes. After=postgresql.service contrôle l'ordre si PostgreSQL fait partie de la même transaction de démarrage. Cela ne prouve pas que la base de données est prête pour les connexions, et cela ne remplace pas la logique de nouvelle tentative de l'application. network-online.target peut aider sur les systèmes qui implémentent correctement la préparation du réseau, mais ce n'est pas une garantie universelle que chaque dépendance distante est accessible.
Si ce service échoue, les premières vérifications sont prévisibles :
systemctl status inventory-api.service
journalctl -u inventory-api.service -b --no-pager
systemctl cat inventory-api.service
sudo -u inventory /srv/inventory-api/.venv/bin/gunicorn app:app --bind 127.0.0.1:9000
La dernière commande n'est pas quelque chose que vous laissez tourner en production. C'est un contrôle de diagnostic qui demande : "L'utilisateur configuré peut-il exécuter cette commande du tout ?" Si elle ne peut pas importer l'application, lire le fichier d'environnement ou écrire dans son répertoire de logs, systemd ne résoudra pas cela pour vous.
Directives de Ressources et de Sécurité que Vous Verrez Souvent
De nombreuses unités de production incluent des contrôles de durcissement ou de ressources. Quelques exemples courants :
[Service]
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
MemoryMax=512M
CPUQuota=80%
Ces directives peuvent être très utiles, mais elles peuvent aussi briser des hypothèses. PrivateTmp=true donne au service un /tmp privé, donc un autre processus peut ne pas voir les fichiers qu'il y écrit. ProtectHome=true peut bloquer l'accès à /home, /root et /run/user. ProtectSystem=full rend une grande partie du système en lecture seule du point de vue du service. Si une application ne peut soudainement plus écrire là où elle le faisait, inspectez les paramètres de durcissement avant de blâmer l'application.
Les limites de ressources ont le même compromis. MemoryMax= peut empêcher un service de consommer toute la machine, mais si la valeur est trop basse, le service peut être tué sous charge normale. Vérifiez le journal pour les messages de mémoire insuffisante et comparez la limite avec l'utilisation réelle avant d'augmenter ou de supprimer la limite.
Les Commandes de Débogage les Plus Utiles
Gardez celles-ci à portée de main lorsque vous travaillez avec des unités de service :
systemctl status mon-app.service
systemctl cat mon-app.service
systemctl show mon-app.service
systemd-analyze verify /etc/systemd/system/mon-app.service
journalctl -u mon-app.service -b --no-pager
systemctl show est verbeux, mais il expose les propriétés que systemd a calculées après avoir analysé l'unité. Cela peut révéler une valeur surprenante héritée d'une valeur par défaut, d'un drop-in ou d'une directive de réinitialisation. systemd-analyze verify détecte certaines erreurs de syntaxe et de dépendance avant que vous ne redémarriez un service. Ce n'est pas un remplacement pour tester l'application, mais cela détecte suffisamment d'erreurs pour valoir la peine d'être exécuté.