Linux sans magie #17 — De System V à systemd : ce qui a vraiment changé dans la gestion des services

Comprendre le passage de System V à systemd : scripts init, runlevels, unités, targets, dépendances et supervision des services Linux.

Dans l’article précédent, nous avons vu qu’un programme en cours d’exécution devient un processus, identifiable notamment par un PID. Mais sur un serveur, beaucoup de processus ne sont pas lancés manuellement dans un terminal : ils doivent démarrer au bon moment, rester disponibles, s’arrêter proprement et parfois redémarrer après une panne.

Historiquement, les systèmes Linux ont longtemps utilisé des mécanismes hérités de System V init, souvent abrégé SysV. Aujourd’hui, la majorité des distributions généralistes utilisent systemd.

Dire que systemd « remplace les scripts /etc/init.d » est vrai, mais très incomplet. Le changement est plus profond : on passe d’un modèle largement basé sur des scripts exécutant des actions à un gestionnaire qui raisonne en unités, dépendances et état du système.

L’objectif de cet article est de comprendre ce changement de modèle sans entrer encore dans l’administration quotidienne avec systemctl, qui sera traitée dans l’article suivant.

Contexte et problème

Un serveur ne doit pas seulement savoir lancer un programme. Il doit pouvoir répondre à des questions comme :

  • ce service doit-il démarrer au boot ?
  • doit-il attendre un filesystem, le réseau ou un autre service ?
  • quel processus faut-il surveiller ?
  • que signifie « le service est démarré » ?
  • dans quel ordre arrêter les composants lors d’un shutdown ?
  • comment regrouper plusieurs services pour atteindre un état global du système ?

Avec SysV, une partie importante de cette logique était répartie entre des scripts shell, des conventions de répertoires et des liens symboliques associés à des runlevels.

Avec systemd, cette logique est représentée directement par des unités et les relations qu’elles entretiennent entre elles.

Ce n’est donc pas seulement :

ancienne commande → nouvelle commande

mais plutôt :

ancienne façon de décrire le démarrage
              ↓
nouveau modèle de gestion du système

Concept ou architecture

Avec SysV, le script porte l’action

Dans une organisation SysV classique, un service possède souvent un script sous :

/etc/init.d/

Ce script reçoit une action, par exemple :

/etc/init.d/example start
/etc/init.d/example stop
/etc/init.d/example restart

Le script contient alors la logique nécessaire pour accomplir l’opération demandée.

De manière très simplifiée :

si action = start
    préparer l'environnement
    lancer le daemon
    gérer éventuellement un fichier PID

si action = stop
    retrouver le processus
    lui envoyer un signal
    nettoyer

Le système s’appuie donc largement sur du code impératif : le script décrit les étapes à exécuter.

Cela fonctionne, mais chaque script peut implémenter ces étapes différemment. Deux services peuvent utiliser des conventions différentes pour retrouver leur PID, charger leur environnement ou déterminer s’ils sont réellement actifs.

Avec systemd, le service devient une unité

systemd utilise des objets appelés units.

Une unité n’est pas forcément un service. systemd connaît notamment des unités de type :

.service
.socket
.target
.mount
.timer
.path
.device

Pour un daemon classique, nous rencontrons surtout une unité .service.

Une définition minimale peut ressembler à ceci :

[Unit]
Description=Example service

[Service]
ExecStart=/usr/local/bin/example

Cet exemple n’est pas destiné à être installé tel quel. Il montre surtout la différence de raisonnement.

Le fichier ne dit pas :

ouvre tel fichier
cherche tel PID
lance telle fonction shell

Il déclare plutôt :

voici le processus à démarrer pour ce service

Le gestionnaire peut alors prendre en charge une partie de la supervision qui était auparavant implémentée dans les scripts.

Impératif contre déclaratif : une distinction utile, mais pas absolue

On résume souvent le changement ainsi :

SysV    → impératif
systemd → déclaratif

C’est une bonne grille de lecture, à condition de ne pas la transformer en règle absolue.

Une unité systemd contient elle aussi des commandes telles que ExecStart= ou ExecStop=. La différence essentielle est que l’unité décrit ces commandes dans un modèle connu du gestionnaire, avec des propriétés et des relations standardisées.

systemd dispose ainsi d’une vue globale des unités qu’il doit gérer.

Des runlevels aux targets

Le modèle historique des runlevels

SysV utilise traditionnellement des runlevels numérotés pour représenter différents états de fonctionnement.

Les significations précises peuvent varier selon les systèmes, mais on rencontre historiquement des niveaux correspondant, par exemple, à un mode mono-utilisateur, un mode multi-utilisateur ou l’arrêt du système.

