用 systemd timer 替代 cron:把定时任务的日志、重试和依赖一次管起来
在服务器上实现「每天凌晨跑一次脚本」,多数人的第一反应是写一条 crontab。真到排障的时候才会发现麻烦:脚本输出散落在邮件或某个自建日志里,跑失败了没有重试,依赖的服务是否就绪只能靠 sleep 30 硬等,想临时停掉还得去改注释,改完还容易忘记恢复。其实 systemd 自带的 timer 就能把这几件事一次性收口,而且不需要额外装任何软件。
最小可用的一对单元
timer 不是一个文件,而是两个:.service 描述「做什么」,.timer 描述「什么时候做」,都放在 /etc/systemd/system/ 下,两个文件同名不同后缀即可自动关联。
先写服务单元 /etc/systemd/system/backup-db.service:
[Unit]
Description=Daily database backup
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-db.sh
Restart=on-failure
RestartSec=60
Type=oneshot 表示跑完就退出,正是定时任务该用的类型。After 和 Wants 解决的是「数据库还没起来就开跑」这个经典坑:它声明了顺序和弱依赖,比在脚本里 sleep 猜时间靠谱得多。Restart=on-failure 配合 RestartSec 让偶发失败能自动重试一次——cron 本身没有这个能力,只能自己在脚本里写循环。
再写定时器 /etc/systemd/system/backup-db.timer:
[Unit]
Description=Run backup-db daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300
Unit=backup-db.service
[Install]
WantedBy=timers.target
几个容易忽略但很值的参数:
Persistent=true:如果机器在 03:00 关机或休眠,开机后会立刻补跑一次。cron 对「错过的时间点」是直接跳过的,备份类任务最怕这个。RandomizedDelaySec=300:在 0~5 分钟内随机延迟。几十台机器都配同一个 03:00 时,能避免同时打爆存储或 API。OnCalendar支持Mon..Fri *-*-* 02:30:00、*-*-01 00:00:00这类写法,比 crontab 的五列更可读。
启用的时候注意:要启动的是 timer,不是 service。
sudo systemctl daemon-reload
sudo systemctl enable --now backup-db.timer
systemctl list-timers --all | grep backup
systemctl list-timers 会直接列出「下次触发时间」和「上次触发时间」,不用再猜配置有没有生效。如果手工想跑一次验证脚本本身,用 systemctl start backup-db.service 即可,这不会打乱 timer 的节奏。
排障体验的差别
这是我最推荐迁移的一点。所有 stdout/stderr 自动进 journald,不用在命令行末尾追加 >> /var/log/xxx.log 2>&1:
journalctl -u backup-db.service --since "today"
journalctl -u backup-db.service -n 50 -f
systemctl status backup-db.timer
想按时间范围筛、想看某次执行的退出码、想和别的单元日志对齐时间线,都是一条命令的事。而 cron 环境下,日志去哪了往往取决于写脚本那个人当天的心情。
什么时候仍该用 cron
不是所有场景都值得迁移。容器里通常没有 systemd,Alpine 镜像更是默认不带;这种环境用 cron 或者把调度交给外部的 K8s CronJob 更合适。另外,如果只是一台临时机器上的一次性任务,systemd-run --on-calendar 可以不落文件地建个临时 timer,比改 crontab 还轻。
还有个细节:.service 里没有显式配置 Environment= 时,systemd 不会继承你当前 shell 的任何环境变量——PATH、HOME、代理设置都和手工执行时不同。脚本里尽量写绝对路径,或者用 EnvironmentFile=/etc/backup.env 明确注入,能省掉「手动跑正常、定时跑就报 command not found」这类半天排查。
从 cron 迁到 systemd timer 的成本大概就是两个 ini 文件,换来的是可观测、可重试、可声明依赖、错过能补跑。对于长期跑在生产环境上的定时任务,这笔交换很划算。
- 点赞
- 收藏
- 关注作者
评论(0)