cron 式语法并不是维护任务契约的全部。命令需要一个清晰的服务身份、一个有界的运行时间,以及能区分"未调度"与"运行但失败"的证据。一个 systemd 定时器与 oneshot 服务能把这些关注点分开。

每小时调度、随机延迟、oneshot 激活与结果证据是时间线上的独立阶段。错过的日历事件合并为一次补跑,仍受随机延迟限制。两分钟激活超时还需考虑停止处理与工具自身的有界请求。成功可以回到非活动状态;必须对应 Result、退出状态和新的日志时间戳。服务仍活动时,重复定时器不会再启动实例。
定时器触发只证明发生了调度事件。用服务结果、退出状态和新的时间戳判断本次运行。

把工作定义成服务

使用显式用户与绝对命令路径:

# /etc/systemd/system/site-check.service
[Unit]
Description=Read-only public site check

[Service]
Type=oneshot
User=sitecheck
ExecStart=/usr/local/bin/check-public-site
TimeoutStartSec=2min

TimeoutStartSec 限制 oneshot 的激活过程,其中包括命令运行时间;oneshot 默认没有启动超时。超时处理之后还可能需要停止超时,且不能立即结束不可中断的内核 I/O,因此工具本身也需要有界的网络请求。当契约失败时,helper 应返回非零退出码,并且不得修改它所检查的服务。

调度该服务

# /etc/systemd/system/site-check.timer
[Unit]
Description=每小时运行公共站点检查

[Timer]
OnCalendar=hourly
Persistent=true
RandomizedDelaySec=5min
Unit=site-check.service

[Install]
WantedBy=timers.target

若至少错过一次日历事件,Persistent=true 会在定时器重新激活时触发一次补跑;它不会逐小时重放所有错过的运行。补跑仍受随机延迟限制。RandomizedDelaySec 把一个窗口内的例行工作分散开;当任务必须在精确时刻运行时它并不合适。

启用前先验证

创建两个单元文件后,先验证并重载管理器,再手动启动服务。这些是在目标测试主机上的管理操作,不是只读检查:

systemd-analyze verify /etc/systemd/system/site-check.service \
  /etc/systemd/system/site-check.timer
systemd-analyze calendar hourly
systemctl daemon-reload
systemctl start site-check.service
systemctl status site-check.service
journalctl -u site-check.service -n 50 --no-pager
systemctl show site-check.service -p Result -p ExecMainCode -p ExecMainStatus

只有当服务在预期的用户、环境、路径与网络访问下成功时才启用定时器:

systemctl enable --now site-check.timer
systemctl list-timers site-check.timer

对服务结果告警

定时器触发并不等于成功。要监控 oneshot 服务的退出状态或日志结果。保持 stdout 简洁,调试需要的详细制品写入一个有界的路径。

成功的 oneshot 通常回到 inactive (dead);这本身不是失败,systemctl status 对非活动服务可能返回非零。应将 Result、退出状态与新的日志时间戳对应,避免把旧运行的成功误认成本次结果。不要为了显示绿色而添加 RemainAfterExit=yes:服务仍处于活动状态时,重复定时器不会启动一个新实例。日历时间默认使用主机时区,而且常规定时器精度会在随机延迟之外增加调度余量。

来源