在 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 自动化运维场景。