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. :-D

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 ».

 Logo « 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.

1) Pour information : la commande run-parts /etc/cron.hourly lance tous les scripts contenus dans le répertoire /etc/cron.hourly/. Il n'y a qu'un seul script dans ce répertoire, qui se charge de lancer anacron. Celui-ci est configuré (via son fichier de configuration /etc/anacrontab) pour lancer des programmes de façon quotidienne, hebdomadaire ou mensuelle.
2) Le paramètre Unit= de notre unité .timer indique quelle unité .service doit lancer notre unité .timer. Si ce paramètre n'est pas renseigné, l'unité .timer lancera l'unité .service de même préfixe. Par exemple, tartempion.timer lancera tartempion.service (si tartempion.service existe, évidemment.)
 
remplacer_cron_par_systemd.txt · Dernière modification: Le 28/06/2014 à 14:49 par sylvainb     Haut de page
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki Design by Chirripó