用 systemd timer 管理定时脚本:从首次运行到失败排查
定时脚本最难维护的部分,通常不是时间表达式,而是“它以谁的身份运行、工作目录在哪里、失败后去哪里看”。在使用 systemd 的 Linux 主机上,可以让 service 描述工作,让 timer 描述触发时机。下面使用已经存在的 report 用户与脚本作为示例,安装前请替换路径并确认权限。
AI生成概念配图:时钟触发服务,服务执行任务并留下检查记录,仅辅助理解,不代表真实界面或实测结果。
先让服务独立运行
创建 /etc/systemd/system/site-report.service。示例任务只读取业务数据并生成内部报告;脚本必须已安装、可执行,且 report 用户对目标目录有需要的权限。不要为了解决权限错误就让整项任务长期以 root 运行。
[Unit] Description=Generate site report [Service] Type=oneshot User=report WorkingDirectory=/srv/site-report ExecStart=/srv/site-report/run.sh TimeoutStartSec=10min
这里的十分钟是演示超时,需要根据任务边界调整。ExecStart 不是交互式 shell 命令行,管道、重定向和 shell 展开不应直接照搬进去。复杂逻辑放进脚本,并为关键失败返回非零退出码。否则调度器看到“成功退出”,并不知道报表其实只写到一半。
再添加日历触发器
[Unit] Description=Run site report daily [Timer] OnCalendar=*-*-* 02:30:00 UTC Persistent=true RandomizedDelaySec=5min [Install] WantedBy=timers.target
将以上内容保存为同名的 site-report.timer。未另外指定 Unit 时,它会触发对应的 service。示例明确使用 UTC,避免维护者误把服务器时区当成本地时区。RandomizedDelaySec 会引入随机延迟,所以此配置表达的是附近的时间窗口,不是精确到秒的业务承诺。
Persistent 对 OnCalendar 日历任务有意义:计时器重新激活时,若停用期间错过应触发的时间,会安排一次补跑。它不是逐条重放每个漏掉的日期。如果报表必须覆盖多天,脚本应保存处理水位,自己查找未完成的日期。这个设计能让补跑与手工重试走同一条可靠路径。
启用前做三层检查
sudo systemd-analyze verify \ /etc/systemd/system/site-report.service \ /etc/systemd/system/site-report.timer systemd-analyze calendar '*-*-* 02:30:00 UTC' sudo systemctl daemon-reload sudo systemctl start site-report.service sudo journalctl -u site-report.service --no-pager sudo systemctl enable --now site-report.timer systemctl list-timers --all site-report.timer
先检查语法与日历,再手动运行 service,确认产物无误后才启用 timer。这样能把“脚本问题”和“触发问题”分开。systemctl status 展示当前状态,journalctl 展示执行过程;还应查看输出文件时间、记录数量等业务证据,不能把 Active 一栏当作完整验收。
如果脚本依赖环境变量,不要假设服务会读取登录终端的配置。把必要参数通过经过审查的服务配置或配置文件明确提供,并限制敏感配置的读取权限。排错时只记录配置项是否存在,避免把密钥完整打印进 journal。
把重入与故障当成设计条件
同一 service 仍处于活动状态时,timer 不会为它再启动一个并行实例。但脚本如果自行转入后台、另有人工入口,仍可能产生重叠。让脚本保持前台执行,并按需要使用应用级互斥与幂等处理。不要为一次性任务随意加入 RemainAfterExit=yes,否则它可能保持活动状态,后续计时触发不会按预期再执行。
最后做一次受控失败演练:让测试任务返回错误,确认日志能定位原因、监控会发现产物缺失,再恢复配置。本文配置采用常见指令;发行版打包版本可能不同,具体行为应同时核对本机 man systemd.timer 和 systemd --version。