Le point important n’est pas de mémoriser les numéros. Il est de comprendre le modèle : le système entre dans un état correspondant à un runlevel et les scripts associés sont démarrés ou arrêtés selon les conventions configurées.

systemd utilise des targets

systemd utilise des unités .target pour regrouper d’autres unités et représenter des points de synchronisation ou des états fonctionnels.

On rencontre notamment :

multi-user.target
graphical.target
rescue.target
shutdown.target

Pour faciliter la compatibilité avec l’ancien monde SysV, systemd fournit des correspondances avec certains runlevels historiques.

Mais il serait incorrect de conclure que :

runlevel = target avec un autre nom

Les targets participent au graphe d’unités de systemd et plusieurs targets peuvent être actives en même temps. Elles sont donc plus générales que l’ancien concept de runlevel courant unique.

Le changement intéressant est encore une fois conceptuel : on ne choisit plus seulement un numéro qui détermine quels scripts exécuter ; on construit un ensemble d’unités reliées par des dépendances.

D’un ordre de démarrage à un graphe de dépendances

L’approche séquentielle de SysV

Dans les implémentations SysV traditionnelles, l’ordre de démarrage reposait largement sur les conventions de liens et de numérotation.

On pouvait rencontrer des noms comme :

S10network
S20database
S30application

La numérotation exprimait alors un ordre attendu.

Cette logique est facile à visualiser, mais elle mélange deux questions différentes :

  1. ce composant a-t-il réellement besoin de l’autre ?
  2. doit-il simplement être démarré avant ou après ?

systemd sépare besoin et ordre

Avec systemd, ces relations peuvent être décrites séparément.

Par exemple :

After=database.service

exprime une contrainte d’ordre : si les deux unités sont démarrées dans la même transaction, celle-ci doit être ordonnée après database.service.

Cela ne signifie pas automatiquement :

« démarre database.service parce que j’en ai besoin »

Pour exprimer un besoin, d’autres relations existent, notamment :

Wants=database.service
Requires=database.service

Cette séparation est fondamentale.

Un piège courant consiste à croire que :

After=x.service

équivaut à :

« mon service dépend de x.service »

Ce n’est pas le cas. L’ordre et l’exigence sont deux notions distinctes.

Pourquoi cela permet davantage de parallélisme

Si deux unités n’ont aucune contrainte d’ordre entre elles, systemd peut les démarrer sans attendre artificiellement que l’une soit terminée avant de commencer l’autre.

On passe donc d’une logique ressemblant à :

A
↓
B
↓
C
↓
D

à un graphe qui peut ressembler à :

      ┌── B ──┐
A ────┤       ├── D
      └── C ──┘

Le système connaît les relations réellement déclarées au lieu de devoir déduire l’ensemble du démarrage d’une longue séquence globale.

Démarrer un daemon ou superviser un service

L’article #16 a introduit la distinction entre un programme et un processus. Elle devient particulièrement utile ici.

Historiquement, de nombreux daemons étaient conçus pour :

  1. être lancés par un script ;
  2. faire un fork ;
  3. détacher le processus enfant du terminal ;
  4. éventuellement écrire leur PID dans un fichier ;
  5. laisser le script initial se terminer.

Le système devait ensuite retrouver ou suivre le bon processus.

systemd permet, lorsque le logiciel le supporte, un modèle beaucoup plus direct : le gestionnaire lance le processus et conserve lui-même le lien avec le service qu’il supervise.

Cela ne signifie pas que tous les PID files ou tous les modes de daemonisation ont disparu. systemd sait également prendre en charge des applications plus anciennes. Mais pour un logiciel moderne capable de rester au premier plan, il n’est généralement plus nécessaire de reproduire artificiellement toute la mécanique historique simplement parce que les anciens scripts fonctionnaient ainsi.

C’est précisément ce changement qui explique pourquoi convertir un script SysV ligne par ligne en unité systemd est souvent une mauvaise approche : certaines parties du script existaient uniquement pour compenser les limites du modèle précédent.

Prérequis

Aucun changement système n’est nécessaire pour cet article.

Pour observer les concepts sur une machine existante, vous pouvez utiliser une distribution Linux reposant sur systemd et disposer simplement d’un shell utilisateur.

Les commandes d’observation proposées ci-dessous ne modifient pas les services.

Mise en pratique

L’objectif n’est pas encore d’administrer un service avec systemctl, mais de regarder le modèle que systemd expose.

1. Vérifier quel processus joue le rôle de PID 1

Exécutez :

ps -p 1 -o pid,comm,args

Sur une machine utilisant systemd comme système d’init, vous devriez observer systemd associé au PID 1.

systemd, lorsqu’il s’exécute comme premier processus du système, agit à la fois comme système d’init et comme gestionnaire des services de l’espace utilisateur.

2. Observer différents types d’unités

