Login Logout

Howto systemd

systemd est un gestionnaire de services (le fameux démon avec le PID 1) en alternative au System V. Il est installé par défaut depuis Debian 8. Son utilisation a suscitée des débats au sein de l’équipe, mais nous l’utilisons sur tous nos serveurs en Debian 8.

Utilisation de base

Statut systemd

Afficher le statut :

# systemctl status

Lister les unités qui tournent :

# systemctl
# systemctl list-units

Lister les unités en échecs :

# systemctl --failed

Lister les unités installées :

# systemctl list-unit-files

Note : Les unités sont par défaut dans /usr/lib/systemd/system/ et /etc/systemd/system/ (gestion manuelle)

Gestion des unités

https://www.freedesktop.org/software/systemd/man/systemd.unit.html

Démarrer/arrêter/redémarrer une unité :

# systemctl start <unité>
# systemctl stop <unité>
# systemctl restart <unité>

Recharger la configuration d’une unité :

# systemctl reload <unité>

Afficher le statut d’une unité :

# systemctl status <unité>
# systemctl status <unité> -l

Savoir si une unité est activée ou non :

# systemctl is-enabled <unité>

Note : Attention, pour les pseudo-unités (qui sont en fait des scripts dans /etc/init.d/), is-enabled ne fonctionne pas.

Activer/désactiver une unité au boot :

# systemctl enable <unité>
# systemctl disable <unité>

Masquer/démasquer une unité (mask rend impossible tout stop/start/…) :

# systemctl mask <unité>
# systemctl unmask <unité>

Afficher la man page correspondante au service de l’unité :

# systemctl help <unité>

Recharger systemd pour prendre en compte les unités modifiées :

# systemctl daemon-reload

Lister les unités en état « failed » :

# systemctl --failed --all

Oublier l’état « failed » pour une unité ou l’ensemble des unités :

# systemctl reset-failed
# systemctl reset-failed <unité>

Gestion des timers

Systemd permet aussi la gestion de tâches planifiées. Elles se décrivent via des unité de type timers et vont déclancger une unité de type service pour effectuer une action.

Lister les timers :

# systemctl list-timers --all

NEXT                        LEFT           LAST                        PASSED      UNIT                         ACTIVATES
Fri 2022-03-11 11:39:00 CET 23min left     Fri 2022-03-11 11:09:01 CET 6min ago    phpsessionclean.timer        phpsessionclean.service
Fri 2022-03-11 11:54:22 CET 38min left     Thu 2022-03-10 11:54:22 CET 23h ago     etckeeper.timer              etckeeper.service
Fri 2022-03-11 11:54:22 CET 38min left     Thu 2022-03-10 11:54:22 CET 23h ago     systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Fri 2022-03-11 16:16:09 CET 5h 0min left   Thu 2022-03-10 18:57:22 CET 16h ago     apt-daily.timer              apt-daily.service
Sat 2022-03-12 00:00:00 CET 12h left       Fri 2022-03-11 00:00:01 CET 11h ago     logrotate.timer              logrotate.service
Sat 2022-03-12 00:00:00 CET 12h left       Fri 2022-03-11 00:00:01 CET 11h ago     man-db.timer                 man-db.service
Sat 2022-03-12 06:53:28 CET 19h left       Fri 2022-03-11 06:08:38 CET 5h 6min ago apt-daily-upgrade.timer      apt-daily-upgrade.service

7 timers listed.

On ajoutera l’option --user pour lister les timers de son utilisateur.

$ systemctl --user list-timers --all

Les commandes usuelles (start, stop, disable, enable…) sont donc valide aussi sur des timers. Ainsi :

  • Démarrer un timer : systemctl start <timer>
  • Démarrer un timer au démarrage du système : systemctl enable <timer>
  • Arréter un timer : systemctl stop <timer>
  • Désactiver le démarrage d’un timer au démarrage du système : systemctl disable <timer>

Redémarrer ou éteindre un serveur

Avec systemd, les commandes halt/poweroff/shutdown/reboot n’existent plus ! Ce sont des désormais des liens symboliques vers systemctl.
On conseille donc d’utiliser directement les commandes « natives ».

Redémarrer le serveur :

# systemctl reboot

Éteindre et couper l’alimentation du serveur :

# systemctl poweroff

Note : l’option --force permet de killer tous les process sans attendre leur extinction propre (à éviter bien sûr)

Rédaction des unités

https://www.freedesktop.org/software/systemd/man/systemd.service.html

