systemd 定时器到点不触发?按这五个地方查
定时器明明配了,systemctl list-timers 也显示下次触发时间,到点却什么都没发生。这类问题排查起来很折磨,因为 systemd 不会主动告诉你"我为什么没跑"。下面五个地方按命中率从高到低排。
一、OnCalendar 的星期几写法最容易错
systemd-analyze calendar "Mon..Fri 09:00"
输出里会给你 Next elapse 和 UTC 两个时间。如果这条命令报错,定时器一定不会触发,不用再往下查了。
顺带提醒:OnCalendar=daily 的默认时间是本地 00:00,不是"每天随便什么时候"。想要随机分布得配 RandomizedDelaySec。
二、Persistent=true 不是万能补药
很多人以为加上 Persistent=true 就万事大吉。它的作用只是:机器关机期间错过的触发点,开机后补跑一次。它管不了三件事:
- 服务本身失败(需要用
OnFailure=挂一个告警单元) - 触发点比当前时间早(新建的定时器不会立刻跑,要等到下一个触发点)
ConditionXXX不满足被跳过
最后一条坑最深:Condition 判断失败时,systemctl status 显示的是 inactive (dead),跟"没被触发"长得一模一样。要看真正原因得用:
journalctl -u 你的服务名 -b --no-pager | grep -i condition
三、Type=oneshot 下 RuntimeMaxSec 是无效的
这是我在一个跑两小时的任务上踩过的坑。当时给服务加了 RuntimeMaxSec=600 想限制最长运行时间,结果进程跑了 3 小时也没被杀。
原因是 RuntimeMaxSec 只对 Type=simple(以及 notify、exec)生效。Type=oneshot 的服务,systemd 只在启动阶段盯着,启动完成后的运行时长它不管。要给 oneshot 限时,得用:
TimeoutStartSec=600
区别在于:RuntimeMaxSec 超时算"运行超时",TimeoutStartSec 超时算"启动失败",后者会让服务状态变成 failed,能被监控抓到。对我们来说这反而是想要的效果。
四、服务名和定时器名不一致
[Timer]
Unit=foo-daily.service
漏了这行,定时器会去找一个不存在的 foo.service,然后静默失败。list-timers 里照样显示下次触发时间,照样到点触发,只是触发的目标不存在。
用这条命令快速核对对应关系:
systemctl list-timers --all
看 ACTIVATES 那一列,就是你实际会触发谁。
五、RandomizedDelaySec 让"到点"变成"大概到点"
配了 RandomizedDelaySec=30m 之后,触发时间会往后随机推 0 到 30 分钟。这是设计行为,用来避免一批机器同一秒打爆后端。
问题在于:如果你的监控盯着"09:00 后 5 分钟必须有新日志",就会被这个随机延迟误报。要么把监控窗口放宽到延迟上限之外,要么干脆别在关键任务上用随机延迟。
排查顺序总结
| 顺序 | 检查什么 | 命令 | |
|---|---|---|---|
| 1 | 表达式语法对不对 | systemd-analyze calendar "..." | |
| 2 | 触发的目标单元在不在 | systemctl list-timers --all 看 ACTIVATES | |
| 3 | 服务是不是被 Condition 跳过 | `journalctl -u 服务名 -b \ | grep -i condition` |
| 4 | oneshot 有没有被限时需求 | 用 TimeoutStartSec 而不是 RuntimeMaxSec | |
| 5 | 时间点是不是被随机延迟推后 | 看 RandomizedDelaySec |
一个通用建议
给定时任务加一个"心跳"文件:服务成功结束时 touch 一个时间戳文件,监控只看这个文件的 mtime。比盯 journald 日志可靠得多,因为日志会被轮转、会被你我手滑删掉,而心跳文件是任务自己写的,最诚实。
- 点赞
- 收藏
- 关注作者
评论(0)