用 systemd timer 替代 crontab:更可控的定时任务

crontab 用了二十年,但它有几个真问题:失败无感知、依赖无法表达、错过执行不补跑、日志散落。systemd timer 把这些都解决了。 crontab 的四个痛点 失败了没人知道 —— 除非自己重定向邮件,而邮件服务往往没配 无法表

crontab 用了二十年,但它有几个真问题:失败无感知、依赖无法表达、错过执行不补跑、日志散落。systemd timer 把这些都解决了。

crontab 的四个痛点

  1. 失败了没人知道 —— 除非自己重定向邮件,而邮件服务往往没配
  2. 无法表达依赖 —— “等网络起来再跑”只能写 sleep 30 这种脏办法
  3. 机器休眠就跳过 —— 笔记本合盖,任务直接不执行
  4. 日志难查 —— 各任务自己重定向,格式混乱

基本用法

两个文件:一个 service 定义干什么,一个 timer 定义什么时候干。

# /etc/systemd/system/backup.service
[Unit]
Description= nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description= run backup nightly

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

启用:

systemctl daemon-reload
systemctl enable --now backup.timer

关键参数解释

Persistent=true —— 这是相比 cron 最大的优势之一。任务到点时机器是关的,下次开机会自动补跑。crontab 做不到这点。

RandomizedDelaySec=300 —— 随机延迟 5 分钟内执行。多台机器同时跑同一任务时,能错开对下游的冲击。批量机器上这条特别有用。

OnCalendar 语法:

| 表达式 | 含义 | |—|—| | *-*-* 02:30:00 | 每天 02:30 | | Mon *-*-* 09:00:00 | 每周一 09:00 | | *-*-01 00:00:00 | 每月 1 号 | | *:0/15 | 每 15 分钟 | | hourly / daily / weekly | 简写 |

依赖与前置条件

这是 systemd timer 真正的杀手级能力:

[Unit]
After=network-online.target
Requires=network-online.target

备份任务要等网络就绪。用 cron 只能猜一个 sleep 时间,现在能精确声明。

查看与调试

systemctl list-timers --all          # 看所有定时任务及下次执行时间
systemctl status backup.timer        # 看状态
journalctl -u backup.service -n 50   # 看上次执行的完整日志

list-timers 会显示 NEXT 和 LEFT,确认任务真的排上了,比 cron 的”不知道跑没跑”直观得多。

日志自动进 journald

不需要任何重定向,stdout/stderr 自动进 journald,journalctl -u 服务名 就能看。也不会无限增长——journald 自带配额和轮转。

从 cron 迁移的对照

| cron | systemd timer | |—|—| | 0 2 * * * cmd | OnCalendar=*-*-* 02:00:00 | | */15 * * * * cmd | OnCalendar=*:0/15 | | 失败无通知 | journalctl + 自行加告警 | | 休眠跳过 | Persistent=true 补跑 |

什么时候还留着 cron

一次性临时任务、容器里没有 systemd 的环境、或者团队所有人都在用 cron——一致性也是价值。但生产服务器的常规定时任务,迁移到 systemd timer 收益明显,尤其是 Persistent=true 和依赖声明这两点。