Modifier une unité

# cp -a /lib/systemd/system/<service>.service /etc/systemd/system/
# vim /etc/systemd/system/<service>.service
# systemctl daemon-reload

Exemple avec ssh.service sous Debian 8 :

[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure

[Install]
WantedBy=multi-user.target
Alias=sshd.service

Note : Chaque fois qu’une unité est modifiée, il est nécessaire de recharger systemd avec systemctl daemon-reload.

N.B. : La commande donnée dans la variable ExecStart n’est pas exécutée dans un shell. Les symboles tels que >> ou && ne seront donc par pris en compte comme de la syntaxe shell, mais comme des arguments pour le programme qu’on souhaite exécuter. Si on veut quand même utiliser >> par exemple, on fera :

ExecStart=/bin/sh -c '/path/to/program >> /path/to/log'

Modifier partiellement une unité

Au lieu de tout ré-écrire, on peut surcharger une partie de l’unité en créant un fichier dans /etc/systemd/system/NOM_UNITÉ.service.d/ qui précisera les modifications à faire. Exemple avec l’unité de varnish on crée le fichier /etc/systemd/system/varnish.service.d/override.conf :

[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -a 0.0.0.0:80 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,16G -p thread_pools=8 -p thread_pool_add_delay=2 -p thread_pool_min=500 -p thread_pool_max=5000
ExecReload=
ExecReload=/etc/varnish/reload-vcl.sh

A noter que les premières directives “ExecStart” et“ExecReload” sont vides : ce ne sont pas des erreurs mais permettent d’effacer les valeurs présentes dans l’unité par défaut avant de les réassigner !

On peut directement utiliser la commande suivante pour surcharger une unité :

# systemctl edit varnish

On pourra ensuite lister l’unité complète vue par systemd :

# systemctl cat varnish

A noter que cela fonctionne aussi pour les services qui n’ont pas d’unité systemd mais encore une unité sysvinit dans /etc/init.d/, on pourra par exemple mettre en place un redémarrage automatique d’un service :

[Service]
PIDFile=/run/service.pid
Restart=always
RemainAfterExit=no

Créer une unité systemd

Modèle simple :

# /etc/systemd/system/<unit_name>.service

[Unit]
Description=<Unit description>
After=network.target

[Service]

ExecStart=<Start command>
#ExecStop=<Stop command>
#Restart=on-failure

Type=simple
# If main process exits after being run.
#Type=oneshot
#RemainAfterExit=yes

#User=<user>
#Group=<group>
#UMask=027

[Install]
WantedBy=multi-user.target

Puis :

systemctl daemon-reload
systemctl enable --now <unit_name>

Créer et utiliser un template d’unité

On peut créer et utiliser des template d’unité. Cela permet de gérer à la volée des instances de services.

Par exemple pour redis via /etc/systemd/system/redis@.service.

[Unit]
Description=Advanced key-value store instance %i
After=network.target

[Service]
Type=forking
ExecStart=/usr/bin/redis-server /etc/redis/%i.conf
ExecStartPost=/bin/chgrp %i /var/run/redis/%i.sock
ExecStop=/usr/bin/redis-cli shutdown
Restart=always
User=redis
Group=%i

[Install]
WantedBy=multi-user.target

%i correspond au nom d’instance après le @. On pourra ensuite démarrer l’instance foo et bar avec :

# systemctl start redis@foo redis@bar

systemd par utilisateur

systemd fonctionne avec le PID 1. Mais il est aussi possible de lancer des instances par utilisateur avec le paramètre --user. C’est très pratique pour donner les droits à un utilisateur de gérer lui-même ses services (instances Tomcat par exemple).
Il faut avoir le paquet libpam-systemd installé : une session systemd est lancée quand on est connecté à la machine :

$ systemctl --user status
● bar
    State: running
     Jobs: 0 queued
   Failed: 0 units
    Since: ven. 2016-12-23 21:15:33 CET; 1 weeks 6 days ago
   CGroup: /user.slice/user-1001.slice/user@1001.service
           ├─1758 /lib/systemd/systemd --user
           └─1760 (sd-pam)  

Attention, si l’on veut une session persistante pour un utilisateur, il faut l’activer avec loginctl (si l’on ne fait pas cela, les services lancés via systemctl --user seront coupés à la déconnexion). Par exemple pour l’utilisateur foo :

# loginctl enable-linger foo

On peut aussi désactiver cela :

# loginctl disable-linger foo

Attention, si l’on renomme l’utilisateur foo ou que l’on change son UID, il faut supprimer son “linger” :

# rm /var/lib/systemd/linger/foo

L’utilisateur peut créer son unité systemd dans son /home dans le dossier suivant :

~/.config/systemd/user/

A créer si les dossier n’existe pas.

Exemple d’unité utilisateur pour Tomcat :

/etc/systemd/user/tomcat.service

[Unit]
Description=Tomcat %u.
After=network.target

[Service]
WorkingDirectory=/home/%u
Environment="CATALINA_BASE=/home/%u/tomcat"
EnvironmentFile=/home/%u/tomcat/conf/env
UMask=0002
ExecStart=/usr/share/tomcat7/bin/startup.sh
ExecStop=/usr/share/tomcat7/bin/shutdown.sh
ExecStopPost=/bin/sh -c date | /usr/bin/mail -s "%H/%u : Shutdown instance" foo@bar.com
Type=forking

[Install]
WantedBy=default.target

Note : il est important que WantedBy soit default.target car en mode utilisateur multi-user.target n’existe pas et donc le service n’est jamais démarré automatiquement.

L’utilisateur pourra ainsi gérer lui-même le service :

$ systemctl --user enable tomcat
$ systemctl --user start tomcat
$ systemctl --user status -l tomcat
$ systemctl --user restart tomcat
$ systemctl --user stop tomcat
$ systemctl --user daemon-reload

Si l’on rencontre l’erreur Failed to connect to bus: No such file or directory ou que l’on veut pouvoir gérer systemctl --user via sudo -iu foo, on conseille de mettre dans /etc/profile.d/systemd.sh :

export XDG_RUNTIME_DIR="/run/user/$UID"

Attention, le fichier /etc/profile.d/systemd.sh doit avoir les droits 644.

Utilisation avancée

Afficher plein d’informations sur l’unité :

# systemctl show <service>

Debug au démarrage accessible sur tty9 :

# systemctl enable debug-shell

Débug d’une unité :

# systemctl stop unit
# SYSTEMD_LOG_LEVEL=debug /lib/systemd/unit

Analyser ce qui prend du temps au démarrage :

# systemd-analyze 
Startup finished in 5.019s (firmware) + 6.128s (loader) + 5min 35.864s (kernel) + 20min 3.148s (userspace) = 25min 50.160s
# systemd-analyze blame | head -3
2.029s systemd-udev-settle.service
1.400s docker.service
1.215s uml-utilities.service

journalctl

https://manned.org/man/debian/journalctl

systemd utilise ses propres logs, que l’on peut lire avec la commande journalctl.

Voici quelques options utiles que l’on pourra combiner :

  • journalctl -u <unité> : logs d’une unité
  • journalctl -b : logs depuis le dernier boot
  • journalctl -b-1 : logs depuis l’avant-dernier boot, etc.
  • journalctl --since "2024-02-29 13:37:00" : logs depuis une certaine date
  • journalctl --until "2024-02-29 14:37:00" : logs jusqu’à une certaine date
  • journalctl -f : sortie dynamique des derniers logs similaire à tail -f

FAQ

systemd et Debian 8

Sous Debian 8, l’adoption de systemd est récente et de nombreuses services ont des unités mal écrites. Notamment elles ne gèrent souvent pas les options dans /etc/default/ alors qu’un fichier est créé par défaut. On conseille alors de corriger l’unité. En général, les corrections sont intégrées en Debian 9.

Exemple avec Bind qui ne gère par les options dans /etc/default/bind9 en Debian 8. Pour corriger il faut copier l’unité :

# cp -a /lib/systemd/system/bind9.service /etc/systemd/system/

et ajuster la section [Service] :

EnvironmentFile=-/etc/default/bind9
ExecStart=/usr/sbin/named -f $OPTIONS

puis :

# systemctl daemon-reload

Limiter les accès disque

Ces directives ajouté dans la section Service seront appliqué au processus parent mais aussi au processus enfant. Prenons le cas de Dovecot où l’on a besoin de réduire sa priorité d’accès en lecture :

cat /etc/systemd/system/dovecot.service.d/override.conf
[Service]
IOSchedulingClass=best-effort
IOSchedulingPriority=5

strace d’un process

Avant systemd, une astuce efficace pour stracer un démon multi-process était de faire strace -ff /etc/init.d/<démon> start …mais ça n’est plus possible avec systemd : il faut désormais détecter le PID du processus père du démon pour faire un strace -ff -p<PID>.

Exemple pour Apache, on récupère le PID, on lance le strace puis on fait un graceful :

# pstree -pan | grep -v grep | grep apache | head -1
# strace -ff -s65535 -p<PID>
# apache2ctl graceful

Autre astuce pour récupérer les logs “strace” dans /tmp/strace.log :

# strace -f -o /tmp/strace.log -s 2048 -p 1 & systemctl restart <SERVICE>

networking

Sous Debian 8, le démarrage du système se bloque complètement (dead lock) si l’initialisation de process dépendant du réseau (comme NFS, NTP, firewalling…) se fait avant que le réseau soit démarré. Notamment quand ils sont démarrés via des hooks dans /etc/network/if-*/… Cela n’était pas bloquant avec SysV, cf https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=754218#30

Contournement « bourrin » : ajouter exit 0 dans /etc/init.d/networking et voir le souci via systemctl status networking -l ; modifier JobTimeoutUSer => systemctl show networking -p JobTimeoutUSer

Failed to get D-Bus connection: Unknown error -1

Si en utilisant systemctl vous obtenez une erreur “Failed to get D-Bus connection: Unknown error -1” vérifiez si vous êtes bien passé à systemd et n’êtes resté à sysvinit !

Si cela se produit dans un conteneur LXC, voir la section LXC et cgroupv2 de la page HowtoDebian/MigrationBusterBullseye.

Failed to get D-Bus connection: No such file or directory

Si en tant qu’utilisateur, on obtient l’erreur :

$ systemctl --user status
Failed to get D-Bus connection: No such file or directory

S’assurer que l’on a bien une variable d’environnement du type :

XDG_RUNTIME_DIR=/run/user/1042

Avec un strace on devrait voir quelque chose comme :

$ strace systemctl --user status
connect(3, {sa_family=AF_LOCAL, sun_path="/run/user/1042/systemd/private"}, 32) = -1 ECONNREFUSED (Connection refused)
connect(3, {sa_family=AF_LOCAL, sun_path="/run/user/1042/bus"}, 20) = -1 ENOENT (No such file or directory)

C’est surtout le premier élément qui est intéressant. systemd doit maintenir des sockets Unix fonctionnelles quand un utilisateur est logué :

$ lsof | grep /run/user/1042
systemd     844       foo   11u     unix 0xffff88082dbb9480        0t0   69531078 /run/user/1042/systemd/notify
systemd     844       foo   12u     unix 0xffff88082dbb9c00        0t0   69531080 /run/user/1042/systemd/private

Pour le forcer à les récréer on pourra tester dans l’ordre :

  • la commande loginctl enable-linger foo pour les rendre persistantes et voir l’effet (quitte à désactiver après)
  • mv /var/lib/systemd/linger/foo /tmp puis loginctl enable-linger foo
  • mv /run/user/1042 puis loginctl enable-linger foo
  • redémarrer dbus : systemctl restart dbus
  • redémarrer systemd-logind : systemctl restart systemd-logind
  • stopper tous les process de l’utilisateur (voir avec lsof) puis se loguer “proprement” avec ssh ou login (et non pas sudo ou su)

Cas d’une installation non standard, tel un container, vérifiez que vous avez bien accès à /dev. On peut le vérifier avec dbus-launch.

foo@bar:~$ dbus-launch
Failed to open /dev/null: Permission denied

Parfois avec OpenVZ, les containers sont démarrés avec /dev en 700.

Please remove executable permission bits

Contrairement à SysVinit, les unités systemd sont des fichiers de configurations et non des éxécutables, les droits doivent donc etre en 644 pour éviter le warning suivant:

Configuration file /etc/systemd/XXXX/XXXX.service is marked executable. Please remove executable permission bits. Proceeding anyway.

systemd-fsck : Dependency failed

Vous obtenez systemd-fsck : Dependency failed for File System Check on /dev/disk/foo ?

Par défaut, systemd-fsck attend 1m30 l’accès à chaque device disque. Pour diverses raisons (accès à un SAN externe, etc.) cela peut échouer, vous aurez alors :

systemd[1]: Job dev-disk-by\x2dlabel-foo.device/start timed out.
systemd[1]: Timed out waiting for device dev-disk-by\x2dlabel-foo.device.
systemd[1]: Dependency failed for /backup.
systemd[1]: Dependency failed for Local File Systems.
systemd[1]: Triggering OnFailure= dependencies of local-fs.target.
systemd[1]: Dependency failed for File System Check on /dev/disk/by-label/foo.

Vous pouvez alors ajouter différentes options via le fstab, du type :

LABEL=foo    /foo ext4    defaults,nofail,x-systemd.device-timeout=120 0 0

Notamment nofail pour éviter que cela fasse échouer votre séquence de boot !

systemd VS systemD VS SystemD

systemd ne prend aucune majuscule. Ce n’est pas SystemD ou systemD.

Migrer de sysvinit à systemd

Cette action peut être nécessaire lorsque l’on migre de Debian Wheezy à Jessie.

# apt install systemd-sysv
The following packages will be REMOVED:
sysvinit-core
# reboot

et vérifier que GRUB ne se lance pas avec l’option init=/lib/sysvinit/init.

Et les scripts dans /etc/init.d/ ?

Avec systemd, pourquoi reste-t-il des scripts dans /etc/init.d/ ?

systemd prend en compte les scripts dans /etc/init.d/ :

  • les scripts « classiques » sont pris en compte si il n’existe pas d’unité systemd avec le même nom (sa description commence alors par LSB:)
  • certains scripts sont juste là par compatibilité afin de pouvoir faire /etc/init.d/foo start/stop/restart/status mais c’est en fait l’unité systemd qui est prise en compte

Boot bloqué pour partition inaccessible

Si on modifie le partitionnement et qu’on oublie de mettre à jour le fstab, ou qu’un disque n’est plus détecté, il arrive que le démarrage soit bloqué. Au démarrage du serveur, à l’écran de boot, on choisira e pour “éditer” les options de démarrage. À la fin de la ligne linux on ajoutera init=/bin/bash puis on demande la suite du boot (probablement F10). On a alors la possibilité de remonter la partition / en écriture avec mount -o remount,rw / puis d’éditer le fstab avec vi /etc/fstab. Au prochain démarrage les paramètres spécifiques de grub sont oubliés et le démarrage devrait se faire sans encombre.

NB : l’édition des paramètres de grub et le shell tmeporaire sont habituellement en keymap qwerty !

Exemples d’unités

Process Node.js géré via forever

forever est lui-même un gestionnaire de process, mais dans certains cas c’est pertinent d’empiler les 2 gestionnaires. Voici un exemple fonctionnel (qu’on pourrait vouloir transformer en unité “user”, d’ailleurs) :

# /etc/systemd/system/foo_chat.service
[Unit]
Description=Node.js process for Foo chat
After=network.target

[Service]
ExecStart=/usr/local/bin/forever start --pidFile /home/foo/www/foo.pid -o /home/foo/www/log/out.log -e /home/foo/www/log/err.log /home/foo/www/bin/www
ExecStop=/usr/local/bin/forever stop /home/foo/www/bin/www
PIDFile=/home/foo/www/foo.pid 
Type=forking
LimitNOFILE=4096
User=nod
Restart=always

[Install]
WantedBy=default.target

Process Java / JBoss (exemple avec wildfly)

Il faut mettre les variables d’environnement java dans un fichier que l’on appelle avec la variable EnvironmentFile= dans l’unité systemd, voici un exemple Environment File :

JBOSS_HOME=/home/wildfly/wildfly-10.1.0.Final/
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
JAVA=/usr/lib/jvm/java-8-openjdk-amd64/bin/java
JBOSS_LOG_DIR=/home/wildfly/wildfly-10.1.0.Final/analysis/log/
JBOSS_CONFIG_DIR=/home/wildfly/wildfly-10.1.0.Final/analysis/configuration/
JBOSS_CONFIG=standalone.xml
MODULE_OPTS=
JBOSS_MODULEPATH="/home/wildfly/wildfly-10.1.0.Final/modules"
instance_name=analysis
JAVA_OPTS="-server -Xms256m -Xmx2048M -XX:MetaspaceSize=192M -XX:MaxMetaspaceSize=2048m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -Djboss.server.base.dir=/home/wildfly/wildfly-10.1.0.Final/analysis -Djboss.node.name=analysis -Djboss.as.management.blocking.timeout=701"

Voici un exemple d’unité systemd :

[Unit]
Description=Jboss Application Server
After=network.target

[Service]
Type=simple
EnvironmentFile=/home/wildfly/prod_wildfly_analyses_env
User=wildfly
Group=wildfly
ExecStart=/home/wildfly/wildfly-10.1.0.Final/bin/standalone.sh
#TimeoutStartSec=600
#TimeoutStopSec=600

[Install]
WantedBy=multi-user.target