Writing
用 systemd timer 做一件可观察的小事
从一个无害的定时示例理解 timer 与 oneshot:怎样安排时间、处理漏跑,并从日志确认它确实执行过。
不少主机上已经有 cron,于是需要定时做一件小事时,第一反应往往是往 crontab 里补一行。systemd 环境里还有另一种更容易回看的做法:把“什么时候触发”和“实际运行什么”拆成两个 unit。前者是 .timer,后者通常是一次运行后退出的 .service。
先让一条日志按时出现
下面用一个小实验走通配置、触发、查日志的过程。示例每周一早晨写一次当前时间到 journal,只执行 date,不做清理或业务数据修改。需要在一台使用 systemd 的测试主机上创建以下两个文件;已有同名 unit 时应另取名字,不要覆盖:
# /etc/systemd/system/weekly-note.service
[Unit]
Description=Write a weekly maintenance note
[Service]
Type=oneshot
ExecStart=/usr/bin/date '+weekly note: %%F %%T %%Z'
# /etc/systemd/system/weekly-note.timer
[Unit]
Description=Run weekly-note every Monday morning
[Timer]
OnCalendar=Mon *-*-* 09:10:00
Persistent=true
Unit=weekly-note.service
[Install]
WantedBy=timers.target
Type=oneshot 会等待命令完成。此例没有 RemainAfterExit=yes,正常执行后会回到 inactive,不能因此认定失败。ExecStart= 中的 %% 用于把百分号交给 date;这里不会经过 shell 展开。先用 command -v date 确认程序路径。
.timer 默认关联同名 .service;写出 Unit= 是为了方便对照。OnCalendar= 按主机时区解释这里的时间,不保证精确到指定秒触发。不确定表达式时,先用 systemd-analyze calendar 'Mon *-*-* 09:10:00' 看解析及下次时间;跨时区主机尤其要核对这一项。
启用之后,分别检查 timer 和 service
把文件放好后,先让 systemd 重读配置,再启用 timer:
sudo systemctl daemon-reload
sudo systemctl enable --now weekly-note.timer
systemctl list-timers weekly-note.timer
最后一条最有用。它能同时显示下次和上次触发时间;只看到 enabled 并不能说明任务已经运行。想在不用等待下周一的情况下检查服务本身,可以手动执行一次:
sudo systemctl start weekly-note.service
systemctl status weekly-note.service
systemctl show weekly-note.service -p Result -p ExecMainStatus
journalctl -u weekly-note.service -n 20 --no-pager
日志中应能看到 date 写出的那一行,Result=success、ExecMainStatus=0 是此例正常退出的预期。读取系统 journal 可能需要 sudo 或相应组权限。status 因服务已经 inactive 返回非零时,结合退出结果判断,不要只看一条命令的退出码。
替换 ExecStart= 前,还要确认账户、工作目录、环境变量和文件权限。系统级 service 默认可能以 root 运行,登录 shell 里测试成功也不保证服务环境中成功;真正的维护脚本应明确所需身份,避免顺手继承过大的权限。
漏跑一次,不等于补齐所有历史任务
Persistent=true 容易被理解成“无论错过多少次都会补齐”。它实际解决的是另一件事:带 OnCalendar= 的 timer 在机器关机或 timer 未运行期间错过了一个触发点,下一次重新激活后可以补做一次。它不是任务队列,也不会把停机期间的每一轮都逐个补回来。对有副作用的操作,这个区别很重要:例如删除、同步、发通知之前,要先判断补跑一次是否仍然合适。
timer 到点时,若目标 service 已经处于 active 状态,不会为它再创建一个并行实例。不过两个不同的 service 仍可能同时碰同一个目录或数据库。共享资源需要锁或明确的流程协调,把两个 timer 错开几分钟并不能保证互斥。
试完以后怎样停下
不再需要这项实验时,先取消定时触发:
sudo systemctl disable --now weekly-note.timer
这不会终止已经运行的 service,也不会删除 unit 文件和 journal 记录。确认 service 已结束后,再按创建时记录的路径处理这两个示例文件;删除或修改 unit 后需要 daemon-reload。真正上业务任务前,至少确认下一次触发时间、上次退出结果,以及补跑是否合适。
参考资料:
- systemd.timer 手册:
OnCalendar=、Persistent=与 timer 的触发语义。 - systemd.service 手册:
Type=oneshot和服务执行模型。 - systemctl 手册:
enable、start、status和list-timers的系统管理接口。