Pythian Blog: Technical Track

Systemd timers as a replacement for Cron

Cron is not standard!

Most distributions treat cron as if it were a standard, but almost no two distributions actually use the same cron daemon. Debian uses Dillon’s Cron which places an emphasis on being lightweight. CentOS uses Vixie Cron for its SELinux support. Fedora, despite being in the REHL family, forked Vixie Cron to make Cronie, which is the recommended cron for Archlinux also. There are even more with their own unique use cases such as Bcron which tries to be more secure or the not-so-cronlike Anacron, which makes assumptions that the computer will be off. Some might be tempted to note that most cron daemons are mostly compatible, but there are quite a few differences between them. For instance, Debian’s Dillon’s Cron doesn’t even run the jobs with a shell or environment variables, which can easily break quite a few "standard" jobs. Beyond this, there are more specific things related to SELinux that have to be specified in order to work if Vixie Cron or Cronie are used. Due to this, we obviously need another cron implementation that satisfies everyone’s needs to become standard.

Systemd Timers

Systemd is the new standard for init systems, intended to replace the aging SysV init system. Since then it has added a ton of features that start to encroach on other Linux utilities. Systemd timers are one of those features. Systemd timers are a way of running service units based on time. These timer units have quite a bit of flexibility, leading them to be a potential replacement for cron. Here we will explore some potential setups with Systemd timers, pitfalls, and how they improve on traditional cron. Traditional cron has all jobs for a user in a single file called the crontab. This has a few problems with configuration management, such as multiple services that require cron entries have to coordinate between themselves to prevent stepping on each other. Also any changes are non-trivial to implement since it can be difficult to determine which line is the old version and if the line exists at all. Systemd timers are implemented in twp files per job: one file describing the timing of the job, and one describing the actual job to be executed. The timing is in a .timer file, and the job is in a .service file. Timer files are special within systemd, but the .service files associated with them are written in the same way as any other .service file. Here is an example, really simple timer job that implements running clamscan:
clamscan.timer clamscan.service
[Unit] Description=Run clamscan every day at 3:30AM [Timer] OnCalendar=*-*-* 03:30:00 [Install] WantedBy=timers.target [Unit] Description=Run a clamscan scan [Service] Type=oneshot ExecStart=/usr/bin/ionice -c 3 /usr/bin/nice \ -n 19 /usr/bin/clamscan --recursive \ --infected --exclude-dir='^/sys|^/dev' / \ User=root
To enable the job, run systemctl enable clamscan.timer. Note that you do not enable the .service, nor does the .service file have an install section. An administrator can run the job ad-hoc by running systemctl start clamscan.service. This is much easier for configuration management to maintain since there are separate files per job, and packages can install their own jobs and ensure they are enabled with no chance of stepping on existing jobs, or running the risk of double scheduling jobs. Since they are services, one can also have dependency resolution. For instance, a job that requires Apache to be running can add Requisite=apache2.service to the job, where the job will instantly fail of Apache is not started. The opposite can also be done with WantedBy=, making sure that a job runs when a service is started. This allows for easy management of periodic events, such as making sure clamscan runs when postfix is started, but the timer will be disabled alongside the postfix service when postfix is stopped. User units can be utilized to enable adding jobs without privileged access. They work the exact same as normal units, but are placed in ~/.config/systemd/user instead of /etc/systemd/system/.
Requisite Service WantedBy Timer
[Unit] Description=Fails when apache is not runnning Requisite=apache.service [Service] Type=oneshot ExecStart=/usr/bin/echo “Apache is running!” [Unit] Description=Scheduled when apache is started WantedBy=apache.service [Timer] OnCalendar=*-*-* 00:00:00 [Install] WantedBy=timers.target
Logging is also changed quite a bit. By default, any output from a cronjob is mailed to support logging. In systemd, the jobs are units, so they are logged to the syslog. This enables any integration that would normally be possible using Logstash or journalctl for instance. Logging to the syslog also means that the powerful features of journalctl can be used. Let’s assume that an admin is investigating a job that is suspected to have crashed the server 2 reboots ago. The admin can get granular access through a command like journalctl --boot=-2 --unit=thejob.service. Another common issue with cron is that similarly scheduled jobs need a gap between them to prevent overloading the system. In traditional cron, this requires the administrator to manually place the jobs far enough apart so that they will probably not be run at the same time. With systemd timers, a Conflicts= section can be added to ensure that this does not happen. Furthermore, a randomized delay can be added with RandomizedDelaySec= added to the timer file in the [Timer] section. This adds a randomized delay to the start of the job so that similarly scheduled jobs are further separated. Some cron allows for this using the RANDOM_DELAY environment variable, but that is a global value for all jobs. This gives Systemd timers more flexibility. Systemd timers do not only support time of day scheduling; they also support relative scheduling, allowing for jobs to be scheduled relative to other events such as boot or timer activation. This is done by specifying a relative time like 15min or 1y 12month on the relevant Sec entry such as OnBootSec or OnUnitActiveSec. Systemd timers also have more specific specification abilities, including precision down to the second and specification from the end of months allowing for more flexibility in time specifications. Since timers are also services, they get their own cgroup, meaning they can get all the features that come with it such as memory and cpu limits, and even more features such as IP accounting or IP blocklists. These are documented in systemd.resource-control(5) and are specified in the [Service] section.
A Systemd timer with memory limits
[Unit] Description=Fails when apache is not runnning Requisite=apache.service [Service] Type=oneshot ExecStart=/usr/bin/echo “I and all my children can only use 100M of memory!” MemoryLimit=100M
All in all these features make Systemd’s timers a powerful potential replacement for traditional cron. It offers several benefits for modern cloud deployments, and has enhanced management techniques. It supports a wide range of modern accounting and limitation features which help your system remain stable, even in the face of bugs and failures. Its cross distribution support means that jobs are portable, unlike traditional Cron. Although there is a learning curve, it is a powerful tool for any sysadmin in the modern cloud.

No Comments Yet

Let us know what you think

Subscribe by email