Linux systemd Service 与 Timer 定时任务原理及实战

在 Linux 运维中,我们经常需要周期执行一些任务,例如:

  • 定时执行安全巡检
  • 定时备份数据库
  • 定时清理日志
  • 定时同步文件
  • 定时检查磁盘
  • 定时执行监控脚本

传统方案通常使用 crontab

在使用 systemd 的 Linux 系统中,还可以使用:

text 复制代码
.service + .timer

实现定时任务。

本文以一个实际的安全巡检任务为例:

text 复制代码
process-security-check.service
process-security-check.timer

介绍 systemd Service 和 Timer 是如何配合工作的,以及日常应该如何部署、查看和维护。


1. Service 和 Timer 分别负责什么?

首先记住一句话:

text 复制代码
.service = 做什么
.timer   = 什么时候做

例如我们的安全巡检脚本:

bash 复制代码
/usr/local/sbin/process-security-check --check

真正执行这个命令的是:

text 复制代码
process-security-check.service

而决定什么时候执行的是:

text 复制代码
process-security-check.timer

整个调用关系:

text 复制代码
process-security-check.timer
        │
        │ 到达触发时间
        ▼
process-security-check.service
        │
        │ ExecStart
        ▼
/usr/local/sbin/process-security-check --check
        │
        ▼
执行安全巡检
        │
        ▼
写入日志
        │
        ▼
脚本退出

因此 Timer 本身并不执行我们的 Shell 脚本。

它只负责:

text 复制代码
时间调度

真正执行命令的是 Service。


2. Service 配置

创建:

bash 复制代码
vim /etc/systemd/system/process-security-check.service

内容:

ini 复制代码
[Unit]
Description=Linux Process Security Check
After=local-fs.target network.target

[Service]
Type=oneshot
EnvironmentFile=-/etc/process-security-check.env
ExecStart=/usr/local/sbin/process-security-check --check
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
NoNewPrivileges=true

这里最重要的是:

ini 复制代码
Type=oneshot
ExecStart=/usr/local/sbin/process-security-check --check

意思是:

text 复制代码
启动 Service
       ↓
执行 ExecStart
       ↓
运行 process-security-check --check
       ↓
等待脚本执行结束
       ↓
Service 本次任务完成

3. 为什么使用 Type=oneshot?

我们的安全巡检程序不是常驻服务。

它的工作方式是:

text 复制代码
开始
 ↓
执行检查
 ↓
生成日志
 ↓
结束

所以非常适合:

ini 复制代码
Type=oneshot

例如手工执行:

bash 复制代码
systemctl start process-security-check.service

实际上 systemd 就会执行:

bash 复制代码
/usr/local/sbin/process-security-check --check

脚本执行结束以后:

bash 复制代码
systemctl status process-security-check.service

可能看到:

text 复制代码
Active: inactive (dead)

对于 oneshot 类型的任务,这并不意味着异常。

应该继续观察:

text 复制代码
status=0/SUCCESS

如果执行结果为成功,就说明本次巡检已经正常完成。


4. Timer 配置

创建:

bash 复制代码
vim /etc/systemd/system/process-security-check.timer

内容:

ini 复制代码
[Unit]
Description=Run Linux Process Security Check

[Timer]
OnBootSec=3min
OnUnitActiveSec=5min
RandomizedDelaySec=30
Persistent=true

[Install]
WantedBy=timers.target

Timer 负责控制:

text 复制代码
什么时候执行 Service

它本身不会执行:

bash 复制代码
/usr/local/sbin/process-security-check

而是到时间以后启动:

text 复制代码
process-security-check.service

5. Timer 怎么知道要启动哪个 Service?

这是 systemd 一个非常重要的机制。

我们的文件名称分别是:

text 复制代码
process-security-check.timer
process-security-check.service

它们具有完全相同的基础名称:

text 复制代码
process-security-check

所以:

text 复制代码
process-security-check.timer
            │
            │ 默认关联
            ▼
process-security-check.service

Timer 到达触发时间以后,相当于 systemd 执行:

