Maîtrise de Systemd : Création de votre premier fichier d'unité de service personnalisé
Apprenez les fondamentaux de la gestion des services Systemd en créant un fichier d'unité personnalisé. Ce tutoriel décompose les sections essentielles `[Unit]`, `[Service]` et `[Install]`, fournissant des instructions étape par étape pour définir, activer, démarrer et vérifier un service d'arrière-plan de base sur Linux à l'aide de `systemctl`.
Maîtrise de Systemd : Création de votre premier fichier d'unité de service personnalisé
Une unité de service systemd personnalisée est ce que vous utilisez lorsqu'un script ou une petite application a dépassé une session de terminal, une fenêtre screen ou une solution de contournement fragile avec cron. Peut-être avez-vous un worker qui doit redémarrer après un crash. Peut-être une petite API interne doit démarrer après que le réseau est prêt. Peut-être un script de sauvegarde doit s'exécuter en tant que service contrôlé afin que ses journaux vivent dans le journal et que les opérateurs puissent utiliser les mêmes commandes systemctl qu'ils utilisent pour tout le reste.
L'utilité de systemd n'est pas que le fichier d'unité soit compliqué. C'est que le fichier d'unité rend le processus explicite : ce qui s'exécute, sous quelle identité, quand il démarre, comment il s'arrête, où vont les journaux, et ce que systemd doit faire en cas d'échec. Une fois ces décisions écrites, le service devient beaucoup plus facile à exploiter.
Cette procédure pas à pas construit un petit service à partir de zéro. L'exemple est volontairement simple, mais les modèles sont les mêmes que ceux que vous utiliseriez pour un worker d'arrière-plan, un consommateur de file d'attente, un exportateur de métriques ou un démon interne.
Commencez par une commande réelle, pas un fichier d'unité
Une bonne unité de service commence par une commande qui fonctionne déjà manuellement. Avant d'écrire la configuration systemd, assurez-vous de pouvoir exécuter le programme directement et de comprendre ce qu'il fait au premier plan.
Pour cet exemple, créez un petit script de rapport :
sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh
Ajoutez ce contenu :
#!/usr/bin/env bash
set -euo pipefail
while true; do
echo "$(date --iso-8601=seconds) reporter heartbeat"
sleep 10
done
Rendez-le exécutable et testez-le :
sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh
Arrêtez-le avec Ctrl-C après avoir vu quelques lignes. Remarquez que le script écrit sur la sortie standard au lieu d'ajouter directement à /var/log/reporter.log. C'est délibéré. Pour la plupart des services personnalisés, laisser systemd capturer stdout et stderr dans le journal est plus propre que de faire gérer à chaque script ses propres permissions de fichier journal, sa rotation et son comportement en cas d'échec.
Créez un utilisateur de service dédié
Évitez d'exécuter les services d'application en tant que root sauf s'ils ont réellement besoin de privilèges root. Un script de pulsation n'en a pas besoin. Une application web généralement non plus. Un worker qui lit une file d'attente et écrit dans une base de données non plus.
Créez un utilisateur système verrouillé :
sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter
Si votre distribution utilise un chemin nologin différent, vérifiez-le avec :
command -v nologin || command -v false
L'utilisateur du service ne doit posséder que les fichiers qu'il doit écrire. Dans cet exemple, le script écrit dans le journal via systemd, donc il n'a pas besoin de la propriété de /opt/my-custom-service.
Écrivez l'unité de service
Les unités système personnalisées gérées par l'administrateur se trouvent normalement dans /etc/systemd/system/. Les unités des paquets fournisseurs se trouvent généralement dans /usr/lib/systemd/system/ ou /lib/systemd/system/, selon la distribution. N'éditez pas directement les fichiers d'unité des fournisseurs lorsque vous pouvez l'éviter ; utilisez /etc/systemd/system/ pour vos propres unités et les fichiers de remplacement pour les surcharges.
Créez l'unité :
sudo nano /etc/systemd/system/my-reporter.service
Utilisez ceci comme première version pratique :
[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
La section [Unit] décrit les relations. After=network-online.target contrôle l'ordre ; il ne tire pas la cible network-online par lui-même. Wants=network-online.target demande à systemd de démarrer également cette cible. Si votre service n'a pas besoin du réseau, supprimez les deux lignes et gardez l'unité plus simple.
La section [Service] décrit le processus. Type=simple est approprié pour un processus au premier plan qui ne se bifurque pas en arrière-plan. C'est le cas courant pour les services modernes. Si un démon hérité se bifurque, écrit un fichier PID et rend le contrôle au shell, alors vous pourriez avoir besoin de Type=forking, mais ne l'utilisez pas simplement parce que le mot semble plus proche d'un démon.
ExecStart doit être un chemin absolu. Les fonctionnalités du shell telles que les pipes, les redirections et && ne sont pas interprétées à moins que vous n'exécutiez explicitement un shell, par exemple ExecStart=/bin/bash -lc 'commande une && commande deux'. Préférez un script lorsque la commande nécessite une logique shell ; c'est plus facile à tester et à lire.
Restart=on-failure indique à systemd de redémarrer le service après des sorties anormales. Il ne redémarrera pas après un systemctl stop propre. RestartSec=5s empêche une boucle de redémarrage serrée de marteler la machine.
Les options de durcissement ici sont modestes mais utiles. NoNewPrivileges=true empêche le processus et ses enfants d'acquérir de nouveaux privilèges via des binaires setuid ou des capacités de fichiers. PrivateTmp=true donne au service une vue /tmp privée. Ceux-ci sont généralement sûrs pour les services simples, mais testez-les avec des applications réelles car certains logiciels s'attendent à des chemins temporaires partagés.
Chargez et démarrez l'unité
Après avoir ajouté ou modifié un fichier d'unité, rechargez la configuration du gestionnaire systemd :
sudo systemctl daemon-reload
Démarrez le service maintenant :
sudo systemctl start my-reporter.service
Vérifiez son état :
systemctl status my-reporter.service
Vous voulez voir Active: active (running). S'il a échoué, ne devinez pas. Lisez les journaux :
journalctl -u my-reporter.service -n 50 --no-pager
Suivez les journaux en direct pendant les tests :
journalctl -u my-reporter.service -f
Si le chemin du script est incorrect, les permissions manquent, l'utilisateur n'existe pas ou la commande se termine immédiatement, systemd le dira généralement clairement dans le journal.
Activez le démarrage au démarrage
Démarrer un service et activer un service sont des actions différentes. start l'exécute maintenant. enable le branche dans la cible de démarrage pour qu'il démarre lors des prochains démarrages.
sudo systemctl enable my-reporter.service
Vous pouvez faire les deux en une seule commande après que l'unité a été testée :
sudo systemctl enable --now my-reporter.service
Pour voir s'il est activé :
systemctl is-enabled my-reporter.service
Facilitez le diagnostic des échecs
Les échecs les plus courants des premiers services sont des problèmes Linux ordinaires déguisés en vêtements systemd.
Si vous voyez status=203/EXEC, systemd n'a pas pu exécuter la commande. Vérifiez le chemin, le bit exécutable, la ligne shebang et les fins de ligne. Un script copié depuis Windows avec des fins de ligne CRLF peut échouer même s'il semble correct dans un éditeur.
Si vous voyez des erreurs de permission, rappelez-vous que le service s'exécute en tant que reporter, pas en tant qu'utilisateur de votre shell. Testez avec :
sudo -u reporter /opt/my-custom-service/reporter.sh
Si le service démarre et s'arrête immédiatement, le processus se termine probablement. Type=simple s'attend à ce que la commande continue de s'exécuter. Une commande de configuration unique devrait utiliser Type=oneshot, pas simple.
Si les journaux manquent, vérifiez si l'application écrit dans des fichiers au lieu de stdout/stderr, ou si elle change d'utilisateurs en interne. Pour la plupart des petits services, écrire sur stdout est l'option la moins surprenante.
Commandes de gestion utiles
Une fois l'unité en place, l'exploitation quotidienne est simple :
sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart
systemctl cat est particulièrement utile sur les machines avec des surcharges de type drop-in car il montre les fragments d'unité effectifs que systemd lit.
Un fichier d'unité personnalisé n'a pas besoin d'être intelligent. Il doit être ennuyeux, explicite et testable. Faites fonctionner la commande manuellement, exécutez-la en tant qu'utilisateur dédié, écrivez la plus petite unité qui décrit le service avec précision, rechargez systemd et utilisez le journal en cas d'échec. Ce flux de travail s'étend d'un script de rapport jouet à de véritables démons de production.
Ajoutez l'environnement et la configuration proprement
Tôt ou tard, le service a besoin de configuration : un port, une URL de base de données, un indicateur de fonctionnalité ou un chemin. Évitez d'enterrer ces valeurs dans le fichier d'unité lorsqu'elles varient selon l'environnement. Un modèle courant est un fichier d'environnement :
sudo nano /etc/my-reporter.env
Exemple :
REPORT_INTERVAL=10
REPORT_LABEL=production
Verrouillez le fichier s'il contient quelque chose de sensible :
sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env
Ensuite, référencez-le depuis l'unité :
[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh
Dans le script, lisez la variable avec une valeur par défaut :
interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"
Pour les secrets, soyez prudent. Une variable d'environnement peut être exposée via l'inspection du processus ou les métadonnées du service en fonction de la configuration système et des permissions. Pour les valeurs hautement sensibles, préférez un gestionnaire de secrets approprié, un fichier d'identifiants avec des permissions strictes, ou les fonctionnalités d'identifiants plus récentes de systemd si votre distribution les supporte. L'habitude importante est de décider délibérément au lieu de parsemer les mots de passe dans les fichiers d'unité parce que c'est pratique.
Utilisez des surcharges de type drop-in pour les modifications locales
Si un paquet installe une unité et que vous devez modifier un paramètre, ne modifiez pas le fichier du fournisseur. Utilisez un drop-in :
sudo systemctl edit my-reporter.service
Cela ouvre un fichier de surcharge sous /etc/systemd/system/my-reporter.service.d/. Par exemple :
[Service]
RestartSec=15s
Rechargez et redémarrez après avoir enregistré :
sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service
Vérifiez le résultat fusionné :
systemctl cat my-reporter.service
Les drop-ins sont importants car les mises à jour de paquets peuvent remplacer les unités des fournisseurs. Vos surcharges dans /etc restent visibles et intentionnelles.
Pensez au comportement d'arrêt
Le démarrage n'est que la moitié du cycle de vie. Un service doit également s'arrêter proprement. Par défaut, systemd envoie SIGTERM, attend, puis peut envoyer SIGKILL si le processus ne se termine pas. Pour de nombreux services simples, cela convient. Pour les workers de file d'attente, les processeurs de téléchargement et les rédacteurs de base de données, vous devrez peut-être gérer la terminaison pour que le processus termine ou abandonne le travail en cours en toute sécurité.
Vous pouvez ajuster le délai d'attente :
[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM
Ne définissez pas des délais d'attente d'arrêt extrêmement longs sauf si vous avez une raison. Les arrêts longs ralentissent les déploiements, les redémarrages et la récupération d'incidents. Un worker devrait généralement arrêter d'accepter de nouveaux travaux, terminer l'élément qu'il traite et se terminer dans un délai limité.
Empêchez les boucles de redémarrage bruyantes
Restart=on-failure est utile, mais un service cassé peut encore redémarrer de manière répétée. Ajoutez des limites lorsque le mode d'échec pourrait être bruyant :
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
Cela indique à systemd d'arrêter d'essayer après trop d'échecs dans l'intervalle. Lorsque vous corrigez le problème, réinitialisez l'état d'échec :
sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service
Cette commande est également utile pendant les tests. Un service peut rester dans un état d'échec même après avoir corrigé le script ou les permissions.
Validez les unités avant de vous y fier
Systemd dispose d'un vérificateur utile :
systemd-analyze verify /etc/systemd/system/my-reporter.service
Il ne détectera pas tous les problèmes d'application, mais il peut détecter les erreurs de syntaxe, les paramètres inconnus sur votre version de systemd et certains problèmes d'ordre. Exécutez-le après des modifications plus importantes ou lors de la copie d'une unité entre distributions. Les fonctionnalités de systemd varient selon la version, donc une option de durcissement qui fonctionne sur un nouveau serveur Fedora peut ne pas exister sur une distribution d'entreprise plus ancienne.
Vérifiez également la vue des dépendances de l'unité lorsque l'ordre de démarrage devient déroutant :
systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service
La première commande montre ce que le service tire ou dont il dépend. La vue inverse montre ce qui en dépend. Ceci est utile lorsqu'un service démarre de manière inattendue ou lorsque la désactivation d'une unité en affecte une autre.
Décidez si le service est au niveau système ou au niveau utilisateur
Cet article utilise un service système sous /etc/systemd/system/. C'est le bon choix pour les services machine qui doivent démarrer au démarrage et fonctionner indépendamment d'une session de connexion. Systemd prend également en charge les services utilisateur, généralement gérés avec systemctl --user, pour les processus d'arrière-plan par utilisateur.
N'utilisez pas un service utilisateur pour les démons d'infrastructure juste pour éviter sudo. Les services utilisateur ont des règles de cycle de vie, une gestion de l'environnement et un comportement de connexion différents. Pour les workers d'application, les exportateurs et les agents au niveau de l'hôte, un service système avec un utilisateur dédié aux privilèges minimaux est généralement plus facile à raisonner.
Gardez la première unité ennuyeuse
Il est tentant d'ajouter toutes les directives de durcissement que vous trouvez en ligne : périphériques privés, chemins en lecture seule, filtres d'appels système, limitation des capacités, restrictions d'espace de noms, etc. Ce sont des outils précieux, mais ajoutez-les un par un après que le service fonctionne. Lorsqu'un service fortement restreint ne parvient pas à démarrer, les débutants ne peuvent souvent pas dire si l'unité est erronée, si l'application est erronée ou si un paramètre de bac à sable a bloqué un fichier dont elle a besoin.
Un bon chemin de production est incrémental : service fonctionnel, utilisateur dédié, journalisation fiable, politique de redémarrage, durcissement de base, puis un sandboxing plus fort après avoir un test qui prouve que l'application se comporte toujours correctement.