Tom Daluzeau
← Parcours Linux
Linux · leçon 6 / 6Ch. 1 · systemd12 minTerminée

systemd : timers, sockets et drop-in

Tu sais planifier une tâche avec un timer, expliquer l'activation par socket, dire où vivent les fichiers d'unité et lequel l'emporte, modifier une unité proprement avec un drop-in, et passer des variables d'environnement à un service.

Mis à jour le

Pourquoi on voit ça

Tu sais écrire une unité et la piloter avec systemctl. Il reste quatre savoir-faire que tu rencontreras très vite en entreprise :

  • lancer une tâche à heure fixe (sauvegarde, nettoyage) ;
  • ne lancer un service que lorsqu’on en a besoin ;
  • modifier l’unité d’un paquet sans la casser à la prochaine mise à jour ;
  • donner à un service ses réglages (port, mot de passe) sans les écrire en dur.

Les timers : planifier une tâche

Un timer est une unité .timer qui déclenche un service à un moment donné. Il fonctionne toujours en duo :

  • le .service dit quoi faire ;
  • le .timer dit quand.

Par défaut, un .timer déclenche le .service du même nom : sauvegarde.timer lance sauvegarde.service.

Si les noms diffèrent, la directive Unit= de la section [Timer] désigne le service à lancer. Le timer remplace cron, le planificateur historique de Linux, avec trois avantages : les exécutions apparaissent dans le journal de systemd, la tâche peut avoir des dépendances, et une exécution manquée peut être rattrapée.

sauvegarde.timer dit quand : OnCalendar= déclenche sauvegarde.service dit quoi : ExecStart= logs journal journalctl -u enable --now sauvegarde.timer c'est lui qu'on active -u sur le service, pas le timer même nom, seule l'extension change
Le timer tient l'agenda, le service fait le travail, le journal garde la trace.

Le service, de type oneshot puisqu’il fait son travail puis se termine :

[Unit]
Description=Sauvegarde de la base

[Service]
Type=oneshot
ExecStart=/usr/local/bin/sauvegarde.sh

Le timer :

[Unit]
Description=Sauvegarde chaque nuit à 3 h

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target
Directive Sens Exemple
OnCalendar= à une date ou une heure du calendrier *-*-* 03:00:00 (tous les jours à 3 h), daily, weekly
OnBootSec= un délai après le démarrage de la machine OnBootSec=10min
Persistent=true rattrape au démarrage une échéance manquée pendant que la machine était éteinte

Le format de OnCalendar se lit année-mois-jour heure:minute:seconde, et * veut dire « tous ».

On peut faire précéder la date d’un jour de la semaine en anglais (Mon, Tue..Thu : .. donne un intervalle). L’heure s’écrit sur 24 h : 15:00, c’est 15 h, et 3:17 c’est 3 h 17 du matin.

Une échéance manquée pendant que le serveur dort serveur éteint 03:00 : échéance 07:00 : démarrage 03:00 le lendemain Persistent=true : lancée au démarrage sans Persistent : rien avant le lendemain
Persistent=true note la dernière exécution sur le disque et rattrape celle qui a été manquée.

C’est le timer qu’on active, pas le service :

systemctl enable --now sauvegarde.timer

Remarque l’extension : sans elle, systemctl comprendrait sauvegarde.service. Pour voir tous les timers, avec leur prochaine et leur dernière échéance :

systemctl list-timers

Pour voir ce que la tâche a fait, on lit le journal du service, pas celui du timer :

journalctl -u sauvegarde.service

L’activation par socket

Normalement, un service démarre, ouvre son port et attend, même si personne ne l’utilise de la journée. Avec l’activation par socket, les rôles changent :

  1. systemd écoute sur le port à la place du service ;
  2. le service n’est pas lancé ;
  3. à la première connexion, systemd démarre le service et lui passe la connexion.

C’est comme la sonnette d’une loge : le gardien (systemd) entend sonner, puis va chercher la personne concernée. Elle n’a pas besoin d’attendre toute la journée derrière la porte.

client connexion systemd monapp.socket écoute sur le port 8080 lance monapp.service lancé à la 1re connexion systemctl enable --now monapp.socket on active la socket, pas le service
Le gardien entend la sonnette et va chercher la bonne personne.

Là encore, c’est un duo par le nom : monapp.socket active monapp.service.

[Unit]
Description=Port d'écoute de monapp

[Socket]
ListenStream=8080

[Install]
WantedBy=sockets.target

Les avantages : un démarrage du serveur plus rapide, de la mémoire économisée pour les services rarement utilisés, et aucune connexion perdue pendant que le service se lance.

Comme pour le timer, c’est la socket qu’on active, pas le service :

systemctl enable --now monapp.socket

Tant que personne ne se connecte, ps aux ne montre aucun processus monapp, mais le port 8080 est déjà en écoute.

Où vivent les fichiers d’unité

Dossier Qui y écrit Priorité
/usr/lib/systemd/system (ou /lib/systemd/system) les paquets installés avec apt basse
/run/systemd/system systemd lui-même, généré à chaque démarrage entre les deux
/etc/systemd/system l’administrateur : toi haute