bash 复制代码
systemctl start process-security-check.service

因此 Timer 文件中不需要再写:

ini 复制代码
ExecStart=

ExecStart 属于 Service。


6. Timer 参数解释

6.1 OnBootSec

ini 复制代码
OnBootSec=3min

表示:

text 复制代码
系统启动后大约 3 分钟触发

例如服务器:

text 复制代码
10:00 开机

那么第一次任务大约:

text 复制代码
10:03

开始具备触发条件。


6.2 OnUnitActiveSec

ini 复制代码
OnUnitActiveSec=5min

表示按照该 Timer 单元上次被激活的时间继续计算下一次触发。

在这个场景中,就是让安全巡检以约 5 分钟的周期持续触发。

可以理解为:

text 复制代码
第一次触发
   │
   ├── 5 分钟
   ▼
再次触发
   │
   ├── 5 分钟
   ▼
再次触发

6.3 RandomizedDelaySec

ini 复制代码
RandomizedDelaySec=30

表示 systemd 可以在计划时间基础上增加随机延迟。

例如原计划:

text 复制代码
10:05:00

实际可能:

text 复制代码
10:05:17

这样设计对于大量服务器特别有价值。

假设有:

text 复制代码
500 台服务器

如果全部严格:

text 复制代码
10:05:00

执行安全扫描,可能同时产生 CPU、磁盘和日志压力。

增加:

ini 复制代码
RandomizedDelaySec=30

后可以把执行时间适当分散。


7. Persistent=true

配置:

ini 复制代码
Persistent=true

对于基于日历时间的 Timer 特别有价值。

例如深度扫描配置:

ini 复制代码
OnCalendar=*-*-* 03:30:00
Persistent=true

原计划:

text 复制代码
03:30 执行

但是服务器:

text 复制代码
03:00 关机
08:00 开机

systemd 可以记录这个日历型 Timer 在关机期间错过了执行时间,并在恢复后进行补触发。

因此非常适合:

text 复制代码
数据库备份
安全扫描
日志维护
每日巡检

等任务。


8. Service 与 Timer 完整执行流程

假设:

text 复制代码
10:00
服务器启动

systemd 启动:

text 复制代码
process-security-check.timer

Timer 进入:

text 复制代码
active (waiting)

状态。

然后:

text 复制代码
10:03 左右

Timer 触发:

text 复制代码
process-security-check.service

Service 执行:

bash 复制代码
/usr/local/sbin/process-security-check --check

脚本执行:

text 复制代码
隐藏进程检查
      ↓
deleted executable 检查
      ↓
临时目录执行程序检查
      ↓
监听端口检查
      ↓
systemd 基线检查
      ↓
cron 基线检查
      ↓
用户基线检查
      ↓
SSH Key 基线检查
      ↓
写入日志

脚本结束以后:

text 复制代码
Service
   ↓
执行完成

但是:

text 复制代码
Timer

不会结束。

它继续:

text 复制代码
active (waiting)

等待下一次触发。

因此整体结构是:

text 复制代码
              systemd
                 │
                 ▼
process-security-check.timer
                 │
                 │ 时间到
                 ▼
process-security-check.service
                 │
                 ▼
      process-security-check
                 │
                 ▼
              --check
                 │
                 ▼
              执行完成
                 │
                 ▼
              Service结束

Timer继续等待
      │
      └────────── 5分钟左右 ──────────┐
                                      │
                                      ▼
                              再次启动Service

9. 为什么不直接写 while true?

我们完全可以写:

bash 复制代码
while true
do
    /usr/local/sbin/process-security-check --check

    sleep 300
done

然后创建一个常驻 Service。

但是这种方式需要脚本自己管理:

text 复制代码
循环
sleep
异常退出
任务周期
进程生命周期

使用:

text 复制代码
systemd Timer + oneshot Service

以后,调度问题交给 systemd。

结构更加清晰:

text 复制代码
Timer
 │
 ├── 管时间
 │
 ▼
Service
 │
 ├── 管执行
 │
 ▼
