systemd timer 定时任务:补跑、日志与执行结果验证
定时任务最容易产生的误会,是“已经启用”就等于“每天都成功”。Ubuntu 24.04 上可以用一份 service 描述任务,再用一份 timer 描述何时触发。本文使用只输出时间的演示任务,不会执行备份、清理或发送通知;替换成业务脚本前,应先确认它可以重复运行且不会破坏现有数据。
先定义任务完成的证据
写清任务应该读取什么、产生什么以及失败时谁处理。比如导出文件应有可验证的日期、数量和摘要,不能只看到进程退出就认定备份有效。演示则以日志中出现预期时间和退出状态成功为验收依据。
需要处理历史数据的任务,还要决定停机几天后是补一次最新结果,还是逐个处理漏掉的日期。计时器只能发起执行,业务是否补齐历史,要由脚本自身实现。
为任务创建 oneshot 服务
确认名称未使用后,新建 /etc/systemd/system/demo-clock.service:
[Unit]
Description=Timer demonstration job
[Service]
Type=oneshot
DynamicUser=yes
ExecStart=/usr/bin/date --iso-8601=seconds
oneshot 适合运行后退出的工作。这里没有设置 RemainAfterExit=yes,避免成功后长期保持 active 影响后续按时再次执行。业务任务需要的工作目录、身份和环境也应明确指定,不能依赖管理员终端中的临时状态。
先手动启动这个服务,查看对应 journal 日志和退出结果。若独立运行都失败,不要先调定时表达式;先把文件权限、依赖和程序参数处理正确。
再创建带时区的 timer
新建同目录下的 demo-clock.timer:
[Unit]
Description=Schedule timer demonstration
[Timer]
OnCalendar=*-*-* 03:10:00 UTC
Persistent=yes
RandomizedDelaySec=2m
Unit=demo-clock.service
[Install]
WantedBy=timers.target
明确写 UTC,可以减少管理员所在地和服务器显示时区不同造成的误判。随机延迟用于避免很多机器同时启动维护任务,所以执行时间不应被理解为精确到某一秒。systemd 时间表达式手册说明了日历与时区语法。
Persistent=yes 适用于日历计时器:计时器停用期间错过了触发,恢复后可能补触发一次,不会自动把每天错过的任务逐条重放。已经运行的目标服务,也不会因为又到一个时间点就自动叠加同一个实例。timer 手册说明了这些行为。
校验时间和真实执行结果
在服务器执行:
systemd-analyze calendar '*-*-* 03:10:00 UTC'
sudo systemd-analyze verify /etc/systemd/system/demo-clock.service /etc/systemd/system/demo-clock.timer
sudo systemctl daemon-reload
sudo systemctl start demo-clock.service
sudo journalctl -u demo-clock.service -n 20 --no-pager
先检查表达式算出的下次时间,再检查单元语法和手动运行结果。全部符合预期后,执行 sudo systemctl enable --now demo-clock.timer,然后用 systemctl list-timers --all 查看上次及下次触发。
计时器触发成功只说明请求已发出。检查服务的 Result、ExecMainStatus 与业务产物,才能确认任务完成。脚本如果吞掉错误并返回零,系统管理器也可能把失败任务标记成成功,必须在脚本层正确传递错误。
设计重试、并发和停用方法
任务重试应考虑幂等性。重复导出可以采用独立文件名,重复写账、删除或通知则需要更严格的业务控制。任务运行时间超过间隔时,不要误以为每个时间点都被排队保存;应在业务层记录处理进度。
需要暂停未来触发,可执行 sudo systemctl disable --now demo-clock.timer。这不会自动终止已经运行的 service,若还需停止当前任务,要评估它是否能安全中断,再单独处理。撤回配置时保留原文件和状态记录,重新加载后复查计划列表。
从 cron 迁移时,先避免新旧两套计划同时运行同一任务。完成一个完整周期的结果验证后,再退役旧入口。真正可靠的定时任务,应能够回答上次处理了什么、失败在哪里、下一次怎样恢复。