Schedule jobs with systemd timers, a cron alternative xcron/xcrontab

2025-06-08

使用 systemd 定时器(cron 的替代方案)来安排作业

xcron/xcrontab

对于任何系统管理员、DevOps 工程师或普通 Linux 爱好者来说,自动化那些烦人/无聊/困难的工作至关重要。而任务调度在自动化中起着关键作用。

对于调度作业,老牌的方案是cron。其核心文件(crontab)包含作业列表、执行命令和时间安排。只要你掌握了调度表达式,cron 就是一个强大而优雅的解决方案。

对于 Linux 系统管理员来说,还有一种替代方案可以提供与systemd更紧密的集成,直观地命名为systemd 计时器

虽然可能性不大,但您可能使用的 Linux 发行版(或 BSD 或其他类 Unix 系统)没有安装 systemd。如果您使用* B S D Alpine LinuxGentooKnoppixVoidTiny CoreDevuanArtix Linux以及其他使用非 systemd 初始化系统的其他 Linux 系统,那么本文可能只是您的好奇心作祟。请继续阅读,或者直接享受您现有的 cron 服务。

选择 systemd 定时器而不是 cron

如果 cron 有效,为什么还要使用 systemd 定时器?我认为这与优劣无关。两者都很好用,各有优缺点。

在以下情况下我会使用 systemd 计时器:

  • systemd 已经可用(换句话说,它就在那里,所以为什么不使用它而不是安装另一个包)
  • 需要时区处理(为了遵守夏令时,或者只是将时间设置为 UTC 以外的时间)
  • 日志记录应该很好地集成并可访问journalctl
  • 希望对工作进行单独测试,无需等待时间表

另一方面,如果您想要直接的电子邮件通知,并且您和您的团队已经非常熟悉该工具,那么 cron 可能会胜出。

ArchWiki还列出了使用 systemd 计时器而非 cron 的一些优点和注意事项。

服务

对于 systemd 计时器,需要创建两个文件:

  1. 将要启动的服务
  2. 安排服务的计时器

我觉得这里的服务命名有点混乱。但需要指出的是,systemd 服务不一定是长期运行的。在这种情况下,“一次性”服务就足够了。即使它立即退出,也仍然被称为服务。

一个简单的例子,安装如下/etc/systemd/system/motd-weather.service

[Unit]
Description=Update message of the day with current weather

[Service]
ExecStart=/usr/bin/curl -o /etc/motd http://wttr.in/?1Fq
Type=oneshot
Enter fullscreen mode Exit fullscreen mode

几点说明:

  • 如果您喜欢高效简洁的格式,文件的命名非常重要。只要服务和计时器的名称(扩展名除外)相同,它们就能自动找到彼此,而无需明确引用文件名。服务的扩展名应为.service,计时器的扩展名应为.timer
  • 你可以做Description任何你想做的事
  • ExecStart应分配给相应的命令。通常需要可执行文件的完整路径,因此我们/usr/bin/curl在此指定,而不仅仅是curl
  • 假设命令执行后完成,我设置了Type=oneshot。这向 systemd 发出信号,表示服务不会因为完成就被视为“死亡”。

在我们安装计时器之前,可以测试该服务。

sudo systemctl start motd-weather.service
Enter fullscreen mode Exit fullscreen mode

上述有改变吗/etc/motd

出色的。

计时器

systemd 的定时器是一个双文件系统。我们完成了一部分,即服务,现在我们再创建一个与之匹配的定时器来调度服务。

与上述示例服务关联的计时器文件:

[Unit]
Description=Download weather to motd nightly

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1h

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

如果上述服务位于 中/etc/systemd/system/motd-weather.service,则此文件应为/etc/systemd/system/motd-weather.timer。请注意,唯一的区别是扩展名:.servicevs. ,.timer而词干motd-weather保持不变以便于自动发现。

几点说明:

  • 你可以做Description任何你想做的事
  • 为简单起见,OnCalendar使用daily在午夜运行服务。有关更多灵活性,请参阅下文。
  • 如果服务器因维护或服务器/网络故障而关闭或断开连接怎么办?如果服务器恢复上线后,当天的消息显示昨天的天气,那真是太可惜了!因此,我们使用Persistent=true此选项,如果服务应该在离线期间运行,则下次启动时会触发该服务。如果不需要,请省略此行,因为默认值为 false。
  • 假设我们用 运行了一堆计时器OnCalendar=daily,那么我们可能会面临大量服务在午夜运行并影响系统性能的风险。daily当然,我们可以将其更改为特定时间。但在本例中,我将其设置RandomizedDelaySec为 3600。没有看到 3600 这个数字?这是因为systemd 时间跨度缩写允许我们将 3600 秒表示为 ,1h原因显而易见。最终结果是 systemd 会在午夜后 1 小时内随机选择一个启动时间。如果我们对其他daily计时器也这样做,就能实现和谐平衡,晚上就能睡得更好。
  • 在本[Install]节中,我们让 systemd 知道系统timers.target Wants已启用此计时器。这样,系统重启后,当timers.target启动时,它也会将此计时器和其他相关计时器联机。这并不意味着相关服务会被触发;而只是意味着这些计时器在启动时被激活。有趣的是:timers.target 也适用于用户范围的 systemd 计时器

启用并启动计时器

假设我们已经测试了该服务,并且它运行正常,那么我们就可以启动计时器了。操作如下:

sudo systemctl enable --now motd-weather.timer
Enter fullscreen mode Exit fullscreen mode

请注意,我们启用并启动了计时器,然后计时器会在预定时间调用服务。我们不会直接启动服务。