Script
 │
 └── 管业务逻辑

也就是:

text 复制代码
调度层 → Timer
执行层 → Service
业务层 → Script

这是一个非常清晰的职责分离。


10. 为什么只 enable Timer?

部署完成以后执行:

bash 复制代码
systemctl daemon-reload

然后:

bash 复制代码
systemctl enable --now process-security-check.timer

通常不需要:

bash 复制代码
systemctl enable process-security-check.service

原因是我们希望:

text 复制代码
服务器启动
     ↓
Timer 自动启动
     ↓
Timer 到时间
     ↓
Service 被调用

而不是:

text 复制代码
服务器启动
     ↓
直接启动一次 Service

所以真正需要设置开机自动启动的是:

text 复制代码
process-security-check.timer

11. enable、start 和 --now 的区别

执行:

bash 复制代码
systemctl start process-security-check.timer

表示:

text 复制代码
现在启动 Timer

但是不代表以后开机会自动启动。

执行:

bash 复制代码
systemctl enable process-security-check.timer

表示:

text 复制代码
以后开机自动启动 Timer

但不会主动启动当前尚未运行的 Timer。

所以通常使用:

bash 复制代码
systemctl enable --now process-security-check.timer

相当于:

bash 复制代码
systemctl enable process-security-check.timer
systemctl start process-security-check.timer

12. 查看 Timer 状态

执行:

bash 复制代码
systemctl status process-security-check.timer

正常应该看到类似:

text 复制代码
Loaded: loaded
Active: active (waiting)
Trigger: ...
Triggers: process-security-check.service

这里最重要的是:

text 复制代码
Active: active (waiting)

表示:

text 复制代码
Timer 正在运行
正在等待下一次触发

以及:

text 复制代码
Triggers: process-security-check.service

表示:

text 复制代码
这个 Timer 会触发 process-security-check.service

13. 查看所有 Timer

这是日常运维非常实用的命令:

bash 复制代码
systemctl list-timers --all

只查看我们的安全巡检:

bash 复制代码
systemctl list-timers --all | grep process-security

通常可以看到:

text 复制代码
NEXT
LEFT
LAST
PASSED
UNIT
ACTIVATES

其中最重要的是:

text 复制代码
UNIT                              ACTIVATES

process-security-check.timer  →   process-security-check.service

可以非常直观地看到:

text 复制代码
Timer
  ↓
Service

的对应关系。


14. 手工触发 Service

即使 Timer 正在运行,我们仍然可以手工执行:

bash 复制代码
systemctl start process-security-check.service

相当于立即执行:

bash 复制代码
/usr/local/sbin/process-security-check --check

关系变成:

text 复制代码
Timer 定时触发 ──────┐
                     │
                     ▼
                   Service
                     ▲
                     │
管理员手工触发 ──────┘

所以 Service 可以:

text 复制代码
Timer 自动启动

也可以:

text 复制代码
管理员手工启动

15. 查看 Service 执行结果

执行:

bash 复制代码
systemctl status process-security-check.service

查看 systemd 日志:

bash 复制代码
journalctl \
    -u process-security-check.service \
    -n 100 \
    --no-pager

实时观察:

bash 复制代码
journalctl \
    -u process-security-check.service \
    -f

也可以直接查看安全巡检日志:

bash 复制代码
ls -lht \
    /var/log/process-security-check/check-*.log |
    head

如果日志持续生成,例如:

text 复制代码
check-20260920-110500.log
check-20260920-110000.log
check-20260920-105500.log

就说明:

text 复制代码
Timer
  ↓
Service
  ↓
Script
  ↓
Log

整条执行链路正常。


16. Service 执行时间超过周期怎么办?

假设:

text 复制代码
10:00
Service 开始执行

因为服务器负载较高,到:

text 复制代码
10:07

才执行完成。

对于同一个普通 Service 单元,systemd 不会简单地因为 Timer 再次到期就同时启动多个相同的 active Service 实例。

对于我们的安全脚本,还增加了:

bash 复制代码
flock

进行第二层保护。

因此:

