用 systemd timer 替代 cron:一次让定时任务可观测的实践
很多服务器上的定时任务,都是从一行 crontab 开始的:每天凌晨两点备份数据库,每小时清理一次过期日志,每周日生成一份报表。它简单到几乎没有学习成本,也正因为太简单,出了问题才难定位——任务到底跑没跑、跑了多久、失败了没有,cron 只留下一封可能没人看的邮件。最近我把一台个人服务器上的几个关键定时任务迁到了 systemd timer 上,记录一下这次迁移的思路和踩过的坑。
cron 的三处短板
第一是没有统一的运行记录。cron 的执行结果散落在邮件和各应用自己的日志里,想回答“上周三凌晨的备份成功了吗”往往要翻好几个地方。第二是环境不可控。cron 的环境变量极少,PATH、时区、语言环境都可能和交互式 Shell 不一样,脚本在终端跑得好好的,放进 crontab 就悄悄失败,是很多人的共同经历。第三是缺少依赖和重试。任务依赖的服务还没启动、磁盘满了、网络抖动,cron 不会等,也不会重试,只会按点准时失败。
把任务拆成 service 加 timer
systemd 的做法是把“做什么”和“什么时候做”分开。service 文件只描述任务本身,timer 文件只描述触发规则。比如一个每日备份任务,service 里写好要执行的脚本和运行用户,timer 里写 OnCalendar=daily 和 Persistent=true。Persistent 很关键:机器在预定时间关机或休眠,醒来后会补跑一次,避免整整一天没有备份。
# /etc/systemd/system/backup.service
[Unit]
Description=每日备份数据库
After=network-online.target
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=每天凌晨两点备份
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
写好后用 systemctl enable --now backup.timer 启用,再用 systemctl list-timers --all 看到下一次触发时间,心里就有了底。
可观测性是这次迁移最大的收获
迁移之后,排障流程变成固定的三步:先看 systemctl list-timers 确认下一次触发时间对不对,再看 systemctl status backup.service 确认上一次执行结果,最后用 journalctl -u backup.service 看完整日志。日志由 journald 统一收集,不用再猜脚本把输出写到了哪里。还可以给 service 加上 OnFailure,通过邮件或 webhook 通知,失败不再静默。
迁移清单与回滚
迁移前我先列了一张清单:逐条核对 crontab 里的每一行,确认脚本的绝对路径、运行用户、环境变量和工作目录;把脚本里的相对路径全部改成绝对路径;在 service 里显式设置 Environment 补齐必要的变量;先用 systemd-run 手动跑一次验证。确认无误后才注释掉对应的 crontab 行,保留一周作为回滚手段。
迁移不是为了追新,而是让定时任务从“黑盒”变成“可观测、可重试、可回滚”的常规服务。如果你也有一台跑着多年 crontab 的机器,不妨先挑一个最关键、也最常出问题的任务试试,收益会比想象中明显。
- 点赞
- 收藏
- 关注作者
评论(0)