systemd (简体中文)/Timers (简体中文)

From ArchWiki
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

翻译状态:本文是 Systemd/Timers翻译。上次翻译日期:2021-02-27。如果英文版本有所更改,则您可以帮助同步翻译。

Timers 是以 .timer 为后缀名的 systemd 单元文件,用于控制 .service 文件或事件。Timers 可用来替换 cron(阅读 #替代 cron)。Timers 内置了实时定时事件和单调定时事件的支持,并可以异步执行这些事件。

定时器单元

Timers 是以 .timer 为后缀的 systemd 单元文件。Timers 和其他单元配置文件是类似的,它通过相同的路径加载,不同的是包含了 [Timer] 部分。 [Timer] 部分定义了何时以及如何激活定时事件。Timers 可以被定义成以下两种类型:

  • 实时定时器 (亦称"挂钟定时器") 通过日历事件激活(类似于 cronjobs )定时任务。使用 OnCalendar= 来定义实时定时器。
  • 单调定时器 即在一个时间点经过一段时间后激活定时任务。所有的单调计时器都遵循如下形式: OnTypeSec=OnBootSecOnActiveSec 是常用的单调定时器。

要查阅完整的定时器选项,参见 systemd.timer(5)。关于日历事件和时间段的定义参见 systemd.time(7)

服务单元

每个 .timer 文件所在目录都得有一个对应的 .service 文件(如 foo.timerfoo.service)。.timer 文件激活并控制 .service 文件。对应的 .service 文件中不需要包含 [Install] 部分,因为这由 timer 单元接管。必要时可以通过在定时器的 [Timer] 部分指定 Unit= 选项来控制一个与定时器不同名的服务单元。

管理

使用 timer 单元只需像其他单元一样 enablestart 即可(别忘了添加 .timer 后缀)。要查看所有已启用的定时器,运行:

$ systemctl list-timers
NEXT                          LEFT        LAST                          PASSED     UNIT                         ACTIVATES
Thu 2014-07-10 19:37:03 CEST  11h left    Wed 2014-07-09 19:37:03 CEST  12h ago    systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Fri 2014-07-11 00:00:00 CEST  15h left    Thu 2014-07-10 00:00:13 CEST  8h ago     logrotate.timer              logrotate.service
注意:
  • 列出所有定时器(包括非活动的),使用下列命令: systemctl list-timers --all
  • 一个由定时器启动的服务的状态,如果不是正好被触发的话,通常是未激活的。
  • 若一个定时器不再同步,它可能会删除它在 /var/lib/systemd/timers 下的 stamp-* 文件。这些空文件只用于表示每个定时器上次运行的时间。删除后,他们将在下次定时器运行时自动重建。

示例

通过定时器预定执行的服务文件一般无需任何修改。以下示例将预定执行 foo.service,因此它的定时器应该被命名为 foo.timer

单调定时器

定义一个在系统启动 15 分钟后执行,且之后每周都执行一次的定时器:

/etc/systemd/system/foo.timer
[Unit]
Description=Run foo weekly and on boot

[Timer]
OnBootSec=15min
OnUnitActiveSec=1w 

[Install]
WantedBy=timers.target

实时定时器

定义一个每周执行一次(具体来讲,指周一凌晨零点)的定时器。如果上次未执行(比如说系统当时没有开机,这个行为由 Persistent=true 定义)就立即执行服务。

/etc/systemd/system/foo.timer
[Unit]
Description=Run foo weekly

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target

更精确的时间可以通过 OnCalendar 参数以下列方式指定:

星期 年-月-日 时:分:秒

可以用星号来表示任意值,用逗号分列可能值。以 .. 分隔的两个值指两个值间的连续序列。

这是在每个月的前四天晚上中午 12 点,但仅当这一天是周一和周四时运行的例子:

OnCalendar=Mon,Tue *-*-01..04 12:00:00

在每个月的第一个星期六运行:

 OnCalendar=Sat *-*-1..7 18:00:00

如果不需要以星期几来界定,比如说在每天四点运行,请去掉星期项:

 OnCalendar=*-*-* 4:00:00

在不同的时间运行一个服务可以以多次指定 OnCalendar 的方式表示。在下面的例子中,这个服务在周内的 22:30 和周末的 20:00 运行:

 OnCalendar=Mon..Fri 22:30
 OnCalendar=Sat,Sun 20:00

更多信息请参阅 systemd.time(7)

提示:
  • 可以用 systemd-analyze 工具检测 OnCalendar 项是否填写正确,并计算下一次这个条件成立的时间。比如说可以用 systemd-analyze calendar weeklysystemd-analyze calendar "Mon,Tue *-*-01..04 12:00:00"
  • libfaketime 包提供的 faketime 命令在测试各种情况时非常有用。
  • 特殊的事件表达式如 dailyweekly 表示 特定的启动时间,因此任何共享该日历事件的定时器将同时启动。如果该定时器的服务会竞争系统资源,那么共享该日历事件的定时器可能会引起系统性能下降。使用 [Timer] 部分中的 RandomizedDelaySec 选项可以通过随机推迟定时器启动来避免这个问题。参见 systemd.timer(5)
  • [Timer] 部分添加 AccuracySec=1us 选项可以避免原有默认的精确值 1 分钟造成的误差。参见 systemd.timer(5)

瞬态 .timer 单元

可以使用 systemd-run 创建一个瞬态 .timer 单元。即可以在不创建服务文件的情况下设置定时运行某个命令。比如说下面的命令可以在 30 秒钟后创建一个文件:

# systemd-run --on-active=30 /bin/touch /tmp/foo

也可以建立指定一个已经存在但没有定义 timer 的服务文件。比如说,下面的命令会在 12 小时 30 分钟后启动名为 someunit.service 的服务:

# systemd-run --on-active="12h 30m" --unit someunit.service

更多信息和例子参见 systemd-run(1)

替代 cron

尽管 cron 毋庸置疑是最有名的计划任务管理器,systemd 定时器仍可以作为一个替代品。

优势

使用定时器的最主要的优势在于每个任务都有它自己的 systemd 服务。这样做的好处包括:

  • 任务可以简单地独立于他们的定时器启动,简化调试。
  • 每个任务可配置运行于特定的环境中(参见 systemd.exec(5))。
  • 任务可以使用 cgroups 特性。
  • 任务可以配置依赖于其他 systemd 单元。
  • 任务会被记录于 systemd 日志,便于调试。

注意事项

有些可以用 cron 轻易做到的事情仅仅使用 timer 组件会比较难办:

  • 创建过程:相比于在 crontab 中只需添加一行任务,使用 systemd 配置计划任务需要创建两个文件并运行好几次 systemctl 命令。
  • 邮件:目前还没有内置与 cron MAILTO 类似的任务失败时发送邮件的功能。可以在每个服务文件中配置 OnFailure= 来实现同样的功能。

另外, 用户定时器默认只会在用户登录时进行。但是,lingering 允许在启动,且用户没有活动登录会话时激活。

  • 随机延时:目前没还有内置与 cron 类似的 RANDOM_DELAY 功能来指定一个数字用于定时器延时执行。(参见 bug report)。 你不想同时执行的服务必须手动设置它们的定时器。
注意: AccuracySec 选项对于随机错开定时器执行时间是 没有 作用的,因为它"会在所有本地定时器单元间同步" (systemd.timer(5))。换句话说, AccuracySec 会以相同的量改变所有定时器激活时间。例如,所有 OnCalendar=daily 的定时器单元,指定 AccuracySec=15m 将同时在 00:00 到 00:15 触发相关的服务。

发送邮件

可以配置 systemd 在单元失败时发送电子邮件。Cron 的 MAILTO 会在任务向标准输出或标准错误输出时发送邮件,许多任务只在发生错误时输出。首先需要两个文件:一个用来发送邮件的可执行文件和一个用于启动这个可执行文件的 .service 文件。在本例中,可执行文件只是一个由提供 smtp-forwarder 的包中给出的调用 sendmail 的shell 脚本:

/usr/local/bin/systemd-email
#!/bin/sh

/usr/bin/sendmail -t <<ERRMAIL
To: $1
From: systemd <root@$HOSTNAME>
Subject: $2
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8

$(systemctl status --full "$2")
ERRMAIL

不论你用什么可执行文件,应该都会像这个脚本一样接收至少两个参数:目标邮箱地址和用来获取状态的单元文件名。我们创建的 .service 文件会传递这些参数:

/etc/systemd/system/status_email_user@.service
[Unit]
Description=status email for %i to user

[Service]
Type=oneshot
ExecStart=/usr/local/bin/systemd-email address %i
User=nobody
Group=systemd-journal

其中,user 是被通知的用户名,address 是该用户的邮件地址。尽管这个地址被硬编码在代码里,但是报告的单元文件会在实例参数中指定,因此可以作为许多其他单元的邮件服务端。此时你可以 start status_email_user@dbus.service 服务来检查你能否收信。

成功后,仅需 编辑 你需要发送失败邮件的服务,向 [Unit] 部分添加 OnFailure=status_email_user@%n.service%n 会向模板传递单元的名字。

注意:
  • 如果你根据 sSMTP#Security 配置了 sSMTP 安全,nobody 用户会没有访问 /etc/ssmtp/ssmtp.conf 文件的权限,systemctl start status_email_user@dbus.service 命令就会失败。一个解决办法是使用 root 作为 status_email_user@.service 单元的执行者。
  • 如果你希望在你的邮件脚本中使用 mail -s somelogs addressmail 会以 fork 的形式启动,systemd 会把在你的脚本退出时一并将这个进程结束掉。可以用 mail -Ssendwait -s somelogs address 来让 mail 不以 fork 的形式启动。

使用 crontab

有些注意事项中的部分可以通过安装一些将传统 crontab 项目翻译成 timers 配置的包来完成,例如AUR 中的 systemd-crontab-generatorAURsystemd-cronAUR 包。他们也可以提供缺失的 MAILTO 特性。

同样,与 crontab 一样,可以通过 systemctl 来获取一个统一的计划任务视图。参见 #管理章节。

参见

https://github.com/systemd-cron/systemd-cron-next || systemd-cron-nextAUR
  • systemd-cron — 提供 systemd 单元运行 cron 脚本;使用 systemd-crontab-generator 转换 crontabs
https://github.com/systemd-cron/systemd-cron || systemd-cronAUR