text 复制代码
systemd
   │
   ├── 管理 Service 生命周期
   │
   ▼
process-security-check
   │
   ├── flock
   │
   ▼
防止脚本重复运行

即使管理员误操作同时手工执行:

bash 复制代码
/usr/local/sbin/process-security-check --check

脚本自身的 flock 也可以进一步降低多个实例同时运行的风险。


17. 快速巡检和深度巡检

实际生产环境中,我们可以设计两套 Timer。

快速巡检

text 复制代码
process-security-check.timer
          │
          ▼
process-security-check.service
          │
          ▼
process-security-check --check

例如:

text 复制代码
每 5 分钟执行

用于:

text 复制代码
隐藏进程
监听端口
临时目录程序
deleted executable
systemd
cron
用户
SSH Key

等快速检查。


深度巡检

另外创建:

text 复制代码
process-security-deep-check.timer

对应:

text 复制代码
process-security-deep-check.service

Service:

ini 复制代码
[Unit]
Description=Linux Deep Process Security Check
After=local-fs.target network.target

[Service]
Type=oneshot
EnvironmentFile=-/etc/process-security-check.env
ExecStart=/usr/local/sbin/process-security-check --deep
Nice=15
IOSchedulingClass=idle

Timer:

ini 复制代码
[Unit]
Description=Daily Linux Deep Security Check

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=10min
Persistent=true

[Install]
WantedBy=timers.target

启动:

bash 复制代码
systemctl daemon-reload

systemctl enable --now \
    process-security-deep-check.timer

最终形成:

text 复制代码
                 Linux Security Check

          ┌──────────────┴──────────────┐
          │                             │
          ▼                             ▼
      快速巡检                       深度巡检

每 5 分钟左右                       每天 03:30
          │                             │
          ▼                             ▼
check.timer                     deep-check.timer
          │                             │
          ▼                             ▼
check.service                   deep-check.service
          │                             │
          ▼                             ▼
--check                         --deep

18. 修改 Service 或 Timer 后为什么要 daemon-reload?

例如修改:

bash 复制代码
vim /etc/systemd/system/process-security-check.timer

把:

ini 复制代码
OnUnitActiveSec=5min

修改成:

ini 复制代码
OnUnitActiveSec=10min

保存文件以后需要:

bash 复制代码
systemctl daemon-reload

因为 systemd manager 已经在内存中加载了 Unit 配置。

daemon-reload 的作用可以理解为:

