Why do I recommend using systemd timer instead of cronjob?

This article was last updated on: July 24, 2024 am

overview

The other day I was using Terraform + cloud-init to batch initialize my lab Linux machines. It happened to be found that some timing scenarios need to use cronjob, further understand that systemd timer can completely replace cronjob, and systemd timer has some very interesting features.

Back to the topic: Why do I recommend using systemd timer instead of cronjob? Because systemd timer has these advantages over cronjob:

  • Can cover all features of CronJob
  • Unified logs are collected into systemd logs
  • Configuration items for more detailed time accuracy
  • In addition to timed scenarios, event-based triggering is also supported
  • More flexible syntax than cronjob
  • Richer set of usage/O&M commands

Let’s introduce them one by one.

First of all, we familiarize ourselves with this new gadget through the timer that comes with the system.

The system comes with a timer

When Ubuntu or any systemd-based distribution is installed on a new system, it creates several timers as part of the system maintenance program in the background of any Linux host. These timers trigger events required for common maintenance tasks, such as updating system databases, cleaning temporary directories, cutting log files, and so on.

We usesystemctl status *timerCommand to list all timers on my console:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
casey@casey-Virtual-Machine:~$ systemctl status *timer
● plocate-updatedb.timer - Update the plocate database daily
Loaded: loaded (/lib/systemd/system/plocate-updatedb.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2023-04-04 16:49:49 CST; 19s ago
Trigger: Wed 2023-04-05 00:40:16 CST; 7h left
Triggers: ● plocate-updatedb.service

4 月 04 16:49:49 casey-Virtual-Machine systemd[1]: Started Update the plocate database daily.

● fwupd-refresh.timer - Refresh fwupd metadata regularly
Loaded: loaded (/lib/systemd/system/fwupd-refresh.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2023-04-04 16:49:49 CST; 19s ago
Trigger: Wed 2023-04-05 01:54:51 CST; 9h left
Triggers: ● fwupd-refresh.service

4 月 04 16:49:49 casey-Virtual-Machine systemd[1]: Started Refresh fwupd metadata regularly.

● update-notifier-motd.timer - Check to see whether there is a new version of Ubuntu available
Loaded: loaded (/lib/systemd/system/update-notifier-motd.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2023-04-04 16:49:50 CST; 19s ago
Trigger: Sat 2023-04-08 03:19:02 CST; 3 days left
Triggers: ● update-notifier-motd.service

4 月 04 16:49:50 casey-Virtual-Machine systemd[1]: Started Check to see whether there is a new version of Ubuntu available.

● fstrim.timer - Discard unused blocks once a week
Loaded: loaded (/lib/systemd/system/fstrim.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2023-04-04 16:49:49 CST; 19s ago
Trigger: Tue 2023-04-04 17:58:23 CST; 1h 8min left
Triggers: ● fstrim.service
Docs: man:fstrim

4 月 04 16:49:49 casey-Virtual-Machine systemd[1]: Started Discard unused blocks once a week.
...

Each timer has at least six lines of information associated with it:

  • The first line is the file name of the timer and a short description of its purpose.
  • The second line shows the status of the timer, whether it was loaded, the full path to the timer unit file, and the vendor’s preset.
  • The third line shows its activity status, including the date and time the timer started the activity.
  • The fourth line contains the date and time when the timer is next fired, and until the trigger occursapproximatelyTime.
  • The fifth line shows the name of the event or service triggered by the timer.
  • Some, but not all, systemd unit files have pointers to related documents. As above Docs: man:fstrim
  • The last line is the log entry for the most recent instance of the service triggered by the timer.

Create a timer

One advantage: Unified logs are collected into systemd logs

To learn about the timer faster, we created our own service unit and timer unit to trigger.

The specific purpose: Tailscale is updated regularly every week.

First, create the tailscale update service, as follows:

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Tailscale update
Wants=tailscale-weekly-update.timer

[Service]
Type=oneshot
ExecStart=/usr/bin/tailscale update -yes

[Install]
WantedBy=multi-user.target

Then, create a tailscale update timer, as follows:

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Tailscale update
Requires=tailscale-weekly-update.service

[Timer]
Unit=tailscale-weekly-update.service
OnCalendar=weekly

[Install]
WantedBy=timers.target

Finally, enable the timer:

1
systemctl enable tailscale-weekly-update.timer 

So that’s fine, but for demonstration, execute:systemctl start tailscale-weekly-update.service Run it manually once.

The output is integrated directly into the systemd log and can be passed through journalctl View: (contains logs of manual execution, and logs of subsequent automatic periodic executions)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ sudo journalctl -S "2023-03-29 00:00:00" -u tailscale-weekly-update.service
4 月 02 09:14:28 casey-Virtual-Machine systemd[1]: Starting Tailscale node agent...
4 月 02 09:14:30 casey-Virtual-Machine tailscale[6898]: 获取:1 https://pkgs.tailscale.com/stable/ubuntu jammy InRelease
4 月 02 09:14:30 casey-Virtual-Machine tailscale[6898]: 获取:2 https://pkgs.tailscale.com/stable/ubuntu jammy/main amd64 Packages [7,853 B]
4 月 02 09:14:32 casey-Virtual-Machine tailscale[6898]: 已下载 13.9 kB,耗时 1 秒 (14.4 kB/s)
4 月 02 09:14:32 casey-Virtual-Machine tailscale[6898]: 正在读取软件包列表。..
4 月 02 09:14:33 casey-Virtual-Machine tailscale[7101]: 正在读取软件包列表。..
4 月 02 09:14:33 casey-Virtual-Machine tailscale[7101]: 正在分析软件包的依赖关系树。..
4 月 02 09:14:33 casey-Virtual-Machine tailscale[7101]: 正在读取状态信息。..
4 月 02 09:14:33 casey-Virtual-Machine tailscale[7101]: 下列软件包将被升级:
4 月 02 09:14:33 casey-Virtual-Machine tailscale[7101]: tailscale
4 月 02 09:14:34 casey-Virtual-Machine tailscale[7101]: 升级了 1 个软件包,新安装了 0 个软件包,要卸载 0 个软件包,有 4 个软件包未被升级。
4 月 02 09:14:34 casey-Virtual-Machine tailscale[7101]: 需要下载 23.0 MB 的归档。
4 月 02 09:14:34 casey-Virtual-Machine tailscale[7101]: 解压缩后将会空出 1,024 B 的空间。
4 月 02 09:14:34 casey-Virtual-Machine tailscale[7101]: 获取:1 https://pkgs.tailscale.com/stable/ubuntu jammy/main amd64 tailscale amd64 1.38.3 [23.0 MB]
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7115]: debconf: 无法初始化前端界面:Dialog
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7115]: debconf: (系统未设定 TERM 环境变量,所以对话框界面将不可使用。)
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7115]: debconf: 返回前端界面:Readline
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7115]: debconf: 无法初始化前端界面:Readline
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7115]: debconf: (这个界面要求可控制的 tty。)
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7115]: debconf: 返回前端界面:Teletype
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7115]: dpkg-preconfigure: 重新开启标准输入失败:
4 月 02 09:15:13 casey-Virtual-Machine tailscale[7101]: 已下载 23.0 MB,耗时 40 秒 (577 kB/s)
4 月 02 09:15:14 casey-Virtual-Machine tailscale[7101]: [729B blob data]
4 月 02 09:15:14 casey-Virtual-Machine tailscale[7101]: 准备解压 .../tailscale_1.38.3_amd64.deb ...
4 月 02 09:15:14 casey-Virtual-Machine tailscale[7101]: 正在解压 tailscale (1.38.3) 并覆盖 (1.38.2) ...
4 月 02 09:15:15 casey-Virtual-Machine tailscale[7101]: 正在设置 tailscale (1.38.3) ...
4 月 02 09:15:23 casey-Virtual-Machine tailscale[7325]: Running kernel seems to be up-to-date.
4 月 02 09:15:23 casey-Virtual-Machine tailscale[7325]: Services to be restarted:
4 月 02 09:15:23 casey-Virtual-Machine tailscale[7325]: systemctl restart tailscale-weekly-update.service
4 月 02 09:15:23 casey-Virtual-Machine tailscale[7325]: No containers need to be restarted.
4 月 02 09:15:23 casey-Virtual-Machine tailscale[7325]: No user sessions are running outdated binaries.
4 月 02 09:15:23 casey-Virtual-Machine tailscale[7325]: No VM guests are running outdated hypervisor (qemu) binaries on this host.
4 月 02 09:15:24 casey-Virtual-Machine systemd[1]: tailscale-weekly-update.service: Deactivated successfully.
4 月 02 09:15:24 casey-Virtual-Machine systemd[1]: Finished Tailscale node agent.
4 月 02 09:15:24 casey-Virtual-Machine systemd[1]: tailscale-weekly-update.service: Consumed 6.317s CPU time.

