Gérer les services sur les distributions Linux modernes signifie travailler avec systemd, le système d’initialisation et gestionnaire de services devenu le standard sur la plupart des distributions. Ce guide couvre tout ce que vous devez savoir sur la création d’unités de service personnalisées, la gestion des dépendances entre services, l’analyse des logs avec journalctl, le remplacement de cron par les timers systemd et le diagnostic des problèmes lorsque les services ne démarrent pas ou s’arrêtent de manière inattendue.
Prérequis
- Une distribution Linux exécutant systemd (Ubuntu 16.04+, CentOS 7+, Debian 8+, Fedora 15+)
- Accès au terminal avec privilèges sudo
- Compréhension basique des permissions de fichiers Linux et de la gestion des processus
- Un éditeur de texte installé (vim, nano ou similaire)
Comprendre les Unités Systemd
Systemd organise tout en unités — des ressources que le système sait gérer. Les types d’unités les plus courants sont :
| Type d’Unité | Extension | Objectif |
|---|---|---|
| Service | .service | Processus daemon et tâches ponctuelles |
| Timer | .timer | Exécution planifiée (remplace cron) |
| Socket | .socket | Activation de sockets IPC et réseau |
| Mount | .mount | Points de montage du système de fichiers |
| Target | .target | Groupes d’unités (comme les runlevels) |
Les fichiers d’unité résident dans trois emplacements, avec priorité décroissante :
/etc/systemd/system/ # Unités personnalisées de l'admin (priorité maximale)
/run/systemd/system/ # Unités d'exécution
/lib/systemd/system/ # Unités par défaut de la distribution
Pour lister toutes les unités chargées et leurs états :
systemctl list-units --type=service
systemctl list-units --type=service --state=failed
Création d’Unités de Service Personnalisées
Un fichier d’unité de service bien structuré comporte trois sections. Voici un exemple complet pour une application Node.js :
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js Application
Documentation=https://example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStartPre=/usr/bin/node --check /opt/myapp/server.js
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
# Renforcement de la sécurité
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Directives clés dans la section [Service] :
- Type :
simple(défaut, processus au premier plan),forking(pour les daemons qui font un fork),oneshot(s’exécute une fois et se termine),notify(signale la disponibilité via sd_notify) - ExecStartPre : Commande pour exécuter une validation avant de démarrer le processus principal
- Restart :
on-failure,always,on-abnormalouno - RestartSec : Délai entre les tentatives de redémarrage en secondes
Après avoir créé le fichier, chargez-le et démarrez-le :
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service
Gestion des Dépendances
Systemd utilise plusieurs directives pour exprimer les relations entre unités :
[Unit]
# Ordonnancement (quand démarrer, pas si)
After=network-online.target # Démarrer après que le réseau soit prêt
Before=nginx.service # Démarrer avant nginx
# Force de la dépendance
Requires=postgresql.service # Dépendance forte — échoue si postgres échoue
Wants=redis.service # Dépendance faible — continue si redis échoue
BindsTo=docker.service # Cycle de vie lié — s'arrête quand docker s'arrête
# Résolution de conflits
Conflicts=iptables.service # Ne peut pas fonctionner avec iptables
La différence entre After et Requires est critique. After contrôle l’ordonnancement (séquence) tandis que Requires contrôle l’activation (s’il faut inclure une dépendance). Vous avez généralement besoin des deux :
Requires=postgresql.service
After=postgresql.service
Utiliser Requires seul ne garantit pas l’ordre — les deux services peuvent démarrer simultanément. Utiliser After seul n’inclut pas la dépendance — il les ordonne seulement si les deux sont en cours de démarrage.
Pour visualiser l’arbre de dépendances d’un service :
systemctl list-dependencies myapp.service
systemctl list-dependencies myapp.service --reverse
Analyse des Logs avec Journalctl
Journalctl est l’outil pour interroger le journal systemd. Voici les modèles les plus utiles :
# Suivre les logs d'un service spécifique en temps réel
journalctl -u myapp.service -f
# Afficher les logs depuis le dernier démarrage
journalctl -u myapp.service -b
# Filtrer par plage horaire
journalctl -u myapp.service --since "2026-02-20 08:00" --until "2026-02-20 18:00"
# Afficher uniquement les erreurs et au-dessus
journalctl -u myapp.service -p err
# Sortie en format JSON pour traitement
journalctl -u myapp.service -o json-pretty --no-pager
# Afficher les messages du noyau liés aux OOM kills
journalctl -k --grep="Out of memory"
# Vérifier l'utilisation disque du journal
journalctl --disk-usage
# Nettoyer les anciens logs pour libérer de l'espace
sudo journalctl --vacuum-time=7d
sudo journalctl --vacuum-size=500M
Les niveaux de priorité suivent la convention syslog : emerg (0), alert (1), crit (2), err (3), warning (4), notice (5), info (6), debug (7). Utiliser -p err affiche err et tout ce qui est plus sévère.
Pour configurer le stockage persistant du journal (survit aux redémarrages) :
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
Timers Systemd
Les timers systemd remplacent cron avec une meilleure journalisation, gestion des dépendances et fiabilité. Un timer se compose de deux fichiers — le .timer et son .service associé :
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily database backup timer
[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=900
Persistent=true
Unit=backup.service
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Database backup job
[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup-db.sh
StandardOutput=journal
Expressions OnCalendar courantes :
| Expression | Signification |
|---|---|
*-*-* 02:00:00 | Quotidiennement à 2h du matin |
Mon *-*-* 09:00:00 | Chaque lundi à 9h |
*-*-01 00:00:00 | Premier jour de chaque mois |
*-*-* *:00/15:00 | Toutes les 15 minutes |
hourly | Toutes les heures (raccourci) |
Activer et gérer les timers :
sudo systemctl enable --now backup.timer
systemctl list-timers --all
systemctl status backup.timer
La directive Persistent=true garantit que si le système était éteint lorsque le timer devait se déclencher, il exécute le travail immédiatement au prochain démarrage.
Comparaison : Systemd vs SysVinit vs Upstart
| Fonctionnalité | systemd | SysVinit | Upstart |
|---|---|---|---|
| Démarrage parallèle | Oui | Non | Partiel |
| Gestion des dépendances | Directives déclaratives | Ordonnancement manuel par numéros | Basé sur événements |
| Supervision des services | Politiques de redémarrage intégrées | Aucune (nécessite outils externes) | Directive respawn |
| Journalisation | journald (structuré, indexé) | syslog (fichiers texte brut) | syslog |
| Activation par socket | Oui | Non | Non |
| Contrôle des ressources | Intégration cgroups | Aucun | Aucun |
| Minuteurs/planification | Timers intégrés | Nécessite cron | Nécessite cron |
| Format de configuration | Fichiers d’unité style INI | Scripts shell | Fichiers conf à stanzas |
| Analyse du démarrage | systemd-analyze | Aucun | Aucun |
| Statut d’adoption | Par défaut sur la plupart des distros | Systèmes hérités | Abandonné |
Scénario Réel
Vous avez un serveur de production exécutant une API Python qui dépend de PostgreSQL et Redis. L’API doit démarrer après que les deux bases de données soient prêtes, redémarrer en cas de crash avec une stratégie de back-off, et exécuter un travail de nettoyage toutes les 6 heures.
Unité de service (/etc/systemd/system/api.service) :
[Unit]
Description=Production Python API
After=network-online.target postgresql.service redis.service
Requires=postgresql.service
Wants=redis.service
[Service]
Type=simple
User=apiuser
WorkingDirectory=/opt/api
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/api/venv/bin/gunicorn -w 4 -b 0.0.0.0:8000 app:create_app()
Restart=on-failure
RestartSec=10
StartLimitIntervalSec=300
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Timer de nettoyage (/etc/systemd/system/api-cleanup.timer) :
[Unit]
Description=API cleanup every 6 hours
[Timer]
OnBootSec=15min
OnUnitActiveSec=6h
Persistent=true
[Install]
WantedBy=timers.target
Cette configuration garantit que l’API ne démarre que lorsque PostgreSQL est en cours d’exécution (dépendance forte) et utilise optionnellement Redis (dépendance faible). La politique de redémarrage permet 5 tentatives en 5 minutes avant d’abandonner.
Pièges et Cas Particuliers
- ExecStart doit utiliser des chemins absolus : Les chemins relatifs causent des échecs silencieux. Utilisez toujours
/usr/bin/node, pasnode - Type=forking nécessite PIDFile : Si votre daemon fait un fork, systemd doit suivre le PID enfant. Définissez
PIDFile=/run/myapp.pidet assurez-vous que votre app l’écrit - Fichiers Environment vs inline : Pour de nombreuses variables, utilisez
EnvironmentFile=/etc/myapp/envau lieu de multiples lignesEnvironment=. Le format estKEY=value(sansexport) - Services utilisateur vs services système : Les unités utilisateur dans
~/.config/systemd/user/s’exécutent sans sudo mais uniquement tant que l’utilisateur est connecté (sauf siloginctl enable-lingerest activé) - Reload vs restart vs daemon-reload :
systemctl restart myappredémarre le processus.systemctl daemon-reloadrecharge les définitions des fichiers d’unité. Après modification d’un fichier d’unité, vous devez d’abord exécuterdaemon-reload - Le target de WantedBy compte : Utilisez
multi-user.targetpour les serveurs sans interface graphique,graphical.targetpour les services de bureau
Résolution de Problèmes
Lorsqu’un service ne démarre pas, suivez ce flux de diagnostic :
# 1. Vérifier l'état actuel et les logs récents
systemctl status myapp.service
# 2. Obtenir des informations détaillées sur l'échec
journalctl -u myapp.service -e --no-pager -n 50
# 3. Valider la syntaxe du fichier d'unité
systemd-analyze verify /etc/systemd/system/myapp.service
# 4. Vérifier les cycles d'ordonnancement
systemd-analyze critical-chain myapp.service
# 5. Tester la commande ExecStart manuellement en tant qu'utilisateur du service
sudo -u appuser /usr/bin/node /opt/myapp/server.js
# 6. Vérifier les refus SELinux ou AppArmor
sudo ausearch -m avc -ts recent 2>/dev/null || sudo journalctl -k --grep="apparmor"
# 7. Vérifier les permissions des fichiers
ls -la /opt/myapp/
namei -l /opt/myapp/server.js
Causes courantes d’échec et corrections :
| Symptôme | Cause | Correction |
|---|---|---|
code=exited, status=203/EXEC | Binaire introuvable ou non exécutable | Vérifiez le chemin avec which, définissez chmod +x |
code=exited, status=217/USER | L’utilisateur spécifié dans User= n’existe pas | Créez l’utilisateur avec useradd -r -s /sbin/nologin |
Start request repeated too quickly | Boucle de redémarrage atteignant StartLimitBurst | Augmentez RestartSec, corrigez le crash sous-jacent, puis systemctl reset-failed |
code=exited, status=200/CHDIR | WorkingDirectory n’existe pas | Créez le répertoire et corrigez la propriété |
| Service démarre mais port inaccessible | Pare-feu ou adresse de liaison incorrecte | Vérifiez avec ss -tlnp, vérifiez les règles firewall-cmd ou ufw |
Résumé
- Les fichiers d’unité systemd utilisent des sections déclaratives style INI (
[Unit],[Service],[Install]) pour définir le comportement du service - Les unités personnalisées vont dans
/etc/systemd/system/— exécutez toujoursdaemon-reloadaprès les modifications - Utilisez
Requires=avecAfter=ensemble pour les dépendances fortes avec ordonnancement correct - Journalctl fournit un filtrage puissant par unité, priorité, plage horaire et format de sortie
- Les timers systemd offrent une meilleure fiabilité que cron avec planification persistante et journalisation par tâche
- Les directives de renforcement de sécurité comme
ProtectSystem=strictetPrivateTmp=truedevraient être standard en production - Diagnostiquez avec
systemctl status,journalctl -uetsystemd-analyze verify