Vous pouvez demander au gestionnaire la liste des unités chargées :

systemctl list-units --no-pager

Sans chercher encore à maîtriser la commande, observez simplement la colonne UNIT.

Vous verrez probablement des noms se terminant par plusieurs suffixes :

.service
.socket
.target
.mount

C’est une différence importante avec une vision réduite à /etc/init.d : systemd ne gère pas uniquement une collection de scripts de démarrage de services.

3. Observer les targets actives

Exécutez :

systemctl list-units --type=target --state=active --no-pager

Plusieurs targets peuvent être actives simultanément.

Ce simple résultat permet de constater pourquoi il est trompeur d’assimiler directement une target à un runlevel unique.

Résultat attendu

À ce stade, vous devez pouvoir observer trois éléments :

PID 1 → systemd

plusieurs types d'unités

plusieurs targets actives simultanément

Vérification

Le lab est réussi si les commandes vous permettent de confirmer le modèle sans modifier le système :

processus
   ↓
systemd en PID 1
   ↓
unités de plusieurs types
   ↓
targets intégrées dans un ensemble d'unités actives

Nous garderons les opérations start, stop, restart, enable et le diagnostic détaillé pour l’article suivant consacré à systemctl.

Bonnes pratiques

Lorsque vous rencontrez une ancienne documentation SysV, cherchez d’abord l’intention de la configuration plutôt que sa traduction littérale.

Demandez-vous :

  • quel processus doit réellement fonctionner ?
  • quelle unité représente cette fonction ?
  • quelle relation est un besoin fonctionnel ?
  • quelle relation n’est qu’un ordre de démarrage ?
  • quel mécanisme historique n’est plus nécessaire lorsque systemd supervise directement le processus ?

Évitez notamment d’utiliser After= comme synonyme de dépendance fonctionnelle. Une relation d’ordre ne suffit pas à demander le démarrage d’une autre unité.

Enfin, ne recopiez pas automatiquement un ancien modèle de daemonisation, un PID file ou des scripts de préparation sans vérifier pourquoi ils existaient.

Sécurité

Le passage de SysV à systemd n’améliore pas automatiquement la sécurité d’un service.

systemd fournit de nombreux mécanismes permettant de limiter les privilèges et d’isoler davantage un service, mais ces fonctions ne doivent pas être ajoutées mécaniquement lors d’une simple comparaison conceptuelle.

Le principe reste le même que dans les articles précédents : comprendre d’abord quel processus doit faire quoi et avec quels privilèges, puis appliquer les restrictions appropriées.

La migration opérationnelle d’un service existant et son éventuel hardening doivent être testés séparément.

Coûts

Aucun coût spécifique n’est associé à cet article ou à son lab d’observation.

Les commandes sont purement locales et ne nécessitent aucun service cloud, aucune licence supplémentaire ni aucune ressource temporaire.

Limites et points de vigilance

Le comportement exact d’un ancien environnement SysV dépend de la distribution et de son implémentation historique. Tous les systèmes n’ont pas utilisé exactement les mêmes conventions de scripts ou de runlevels.

De même, la simple présence de systemd ne signifie pas que tous les logiciels utilisent uniquement des unités natives modernes. Des couches de compatibilité ont existé pour prendre en charge des scripts SysV.

Ce point est particulièrement actuel sur Ubuntu : Canonical indique qu’Ubuntu 26.04 LTS est la dernière version Ubuntu dont systemd prend en charge la compatibilité avec les scripts de service System V. Cette information est spécifique au cycle Ubuntu 26.04 et ne doit pas être généralisée à toutes les distributions.

Notre article distinct consacré à Ubuntu 26.04 explique précisément comment inventorier puis migrer un service SysV vers une unité native. Ici, nous restons volontairement au niveau du modèle de fonctionnement.

Rollback ou nettoyage

Aucun rollback n’est nécessaire.

Les commandes utilisées dans le lab :

ps -p 1 -o pid,comm,args
systemctl list-units --no-pager
systemctl list-units --type=target --state=active --no-pager

ne modifient ni la configuration ni l’état des services.

Références officielles

Conclusion

Le passage de System V à systemd ne se résume pas au remplacement d’une commande ou d’un répertoire.

SysV s’appuie largement sur des scripts qui exécutent des actions dans un ordre organisé autour des runlevels. systemd gère un ensemble d’unités reliées par des dépendances, distingue l’ordre du besoin fonctionnel et peut superviser directement les processus associés aux services.

Le changement essentiel est donc celui-ci :

scripts à exécuter
       ↓
unités et relations à gérer

Comprendre ce modèle rend beaucoup plus simple l’étape suivante : utiliser systemctl pour observer, démarrer, arrêter et diagnostiquer un service sans traiter la commande comme une boîte noire.