Linux 入门:用 systemd timer 代替 cron 做定时任务
云主机上要做「每天备份、每小时清理日志、开机后延迟跑一次健康检查」,很多人第一反应是安装 cron。现代发行版里,systemd timer 往往更省事:它和现有 service 共用同一套启动依赖、用户权限和日志通道,失败时直接 systemctl status 和 journalctl 就能看到原因,不必再翻 /var/log/cron,也不用猜任务到底有没有被静默跳过。
对已经用 systemd 管理业务进程的机器来说,再单独维护一份 crontab,等于两套调度系统并行。时间一长,常见后果是:改了脚本路径却忘了改 cron、机器关机错过触发后没有补跑、多人修改 crontab 时互相覆盖。把定时逻辑收进 timer,可以减少这类「看起来在跑、其实没跑」的隐患。
1. 先有一个普通 service
timer 只负责「什么时候触发」,真正干活的是一个 Type=oneshot 的服务。例如每天备份应用数据:
# /etc/systemd/system/app-backup.service
[Unit]
Description=Backup app data
[Service]
Type=oneshot
User=app
ExecStart=/usr/local/bin/app-backup.sh
Nice=10
先单独跑一遍,确认脚本本身没问题:
sudo systemctl start app-backup.service
sudo systemctl status app-backup.service
journalctl -u app-backup.service -n 50 --no-pager
脚本权限不对、备份目录不存在、或者依赖的环境变量缺失,都会在这一步暴露。service 都跑不通时,timer 再准时也没有意义,所以不要一上来就先写日历表达式。
2. 再写对应的 timer
# /etc/systemd/system/app-backup.timer
[Unit]
Description=Daily app backup
[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true
RandomizedDelaySec=5m
[Install]
WantedBy=timers.target
几个常用字段值得记住:
OnCalendar:日历表达式,比 cron 的五段格式好读。hourly、daily、Mon *-*-* 09:00:00都可以。Persistent=true:如果到点时机器正好关机或维护,下次开机后会补跑一次,特别适合备份、报表这类「错过了最好补上」的任务。RandomizedDelaySec:加一点随机抖动。机房里几十台机器如果都在 03:15 整点打同一套备份存储,很容易把自己和对象存储一起打满。
启用并确认下次触发时间:
sudo systemctl daemon-reload
sudo systemctl enable --now app-backup.timer
systemctl list-timers app-backup.timer
list-timers 会显示上次和下次触发时间,这是确认表达式写对了最直观的办法。如果这里显示的下次时间不是你预期的窗口,先改 OnCalendar,不要等到第二天早上去看备份在不在。
想预览日历表达式是否合法,可以用:
systemd-analyze calendar '*-*-* 03:15:00'
systemd-analyze calendar 'Mon *-*-* 09:00:00'
它会告诉你规范化之后的时间,以及下一次匹配点,排错比盯着 unit 文件猜测更快。
3. 和 cron 怎么选
并不是所有场景都要迁移:
- 任务本来就是一个 systemd 服务、需要等网络或数据盘就绪、希望日志进入
journalctl:优先 timer。 - 只要临时跑一句命令,或镜像里根本没有 systemd(很多极简容器):继续用 cron、Kubernetes CronJob 或外部调度器即可。
- 不要让 cron 和 timer 同时调度同一脚本,否则会重复执行,备份互相踩、清理任务删到一半再删一次都麻烦。迁移时先
enable --now新 timer,观察一次成功后再注释掉 crontab 里的旧行。
如果脚本需要「开机后等网络好了再跑」,可以在 service 里加 After=network-online.target 和 Wants=network-online.target,这比在 cron 里 sleep 30 可靠得多。
4. 几个常见坑
- 只写了
.timer忘记配套.service,或两个文件名对不上。惯例是名字.service配名字.timer,systemd 才会自动关联。 - 改完 unit 没有执行
daemon-reload,看起来像没生效,其实还在用旧配置。 - 写成
OnCalendar=daily时,默认是当天 00:00,不一定是业务低峰。不确定就写成完整的*-*-* 03:15:00。 - 脚本依赖环境变量、密钥或工作目录时,把它们写进 service 的
Environment=、EnvironmentFile=和WorkingDirectory=。不要假设 timer 会继承你交互登录时的 shell profile。 - 用普通用户写的脚本,service 里却忘了
User=,结果任务以 root 跑起来,生成的文件权限后续业务读不到。
下次再加定时任务,建议固定顺序:先让 service 能手动跑通,再配 timer,最后用 list-timers 和一次 start 验证。这样出问题时,你面对的是熟悉的 status 和 journalctl,而不是一条被静默跳过、几天后才发现没备份的 cron 行。
- 点赞
- 收藏
- 关注作者
评论(0)