用 systemd timer 代替 cron 跑周期任务
我有一台免费沙盒机器,规则是「30 分钟没有 SSH 会话就挂起」。 最初保活脚本挂在 cron 上,结果经常出现该跑的时候没跑—— 机器挂起期间 cron 不会补,醒来之后它只等下一个整点,中间那段就空着了。
cron 的问题不在于能不能跑
cron 的语义是「到点了,如果机器醒着,就执行」。机器没醒,这一次就永久错过了, 没有任何补偿机制,也没有地方记录「我本该跑但没跑」。 对于保活、心跳、证书续期这类任务,漏一次可能就要等很久才发现。
systemd timer 有 Persistent=true,配合 OnCalendar= 使用时,
会把「上次实际触发时间」记在 /var/lib/systemd/timers/ 下。
开机或从挂起恢复后,如果发现错过了一个触发点,会立刻补跑一次。
最小可用配置
两个文件:一个定义要做什么,一个定义什么时候做。
1. service:只负责执行
[Unit]
Description=keepalive ping
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/keepalive.sh
# 失败不无限重试,避免和下一次触发叠在一起
StartLimitIntervalSec=0
2. timer:负责调度
[Unit]
Description=keepalive ping every 10 minutes
[Timer]
OnCalendar=*:0/10
Persistent=true
# 随机延迟,避免多台机器同一秒打同一个接口
RandomizedDelaySec=30
[Install]
WantedBy=timers.target
启用和检查:
systemctl daemon-reload
systemctl enable --now keepalive.timer
# 看下次触发时间和上次结果
systemctl list-timers keepalive.timer
systemctl status keepalive.service
几个实际踩到的点
Type=oneshot 是必须的
周期任务基本都是「跑完就退出」的脚本,用默认的 Type=simple 时,
systemd 会认为主进程一启动就进入运行态,脚本退出后状态显示为 failed 的情况很难读。
明确写 Type=oneshot,退出码就是结果。
日志默认就在 journald 里
不需要在脚本里自己重定向到文件。用
journalctl -u keepalive.service -n 50 就能看到最近几次的输出,
而且自带时间戳和退出码。要长期保留的话再配 Storage=persistent。
OnCalendar 的语法容易记错
| 写法 | 含义 |
|---|---|
*:0/10 | 每 10 分钟(整 10 分触发) |
*-*-* 04:00:00 | 每天凌晨 4 点 |
Mon *-*-* 09:00 | 每周一 9 点 |
hourly / daily | 内置别名,等价于整点 / 每天 0 点 |
不确定的时候用 systemd-analyze calendar '*:0/10' 验证,
它会直接打印出接下来几次的触发时间。
什么时候仍然适合用 cron
如果机器是常驻的、任务对漏跑不敏感、或者只是临时跑个一次性脚本, cron 依然更省事——一行就够,不用建两个 unit 文件。 但只要涉及「不能漏」和「机器可能不在线」,我现在的默认选择是 timer。
← 返回笔记列表