Comment écrire et gérer efficacement les fichiers d'unité systemd personnalisés
Les distributions Linux modernes utilisent majoritairement systemd comme système d'initialisation et gestionnaire de services. Comprendre systemd est crucial pour tout administrateur système ou développeur Linux ayant besoin de déployer et gérer des applications de manière fiable. Bien que de nombreuses applications soient fournies avec des fichiers d'unité systemd pré-intégrés, la capacité d'écrire des fichiers d'unité personnalisés vous permet de standardiser le démarrage, l'arrêt et la gestion du cycle de vie général de vos propres applications, scripts ou de tout processus personnalisé.
Cet article vous guidera à travers le processus de création, de configuration et de gestion des fichiers d'unité .service systemd personnalisés. Nous explorerons les directives essentielles qui définissent le fonctionnement de votre application, établissent les dépendances et assurent un fonctionnement robuste. À la fin, vous serez équipé pour intégrer vos services personnalisés de manière transparente dans le système d'exploitation Linux, en vous assurant qu'ils démarrent automatiquement au démarrage, redémarrent en cas d'échec et sont facilement gérés à l'aide de systemctl.
Maîtriser les fichiers d'unité systemd personnalisés offre un contrôle granulaire sur vos services, améliore la stabilité du système et simplifie les tâches administratives. Plongeons dans les composants clés et les étapes pratiques nécessaires pour gérer vos applications comme un professionnel.
Comprendre les fichiers d'unité systemd
Systemd gère diverses ressources système, connues sous le nom d'unités, qui sont définies par des fichiers de configuration. Ces unités incluent les services (.service), les points de montage (.mount), les périphériques (.device), les sockets (.socket), et plus encore. Pour la gestion des applications et des processus d'arrière-plan, le type d'unité .service est le plus courant et le plus pertinent.
Les fichiers d'unité systemd sont des fichiers texte brut généralement stockés dans des répertoires spécifiques. Les emplacements principaux, par ordre de priorité, sont :
/etc/systemd/system/: C'est l'emplacement recommandé pour les fichiers d'unité personnalisés et les remplacements, car ils ont priorité sur les valeurs par défaut du système et persistent après les mises à jour système./run/systemd/system/: Utilisé pour les fichiers d'unité générés au moment de l'exécution./usr/lib/systemd/system/: Contient les fichiers d'unité fournis par les paquets installés. Ne modifiez pas directement les fichiers de ce répertoire.
En plaçant vos fichiers d'unité personnalisés dans /etc/systemd/system/, vous vous assurez qu'ils sont correctement reconnus et gérés par systemd.
Anatomie d'un fichier d'unité .service
Un fichier d'unité systemd .service est structuré en plusieurs sections, chacune étant délimitée par [NomSection], contenant diverses directives (paires clé-valeur). Les trois sections principales pour une unité de service sont [Unit], [Service] et [Install].
Détaillons les directives les plus cruciales que vous utiliserez :
Section [Unit]
Cette section contient des options génériques concernant l'unité, sa description et ses dépendances.
Description: Une chaîne de caractères lisible par l'homme décrivant le service. Ceci apparaît dans la sortie desystemctl status.
ini Description=Mon application Web Python personnaliséeDocumentation: Une URL pointant vers la documentation du service (facultatif).
ini Documentation=https://example.com/docs/mon-appAfter: Spécifie que cette unité doit démarrer après les unités listées. Cela aide à gérer l'ordre de démarrage. Pour les applications Web, vous pourriez vouloir vous assurer que le réseau est opérationnel.
ini After=network.targetRequires: Similaire àAfter, mais implique une dépendance plus forte. Si l'unité requise échoue, cette unité ne sera pas démarrée ou sera arrêtée.
ini Requires=docker.serviceWants: Une forme plus faible deRequires. Si l'unité souhaitée échoue ou n'est pas trouvée, cette unité tentera quand même de démarrer. Ceci est généralement préféré àRequirespour les dépendances non critiques.
ini Wants=syslog.target
Section [Service]
Cette section définit les paramètres d'exécution de votre service, y compris comment il démarre, s'arrête et se comporte.
-
Type: Définit le type de démarrage du processus. Crucial pour la manière dont systemd surveille votre service.simple(par défaut) : La commandeExecStartest le processus principal du service. Systemd considère le service comme démarré immédiatement après l'invocation deExecStart. Il s'attend à ce que le processus s'exécute indéfiniment au premier plan.forking: La commandeExecStartcrée un processus enfant et le processus parent se termine. Systemd considère le service comme démarré une fois que le processus parent se termine. Utilisez ceci si votre application se transforme en démon.oneshot: La commandeExecStartest un processus unique qui se termine une fois sa tâche accomplie. Utile pour les scripts qui effectuent une tâche et se terminent (par exemple, un script de sauvegarde).notify: Similaire àsimple, mais le service envoie une notification à systemd lorsqu'il est prêt. Nécessitelibsystemd-devet du code spécifique dans votre application.idle: La commandeExecStartest exécutée uniquement lorsque tous les travaux sont terminés, retardant l'exécution jusqu'à ce que le système soit largement inactif.
ini Type=simple -
ExecStart: La commande à exécuter lorsque le service démarre. C'est la directive la plus importante de cette section. Utilisez toujours le chemin absolu de votre exécutable ou script.
ini ExecStart=/usr/bin/python3 /opt/mon_app/app.py ExecStop: La commande à exécuter lorsque le service est arrêté (facultatif). Si non spécifié, systemd envoieSIGTERMaux processus.
ini ExecStop=/usr/bin/pkill -f 'mon_app/app.py'ExecReload: La commande à exécuter pour recharger la configuration du service (facultatif).
ini ExecReload=/bin/kill -HUP $MAINPIDUser: Le compte utilisateur sous lequel les processus du service s'exécuteront. Essentiel pour la sécurité ; évitezrootsauf nécessité absolue.
ini User=utilisateur_mon_appGroup: Le compte de groupe sous lequel les processus du service s'exécuteront.
ini Group=groupe_mon_appWorkingDirectory: Le répertoire de travail pour les commandes exécutées.
ini WorkingDirectory=/opt/mon_appRestart: Définit quand le service doit être redémarré automatiquement.no(par défaut) : Jamais redémarrer.on-success: Redémarrer uniquement si le service se termine proprement.on-failure: Redémarrer uniquement si le service se termine avec un code de sortie non nul ou est arrêté par un signal.always: Toujours redémarrer le service, quel que soit le code de sortie.
ini Restart=on-failure
RestartSec: Combien de temps attendre avant de redémarrer le service (par exemple,5spour 5 secondes).
ini RestartSec=5sEnvironment: Définit les variables d'environnement pour les commandes exécutées.
ini Environment="APP_ENV=production" "DEBUG=false"EnvironmentFile: Lit les variables d'environnement à partir d'un fichier. Chaque ligne doit êtreCLE=VALEUR.
ini EnvironmentFile=/etc/default/mon_appLimitNOFILE: Définit le nombre maximum de descripteurs de fichiers ouverts autorisés pour le service (par exemple,100000). Important pour les applications à forte concurrence.
ini LimitNOFILE=65536
Section [Install]
Cette section définit comment le service est activé pour démarrer automatiquement au démarrage du système.
WantedBy: Spécifie l'unité cible qui "veut" ce service. Lorsque l'unité cible est activée, ce service sera lié symboliquement dans son répertoire.wants, le faisant effectivement démarrer avec la cible.multi-user.target: La cible standard pour la plupart des services serveur, indiquant un système avec des connexions multi-utilisateurs non graphiques.graphical.target: Pour les services qui nécessitent un environnement graphique.
ini WantedBy=multi-user.target
RequiredBy: Similaire àWantedBy, mais une dépendance plus forte. Si la cible est activée, cette unité est également activée, et si cette unité échoue, la cible échouera également.
Astuce : Pour la plupart des services personnalisés destinés à s'exécuter en arrière-plan sur un serveur,
Type=simpleetWantedBy=multi-user.targetsont les choix les plus courants et appropriés.
Étapes : Créer et gérer un service systemd personnalisé
Créons un exemple pratique : un simple serveur HTTP Python qui sert des fichiers d'un répertoire spécifié. Nous allons le configurer comme un service systemd.
Étape 1 : Préparer votre application/script
Tout d'abord, créez le script de l'application. Pour cet exemple, nous utiliserons un simple serveur HTTP Python. Créez un répertoire pour votre application, par exemple /opt/mon_app, et placez app.py à l'intérieur.
# /opt/mon_app/app.py
import http.server
import socketserver
import os
PORT = int(os.environ.get("PORT", 8000))
DIRECTORY = os.environ.get("DIRECTORY", os.getcwd())
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=DIRECTORY, **kwargs)
print(f"Serveur le répertoire {DIRECTORY} sur le port {PORT}")
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("Serveur démarré.")
httpd.serve_forever()
Créez le répertoire et le fichier :
sudo mkdir -p /opt/mon_app
sudo nano /opt/mon_app/app.py
(Collez le code Python)
Assurez-vous que le script est exécutable (facultatif pour la commande python3, mais bonne pratique) :
sudo chmod +x /opt/mon_app/app.py
Envisagez de créer un utilisateur dédié pour votre service pour des raisons de sécurité :
sudo useradd --system --no-create-home utilisateur_mon_app
Définissez la propriété appropriée pour votre répertoire d'application :
sudo chown -R utilisateur_mon_app:utilisateur_mon_app /opt/mon_app
Étape 2 : Créer le fichier d'unité
Maintenant, créez le fichier d'unité systemd pour notre application Python. Nous l'appellerons mon_app.service.
sudo nano /etc/systemd/system/mon_app.service
Collez le contenu suivant :
# /etc/systemd/system/mon_app.service
[Unit]
Description=Mon serveur HTTP Python personnalisé
Documentation=https://github.com/exemple/mon_app
After=network.target
[Service]
Type=simple
User=utilisateur_mon_app
Group=utilisateur_mon_app
WorkingDirectory=/opt/mon_app
Environment="PORT=8080" "DIRECTORY=/var/www/html"
ExecStart=/usr/bin/python3 /opt/mon_app/app.py
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Note : Nous avons défini
StandardOutput=journaletStandardError=journalpour rediriger la sortie du service vers le journal systemd, ce qui permet de visualiser facilement les journaux avecjournalctl.
Étape 3 : Placer le fichier d'unité
Comme indiqué, nous avons placé le fichier d'unité dans /etc/systemd/system/. C'est là que doivent résider les fichiers d'unité personnalisés.
Étape 4 : Recharger le démon systemd
Après avoir créé ou modifié un fichier d'unité, systemd doit être informé des changements. Ceci est fait en rechargeant le démon systemd :
sudo systemctl daemon-reload
Étape 5 : Démarrer le service
Vous pouvez maintenant démarrer votre service :
sudo systemctl start mon_app.service
Étape 6 : Vérifier le statut et les journaux du service
Vérifiez que votre service s'exécute correctement :
systemctl status mon_app.service
Exemple de sortie (tronquée) :
● mon_app.service - Mon serveur HTTP Python personnalisé
Loaded: loaded (/etc/systemd/system/mon_app.service; disabled; vendor preset: enabled)
Active: active (running) since Tue 2023-10-26 10:30:00 UTC; 5s ago
Docs: https://github.com/exemple/mon_app
Main PID: 12345 (python3)
Tasks: 1 (limit: 1100)
Memory: 6.5M
CPU: 45ms
CGroup: /system.slice/mon_app.service
└─12345 /usr/bin/python3 /opt/mon_app/app.py
Oct 26 10:30:00 votrehostname python3[12345]: Serveur le répertoire /var/www/html sur le port 8080
Oct 26 10:30:00 votrehostname python3[12345]: Serveur démarré.
Pour visualiser les journaux du service, utilisez journalctl :
journalctl -u mon_app.service -f
Cette commande affiche les journaux pour mon_app.service et -f (follow) affichera les nouveaux journaux en temps réel.
Vous pouvez également tester le serveur depuis votre navigateur ou avec curl sur http://localhost:8080 (en supposant que /var/www/html existe et contient des fichiers).
Étape 7 : Activer le service pour le démarrage automatique
Pour que votre service démarre automatiquement à chaque démarrage du système, vous devez l'activer :
sudo systemctl enable mon_app.service
Cette commande crée un lien symbolique de /etc/systemd/system/multi-user.target.wants/mon_app.service vers /etc/systemd/system/mon_app.service.
Étape 8 : Arrêter et désactiver le service
Pour arrêter un service en cours d'exécution :
sudo systemctl stop mon_app.service
Pour empêcher un service de démarrer automatiquement au démarrage (tout en le laissant activé pour être démarré manuellement) :
sudo systemctl disable mon_app.service
Si vous souhaitez supprimer complètement le service, désactivez-le d'abord, arrêtez-le, puis supprimez le fichier .service de /etc/systemd/system/ et exécutez sudo systemctl daemon-reload.
Étape 9 : Mettre à jour un service
Si vous modifiez votre script app.py ou le fichier d'unité mon_app.service, vous devrez mettre à jour systemd et redémarrer le service :
- Modifiez
/opt/mon_app/app.pyou/etc/systemd/system/mon_app.service. - Si vous avez modifié le fichier d'unité, exécutez
sudo systemctl daemon-reload. - Redémarrez le service :
sudo systemctl restart mon_app.service.
Bonnes pratiques et dépannage
- Chemins absolus : Utilisez toujours des chemins absolus pour
ExecStart,WorkingDirectory, et tout autre chemin de fichier dans votre fichier d'unité. Les chemins relatifs peuvent entraîner un comportement inattendu. - Utilisateurs dédiés : Exécutez les services sous des comptes utilisateurs dédiés et non privilégiés (par exemple,
utilisateur_mon_app) pour améliorer la sécurité et limiter les dommages potentiels en cas de compromission. - Journalisation claire : Utilisez
StandardOutput=journaletStandardError=journalpour rediriger la sortie du service vers le journal systemd. Utilisezjournalctl -u <nom_du_service>pour afficher les journaux. - Dépendances : Considérez attentivement
After,WantsetRequirespour vous assurer que votre service démarre dans le bon ordre par rapport à ses dépendances (par exemple, réseau, bases de données). - Test des modifications : Avant d'activer un service pour qu'il démarre au démarrage, testez-le minutieusement en le démarrant et en l'arrêtant manuellement. Vérifiez son statut et ses journaux.
- Limites de ressources : Utilisez des directives telles que
LimitNOFILE,LimitNPROC,MemoryLimit, etc., pour empêcher les services débridés de consommer toutes les ressources système. - Variables d'environnement : Utilisez
Environment=ouEnvironmentFile=pour les valeurs de configuration qui peuvent changer ou varier entre les environnements, plutôt que de les coder en dur dans le fichier d'unité ou le script. - Gestion des erreurs dans les scripts : Assurez-vous que vos scripts d'application gèrent les erreurs avec grâce. Un code de sortie non nul déclenchera
Restart=on-failure.
Attention : Évitez de modifier directement les fichiers d'unité dans
/usr/lib/systemd/system/. Toute modification sera probablement écrasée par les mises à jour des paquets. Utilisez/etc/systemd/system/pour les unités personnalisées ou les remplacements.
Conclusion
Les fichiers d'unité systemd sont un mécanisme puissant et flexible pour gérer les processus et les applications sur les systèmes Linux. En comprenant leur structure et leurs directives clés, vous pouvez standardiser efficacement le démarrage, l'arrêt et la surveillance de vos services personnalisés, améliorant ainsi la stabilité du système et simplifiant l'administration. De la définition des commandes de démarrage avec ExecStart à la gestion des dépendances avec After et à l'activation du démarrage automatique avec WantedBy, vous disposez désormais des outils nécessaires pour intégrer vos applications de manière transparente dans l'écosystème systemd. Cette compétence fondamentale est inestimable pour maintenir des déploiements Linux robustes et fiables.
Continuez à explorer les fonctionnalités avancées de systemd telles que les timers (.timer), l'activation par socket (.socket) et les cgroups pour des scénarios de gestion de services plus sophistiqués.