systemd timer 只执行一次:oneshot 服务仍是 active (exited)
systemd 定时器只执行一次且服务保持 active (exited) 时,检查 RemainAfterExit;一次任务应结束为 inactive,带 ExecStop 的服务须先核对生命周期。
定时器第一次能启动脚本,脚本也正常结束,但后面的时间点没有新的执行记录。查看对应服务时,它仍显示 active (exited),配置里同时有 Type=oneshot 和 RemainAfterExit=yes,这通常说明服务被保留为 active,后续 timer 到期不会再次启动它。
systemd timer 激活一个已经 active 的单元时,会让它继续保持当前状态,不会自动执行一次 restart。进程已经退出与服务已经 inactive 是两件事。下面针对需要每次到期重新完成一项任务的 oneshot 服务;用于建立持续状态、等待以后撤销的服务,要保留它原本的生命周期设计。
把定时器状态和服务状态放在一起看
以下 example-task 是示例单元名;如果 timer 用 Unit= 指向了不同服务,应查询那个实际服务:
systemctl cat example-task.timer example-task.service
systemctl list-timers --all example-task.timer
systemctl show example-task.service -p Type -p RemainAfterExit -p ActiveState -p SubState
journalctl -u example-task.service --since today
cat 展示单元及 drop-in,确认真正生效的配置;list-timers 观察触发记录,日志用来判断脚本到底有没有再次执行。不能只看某一次 NEXT 为 n/a 就认定原因,各版本和定时器状态下显示可能不同。
服务属性若类似下面的示例,说明它没有运行中的主程序,却仍被 systemd 当成 active:
Type=oneshot
RemainAfterExit=yes
ActiveState=active
SubState=exited
对一次任务来说,这会阻止下一次通过 start 重新执行。若服务实际是 activating,或仍有正在运行的进程,应先查任务耗时、等待和卡住的操作;关闭 RemainAfterExit 不会让尚未完成的工作提前结束。
一次任务结束后,让单元回到 inactive
适合重复运行的简单示例服务为:
[Unit]
Description=Run example periodic task
[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/usr/local/bin/example-task
脚本路径是示例,要换成已有、可执行且按设计在任务完成时退出的程序。RemainAfterExit=no 也是默认值;显式写出它可以覆盖原单元或 drop-in 里的 yes。
对应的示例定时器为:
[Unit]
Description=Schedule example periodic task
[Timer]
OnCalendar=minutely
Unit=example-task.service
[Install]
WantedBy=timers.target
minutely 用来表达每分钟的日历计划,实际触发可能受 AccuracySec= 等设置影响,不应按秒级精度验收。频率应按业务需要选择;任务仍在运行时,timer 也不会自动为同一服务并行创建新实例。
修改前备份原单元及 drop-in。对已经处于 active 的服务,仅执行 daemon-reload 不会自动撤销它的当前状态。确认停止动作和业务影响后,可以停止定时器和服务、重新加载,再启动定时器:
sudo systemctl stop example-task.timer
sudo systemctl stop example-task.service
sudo systemctl daemon-reload
sudo systemctl start example-task.timer
停止 timer 会暂停后续调度,停止 service 可能终止仍在运行的进程,并执行其停止阶段配置。不要在一个正在写备份或处理数据的任务中无计划执行这组操作,也不要顺手改成 restart 来强行覆盖尚未结束的任务。
配有 ExecStop 时,先检查任务是否真的应该结束
有些 oneshot 服务的 ExecStart= 建立防火墙规则或其他持续状态,ExecStop= 用来撤销。此时 RemainAfterExit=yes 使这项状态保持 active,有实际用途;每分钟简单删除这个设置,可能让服务在启动动作结束后马上进入停止阶段,执行 ExecStop=,把刚建立的状态撤掉。
如果当前服务承担这类职责,应把周期检查或刷新整理为独立的一次任务单元,保留原服务的启动和停止规则。若 ExecStop= 只是任务清理,也需确认它在任务完成后的停止阶段执行是否正确。不要为了让 timer 重复执行而直接删掉必要的清理或取消进程管理。
用连续两轮记录验收
修改后,正常完成的周期任务应回到 inactive (dead),下一个触发时间到达后,日志里出现第二轮新的执行记录,退出状态也应正常。检查的是两次业务任务都实际完成,而不只是 timer 仍显示 active。
如果服务已经 inactive 却仍没有新记录,再检查 timer 是否启动、Unit= 是否匹配、日历表达式和时区,以及实际有效的 drop-in。Persistent= 处理部分日历定时器停用期间的补触发,不会把已 active 的服务变成自动 restart。
需要回退时,恢复备份中的单元和 drop-in,重新加载并按原业务规则恢复启停状态。回退配置不会撤销脚本已经完成的数据修改;涉及业务数据的恢复仍应使用该任务自己的恢复流程。