$ sudo journalctl -S "2023-03-29 00:00:00" -u tailscale-weekly-update.timer
4 月 02 09:14:28 casey-Virtual-Machine systemd[1]: Started Tailscale node agent.
4 月 02 20:01:52 casey-Virtual-Machine systemd[1]: tailscale-weekly-update.timer: Deactivated successfully.
4 月 02 20:01:52 casey-Virtual-Machine systemd[1]: Stopped Tailscale node agent.

As shown in the above log, it is convenient to check the status of the timer and service.

In terms of logging, you don’t need to do anything special to make ittailscale-weekly-update.service unitExecStartof the triggerSTDOUTStored in logs. This is all part of running the service with systemd.

Systemd timer time precision

One of the advantages: more detailed configuration items for time accuracy

From the above log, if you look closely, the timer will not be there:00It is accurate in seconds, not even within one minute of the previous instance. This is intentional, but its default configuration can be overridden if necessary.

The reason for this behavior is to prevent multiple services from being triggered at exactly the same time. For example, you can use time specifications such as weekly, daily, and so on. These shortcuts are defined to fire at 00:00:00 on the day they are triggered. When multiple timers are specified in this way, it is likely that they will attempt to start at the same time.

The systemd timer is intentionally designed to trigger randomly for a specified time to prevent simultaneous triggering. They trigger semi-randomly within a time window. according tosystemd.timerManual, this trigger time remains in a stable position relative to all other defined timer units.

