本文改编自: Use systemd timers instead of cronjobs

systemd timers 有着比 cronjob 更精细的事件控制。

我正在将我的 cron 作业转换为 systemd timers。systemd timers 有一些非常有趣的功能。

与其他 cron 作业一样,systemd timers 可以在指定的时间间隔内触发事件(shell 脚本和程序),例如每天一次,在一个月的特定日期(可能只有在星期一),或在工作时间每 15 分钟触发一次从早上 8 点到下午 6 点。定时器还可以做一些 cron 作业不能做的事情。例如,systemd timers 可以触发脚本或程序在某个事件(例如引导、启动、上一任务完成,甚至计时器调用的 service 单元上一完成)之后等待一段时间再运行。

系统维护计时器

当 Fedora 或任何基于 systemd 的发行版安装在新系统上时,它会创建多个计时器,这些计时器主要用于在后台对 Linux 主机进行系统维护。这些计时器会触发常见维护任务所需的事件,例如更新系统数据库、清理临时目录、轮换日志文件等。

例如,我们可以通过使用 systemctl status *timer 命令列出主机上的所有计时器。此命令中星号符号是通配符,因此此命令列出了所有 systemd 计时器单元:

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
43
44
45
46
47
48
49
50
51
52
53
54
# systemctl status *timer
---
● mlocate-updatedb.timer - Updates mlocate database every day
Loaded: loaded (/usr/lib/systemd/system/mlocate-updatedb.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left
Triggers: ● mlocate-updatedb.service

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Updates mlocate database every day.

● logrotate.timer - Daily rotation of log files
Loaded: loaded (/usr/lib/systemd/system/logrotate.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left
Triggers: ● logrotate.service
Docs: man:logrotate(8)
man:logrotate.conf(5)

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Daily rotation of log files.

● fstrim.timer - Discard unused blocks once a week
Loaded: loaded (/usr/lib/systemd/system/fstrim.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Mon 2020-06-08 00:00:00 EDT; 3 days left
Triggers: ● fstrim.service
Docs: man:fstrim

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Discard unused blocks once a week.

● sysstat-collect.timer - Run system activity accounting tool every 10 minutes
Loaded: loaded (/usr/lib/systemd/system/sysstat-collect.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Thu 2020-06-04 08:50:00 EDT; 41s left
Triggers: ● sysstat-collect.service

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Run system activity accounting tool every 10 minutes.

● dnf-makecache.timer - dnf makecache --timer
Loaded: loaded (/usr/lib/systemd/system/dnf-makecache.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Thu 2020-06-04 08:51:00 EDT; 1min 41s left
Triggers: ● dnf-makecache.service

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started dnf makecache –timer.

● systemd-tmpfiles-clean.timer - Daily Cleanup of Temporary Directories
Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-clean.timer; static; vendor preset: disabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Fri 2020-06-05 08:19:00 EDT; 23h left
Triggers: ● systemd-tmpfiles-clean.service
Docs: man:tmpfiles.d(5)
man:systemd-tmpfiles(8)

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Daily Cleanup of Temporary Directories.

每个计时器至少有六行与之相关的信息:

  • 第一行是计时器的文件名和对其用途的简短描述。
  • 第二行显示计时器的状态、是否已加载、计时器单元文件的完整路径以及供应商预设。
  • 第三行指示其活动状态,其中包括计时器变为活动状态的日期和时间。
  • 第四行包含下一次触发计时器的日期和时间,以及触发发生前的大致时间。
  • 第五行显示由计时器触发的事件或服务的名称。
  • 一些(但不是全部)systemd 单元文件包含指向相关文档的指针。我的虚拟机输出中的三个计时器具有指向文档的指针。这是一个不错的(但可选的)数据位。
  • 最后一行是由计时器触发的最近服务实例的日志条目。

您的主机有可能列出一组完全不同的计时器单元,你可以自行查看研究一下。

创建计时器

虽然我们可以解构一个或多个现有的计时器来了解它们的工作原理,但最好通过自己创建一个计时器单元来学习计时器的工作原理。为了保持简单,我们将使用一个相当简单的例子。完成此操作后,将更容易理解其他计时器的工作方式并确定它们在做什么。

首先,创建一个简单的服务来运行一些基本的东西,比如 free 命令。例如,您可能希望定期监视空闲内存。在 /etc/systemd/system 目录中创建以下 myMonitor.service 单元文件。它不需要是可执行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# This service unit is for testing timer units
# By David Both
# Licensed under GPL V2
#

[Unit]
Description=Logs system statistics to the systemd journal
Wants=myMonitor.timer

[Service]
Type=oneshot
ExecStart=/usr/bin/free

[Install]
WantedBy=multi-user.target

这是就是一个最简单的 service 单元的示例。现在让我们查看状态并测试我们的 service 单元,以确保它按预期工作。

1
2
3
4
5
6
7
8
# systemctl status myMonitor.service
---
● myMonitor.service - Logs system statistics to the systemd journal
Loaded: loaded (/etc/systemd/system/myMonitor.service; disabled; vendor preset: disabled)
Active: inactive (dead)

# systemctl start myMonitor.service
---

默认情况下,systemd service 单元运行的程序的标准输出 (STDOUT) 被发送到 systemd 日志,它会留下您现在或以后可以查看的记录——直到某个时间点。现在我们来查看专门针对您的服务单位和仅针对今天的日志。使用 -S 参数,-S--since 的缩写,该参数可以查看您指定的时间段的日志。如果你的主机已经运行了很长时间将可能会积累了大量的日志条目,使用 -S 参数可以缩短在日志上的搜索时间:

1
2
3
4
5
6
7
# journalctl -S today -u myMonitor.service
-- Logs begin at Mon 2020-06-08 07:47:20 EDT, end at Thu 2020-06-11 09:40:47 EDT. --
Jun 11 09:12:09 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 11 09:12:09 testvm1.both.org free[377966]: total used free shared buff/cache available
Jun 11 09:12:09 testvm1.both.org free[377966]: Mem: 12635740 522868 11032860 8016 1080012 11821508
Jun 11 09:12:09 testvm1.both.org free[377966]: Swap: 8388604 0 8388604
Jun 11 09:12:09 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.

由服务触发的任务可以是单个程序、一系列程序或用任何脚本语言编写的脚本。通过将以下行添加到 myMonitor.service 单元文件的 [Service] 部分的末尾,向服务添加另一个任务:

1
ExecStart=/usr/bin/lsblk

再次启动服务并检查日志以获取结果,结果应如下所示。您应该会在日志中看到这两个命令的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Jun 11 15:42:18 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 11 15:42:18 testvm1.both.org free[379961]: total used free shared buff/cache available
Jun 11 15:42:18 testvm1.both.org free[379961]: Mem: 12635740 531788 11019540 8024 1084412 11812272
Jun 11 15:42:18 testvm1.both.org free[379961]: Swap: 8388604 0 8388604
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: sda 8:0 0 120G 0 disk
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─sda1 8:1 0 4G 0 part /boot
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: └─sda2 8:2 0 116G 0 part
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: sr0 11:0 1 1024M 0 rom
Jun 11 15:42:18 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 11 15:42:18 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.

现在您知道您的服务按预期工作,在 /etc/systemd/system 中创建计时器单元文件 myMonitor.timer,并添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# This timer unit is for testing
# By David Both
# Licensed under GPL V2
#

[Unit]
Description=Logs some system statistics to the systemd journal
Requires=myMonitor.service

[Timer]
Unit=myMonitor.service
OnCalendar=*-*-* *:*:00

[Install]
WantedBy=timers.target

myMonitor.timer 文件中的 OnCalendar 条目为 --* ::00 意思是应触发计时器每分钟执行一次 myMonitor.service 单元。我将在本文稍后部分探讨 OnCalendar 设置。

现在,当它被计时器触发时,观察与运行服务有关的任何日志条目。您也可以关注计时器,但关注服务可以让您近乎实时地查看结果。使用 -f(跟随)选项运行 journalctl

1
2
3
# journalctl -S today -f -u myMonitor.service
---
-- Logs begin at Mon 2020-06-08 07:47:20 EDT. --

启动 myMonitor.timer 定时器,看看它运行一段时间后会发生什么:

1
# systemctl start myMonitor.timer

一个结果会立即出现,而下一个结果每隔一分钟就会出现。看几分钟日志,你就会发现 myMonitor.service 在重复工作:

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
43
44
45
46
47
48
49
50
51
52
53
54
# journalctl -S today -f -u myMonitor.service
---
-- Logs begin at Mon 2020-06-08 07:47:20 EDT. --
Jun 13 08:39:18 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:39:18 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:39:19 testvm1.both.org free[630566]: total used free shared buff/cache available
Jun 13 08:39:19 testvm1.both.org free[630566]: Mem: 12635740 556604 10965516 8036 1113620 11785628
Jun 13 08:39:19 testvm1.both.org free[630566]: Swap: 8388604 0 8388604
Jun 13 08:39:18 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: sda 8:0 0 120G 0 disk
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─sda1 8:1 0 4G 0 part /boot
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: └─sda2 8:2 0 116G 0 part
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: sr0 11:0 1 1024M 0 rom
Jun 13 08:40:46 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:40:46 testvm1.both.org free[630572]: total used free shared buff/cache available
Jun 13 08:40:46 testvm1.both.org free[630572]: Mem: 12635740 555228 10966836 8036 1113676 11786996
Jun 13 08:40:46 testvm1.both.org free[630572]: Swap: 8388604 0 8388604
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: sda 8:0 0 120G 0 disk
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─sda1 8:1 0 4G 0 part /boot
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: └─sda2 8:2 0 116G 0 part
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: sr0 11:0 1 1024M 0 rom
Jun 13 08:40:46 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:40:46 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
Jun 13 08:41:46 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:41:46 testvm1.both.org free[630580]: total used free shared buff/cache available
Jun 13 08:41:46 testvm1.both.org free[630580]: Mem: 12635740 553488 10968564 8036 1113688 11788744
Jun 13 08:41:46 testvm1.both.org free[630580]: Swap: 8388604 0 8388604
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: sda 8:0 0 120G 0 disk
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─sda1 8:1 0 4G 0 part /boot
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: └─sda2 8:2 0 116G 0 part
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: sr0 11:0 1 1024M 0 rom
Jun 13 08:41:47 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:41:47 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.

请务必仔细查看计时器和服务的状态。

您在日志中至少可以注意到两件事。首先,您无需执行任何特殊操作即可将 myMonitor.service 单元中 ExecStart 触发器的 STDOUT 存储在日志中。这就是使用 systemd 运行服务的好处。但是,你需要注意从 service 单元运行的程序会生成多少 STDOUT

第二件事是计时器不会精确的在 :00 秒的时候,或者是前一个实例完成后的那一瞬间触发。这是开发者故意设计成这样的,不过如有必要,你可以强制设定为精确时间触发。

这种设计的原因是为了防止多个服务同时触发。例如,您可以使用每周、每日等时间规范。这些简单的设置都定义为在触发当天的 00:00:00 时触发。当以这种方式设置了多个计时器时,它们很可能会尝试同时启动。

systemd 计时器有意设计为在指定时间左右随机触发,以防止同时触发。它们会在从指定触发时间开始到指定时间加一分钟结束的时间窗口内随机的触发。根据 systemd.timer 手册页,此触发时间相对于所有其他定义的计时器单元保持在稳定位置。您可以在上面的日志条目中看到计时器在启动时立即触发,然后在每分钟后大约 46 或 47 秒触发。

大多数情况下,这种概率触发时间很好。在安排运行备份等任务时,只要它们在非工作时间运行,就不会有问题。系统管理员可以选择确定性的开始时间,例如典型 cron 作业规范中的 01:05:00,以免与其他任务发生冲突,但其实大多数情况下你都不需要在那么精确的时间触发你的任务。因此触发开始时间有一分钟的随机性通常是无关紧要的。

但是,对于某些任务,确切的触发时间是绝对要求。对于那些,您可以通过将这样的语句添加到计时器单元文件的 Timer 部分来指定更高的触发时间跨度精度(在微秒内):

1
AccuracySec=1us

时间颗粒度可用于指定所需的准确度以及定义重复或一次性事件的时间跨度。它识别以下单位:

  • 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 (被定义为 30.44 天)
  • years, year, y (被定义为 365.25 天)

/usr/lib/systemd/system 中的所有默认计时器都指定了更大的精度范围,因为太过精确的时间并不重要。查看系统创建的计时器中的一些规范:

1
2
3
4
5
6
7
8
# 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/logwatch.timer:AccuracySec=12h
/usr/lib/systemd/system/mlocate-updatedb.timer:AccuracySec=24h
/usr/lib/systemd/system/raid-check.timer:AccuracySec=24h
/usr/lib/systemd/system/unbound-anchor.timer:AccuracySec=24h

你可以尝试查看 /usr/lib/systemd/system 目录中一些计时器单元文件的完整内容,了解它们是如何构建的。

您创建的 service 文件不需要是可执行的。您也没有启用 service 单元,因为它是由计时器触发的。如果需要,您仍然可以从命令行手动触发 service 单元。试试看并观察日志。

有关计时器准确性、事件时间规范和触发事件的更多信息,请参阅 systemd.timer 和 systemd.time 的手册页。

定时器类型

systemd 定时器还有一些在 cron 中找不到的功能,cron 只在确定的、重复的、具体的日期和时间触发。systemd 定时器可以被配置成根据其他 systemd 单元状态发生改变时触发。举个例子,定时器可以配置成在系统开机、启动后,或是某个确定的服务单元激活之后的一段时间被触发。这些被称为单调计时器。“单调”指的是一个持续增长的计数器或序列。这些定时器不是持久的,因为它们在每次启动后都会重置。

以下表格列出了一些单调定时器以及每个定时器的简短定义,同时有 OnCalendar 定时器,这些不是单调的,它们被用于指定未来有可能重复的某个确定时间。这个信息来自于 systemd.timer 的手册页,有一些不重要的修改。

定时器 是否单调 定义
OnActiveSec= X 这定义了一个相对于另一个定时器被激活的时刻的定时器。
OnBootSec= X 这定义了一个与机器启动时间相关的计时器。
OnStartupSec= X 这定义了一个与服务管理器首次启动时间相关的计时器。对于系统计时器单元,这与 OnBootSec= 非常相似,因为系统服务管理器通常在启动时很早就启动。当在每用户服务管理器中运行的单元中配置时,它十分有用,因为用户服务管理器通常仅在第一次登录时启动,而不是在引导期间启动。
OnUnitActiveSec= X 这定义了一个与要激活的计时器上次激活时间相关的计时器。
OnUnitInactiveSec= X 这定义了一个与要激活的计时器上次停用时间相关的计时器。
OnCalendar= 使用日历事件表达式定义实时(即挂钟)计时器。有关日历事件表达式语法的更多信息,请参阅 systemd.time(7)。否则,语义类似于 OnActiveSec= 和相关设置。这个计时器与 cron 服务中使用的计时器最相似。

单调计时器可以使用与前面提到的 AccuracySec 语句相同的时间颗粒度的简写,但 systemd 将这些名称标准化为秒。例如,您可能希望指定一个计时器,在系统启动五天后触发一次事件;可能看起来像:OnBootSec=5d。如果主机在 2020-06-15 09:45:27 启动,则计时器将在 2020-06-20 09:45:27 或之后的一分钟内触发。

日历事件格式

日历事件格式是在所需的重复时间触发计时器的关键部分。首先查看一些与 OnCalendar 设置一起使用的规范。

systemd 及其计时器使用与 crontab 中使用的格式不同的时间和日期规范样式。它比 crontab 更灵活,并允许以 at 命令的方式模糊日期和时间。它也应该足够熟悉,易于理解。

使用 OnCalendar= 的 systemd 计时器的基本格式是 DOW YYYY-MM-DD HH:MM:SS。 DOW(星期几)是可选的,其他字段可以使用星号 (*) 来匹配该位置的任何值。所有日历时间形式都转换为规范化形式。如果未指定时间,则假定为 00:00:00。如果未指定日期但指定了时间,则下一场启动可能是今天或明天,具体取决于当前时间。名称或数字可用于月份和星期几。可以指定每个单元的逗号分隔列表。单位范围可以用 .. 在开始值和结束值之间指定。

有几个用于指定日期的有趣选项。波浪号 (~) 可用于指定该月的最后一天或该月最后一天之前的指定天数。 “/”可用于指定一周中的某一天作为修饰符。

以下是 OnCalendar 语句中使用的一些典型时间规范的一些示例。

日历事件格式 描述
DOW YYYY-MM-DD HH:MM:SS
--* 00:15:30 每年每个月的每一天,午夜后 15 分 30 秒
Weekly 每周一 00:00:00
Mon --* 00:00:00 与 Weekly 相同
Mon 与每周相同
Wed 2020-- 2020 年每周三 00:00:00
Mon…Fri 2021-- 2021 年每个工作日 00:00:00
2022-6,7,8-1,15 01:15:00 2022年6月、7月、8月1日、15日 01:15:00
Mon *-05~03 任何一年中5月的倒数第三天,并且这天需要是星期一
Mon…Fri *-08~04 任何年份的8月底之前的第4 天并且该日期需要是周一到周五
*-05~03/2 5月倒数第3天,2天后再次。每年都会重复。请注意,此表达式使用波浪号 (~)
*-05-03/2 5月的第三天,然后在5月剩下的时间里每2天重复一次。每年都会重复。请注意,此表达式使用破折号 (-)

测试日历格式

systemd 提供了一个绝佳的工具用于检测和测试定时器中日历时间事件的格式。即systemd-analyze calendar 工具,它可以提供标准格式和其他有趣的信息,例如下次“经过”(即匹配)的日期和时间,以及距离下次触发之前大概时间。

首先,在有日期没有时间的情况下查看未来的日期(请注意,下一个经过的时间和UTC的时间基于您当地的时区):

1
2
3
4
5
6
7
# 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 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left

现在添加一个时间,在这个例子中,日期和时间是当作无关的两个部分分开解析的:

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 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left
Original form: 15:21:16
Normalized form: *-*-* 15:21:16
Next elapse: Mon 2020-06-15 15:21:16 EDT
(in UTC): Mon 2020-06-15 19:21:16 UTC
From now: 3h 55min left

为了把日期和时间当作一个单元来分析,可以把它们包在引号里。但是你在定时器单元里 OnCalendar= 时间格式中使用的时候记得把引号去掉,否则会报错:

1
2
3
4
5
6
# 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 EDT
(in UTC): Mon 2030-06-17 19:21:16 UTC
From now: 10 years 0 months left

现在我们测试下上表中间里的例子:

1
2
3
4
5
6
7
# systemd-analyze calendar "2022-6,7,8-1,15 01:15:00"
---
Original form: 2022-6,7,8-1,15 01:15:00
Normalized form: 2022-06,07,08-01,15 01:15:00
Next elapse: Wed 2022-06-01 01:15:00 EDT
(in UTC): Wed 2022-06-01 05:15:00 UTC
From now: 1 years 11 months left

让我们看一个例子,这个例子里我们使用参数 --iterations=5 列出了时间表达式的五个经过时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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 EDT
(in UTC): Mon 2023-05-29 04:00:00 UTC
From now: 2 years 11 months left
Iter. #2: Mon 2028-05-29 00:00:00 EDT
(in UTC): Mon 2028-05-29 04:00:00 UTC
From now: 7 years 11 months left
Iter. #3: Mon 2034-05-29 00:00:00 EDT
(in UTC): Mon 2034-05-29 04:00:00 UTC
From now: 13 years 11 months left
Iter. #4: Mon 2045-05-29 00:00:00 EDT
(in UTC): Mon 2045-05-29 04:00:00 UTC
From now: 24 years 11 months left
Iter. #5: Mon 2051-05-29 00:00:00 EDT
(in UTC): Mon 2051-05-29 04:00:00 UTC
From now: 30 years 11 months left

这些应该为你提供了足够的信息去开始测试你的 OnCalendar 时间格式。systemd-analyze 工具也可用于其他有趣的分析,你可以自行摸索哦。

总结

系统定时器可用于执行与 cron 工具相同类型的任务,但在日历和单调时间规范方面提供了更多灵活性,以触发事件。

即使您创建的服务单元通常由计时器触发,您也可以使用 SystemCtl Start MyMonitor.Service 命令随时手动触发它。可以在单个计时器中脚本进行多种维护任务,这些任务可以是Bash脚本或Linux实用程序程序。您可以运行计时器触发的服务以运行所有脚本,或者您可以根据需要运行各个脚本。