Si une unité du même nom existe dans les deux dossiers, c’est celle de /etc/systemd/system qui gagne.

Tu retrouves la logique de la leçon sur le démarrage : ce que tu règles toi-même va dans /etc. Et tu ne modifies jamais un fichier de /usr/lib/systemd/system : la prochaine mise à jour du paquet écraserait ton changement. Tes propres unités (monapp.service, sauvegarde.timer) se rangent donc dans /etc/systemd/system.

Les drop-in : modifier sans tout recopier

Tu veux changer une seule ligne de l’unité de nginx, par exemple ajouter Restart=always. Recopier tout le fichier dans /etc fonctionnerait, mais tu ne profiterais plus des améliorations apportées par les mises à jour. La bonne méthode est le drop-in : un petit fichier qui ne contient que ce que tu changes.

systemctl edit nginx

Cette commande ouvre un éditeur. Tu y écris seulement :

[Service]
Restart=always

À l’enregistrement, systemd crée le fichier /etc/systemd/system/nginx.service.d/override.conf et relit sa configuration tout seul. Le nom du dossier, c’est le nom complet de l’unité suivi de .d. Il te reste à faire systemctl restart nginx pour que le service reparte avec le nouveau réglage.

Même nom : qui gagne ? /etc/systemd/system l'administrateur : gagne /run/systemd/system généré à la volée /usr/lib/systemd/system les paquets : perd priorité Drop-in : on ajoute, on ne recopie pas /usr/lib/…/nginx.service fichier du paquet, jamais modifié + /etc/…/nginx.service.d/override.conf seulement les lignes changées l'unité que voit systemctl cat
Un fichier complet dans /etc remplace celui du paquet. Un drop-in, lui, ne change que ce qu'il contient.

Deux commandes vont avec :

Commande Effet
systemctl cat nginx affiche l’unité telle que systemd la voit : le fichier d’origine, puis chaque drop-in, avec le chemin de chacun
systemctl revert nginx supprime tes modifications et revient à la version du paquet

systemctl cat est le réflexe quand un service ne se comporte pas comme son fichier le laisse croire : un drop-in oublié change peut-être la donne.

Remplacer une directive qui s’accumule

Certaines directives peuvent apparaître plusieurs fois et s’additionnent au lieu de se remplacer : c’est le cas de ExecStart= et de ListenStream=. Dans un drop-in, pour changer leur valeur, il faut d’abord les vider avec une ligne sans valeur :

[Service]
ExecStart=
ExecStart=/usr/local/bin/monapp --verbose

Sans la ligne vide, le drop-in ajoute une deuxième commande à la première, et systemd refuse de charger le service : plusieurs ExecStart= ne sont permis qu’avec Type=oneshot. Pour une socket, c’est plus sournois : sans ListenStream= vide avant ListenStream=1234, aucune erreur, mais la socket écoute à la fois sur l’ancien port et sur le nouveau.

Les variables d’environnement d’un service

Un service ne démarre pas dans ton terminal : il n’hérite pas des variables de ton shell. Un export PORT=8080 tapé avant systemctl start ne sert à rien. Les variables se déclarent dans la section [Service].

Directement dans l’unité :

[Service]
Environment=PORT=8080
Environment=MODE=production
ExecStart=/usr/bin/python3 /opt/monapp/app.py

Ou dans un fichier à part :

[Service]
EnvironmentFile=/etc/monapp/env
ExecStart=/usr/bin/python3 /opt/monapp/app.py

Le fichier /etc/monapp/env contient une variable par ligne :

PORT=8080
DB_PASSWORD=motdepasse
Directive Reçoit Quand l’utiliser
Environment= une variable NOM=valeur une ou deux valeurs sans secret
EnvironmentFile= le chemin d’un fichier beaucoup de variables, ou des secrets

Avec un fichier à part, tu peux restreindre ses droits de lecture, et l’unité reste identique d’un serveur à l’autre : seul le fichier change.

À retenir

  • Un .timer déclenche le .service du même nom. OnCalendar= : à une date ou une heure. OnBootSec= : un délai après le démarrage. Persistent=true : rattrape une échéance manquée.
  • On active le timer (systemctl enable --now nom.timer) et on contrôle avec systemctl list-timers.
  • Activation par socket : systemd écoute sur le port et ne lance le service qu’à la première connexion.
  • Paquets : /usr/lib/systemd/system (ou /lib/systemd/system). Administrateur : /etc/systemd/system, qui a la priorité.
  • Drop-in : systemctl edit nom crée /etc/systemd/system/nom.service.d/override.conf. systemctl cat affiche le tout, systemctl revert annule.
  • Variables d’un service : Environment=NOM=valeur dans l’unité, ou EnvironmentFile=/chemin vers un fichier.

Entraînement

12 questions, dont 4 à compléter. Tu réponds, on passe à la suivante. La note arrive à la fin.

Cartes · 8

Réviser les cartes ici

Espace : retourner