Ubuntu 26.04 : migrer un service SysV vers une unité systemd native

Une méthode pratique pour inventorier un service /etc/init.d, le convertir en unité systemd native, vérifier son démarrage et préparer le rollback avant Ubuntu 26.10.

Les scripts de service System V ont longtemps fourni une interface simple pour démarrer, arrêter et redémarrer les démons Linux. Sur un serveur ancien, il n’est donc pas rare de trouver encore un script sous /etc/init.d/ qui fonctionne parfaitement et qui n’a jamais été converti.

Le problème est désormais daté. Canonical indique qu’Ubuntu 26.04 LTS est la dernière version Ubuntu dont systemd conserve la compatibilité avec les scripts System V. Upstream, systemd 260 a déjà supprimé systemd-sysv-generator, systemd-sysv-install, systemd-rc-local-generator et rc-local.service. Canonical prévoit que cette suppression affecte Ubuntu 26.10.

L’objectif de cet article n’est pas de convertir tous les scripts mécaniquement. Il est de construire une migration contrôlée d’un service legacy vers une unité systemd native, avec inventaire, validation de syntaxe, test fonctionnel et rollback.

Contexte et problème

Un script SysV typique contient des branches start, stop, restart et parfois status. Lorsqu’aucune unité systemd native n’existe, systemd peut encore générer une unité de compatibilité à partir du script dans les versions qui embarquent cette fonctionnalité.

Cette couche masque cependant plusieurs questions importantes :

  • quel binaire est réellement lancé ?
  • sous quel utilisateur ?
  • le processus reste-t-il au premier plan ou se daemonise-t-il ?
  • quelles dépendances doivent être disponibles avant le démarrage ?
  • comment le service signale-t-il son arrêt ?
  • où écrit-il ses logs ?
  • existe-t-il un fichier PID historique ?
  • quelles variables d’environnement ou fichiers de configuration sont chargés par le script ?

Une migration correcte commence donc par comprendre le comportement du service, pas par traduire ligne à ligne le shell SysV.

Concept : passer d’un script impératif à une unité déclarative

Un script SysV décrit une suite d’actions : créer un répertoire, lancer un binaire, récupérer un PID, envoyer un signal, nettoyer un fichier.

Une unité systemd décrit plutôt l’état attendu et la façon de superviser le processus. Les directives importantes se répartissent notamment entre :

[Unit]      dépendances et ordre de démarrage
[Service]   processus, utilisateur, environnement, redémarrage
[Install]   activation au boot

Dans l’idéal, le service est lancé directement au premier plan par systemd. Le PID suivi par systemd devient alors le processus principal réel et il n’est plus nécessaire de reproduire artificiellement les mécanismes historiques de daemonisation.

Pour un programme qui sait rester au premier plan, Type=simple est généralement le modèle le plus direct. Si l’application impose encore une daemonisation classique avec fork, le choix de Type=forking et éventuellement d’un PIDFile= doit être justifié par le comportement réel du logiciel, pas choisi par habitude.

Prérequis

Travaillez d’abord sur une VM de test ou sur un clone représentatif si le service est critique.

Rassemblez :

  • le script SysV concerné ;
  • le chemin du binaire ;
  • son fichier de configuration ;
  • l’utilisateur et le groupe d’exécution ;
  • les variables d’environnement ;
  • les dépendances réseau, stockage ou autres services ;
  • la méthode de vérification fonctionnelle de l’application ;
  • une méthode pour revenir au script d’origine sur Ubuntu 26.04 si le test échoue.

Ne supprimez pas le script SysV avant d’avoir validé l’unité native.

Mise en pratique

1. Inventorier les scripts legacy

Commencez par lister les scripts locaux :

find /etc/init.d -maxdepth 1 -type f -printf '%f\n' | sort

Pour un service donné, vérifiez également si systemd voit déjà une unité native ou une unité générée :

systemctl status example-agent.service
systemctl cat example-agent.service

Remplacez example-agent par le nom réel du service.

Si systemctl cat affiche une unité provenant de /run/systemd/generator..., vous êtes probablement devant une unité produite par la couche de compatibilité plutôt qu’un fichier natif maintenu sous /usr/lib/systemd/system ou /etc/systemd/system.