如果安装正确,您应该在列出 systemd 计时器时看到它和一些调度信息:

systemctl list-timers
Enter fullscreen mode Exit fullscreen mode

如果列表很长,您可以使用通配符进行过滤:

systemctl list-timers motd*
Enter fullscreen mode Exit fullscreen mode

日历表达式

systemd 计时器的一个值得持续研究(或至少是浏览器书签)的组件是OnCalendar设置为:日历表达式

以下几个示例可能有助于介绍语法:

  • 每年第一天的 UTC 午夜:*-01-01 00:00:00 UTC
  • 每年第一天您所在时区的午夜:(*-01-01 00:00:00也可以写成yearly
  • 美国东海岸每天早上 8 点:*-*-* 08:00:00 America/New_York
  • 是的,你可以省略秒数:*-*-* 08:00 America/New_York
  • 仅在工作日凌晨 2 点:Mon..Fri *-*-* 02:00 America/New_York
  • 每周日晚上 10 点:Sun *-*-* 22:00 America/New_York

如上所示,*表示“每个”。有时,为了提醒自己格式,我会运行程序systemd-analyze timestamp now查看当前秒的规范化格式,然后开始*在正确的位置进行替换,并根据需要更改日期、时间和时区。一旦开始使用 替换*,就systemd-analyze timestamp不再起作用;而是使用systemd-analyze calendar(见下文)。

您还可以使用以下简写表达minutely,,,,,,,,hourlydailymonthlyweeklyyearlyquarterlysemiannually

要查看可能的时区列表,请尝试timedatectl list-timezones

systemd-analyze calendar是你的朋友

您可以使用systemd-analyze calendar测试以上任何一项。例如,我希望我的服务每周一、周三和周五晚上 11 点(UTC 时间)运行,但 12 月除外。我猜对了吗?让我们检查一下语法的有效性。

systemd-analyze calendar "Mon,Wed,Fri *-1..11-* 23:00 UTC"
Enter fullscreen mode Exit fullscreen mode

尤里卡!检查结果正常。我可以高兴,但我也可以从规范化的形式中学习,并调整上面的内容Mon,Wed,Fri *-01..11-* 23:00:00 UTC

一个非常有用的健全性检查:systemd-analyze calendar可以显示接下来的几次迭代,只是为了让你放心,并帮助你思考事情何时会发生。例如,我希望该服务在每个月的第一个和第三个星期三启动。这比其他一些示例更复杂一些,所以我想确保我的操作正确。下面将显示一年内(24 次)此类事件的发生情况。

systemd-analyze calendar --iterations=24 "Wed *-*-1..07,15..21 02:00"
Enter fullscreen mode Exit fullscreen mode

一边翻阅着台历,一边煞费苦心地查看了所有24天,一切似乎都正常。哦,等等,我想查看的是日历年份,而不是一年后。为此,我们可以使用该--base-time选项,选择所需年份的1月1日。那么2026年怎么样?

systemd-analyze calendar --base-time="2026-01-01" --iterations=24 "Wed *-*-1..07,15..21 02:00"
Enter fullscreen mode Exit fullscreen mode

一旦你的日历表达式检查成功,OnCalendar=在以.timer

倒计时器

systemd 计时器不必包含单个或重复的日历事件。换句话说,除了 之外,还有其他选项OnCalendar=

这些包括:

  • OnActiveSec=定时器激活后,在指定时间触发服务
  • OnBootSec=OnStartupSec=大致相同,我倾向于使用OnStartupSec=它来提高灵活性。OnBootSec=指的是自系统启动以来的时间,OnStartupSec=指的是自服务管理器启动以来的时间。由于它们非常接近,我更喜欢后者,因为它也适用于只能在登录后启动的用户范围的服务。
  • OnUnitActiveSec=OnUnitInactiveSec=很有趣。它们分别在服务上次激活或停用后的指定时间触发服务。

同样,您可能希望使用systemd 时间跨度缩写,这样如果秒似乎缺乏可读性,您可以以小时或天为单位给出时间。

用户服务经理

systemd 定时器和服务无需安装/etc/systemd/system/,因此可以在系统级别运行。它们可以按用户安装,并在用户服务管理器中运行,通常在登录时启动。ArchWiki上有一篇关于 systemd 用户单元的精彩文章官方的 systemd 单元指南也是不错的参考。

上面的计时器和服务安装在 中可以正常工作~/.config/systemd/user/,但该服务当然无法写入/etc/motd。类似 的ExecStart=/usr/bin/curl -o %E/motd http://wttr.in/?1Fq脚本效果会更好,但也需要在登录时执行的 shell 脚本cat ~/.config/motd末尾添加 。像这样引用(通常)?请参阅systemd 单元文件中可用变量列表.bashrc%E$XDG_CONFIG_HOME~/.config

关于用户服务,有一个很大的警告:它们不一定在启动时运行。而是在登录时运行。不过,有一个不错的解决方法。如果您希望用户服务和计时器在启动时运行,而不仅仅是登录时运行,您可以让特定用户“linger”。这样,即使用户尚未明确登录,一切也能正常工作。操作如下:

sudo loginctl enable-linger my_username
Enter fullscreen mode Exit fullscreen mode

并将其替换my_username为您希望在重启后保留的用户名。

文档

您可能喜欢阅读有关计时器和单位的 systemd 文档:

请随意在评论中发表想法、建议和问题!

鏂囩珷鏉ユ簮锛�https://dev.to/bowmanjd/schedule-jobs-with-systemd-timers-a-cron-alternative-15l8
PREV
何时使用 useCallback - ReactJS?
NEXT
向工程团队提出的问题