Remplacer cron par systemd
Les versions récentes de systemd améliorent le lancement d'évènements selon des critères temporels. Ce tutoriel montre, pour un cas simple, comment remplacer cron par systemd.
Pour apporter plus de précisions, ce tutoriel contient de nombreux liens vers le manuel de systemd. N'hésitez pas à cliquer !
Situation initiale
Chez moi, le répertoire /etc/cron.d/ ne contient qu'un seul et unique fichier, nommé 0hourly dont voici le contenu :
$ cat /etc/cron.d/0hourly
SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=root 00 * * * * root run-parts /etc/cron.hourly
La dernière ligne nous informe que cron exécute la commande
$ run-parts /etc/cron.hourly
toutes les heures, et en tant que root1).
Il n'y a pas d'autre fichier que 0hourly dans le répertoire /etc/cron.d/ ; nous en déduisons que, sur ma machine, cron ne sert qu'à une chose : lancer une commande toutes les heures. Pour ce cas simple, voyons comment remplacer cron par systemd.
Stopper et désactiver cron
Pour commencer, nous stoppons cron :
$ systemctl stop cronie.service
et nous désactivons le lancement automatique de cron au démarrage de l'ordinateur :
$ systemctl disable cronie.service
Remarque : parler de « démarrage de l'ordinateur » est un abus de langage. En fait, nous stoppons le lancement automatique de cron dans les cibles (les « target ») où il est lancé automatiquement. Les cibles sont, sous systemd, l'équivalent des niveaux d'exécution de SysVinit. La principale différence est que plusieurs cibles de systemd peuvent être actives simultanément.
Créer une première unité systemd : un fichier .service
Le paramétrage de systemd se fait par l'intermédiaire de fichiers textes : les unités systemd.
Le gestionnaire de paquets installe ces unités dans le répertoire /lib/systemd/system/ Comme les fichiers de ce répertoire sont susceptibles d'être modifiés ou effacés lors des mises-à-jour, il nous faut créer nos unités systemd dans un autre répertoire, réservé à l'administrateur et protégé des mises-à-jour. Ainsi, c'est le répertoire /etc/systemd/system/ qui est notre terrain de jeu.
Dans un premier temps, nous créons un fichier .service qui décrit la commande que l'on souhaite lancer. Appelons ce fichier taches-horaires.service voici son contenu :
$ cat /etc/systemd/system/taches-horaires.service
[Unit] Description=Exécution des tâches horaires [Service] ExecStart=/usr/bin/run-parts /etc/cron.hourly User=root Type=forking
La ligne ExecStart= indique la commande à lancer. Il est nécessaire de préciser le chemin complet du programme, avec le « /usr/bin/ » devant. La ligne User= précise sous quel utilisateur lancer la commande, et la ligne Type= décrit le type de processus.
Il existe de très nombreux paramètres pour configurer les unités systemd. Les principales sont expliquées dans les pages de manuel systemd.unit, systemd.service et systemd.exec.
Notre unité .service décrit parfaitement la commande que l'on souhaite lancer, mais elle ne contient pas le critère temporel « toutes les heures ». Pour cela, nous créons une seconde unité systemd : un « fichier horloge » .timer
Créer une seconde unité systemd : un fichier .timer
Toujours dans le répertoire /etc/systemd/system/, nous créons le fichier horloge-taches-horaires.timer Il s'agit d'un « fichier horloge » .timer qui précise le caractère « toutes les heures » du lancement de l'unité .service créée précédemment. Le contenu de ce fichier .timer est :
$ cat /etc/systemd/system/horloge-taches-horaires.timer
[Unit] Description=Horloge pour lancer "taches-horaires.service" toutes les heures [Timer] Unit=taches-horaires.service OnCalendar=*-*-* *:00:00 [Install] WantedBy=multi-user.target
Le paramètre Unit= indique quelle unité systemd doit être lancée2). Le paramètre OnCalendar= précise les critères temporels du lancement. Sa syntaxe est AAAA-MM-JJ hh:mm:ss (il est également possible d'utiliser les jours de la semaine, des précisions sur ce lien).
Grâce à notre unité .timer, notre première unité .service sera lancée tous les jours et toutes les heures, à zéro minute et zéro seconde.
Le paramètre WantedBy=multi-user.target sera abordé plus bas.
Mettre à jour systemd
Pour que systemd tienne compte des deux nouvelles unités que nous venons de créer, on recharge la configuration de systemd :
$ systemctl daemon-reload
Lancer et activer notre unité .timer
Ouvrons un autre terminal, et utilisons-le pour suivre le journal des évènements de systemd :
$ journalctl -f
En cas de problème, les messages fournis par le journal des évènements sont intéressants (pour quitter : touches Control+C).
À présent, lançons notre unité .timer :
$ systemctl start horloge-taches-horaires.timer
Nous n'avons pas besoin de lancer l'unité .service : c'est l'unité .timer qui se chargera de la démarrer toutes les heures.
Activons le lancement automatique de notre unité .timer :
$ systemctl enable horloge-taches-horaires.timer
C'est ici qu'entre en jeu le paramètre WantedBy=multi-user.target vu précédemment : notre unité .timer sera maintenant lancée automatiquement lorsqu'on activera la cible multi-user.target, ce qui est le cas à chaque démarrage (si la cible multi-user.target ne s'active pas au démarrage, c'est que vous avez bidouillé, et que vous savez ce que vous faites )
Remarque : le lancement automatique de l'unité se traduit par la création d'un lien symbolique dans le répertoire /etc/systemd/system/multi-user.target.wants/ qui pointe vers notre unité .timer
Épilogue
Après plusieurs jours, nous vérifions le bon fonctionnement de nos deux unités en consultant le journal des évènements de systemd :
$ journalctl -u taches-horaires.service $ journalctl -u horloge-taches-horaires.timer
(pour quitter : touche q)
Conclusion
Pour un cas simple, nous avons remplacé cron par systemd. Nous avons d'abord créé une unité .service qui décrit la commande à lancer, puis une seconde unité .timer qui précise quand lancer l'unité .service
Cette méthode est-elle généralisable à des cas plus complexes ? La pierre angulaire de cette méthode est le paramètre OnCalendar= de l'unité .timer, qui indique quand agir. Sa syntaxe couvre de nombreux cas, aussi il est certainement possible de généraliser cette méthode, et de remplacer totalement cron par systemd.
Systemd permet également de lancer des unités selon des contraintes temporelles plus fines, par exemple : toutes les semaines et cinq minutes après le démarrage de l'ordinateur. Plus d'info sur ce lien.
Notons toutefois que systemd ne peut pas, à l'heure actuelle, remplacer anacron. En effet, systemd perd l'information « date de la dernière activation de l'unité machin » lors d'un arrêt de l'ordinateur. Voir cette question et la réponse apportée. Cette fonctionnalité arrivera peut-être dans une version future … En attendant, nous devrons nous contenter d'anacron pour les tâches planifiées sur des ordinateurs qui ne sont pas allumés en permanence.
Licence de ce tutoriel
Ce tutoriel est publié sous la licence « Do What the Fuck You Want to Public License ».
Ajout de juin 2014 : l'option Persistent=
Notons toutefois que systemd ne peut pas, à l'heure actuelle, remplacer anacron. En effet, systemd perd l'information « date de la dernière activation de l'unité machin » lors d'un arrêt de l'ordinateur.
Cela a changé récemment : depuis sa version 212, systemd offre l'option Persistent= qui enregistre sur le disque dur la date de dernière exécution du service associé au timer. Ainsi, même lorsque l'ordinateur a été redémarré, systemd connaît la date de la dernière activation de l'unité.
Pour un service démarré périodiquement (c'est le cas qui nous intéresse ici) : si une occurrence du service n'a pas été démarrée au moment prévu parce que l'ordinateur était éteint à ce moment-là, le service sera lancé au prochain démarrage de l'ordinateur.
Voici deux exemples d'unité timer utilisant l'option Persistent= :
$ cat /lib/systemd/system/updatedb.timer
[Unit] Description=Lancement quotidien de l'unité updatedb.service [Timer] Unit=updatedb.service OnCalendar=daily Persistent=true
$ cat /etc/systemd/system/mon-ntpd-personnel.timer
[Unit] Description=Lancement hebdomadaire de l'unité mon-ntpd-personnel.service [Timer] Unit=mon-ntpd-personnel.service OnCalendar=weekly Persistent=true AccuracySec=12h [Install] WantedBy=multi-user.target
Remarque : pour en savoir plus sur l'option AccuracySec=.
L'option Persistent= permet de déclencher des évènements périodiques, y compris pour des machines qui ne sont pas allumées en permanence. Systemd peut maintenant remplacer anacron.