2. Lire le script avant de créer l’unité

Inspectez le script :

sudo sed -n '1,240p' /etc/init.d/example-agent

Relevez explicitement :

  • la commande de démarrage ;
  • la commande d’arrêt ;
  • les options du binaire ;
  • les chemins absolus ;
  • les fichiers source ou . chargés ;
  • l’utilisateur utilisé par start-stop-daemon, su, runuser ou un mécanisme équivalent ;
  • l’existence d’un PID file ;
  • les répertoires créés avant démarrage ;
  • les dépendances exprimées dans les en-têtes LSB.

L’objectif est de séparer ce qui appartient réellement à l’application de ce qui n’était qu’un mécanisme d’intégration SysV.

3. Créer une unité native minimale

Supposons que l’application accepte de rester au premier plan avec une option fictive --foreground. Une unité de départ peut prendre cette forme :

[Unit]
Description=Example Agent
After=network.target

[Service]
Type=simple
User=example-agent
Group=example-agent
ExecStart=/usr/local/bin/example-agent --foreground --config /etc/example-agent.conf
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enregistrez-la dans :

/etc/systemd/system/example-agent.service

Les noms et options ci-dessus sont des placeholders : utilisez uniquement les valeurs documentées par votre application.

Évitez de recopier automatiquement After=network.target si le service n’a pas besoin du réseau. De même, n’ajoutez pas network-online.target sans besoin réel : une dépendance d’ordre inutile peut ralentir ou compliquer le boot.

4. Vérifier la syntaxe avant démarrage

Avant de toucher au service actif :

sudo systemd-analyze verify /etc/systemd/system/example-agent.service

Corrigez les erreurs ou directives inconnues avant de poursuivre.

Rechargez ensuite la configuration :

sudo systemctl daemon-reload

Vous pouvez voir la définition finale interprétée par systemd avec :

systemctl cat example-agent.service

5. Éviter le double démarrage

Le point critique est de ne pas laisser le mécanisme SysV et l’unité native démarrer deux fois la même application.

Sur le clone, arrêtez d’abord le service selon la méthode actuellement active, vérifiez qu’aucun processus métier ne subsiste, puis démarrez explicitement l’unité native :

sudo systemctl start example-agent.service
systemctl status example-agent.service --no-pager

Contrôlez le processus principal :

systemctl show example-agent.service -p MainPID -p SubState -p ActiveState

Puis vérifiez les journaux :

journalctl -u example-agent.service -b --no-pager

Un état active (running) n’est pas à lui seul une validation fonctionnelle. Effectuez également le contrôle métier propre au service : port d’écoute, endpoint local, fichier généré, traitement d’une tâche de test ou autre preuve observable.

6. Tester l’arrêt et le redémarrage

Testez ensuite :

sudo systemctl restart example-agent.service
systemctl status example-agent.service --no-pager

Puis un arrêt :

sudo systemctl stop example-agent.service
systemctl is-active example-agent.service

Le résultat attendu après l’arrêt est inactive.

Vérifiez qu’aucun processus orphelin n’est resté actif :

pgrep -a -f '/usr/local/bin/example-agent'

Adaptez évidemment le motif au binaire réel pour éviter les faux positifs.

7. Activer au boot seulement après validation

Une fois les tests start/stop/restart et la validation fonctionnelle réussis :

sudo systemctl enable example-agent.service
systemctl is-enabled example-agent.service

Le résultat attendu est enabled.

Redémarrez ensuite la VM de test dans une fenêtre contrôlée et vérifiez :

systemctl status example-agent.service --no-pager
journalctl -u example-agent.service -b --no-pager

Le test de reboot est essentiel : une unité qui fonctionne lorsqu’on la lance manuellement peut échouer au boot à cause d’une dépendance, d’un montage tardif, d’un secret indisponible ou d’un ordre de démarrage incorrect.

Bonnes pratiques

Gardez l’unité la plus simple possible. N’utilisez pas systemd pour réimplémenter tout le shell historique si certaines actions ne sont plus nécessaires.

