TL;DR — Résumé Rapide
Maitrisez les services systemd sur Linux: anatomie des fichiers unit, exemples Node.js et Python, politiques de redemarrage, limites de ressources et securite.
Maintenir des processus a longue duree de vie sur un serveur Linux necessitait autrefois des scripts SysVinit complexes, des configurations supervisor ou des hacks shell fragiles. systemd a tout change. En tant que systeme init par defaut sur toutes les principales distributions Linux — Ubuntu, Debian, RHEL, Fedora, Arch — systemd offre une maniere unifiee et declarative de definir exactement comment tout processus doit demarrer, s’arreter, redemarrer, journaliser et interagir avec le reste du systeme.
Ce guide couvre tout ce dont vous avez besoin pour creer des services systemd personnalises prets pour la production: l’anatomie d’un fichier unit, des exemples concrets pour Node.js et Python, les types de service, les politiques de redemarrage, les limites de ressources, les directives de securite, l’activation par socket, les unites de minuterie comme alternative a cron, et comment deboguer les pannes avec journalctl.
Prerequis
Avant de commencer, assurez-vous d’avoir:
- Un systeme Linux avec systemd (Ubuntu 16.04+, Debian 8+, CentOS 7+, RHEL 7+, Fedora 15+ ou toute distribution moderne)
- Un terminal avec acces
sudoou root - Une familiarite basique avec la ligne de commande et un editeur de texte (
nanoouvim) - Une application a executer en tant que service (Node.js, Python, binaire Go, etc.)
Verifiez que systemd est en cours d’execution:
systemctl --version
# Doit afficher: systemd 249 (ou numero de version similaire)
Anatomie du Fichier Unit
Chaque service gere par systemd est decrit par un fichier unit — un fichier de configuration en texte brut au format INI. Les fichiers unit de service personnalises se trouvent dans /etc/systemd/system/. Le nom du fichier se termine par .service.
Un fichier unit complet comporte trois sections:
[Unit]
Description=Mon Service d'Application
Documentation=https://exemple.com/docs
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=monapp
Group=monapp
WorkingDirectory=/opt/monapp
ExecStart=/usr/bin/node /opt/monapp/server.js
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
La Section [Unit]
| Directive | Objectif |
|---|---|
Description= | Nom lisible affiche dans la sortie de systemctl status |
After= | Demarre cette unite apres que les unites listees sont actives |
Wants= | Dependance souple — les unites listees demarrent, mais leur echec est tolere |
Requires= | Dependance forte — si l’unite listee echoue, celle-ci echoue aussi |
BindsTo= | Comme Requires= mais arrete aussi cette unite quand la dependance s’arrete |
La Section [Service]
| Directive | Objectif |
|---|---|
Type= | Modele de cycle de vie du processus (simple, forking, oneshot, notify) |
User= / Group= | Execute le processus en tant que cet utilisateur/groupe |
ExecStart= | La commande a executer (doit etre un chemin absolu) |
ExecStartPre= | Commandes a executer avant ExecStart |
Restart= | Quand redemarrer automatiquement |
Environment= | Definit des variables d’environnement en ligne |
EnvironmentFile= | Charge des variables d’environnement depuis un fichier |
StandardOutput= | Ou envoyer la sortie standard (journal, file:, null) |
La Section [Install]
| Directive | Objectif |
|---|---|
WantedBy= | Quelle cible active ce service (generalement multi-user.target) |
Alias= | Noms supplementaires pour cette unite |
Creer un Service Node.js
Supposons que vous ayez une API Node.js dans /opt/monapi/server.js ecoutant sur le port 3000.
Etape 1: Creer un utilisateur dedie
sudo useradd --system --no-create-home --shell /usr/sbin/nologin nodeapi
sudo chown -R nodeapi:nodeapi /opt/monapi
Etape 2: Creer le fichier unit
sudo nano /etc/systemd/system/monapi.service
[Unit]
Description=Mon Service API Node.js
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=nodeapi
Group=nodeapi
WorkingDirectory=/opt/monapi
EnvironmentFile=/etc/monapi/environment
ExecStart=/usr/bin/node /opt/monapi/server.js
ExecStartPre=/usr/bin/node --check /opt/monapi/server.js
Restart=on-failure
RestartSec=5s
TimeoutStopSec=10s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=monapi
MemoryMax=512M
CPUQuota=50%
LimitNOFILE=65536
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
ReadWritePaths=/opt/monapi/logs /var/lib/monapi
[Install]
WantedBy=multi-user.target
Etape 3: Creer le fichier d’environnement
sudo mkdir -p /etc/monapi
sudo nano /etc/monapi/environment
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://utilisateur:motdepasse@localhost/mabd
JWT_SECRET=votre-secret-ici
sudo chmod 600 /etc/monapi/environment
Etape 4: Activer et demarrer
sudo systemctl daemon-reload
sudo systemctl enable monapi.service
sudo systemctl start monapi.service
sudo systemctl status monapi.service
Creer un Service Python
Pour un worker Python dans /opt/worker/worker.py:
[Unit]
Description=Worker Python en Arriere-plan
After=network.target redis.service
Requires=redis.service
[Service]
Type=simple
User=pyworker
Group=pyworker
WorkingDirectory=/opt/worker
ExecStart=/opt/worker/venv/bin/python -u worker.py
EnvironmentFile=/etc/worker/environment
Restart=on-failure
RestartSec=10s
StartLimitIntervalSec=60s
StartLimitBurst=3
StandardOutput=journal
StandardError=journal
MemoryMax=256M
CPUQuota=25%
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Le flag -u sur la commande Python desactive la mise en tampon de sortie pour que les journaux apparaissent immediatement dans journald.
Types de Service
| Type | Quand l’utiliser | Comment systemd suit la disponibilite |
|---|---|---|
simple | La plupart des apps modernes | Considere l’unite demarree des qu’ExecStart fait un fork |
exec | Comme simple, mais attend que exec reussisse | Attend que le binaire s’execute avec succes |
forking | Daemons traditionnels qui font un fork | Attend que le processus parent se termine |
oneshot | Scripts qui s’executent une fois et se terminent | Attend qu’ExecStart se termine avant de marquer comme actif |
notify | Apps qui envoient une notification de disponibilite | Attend la notification avant de marquer comme actif |
Politiques de Redemarrage et Limites de Ressources
Politiques de Redemarrage
Restart=always # Redемarre a chaque terminaison du service
Restart=on-failure # Redемarre uniquement en cas d'echec
Restart=on-abnormal # Redемarre en cas d'echec + timeout watchdog + signaux anormaux
Restart=no # Ne redемarre jamais
Avec limitation du taux pour eviter qu’un service defaillant ne surcharge le systeme:
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=5
Limites de Ressources
MemoryMax=1G # Limite stricte de memoire
MemoryHigh=800M # Limite souple de memoire
MemorySwapMax=0 # Desactiver le swap pour ce service
CPUQuota=200% # Limiter a 2 coeurs CPU complets
LimitNOFILE=65536 # Nombre maximum de descripteurs de fichiers ouverts
LimitNPROC=512 # Nombre maximum de processus/threads
Durcissement de la Securite
[Service]
NoNewPrivileges=true # Empeche l'acquisition de nouveaux privileges
ProtectSystem=strict # Monte /usr, /boot, /etc en lecture seule
PrivateTmp=true # /tmp prive pour ce service
DynamicUser=true # Utilisateur dynamique sans privileges
ProtectHome=true # Bloque l'acces aux repertoires /home
SystemCallFilter=@system-service # Filtre les appels systeme
ProtectKernelTunables=true # Empeche l'ecriture dans les variables du noyau
ProtectKernelModules=true # Empeche le chargement de modules du noyau
Analysez le score de securite de tout service en cours d’execution:
systemd-analyze security monapi.service
Unites de Minuterie: systemd comme Alternative a Cron
sudo nano /etc/systemd/system/sauvegarde-nocturne.service
[Unit]
Description=Sauvegarde Nocturne de la Base de Donnees
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-db.sh
StandardOutput=journal
sudo nano /etc/systemd/system/sauvegarde-nocturne.timer
[Unit]
Description=Lancer la Sauvegarde Nocturne a 2h du matin
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now sauvegarde-nocturne.timer
systemctl list-timers --all
systemd vs Autres Gestionnaires de Processus
| Fonctionnalite | systemd | SysVinit / init.d | supervisord | PM2 |
|---|---|---|---|---|
| Inclus avec l’OS | Oui (toutes distros) | Oui (legacy) | Non (pip install) | Non (npm install) |
| Ordre des dependances | Graphe complet | Ordre manuel | Limite | Non |
| Activation par socket | Oui | Non | Non | Non |
| Limites ressources cgroup | Oui (natif) | Non | Non | Non |
| Sandbox niveau noyau | Oui (etendu) | Non | Non | Non |
| Journaux centralises | journald (structure) | syslog / fichiers | Fichiers seulement | Fichiers PM2 |
| Jobs planifies | Oui (unites timer) | Non | Non | Oui (style cron) |
| Integration Node.js | Bonne | Mauvaise | Bonne | Excellente |
| Courbe d’apprentissage | Moderee | Faible | Faible | Tres faible |
Scenario Reel
Vous avez un serveur de production avec une application Python FastAPI qui traite des travaux depuis une file Redis. Le service doit redemarrer automatiquement en cas de panne et ne jamais consommer plus de 512 Mo de RAM.
[Unit]
Description=FastAPI Job Worker
After=network.target redis.service
Requires=redis.service
StartLimitIntervalSec=120s
StartLimitBurst=4
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/fastapi-worker
ExecStart=/opt/fastapi-worker/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2
EnvironmentFile=/etc/fastapi-worker/environment
Restart=on-failure
RestartSec=8s
TimeoutStartSec=30s
TimeoutStopSec=15s
StandardOutput=journal
SyslogIdentifier=fastapi-worker
MemoryMax=512M
CPUQuota=100%
LimitNOFILE=32768
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
ReadWritePaths=/opt/fastapi-worker/data
[Install]
WantedBy=multi-user.target
Pieges et Cas Particuliers
ExecStart doit etre un chemin absolu. Vous ne pouvez pas utiliser des built-ins shell, des pipes ou des redirections directement dans ExecStart. Encapsulez les commandes complexes dans un script.
Expansion des variables d’environnement. systemd developpe $VAR dans ExecStart quand la variable est definie via Environment= ou EnvironmentFile=, mais ne charge PAS les fichiers d’initialisation du shell. Votre ~/.bashrc n’est jamais charge.
Signes de pourcentage dans les fichiers unit. Dans les fichiers unit, % est un caractere specificateur. Pour utiliser un signe pourcentage litteral, echappez-le en %%.
DynamicUser et permissions de fichiers. Avec DynamicUser=true, l’UID alloue change entre les redemarrages. Utilisez des directives comme StateDirectory= ou LogsDirectory= pour que systemd gere correctement les repertoires persistants.
After= vs Requires=. After= ne controle que l’ordre — il ne cree pas de dependance. Requires= cree la dependance mais ne controle pas l’ordre. Vous voudrez presque toujours les deux ensemble.
Depannage
Le service ne demarre pas
sudo systemctl status monapi.service
journalctl -u monapi.service -b
journalctl -u monapi.service --since "10 minutes ago"
journalctl -u monapi.service -f
Le service demarre mais plante immediatement
journalctl -u monapi.service -n 100 --no-pager
ls -la /usr/bin/node
sudo -u nodeapi /usr/bin/node /opt/monapi/server.js
Reinitialiser un service en echec
sudo systemctl reset-failed monapi.service
sudo systemctl start monapi.service
Verifier l’exposition de securite
systemd-analyze security monapi.service
systemd-analyze verify /etc/systemd/system/monapi.service
Resume
- Les fichiers unit se trouvent dans
/etc/systemd/system/et ont trois sections:[Unit],[Service]et[Install] - Executez toujours
systemctl daemon-reloadapres avoir modifie un fichier unit - Utilisez
Type=simplepour la plupart des apps modernes; utilisezType=notifypour les apps qui signalent leur disponibilite - Stockez les secrets dans un
EnvironmentFile=separe avec les permissions600, pas directement dans le fichier unit - Configurez les politiques de redemarrage avec
Restart=on-failureetStartLimitBurstpour survivre aux pannes transitoires - Appliquez les directives de securite a chaque service de production
- Remplacez les cron jobs par des unites de minuterie pour de meilleurs journaux
- Deboguez avec journalctl —
journalctl -u nom-du-service -fest votre premier recours en cas de panne