从 crontab 迁到 systemd timer:五个坑和一份迁移清单

举报
yd_237032035 发表于 2026/11/09 00:56:14 2026/11/09
【摘要】 记录把一批 cron 作业迁到 systemd timer 的实际过程:PATH 继承、OnCalendar 语法与时区、Persistent 补跑、journald 查日志、oneshot 前台运行五个坑,附最小可用配置和 cron 对照表。

用 crontab 跑了几年定时任务,最近把一台机器上的十几个作业整体迁到了 systemd timer。迁移过程比想象中顺利,但踩的坑也不少。这篇记录一下为什么换、换的时候最容易错在哪,给准备迁移的同学一份对照清单。

一、为什么想换

crontab 的问题不是不能用,而是出了问题不好查:

  • 日志默认进系统邮件或 syslog,容器里经常啥都没有;
  • 没有依赖关系,A 任务跑完再跑 B 只能靠 sleep 硬等;
  • 机器关机错过的任务不会补跑(除非用 anacron);
  • 语法难记,*/5 和 0/5 的区别每次都要查。

systemd timer 把这四件事都解决了:日志统一进 journald、可以依赖 service、支持错过补跑、语法有专门工具校验。

二、五个容易踩的坑

坑 1:PATH 和 cron 一样"干净",但不是同一份

很多人以为换成 systemd 就能读到 ~/.bashrc 里的环境变量,其实不能。systemd 的 user 实例和 system timer 都不会加载 shell 的 profile。表现就是:手动跑脚本一切正常,定时跑就报 command not found。

正确做法是显式声明,别指望继承:

[Service]
Type=oneshot
User=app
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=NODE_ENV=production
ExecStart=/usr/bin/node /opt/app/job.js

ExecStart 也建议写绝对路径。这一条能省掉九成的"手动能跑、定时不行"。

坑 2:OnCalendar 的写法和 cron 完全不是一回事

OnCalendar=*-*-* 06:00:00 表示每天 6 点。注意它不认 cron 的 */5,每 5 分钟要写 *:0/5。写完一定要验证,别靠猜:

systemd-analyze calendar '*-*-* 06:00:00'
# 会打印 Next elapse 和 UTC 时间,一眼就能确认对不对
systemd-analyze calendar '*:0/5' --iterations=3

时区也值得注意:OnCalendar 用的是系统时区(/etc/localtime)。如果服务器是 UTC,写 06:00 实际是北京时间 14:00。要固定北京时间,先把机器时区设成 Asia/Shanghai,再用 timedatectl 确认。

坑 3:错过的时间点不会补跑

这是从 cron 迁过来最容易忽略的一条。机器 3 点到 8 点关机,6 点的任务开机后默认不会执行。加上这一行才会补:

[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true

Persistent=true 会在开机后立刻补跑一次错过的任务(只补最后一次,不会把错过的十次都跑一遍)。

另外默认精度是 1 分钟,AccuracySec=1min。如果一堆任务都压在整点,可以把 AccuracySec=5min 放宽,或者加 RandomizedDelaySec=30s 错峰,避免同一秒把 CPU 和磁盘打满。

坑 4:日志不在 /var/log 里了

迁移后第一件事往往是"日志去哪了"。答案在 journald:

journalctl -u backup-job.service --since today
journalctl -u backup-job.service -f          # 实时跟踪
journalctl --user -u myjob.service           # 用户级 timer

想同时落文件,在 service 里加 StandardOutput=journal 之外,可以让脚本自己重定向,或者用 StandardOutput=append:/var/log/backup.log(systemd 240+)。

坑 5:oneshot 里不要让脚本自己后台化

Type=oneshot 的语义是"脚本跑完、退出码 0,这次执行就算成功"。如果脚本里 nohup xxx & 把真正的工作丢到后台再立刻退出,systemd 会认为任务已完成,而实际上失败了你也不知道,还会连带影响 Persistent 的补跑判断。

定时任务就老老实实前台跑完。真的需要常驻,那它就不是 timer 而是普通的 Type=simple 服务,应该用 Restart=always 管理。

三、一份可以直接抄的最小可用配置

/etc/systemd/system/backup-job.service:

[Unit]
Description=Daily backup
After=network-online.target

[Service]
Type=oneshot
User=app
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/opt/scripts/backup.sh
TimeoutStartSec=30min

/etc/systemd/system/backup-job.timer:

[Unit]
Description=Run daily backup at 06:00

[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true
RandomizedDelaySec=30s
Unit=backup-job.service

[Install]
WantedBy=timers.target

启用和核对:

sudo systemctl daemon-reload
sudo systemctl enable --now backup-job.timer
systemctl list-timers --all | grep backup

四、迁移对照清单

cron systemd timer
*/5 * * * * OnCalendar=*:0/5
0 6 * * * OnCalendar=*-*-* 06:00:00
@reboot 不需要,用 WantedBy=timers.target + Persistent
输出进 syslog journalctl -u xxx.service
无补跑 Persistent=true

小结

systemd timer 的学习成本主要在前半小时:搞清楚 .timer 和 .service 是一对、OnCalendar 要验证、Persistent 要显式打开。这三点过了之后,日志、依赖、补跑这些能力是白送的,长期看比 crontab 省心。

如果只是临时跑一次,systemd-run --on-calendar 更轻,连 unit 文件都不用写。生产环境我还是建议落成文件,便于版本管理和 review。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。