Privilégiez les chemins absolus dans ExecStart= et utilisez un compte de service non privilégié lorsque l’application n’a pas besoin de root.

Pour modifier une unité fournie par un package, préférez systemctl edit et les drop-ins plutôt que l’édition directe d’un fichier sous /usr/lib/systemd/system. Ubuntu documente cette méthode pour éviter que les changements locaux soient écrasés ou entrent en conflit avec les mises à jour de package.

Ne choisissez pas Restart=always automatiquement. Restart=on-failure est souvent plus approprié pour un démon, mais le comportement doit rester cohérent avec les attentes de l’application et les procédures d’exploitation.

Sécurité

Une migration vers systemd est l’occasion de réduire les privilèges, mais les options de hardening ne doivent pas être activées à l’aveugle.

Commencez par conserver le comportement fonctionnel du service avec User= et Group= corrects. Une fois la migration validée, vous pouvez tester séparément des protections systemd comme NoNewPrivileges=, ProtectSystem=, PrivateTmp= ou d’autres mécanismes adaptés à l’application.

N’ajoutez pas toutes les protections en une seule fois : certaines applications ont besoin d’écrire dans des chemins ou d’accéder à des namespaces spécifiques.

Coûts

La migration ne nécessite pas de licence supplémentaire. Le coût principal est opérationnel : temps d’inventaire, VM de test, éventuel snapshot et fenêtre de validation.

Si le clone est hébergé dans un cloud public, arrêtez ou supprimez les ressources temporaires après les tests.

Limites et points de vigilance

Une conversion générique ne peut pas déterminer automatiquement le bon Type= pour tous les démons.

Les applications anciennes peuvent dépendre :

  • d’un fichier PID ;
  • d’un environnement shell particulier ;
  • d’un filesystem monté après le démarrage de base ;
  • d’un réseau réellement configuré ;
  • de scripts pré/post-start ;
  • d’un socket ou d’un autre service.

Ces dépendances doivent être traduites à partir du comportement réel, pas simplement déduites du nom du service.

Ubuntu 26.04 conserve encore la compatibilité SysV ; cela fournit une fenêtre de migration. En revanche, systemd 260 a déjà supprimé la prise en charge upstream, et Canonical annonce cette disparition pour Ubuntu 26.10.

Rollback ou nettoyage

Sur Ubuntu 26.04, si l’unité native échoue dans le lab :

sudo systemctl disable --now example-agent.service
sudo rm /etc/systemd/system/example-agent.service
sudo systemctl daemon-reload

Restaurez ensuite le mécanisme SysV d’origine et vérifiez son fonctionnement selon la procédure historique.

Ne supprimez le script /etc/init.d/... et ses éventuels fichiers auxiliaires qu’après validation complète de la nouvelle unité et après vous être assuré qu’aucun package ne les gère encore.

Ce rollback est une mesure transitoire : il ne constitue pas une solution durable au-delà des versions conservant la couche SysV.

Références officielles

  • Ubuntu 26.04 LTS summary — Canonical : https://documentation.ubuntu.com/release-notes/26.04/summary-for-lts-users/
  • Ubuntu 26.04 LTS changes since 25.10 — Canonical : https://documentation.ubuntu.com/release-notes/26.04/changes-since-previous-interim/
  • systemd v260 release notes — systemd upstream : https://github.com/systemd/systemd/releases/tag/v260
  • Changing package files — Ubuntu Server documentation : https://documentation.ubuntu.com/server/explanation/software/changing-package-files/
  • systemd.service — systemd manual : https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html

Conclusion

La fin de la compatibilité SysV ne doit pas être traitée comme une simple opération de renommage. La migration consiste à comprendre le processus, supprimer les mécanismes hérités devenus inutiles et confier la supervision à une unité systemd native minimale.

Ubuntu 26.04 offre encore une période de transition. Le bon objectif est donc de sortir les services legacy de /etc/init.d/ maintenant, de les tester sur clone, puis de valider boot, arrêt, redémarrage et fonctionnement métier avant que la couche de compatibilité ne disparaisse.