Remplacer les tâches Cron par les timers SystemD

Remplacer les tâches Cron par les timers SystemD

Orchestrer ses jobs avec Cron a ses limites : son environnement d'exécution n'est pas toujours bien configuré, la granularité du temps s'arrête à la minute etc... Regardons comment remplacer un job Cron par un timer SystemD en le déployant avec Ansible !

Intérêt

Les timers SystemD et les jobs Cron sont deux mécanismes différents pour planifier l'exécution de tâches sur un système Linux.
Les avantages d'utiliser un timer SystemD sont :

  • Leur intégration à SystemD ce qui centralise sa gestion.
  • Ils offrent une granularité de temps plus fine que les jobs Cron, avec une précision pouvant aller jusqu'à la seconde.
  • Ils permettent de spécifier des conditions d'activation, telles que l'arrêt d'un autre service, la présence d'un fichier ou la connexion à un réseau.
  • De disposer de ses propres journaux de logs par timer avec journalctl.

Comme les timers SystemD peuvent être amenés à être déployés en masse, regardons comment déployer un timer SystemD avec Ansible.

Le rôle Ansible

Notre rôle Ansible est très court et repose principalement sur deux templates Jinja2.

💡
Si vous ne souhaitez pas déployer votre timer SystemD avec Ansible, il est tout à fait possible de créer les fichiers manuellement. Il s'agira de créer les templates finaux et exécuter les deux commandes suivantes :
systemctl daemon-reload
systemctl enable --now backup.timer

Voici à quoi ressemble l'arborescence de notre rôle Ansible :

.
└── timer.yml/
    ├── timer/
    ├── ├── defaults
    ├── │   └── main.yml
    ├── ├── tasks
    ├── │   └── main.yml
    └── └── templates/
            ├── backup.service.j2
            └── backup.timer.j2

Pour l'exemple, nous allons partir sur l'orchestration d'une commande rsync tous les jours à 7h du matin.
L’équivalent du job Cron correspond au contenu de ce fichier placé dans le répertoire /etc/cron.d :

0 7 * * * root /usr/bin/rsync -avz --delete /var/www/html/ user@remote-server:/backup/

Les variables

Nous allons utiliser des variables qui serviront principalement à notre template Jinja2. En effet, dans le cas d'un changement, il est préférable de modifier un fichier de variables plutôt qu'un template Jinja2 qui peut être parfois complexe.

Créons notre fichier defaults/main.yml :

---
backup:
  path: /usr/bin/rsync

  source:
    folder: /var/www/html/

  destination:
    folder: /backup/
    user: user
    server: remote-server

  service:
    name: backup.service
  timer:
    name: backup.timer
    orchestration: 07:00:00

Vous avez remarqué que mes variables sont ordonnées dans une liste, cela est plus lisible que des variables du type backup_path ou encore backup_source.

Les Templates Jinja2

Pour créer un timer SystemD, il faut créer deux fichiers. Le premier concerne le service SystemD. Je vous invite à consulter mon article dédié à SystemD afin de comprendre de quoi est composé un service SystemD :

Créer un service SystemD en quelques minutes sur Linux !
Place à SystemD, le remplaçant de System V init. Cet orchestrateur de processus sous forme de services est utilisé sur quasiment toutes les distributions Linux. Nous allons voir comment créer son propre service SystemD ! Rends-moi un service Avant de créer son propre service, analysons un service…

Place à la création du Template Jinja2 situé dans templates/backup.service.j2

[Unit]
Description=Backup Service

[Service]
ExecStart={{ backup.path }} -avz --delete {{ backup.source.folder }} {{ backup.destination.user }}@{{ backup.destination.server }}:{{ backup.destination.folder }}
Le rendu final de ce Template Jinja2 correspond à ceci :
[Unit]
Description=Backup Service

[Service]
ExecStart=/usr/bin/rsync -avz --delete /var/www/html/ user@remote-server:/backup/

Le second Template Jinja2 concerne cette fois-ci le timer SystemD. Il faut le voir comme l'équivalent de l'orchestrateur. C'est dans ce fichier que l'on va décrire la périodicité d'exécution du service SystemD ainsi que d'autres paramètres.

Passons à la création du fichier templates/backup.timer.j2 :

[Unit]
Description=Run Backup Service Daily

[Timer]
OnCalendar=*-*-* {{ backup.timer.orchestration }}
Persistent=true

[Install]
WantedBy=timers.target
Le rendu final de ce Template Jinja2 correspond à ceci :
[Unit]
Description=Run Backup Service Daily

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

[Install]
WantedBy=timers.target

Dans le bloc [Timer], la variable OnCalendar correspond à la périodicité d'exécution du service SystemD précédemment créé. C'est l'équivalent de ceci pour un job Cron :

0 7 * * *

Les tasks

Nous allons avoir deux tasks Ansible. Une qui va copier les deux templates Jinja2 sur les serveurs distants et une deuxième qui va activer le nouveau service SystemD ainsi que son timer.

Voici ce que donne le fichier tasks/main.yml :

- name: Configure systemd backup service
  template:
    src: "systemd/system/{{ item }}.j2"
    dest: "/etc/systemd/system/{{ item }}"
    owner: root
    group: root
    mode: '0755'
  loop:
    - "{{ backup.service.name }}"
    - "{{ backup.timer.name }}"

- name: Enable backup timer service
  systemd:
    name: "{{ backup.timer.name }}"
    daemon_reload: yes
    enabled: yes
    state: "started"
    masked: no

Il est important de noter que j'ai utilisé le module Ansible systemd et que j'ai défini la variable masked à no pour être sûr que le service SystemD ne sera pas masked.

Avec ce rôle Ansible, vous pourrez déployer autant de timers SystemD que vous le souhaitez !