text 复制代码
/etc/systemd/system/*.service
/etc/systemd/system/*.timer
            │
            │ 重新读取
            ▼
       systemd manager

然后建议重新启动 Timer:

bash 复制代码
systemctl restart process-security-check.timer

检查:

bash 复制代码
systemctl status process-security-check.timer

以及:

bash 复制代码
systemctl list-timers --all |
    grep process-security

19. 停止 Timer 与停止 Service 的区别

执行:

bash 复制代码
systemctl stop process-security-check.timer

表示:

text 复制代码
停止以后继续定时触发

但是如果 Service 此时正在执行,需要单独判断是否要停止它。

执行:

bash 复制代码
systemctl stop process-security-check.service

表示:

text 复制代码
停止当前 Service

但 Timer 如果还在运行:

text 复制代码
active (waiting)

以后仍然可能再次触发 Service。

因此如果想彻底暂停自动巡检:

bash 复制代码
systemctl stop process-security-check.timer

如果希望同时取消开机启动:

bash 复制代码
systemctl disable --now process-security-check.timer

20. 常用运维命令

查看 Timer:

bash 复制代码
systemctl status process-security-check.timer

查看下一次执行时间:

bash 复制代码
systemctl list-timers --all |
    grep process-security

手工执行一次:

bash 复制代码
systemctl start process-security-check.service

查看 Service:

bash 复制代码
systemctl status process-security-check.service

查看 Service 日志:

bash 复制代码
journalctl \
    -u process-security-check.service \
    -n 100 \
    --no-pager

实时查看:

bash 复制代码
journalctl \
    -u process-security-check.service \
    -f

停止自动巡检:

bash 复制代码
systemctl stop process-security-check.timer

重新启动 Timer:

bash 复制代码
systemctl restart process-security-check.timer

取消开机启动:

bash 复制代码
systemctl disable --now \
    process-security-check.timer

重新启用:

bash 复制代码
systemctl enable --now \
    process-security-check.timer

修改 Unit 后:

bash 复制代码
systemctl daemon-reload

检查 Unit 配置:

bash 复制代码
systemctl cat process-security-check.service

systemctl cat process-security-check.timer

21. Service + Timer 和 crontab 的区别

对于简单任务:

text 复制代码
每天执行一个 Shell

crontab 依然非常方便。

而对于服务器标准化运维,Service + Timer 有一些明显优势。

例如:

text 复制代码
systemctl status
journalctl
依赖关系
资源调度
失败状态
权限限制
随机延迟
开机补执行
Unit 生命周期管理

都可以直接使用 systemd 的能力。

结构也更加明确:

text 复制代码
cron:

时间 + 命令
     │
     └── 写在一起


systemd:

Timer
 │
 └── 时间

Service
 │
 └── 怎么执行

Script
 │
 └── 做什么业务

对于安全巡检、数据库备份、日志维护、磁盘检查等服务器级任务,我更倾向于:

text 复制代码
Timer
  +
oneshot Service
  +
独立 Script

这种结构。


22. 总结

systemd Service 和 Timer 的关系其实并不复杂。

记住:

text 复制代码
.service
负责执行任务

.timer
负责调度任务

完整关系:

text 复制代码
            systemd
               │
               ▼
             Timer
               │
          到达触发时间
               │
               ▼
            Service
               │
            ExecStart
               │
               ▼
             Script
               │
           执行业务逻辑
               │
               ▼
             结束

对于周期任务:

text 复制代码
Timer
  ↓
Service
  ↓
Script
  ↓
退出

Timer继续等待
  ↓
下一次触发

因此在实际运维过程中,需要长期保持:

text 复制代码
active (waiting)

的是:

text 复制代码
process-security-check.timer

而:

text 复制代码
process-security-check.service

使用:

ini 复制代码
Type=oneshot

执行结束后退出是正常现象。

最后可以通过:

bash 复制代码
systemctl list-timers --all |
    grep process-security

快速确认:

text 复制代码
Timer
   ↓
ACTIVATES
   ↓
Service

的调度关系是否正常。

这套机制不仅可以用于安全巡检,也可以直接复用于:

text 复制代码
数据库备份
日志清理
磁盘巡检
文件同步
证书检查
Docker/Kubernetes 运维脚本
监控数据采集
系统健康检查

等大量 Linux 自动化运维场景。

相关推荐
熊明才2 小时前
WSL2 网络突然瘫痪(Network is unreachable / 没有 eth0)最短修复指南(2026年9月20日亲测可用)
linux·windows·wsl2
Tairitsu_H2 小时前
[Linux系统] 自动化构建make / Makefile | 进度条
linux·操作系统·makefile·make
行者全栈架构师2 小时前
大模型运维准确率不到 50%?我们用 DeepSeek V4 测了 5 大场景,结果出人意料
linux·人工智能·开源
zzzyyy5383 小时前
TCP 协议学习笔记:从报文格式到异常处理
linux·网络
傲世仙尊3 小时前
ELF与动态库进阶-链接真相与GOT表
linux
M78佐菲4 小时前
ARM学习笔记(五)
linux·arm开发·笔记·嵌入式硬件·学习
H_oRIZoN_5 小时前
Linux入门DAY45 IMX6ULL 裸机开发笔记:汇编点灯、C 语言点灯、GIC 中断梳理
linux·单片机·嵌入式硬件
临期冰淇淋5 小时前
Unisoc 展锐平台Camera摄像头分辨率适配
linux·c语言·驱动开发
流浪0016 小时前
Linux系统篇36——线程(一) 线程的概念、本质和Linux的实现方式
linux·面试·操作系统·线程