概述
前几天在应用 Terraform + cloud-init 批量初始化我的实验室 Linux 机器。正好发现有一些定时场景须要应用到 cronjob, 进一步理解到 systemd timer 齐全能够替换 cronjob, 并且 systemd timer 有一些十分乏味的性能。
回归话题:为什么我举荐你应用 systemd timer 代替 cronjob? 因为相比 cronjob, systemd timer 有这些劣势:
- 能够笼罩 cronjob 的所有性能
- 对立日志收集到 systemd 日志
- 针对工夫精确度更具体的配置项
- 除了定时场景,还反对基于 event 的触发
- 相比 cronjob 更灵便的语法
- 更丰盛的应用 / 运维命令集
接下来咱们一一介绍。
首先咱们通过零碎自带的 timer 来相熟这个新玩意。
零碎自带的 timer
当 Ubuntu 或任何基于 systemd 的发行版装置在一个新零碎上时,它会创立几个 timer,作为任何 Linux 主机后盾的零碎维护程序的一部分。这些 timer 会触发一般保护工作所需的事件,比方更新零碎数据库、清理长期目录、切割日志文件等等。
咱们应用 systemctl status *timer
命令列出我的主机上的所有 timer:
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.
...
每个 timer 至多有六行信息与之相干:
- 第一行是 timer 的文件名和对其用处的简短形容。
- 第二行显示 timer 的状态,它是否被加载,timer unit 文件的残缺门路,以及供应商的预设。
- 第三行显示其活动状态,包含 timer 开始流动的日期和工夫。
- 第四行蕴含 timer 下次被触发的日期和工夫,以及直到触发产生的 大抵 工夫。
- 第五行显示由 timer 触发的事件或服务的名称。
- 一些(但不是全副)systemd unit 文件有指向相干文档的指针。如下面的
Docs: man:fstrim
- 最初一行是 timer 所触发的服务的最新实例的日志条目。
创立 timer
劣势之一:对立日志收集到 systemd 日志
为了更快理解 timer, 咱们创立本人的 service unit 和 timer unit 来触发。
具体用处为:每周定期更新 tailscale 的版本。
首先,创立 tailscale update 服务,如下:
[Unit]
Description=Tailscale update
Wants=tailscale-weekly-update.timer
[Service]
Type=oneshot
ExecStart=/usr/bin/tailscale update -yes
[Install]
WantedBy=multi-user.target
而后,创立 tailscale update timer, 如下:
[Unit]
Description=Tailscale update
Requires=tailscale-weekly-update.service
[Timer]
Unit=tailscale-weekly-update.service
OnCalendar=weekly
[Install]
WantedBy=timers.target
最初,启用 timer:
systemctl enable tailscale-weekly-update.timer
这样就能够了,然而为了演示,执行:systemctl start tailscale-weekly-update.service
手动运行一次。
输入会间接集成到 systemd 日志里,并能够通过 journalctl
查看:(蕴含手动执行日志,和后续主动定期执行的日志)
$ 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.
如下面的日志,能够很不便地查看 timer 和服务的状态。
在日志这方面,你不须要做任何特地的事件,就能够使 tailscale-weekly-update.service
unit 中的ExecStart
触发器的 STDOUT
存储在日志中。这都是应用 systemd 运行服务的一部分。
Systemd timer 工夫精度
劣势之一:针对工夫精确度更具体的配置项
从下面日志,如果细看,timer 不会在 :00
秒的时候精确触发,甚至不会在上一个实例的一分钟内精确触发。这是成心的,但如果有必要的话,能够笼罩它的默认配置。
这种行为的起因是为了避免多个服务在完全相同的工夫被触发。例如,你能够应用工夫规格,如每周、每天,等等。这些快捷方式都被定义为在它们被触发的那一天的 00:00:00 时触发。当多个 timer 被这样指定时,它们很有可能会试图同时启动。
systemd timer 被无意设计成在指定工夫内随机触发,以避免同时触发。它们在一个工夫窗口内半随机地触发。依据 systemd.timer
手册,这个触发工夫绝对于所有其余定义的 timer 单位来说,放弃在一个稳固的地位。
大多数时候,这种概率性的触发工夫是没有问题的。当安顿备份等工作运行时,只有它们在非工作工夫运行,就不会有问题。一个系统管理员能够抉择一个确定的开始工夫,如典型的 cronjob 标准中的 01:05:00,以不与其余工作抵触,但有很大范畴的工夫值能够达到这个目标。启动工夫中的一分钟随机性通常是不相干的。
然而,对于某些工作,准确的触发工夫是一个相对要求。对于这些工作,你能够通过在 timer unit 文件的 Timer
局部增加这样的配置来指定更高的触发时间跨度精度(如精度在一微秒内):
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
中的所有默认 timer 都指定了一个更大的精度范畴,因为准确的工夫并不要害。看看零碎创立的 timer 中的一些规格:
$ 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 类型
劣势之一:除了定时场景,还反对基于 event 的触发
systemd timer 具备 cron 所不具备的其余性能,cron 只在特定的、反复的、实时的日期和工夫触发。然而,一个 timer 能够被配置为在系统启动后,或在启动后,或在某个定义的服务 unit 激活后的特定工夫内触发。这些被称为 枯燥性 timer。枯燥指的是一个继续减少的计数或序列。这些 timer 不是长久的,因为它们在每次启动后都会重置。
表 1 列出了枯燥的 timer 以及每个 timer 的简短定义,还有 “OnCalendar” timer,它不是枯燥的,用于指定将来的工夫,可能是反复的,也可能不是。
Timer | 枯燥性 | 定义 |
---|---|---|
OnActiveSec= |
X | 这定义了一个绝对于 timer 被激活的时刻的 timer。 |
OnBootSec= |
X | 这定义了一个绝对于机器启动工夫的 timer。 |
OnStartupSec= |
X | 这定义了一个绝对于服务管理器首次启动工夫的计时器。对于零碎 timer unit,这与 OnBootSec= 十分类似,因为零碎服务管理器通常在启动时很早就启动。当配置在每个用户服务管理器中运行的单元时,它次要是有用的,因为用户服务管理器个别只在第一次登录时启动,而不是在启动时。 |
OnUnitActiveSec= |
X | 这定义了一个绝对于要激活的 timer 最初一次被激活的工夫。 |
OnUnitInactiveSec= |
X | 这定义了一个绝对于要激活的 timer 最初被停用的工夫的定时器。 |
OnCalendar= |
这就用日历事件表达式定义了实时 timer。更多对于日历事件表达式的语法信息请参见 systemd.time(7) 。否则,其语义与OnActiveSec= 及相干设置相似。这个 timer 是最像那些与 cron 服务一起应用的 timer。 |
表 1: systemd timer 定义
枯燥 timer 的时间跨度能够应用与后面提到的 AccuracySec
语句雷同的快捷名称,但 systemd 将这些名称规范化为秒。例如,你可能想指定一个 timer,在系统启动 5 天后触发一次事件,能够这样写:OnBootSec=5d
。如果主机在 2020-06-15 09:45:27
启动,timer 将在 2020-06-20 09:45:27
或之后一分钟内触发。
Calendar event 定义
劣势之一:相比 cronjob 更灵便的语法
Calendar event 定义是在所需的反复工夫触发 timer 的要害局部。首先看一下 OnCalendar
设置中应用的一些规格。
systemd 及其 timer 应用的工夫和日期规格与 crontab 中应用的格局不同。它比 crontab 更 灵便 ,容许以at
命令的形式含糊日期和工夫。
应用 OnCalendar=
的 systemdtimer 的根本格局是 DOW YYYY-MM-DD HH:MM:SS
。DOW(星期)是可选的,其余字段能够应用星号(*)来匹配该地位的任何值。所有日历工夫模式都被转换为规范化的模式。如果没有指定工夫,则假设其为 00:00:00。如果没有指定日期但指定了工夫,那么下一个匹配可能是明天或今天,这取决于以后的工夫。名称或数字可用于月份和星期。能够指定每个单位的逗号分隔的列表。单位范畴能够在开始和完结值之间用...
来指定。
有几个乏味的选项用于指定日期。波浪号(~)能够用来指定该月的最初一天或该月最初一天之前的指定天数。”/” 能够用来指定一周中的某一天作为修饰语。
上面是一些在 OnCalendar
语句中应用的典型工夫规格的例子:
Calendar event 定义 | 形容 |
---|---|
DOW YYYY-MM-DD HH:MM:SS |
|
*-*-* 00:15:30 |
每年的每个月的每一天,在午夜后的 15 分钟 30 秒。 |
Weekly |
每个星期一的 00:00:00 |
Mon *-*-* 00:00:00 |
与每周雷同 |
Mon |
与每周雷同 |
Wed 2020-*-* |
2020 年的每个星期三,00:00:00 |
Mon..Fri 2021-*-* |
2021 年的每个工作日的 00:00:00 |
2023-6,7,8-1,15 01:15:00 |
2023 年 6 月、7 月和 8 月的 1 日和 15 日凌晨 01:15:00 |
Mon *-05~03 |
任何一年的 5 月的下一个星期一,也是月末的第三天。 |
Mon..Fri *-08~04 |
任何年份的 8 月底前的第 4 天,如果该天也是工作日,则为 8 月底。 |
*-05~03/2 |
从五月底开始的第三天,两天后再来一次。每年都会反复。请留神,这个表达式应用了(~)。 |
*-05-03/2 |
五月的第三天,而后在五月的其余工夫里每隔一天。每年反复一次。留神,这个表达式应用了破折号(-)。 |
表 2: 示例OnCalendar
event 定义
测试 calendar 定义
劣势之一:更丰盛的应用 / 运维命令集
systemd 提供了一个很好的工具来验证和查看 timer 中的日历工夫事件标准。systemd-analyze calendar
工具解析了一个日历工夫事件标准,并提供了规范化的模式以及其余乏味的信息,比方下一个 “elapse”(即匹配)的日期和工夫,以及达到触发工夫前的大抵工夫。
首先,看一下将来的一个没有工夫的日期:
$ 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
当初增加一个工夫。在这个例子中,日期和工夫作为非相干实体被独自剖析:
$ 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
要把日期和工夫作为一个 unit 来剖析,须要用引号把它们括起来。
$ 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
当初测试表 2 中的条目。选一个简单的:
$ 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
让咱们看一个例子,在这个例子中,咱们列出了工夫戳表达式的下五个执行工夫:
$ 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
这应该给你足够的信息来开始测试你的 OnCalendar
工夫规格。
总结
systemd timer 能够用来执行与 cron 工具雷同类型的工作,但在触发事件的 calendar 和枯燥的工夫规格方面提供了更多的灵活性。
除此之外,systemd timer 还有的劣势包含:
- 对立日志收集到 systemd 日志
- 针对工夫精确度更具体的配置项
- 更丰盛的应用 / 运维命令集
快去尝试迁徙你的 cronjob 到 systemd timer 吧~😛😛😛
参考资料
- Use systemd timers instead of cronjobs
- Fedora 的 systemd 指南
- Fedora 的 systemd cheat sheet