Most of the time, this probabilistic trigger time is no problem. When tasks such as scheduled backups run, as long as they run during non-business hours, there is no problem. A system administrator can choose a definite start time, such as 01:05:00 in the typical CronJob specification, to not conflict with other tasks, but there is a wide range of time values to achieve this. One-minute randomness in startup time is usually irrelevant.

However, for some tasks, precise trigger times are an absolute requirement. For these tasks, you can do so by using the timer unit file Timer The section adds such a configuration to specify higher trigger time span accuracy (e.g. accuracy within one microsecond):

1
AccuracySec=1us

The time span can be used to specify the desired precision and define the time span for repeatability or one-time events. It can recognize the following units:

  • usec, us, µs
  • msec, ms
  • seconds, second, sec, s
  • minutes, minute, min, m
  • hours, hour, hr, h
  • days, day, d
  • weeks, week, w
  • months, month, M (defined as 30.44 days)
  • years, year, y (defined as 365.25 days)

/usr/lib/systemd/systemAll default timers specify a larger range of precision, because precise timing is not critical. Take a look at some of the specifications in the timer created by the system:

1
2
3
4
5
$ grep Accur /usr/lib/systemd/system/*timer
/usr/lib/systemd/system/fstrim.timer:AccuracySec=1h
/usr/lib/systemd/system/logrotate.timer:AccuracySec=1h
/usr/lib/systemd/system/plocate-updatedb.timer:AccuracySec=20min
/usr/lib/systemd/system/snapd.snap-repair.timer:AccuracySec=10min

Timer type

One of the advantages: In addition to timing scenarios, event-based triggering is also supported

Systemd Timer has other features that cron does not have, and cron is only triggered at a specific, repeated, real-time date and time. However, a timer can be configured to trigger after the system starts, or after startup, or at a specific time after a defined service unit is activated. These are calledMonotonicity timer。 Monotony refers to a continuously increasing count or sequence. These timers are not persistent because they are reset after each startup.

Table 1 lists the monotonous timers and a short definition of each timer, as well as the “OnCalendar” timer, which is not monotonic and is used to specify future times, which may or may not be repeated.

| Timer | Monotonicity | definition |
| ----- | ------------ | ---------- ||
| OnActiveSec= | X | This defines a timer relative to the moment the timer is activated. |
| OnBootSec= | X | This defines a timer relative to the machine startup time. |
| OnStartupSec= | X | This defines a timer relative to the time when the Service Manager first started. For system timer units, this is the same as OnBootSec=Very similar, because the System Services Manager usually starts very early at startup. It is primarily useful when configuring cells that run in each User Services Manager, because the User Services Manager is generally started only at first logon, not at startup |
| OnUnitActiveSec= | X | This defines the time when the timer to be activated was last activated relative to the time to be activated. |
| OnUnitInactiveSec= | X | This defines a timer relative to when the timer to be activated was last deactivated. |
| OnCalendar= | | This defines a real-time timer with a calendar event expression. For more information about the syntax of calendar event expressions, see systemd.time(7)。 Otherwise, its semantics are the same as OnActiveSec=Similar to related settings. This timer is most like those timers used with cron services. |

Table 1: Systemd Timer Definitions

The monotonic timer time span can be used with the previous mentionedAccuracySecstatements have the same shortcut names, but systemd normalizes these names to seconds. For example, you might want to specify a timer that triggers an event 5 days after the system starts, something like this: OnBootSec=5d。 If the host is in2020-06-15 09:45:27Start, timer will be2020-06-20 09:45:27or trigger within one minute of the future.

Calendar event definition

One advantage: more flexible syntax than cronjob

The Calendar event definition is the key part that triggers the timer at the desired repeat time. Take a look firstOnCalendarSome of the specifications used in the setup.

Systemd and its timer use different time and date specifications than those used in crontab. It’s more than crontabflexible, allowed toatThe way the command obfuscates the date and time.

useOnCalendar=The basic format of systemdtimer isDOW YYYY-MM-DD HH:MM:SS。 DOW (day of the week) is optional, and other fields can use an asterisk (*) to match any value at that location. All calendar time forms are converted to normalized forms. If no time is specified, it is assumed to be 00:00:00. If no date is specified but a time is specified, then the next match may be today or tomorrow, depending on the current time. Names or numbers can be used for months and weeks. You can specify a comma-separated list of each unit. The unit range can be used between the start and end values...to specify.

There are several interesting options for specifying dates. The tilde (~) can be used to specify the last day of the month or the specified number of days before the last day of the month. “/” can be used to specify the day of the week as a modifier.

Here are some inOnCalendarExamples of typical time specifications used in statements:

Calendar event definition Description
DOW YYYY-MM-DD HH:MM:SS
*-*-* 00:15:30 Every day of each month of the year, 15 minutes and 30 seconds after midnight.
Weekly Every Monday at 00:00:00
Mon *-*-* 00:00:00 Same as weekly
Mon Same as weekly
Wed 2020-*-* Every Wednesday in 2020, 00:00:00
Mon..Fri 2021-*-* 00:00:00
2023-6,7,8-1,15 01:15:00 1st and 15th June, July and August 2023 01:15:00 AM
Mon *-05~03 The next Monday in May of any year is also the third day at the end of the month.
Mon..Fri *-08~04 The 4th day before the end of August in any year, or the end of August if that day is also a working day.
*-05~03/2 The third day from the end of May and two days later. It is repeated every year. Note that this expression uses (~).
*-05-03/2 The third day in May and then every other day for the rest of May. Repeat once a year. Note that this expression uses a dash (-).

Table 2: ExamplesOnCalendar Event definition

Test the calendar definition

One advantage: a richer set of usage/operation and maintenance commands

systemd provides a good tool to validate and check the calendar time event specification in the timer.systemd-analyze calendarThe tool parses a calendar time event specification and provides a normalized form as well as other interesting information, such as the date and time of the next “elapse” (i.e. match) and the approximate time before the trigger time is reached.

First, look at a date in the future that has no time:

1
2
3
4
5
6
$ systemd-analyze calendar 2030-06-17
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 CST
(in UTC): Sun 2030-06-16 16:00:00 UTC
From now: 7 years 2 months left

Now add a time. In this example, the date and time are analyzed separately as unrelated entities:

1
2
3
4
5
6
7
8
9
10
11
12
$ systemd-analyze calendar 2030-06-17 15:21:16
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 CST
(in UTC): Sun 2030-06-16 16:00:00 UTC
From now: 7 years 2 months left

Original form: 15:21:16
Normalized form: *-*-* 15:21:16
Next elapse: Wed 2023-04-05 15:21:16 CST
(in UTC): Wed 2023-04-05 07:21:16 UTC
From now: 21h left

To parse dates and times as a unit, enclose them in quotation marks.

1
2
3
4
5
$ systemd-analyze calendar "2030-06-17 15:21:16"
Normalized form: 2030-06-17 15:21:16
Next elapse: Mon 2030-06-17 15:21:16 CST
(in UTC): Mon 2030-06-17 07:21:16 UTC
From now: 7 years 2 months left

Now test the entries in Table 2. Choose a complex one:

1
2
3
4
5
6
$ systemd-analyze calendar "2023-6,7,8-1,15 01:15:00"
Original form: 2023-6,7,8-1,15 01:15:00
Normalized form: 2023-06,07,08-01,15 01:15:00
Next elapse: Thu 2023-06-01 01:15:00 CST
(in UTC): Wed 2023-05-31 17:15:00 UTC
From now: 1 month 26 days left

Let’s look at an example where we list the next five execution times of the timestamp expression:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ systemd-analyze calendar --iterations=5 "Mon *-05~3"
Original form: Mon *-05~3
Normalized form: Mon *-05~03 00:00:00
Next elapse: Mon 2023-05-29 00:00:00 CST
(in UTC): Sun 2023-05-28 16:00:00 UTC
From now: 1 month 23 days left
Iter. #2: Mon 2028-05-29 00:00:00 CST
(in UTC): Sun 2028-05-28 16:00:00 UTC
From now: 5 years 1 month left
Iter. #3: Mon 2034-05-29 00:00:00 CST
(in UTC): Sun 2034-05-28 16:00:00 UTC
From now: 11 years 1 month left
Iter. #4: Mon 2045-05-29 00:00:00 CST
(in UTC): Sun 2045-05-28 16:00:00 UTC
From now: 22 years 1 month left
Iter. #5: Mon 2051-05-29 00:00:00 CST
(in UTC): Sun 2051-05-28 16:00:00 UTC
From now: 28 years 1 month left

This should give you enough information to start testing yoursOnCalendarTime specification.

summary

Systemd Timer can be used to perform the same types of tasks as cron tools, but provides more flexibility in terms of calendar and monotonous timing specifications that trigger events.

In addition, the advantages of systemd timer include:

  • Unified logs are collected into systemd logs
  • Configuration items for more detailed time accuracy
  • Richer set of usage/O&M commands

Try migrating your cronjob to systemd timer~ 😛😛😛

Resources


Why do I recommend using systemd timer instead of cronjob?
https://e-whisper.com/posts/20484/
Author
east4ming
Posted on
April 4, 2023
Licensed under