先看一个熟悉的场景:服务器跑了几个月没重启,某天部署新服务时 df -h 提示磁盘用了 97%。排查半天,发现一个 20 多 GB 的应用日志静静躺在 /var/log 里。日志不会自己停,写日志的程序也不会替你删旧文件。这件事只有 logrotate 在管,而且它只在配置正确时才管。
logrotate 的张力问题很具体:轮转规则配错,轻则旧日志堆积照样写满磁盘,重则程序继续往已改名的文件里写、日志凭空消失。本文按运维视角把配置语法、两种轮转姿势的取舍、验证方法一次讲清。已有的防火墙加固见服务器安全加固清单,定时机制见 cron 与 systemd timer 选型。
先搞懂它怎么被触发
Ubuntu 24.04 里 logrotate 默认已安装,由 systemd timer 驱动。查它的调度:
systemctl list-timers | grep logrotate
cat /usr/lib/systemd/system/logrotate.timer
timer 每天触发一次 logrotate.service,服务执行 logrotate /etc/logrotate.conf。主配置末尾有一行 include /etc/logrotate.d,于是该目录下的每个文件都会被读入——给新服务配轮转,标准做法是在 /etc/logrotate.d/ 下新建一个文件,永远别动主配置。
两个容易误解的点:
daily 不等于每天必转。 默认状态文件 /var/lib/logrotate/status 记录着每个文件上次轮转的时间,logrotate 一个文件一天最多转一次。昨天刚转过,今天即使条件满足也跳过。
size 与时间条件互斥。 配了 size 100M 之后,logrotate 只看文件大小、无视时间。想「到了周期或超了大小都转」,用 minsize 或 maxsize,它们同时看大小和时间戳。
一个能直接用的配置模板
假设有一个自己写的服务,日志在 /var/log/myapp/app.log。在 /etc/logrotate.d/myapp 写入:
/var/log/myapp/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
create 0640 appuser appgroup
sharedscripts
postrotate
systemctl kill -s HUP myapp.service 2>/dev/null || true
endscript
}
逐行说清每个选择的理由:
| 指令 | 作用 | 为什么选它 |
|---|---|---|
daily / rotate 14 | 每天转一次,保留 14 份 | 两周内的日志可回溯,第 15 份自动删除,磁盘占用有上限 |
missingok | 日志不存在时跳过不报错 | 服务重启期间日志文件可能暂时不存在,避免每天的报错噪音 |
notifempty | 空文件不轮转 | 空日志转出来只是垃圾文件 |
compress + delaycompress | 转过后压缩,最新一份延迟到下轮再压 | 文本日志 gzip 后通常只剩十分之一体积;延迟一份是给「程序还没关掉旧文件句柄」留缓冲 |
dateext | 归档文件名用日期后缀 | app.log-20261006.gz 一眼看出时间范围,比 app.log.3.gz 好查 |
create 0640 appuser appgroup | 轮转后立即按指定权限新建空文件 | 不配的话新文件属 root,应用进程下次写日志直接权限报错 |
sharedscripts | 通配多个文件时脚本只跑一次 | 没有 sharedscripts,通配符匹配 3 个文件 postrotate 就执行 3 次 |
程序不支持重开日志怎么办:copytruncate
上面模板的 postrotate 向进程发 HUP 信号,让它关闭旧文件、打开新文件——这是最干净的姿势。但很多程序不听 HUP,或者你根本没法给它们发信号,它们会攥着旧文件描述符一直写。结果就是:文件被改名了,数据却还在往改名后的文件里灌,新文件永远是空的。
copytruncate 解决这类程序:先把原文件复制一份存档,再把原文件原地截断为零。文件路径和 inode 都没变,程序继续写也不出岔子:
/var/log/legacy-app/*.log {
daily
rotate 7
copytruncate
compress
}
代价要清楚:复制和截断之间有一小段时间差,这段时间里写入的日志会丢。另外 copytruncate 与 create 互斥(原文件还在,无需新建)。能发信号重开日志的程序,优先用 postrotate + create;实在没辙再上 copytruncate,丢几行日志换文件完整性,多数场景划算。
配完先验证,别等明天
logrotate 最友好的地方是调试模式:-d 只打印执行计划,不真改文件、不更新状态。写完配置必跑:
logrotate -d /etc/logrotate.d/myapp
输出会列出它准备处理哪些文件、按什么顺序执行哪些动作,末尾有:
log does not need rotating (log has been rotated at 2026-10-6-6, that is not day yet)
这类提示说明状态文件记录的轮转时间还没到期,属正常。想立刻真刀真枪跑一次验证全流程,加 -f 强制轮转:
logrotate -f -v /etc/logrotate.d/myapp
ls -l /var/log/myapp/
能看到 app.log-20261006.gz 和新生成的 app.log 就对了。注意 -f 也会真实执行 postrotate 脚本,确认脚本无害再按。
其他常用命令:
cat /var/lib/logrotate/status # 每个文件上次轮转时间
logrotate -s /tmp/test.status -f /etc/logrotate.d/myapp # 换状态文件手动测试,不污染线上记录
第二个命令把状态文件指到临时路径,反复测试不留下轮转痕迹,调配置时很好用。
常见问题
Q:配了 daily 为什么日志隔了几天才转?
timer 每天固定时间触发,而 daily 只保证「距上次轮转满一天」;如果文件在两次触发之间才满足条件,要等下一次触发才处理。想更精细就叠加 maxsize,超大小提前转。
Q:日志文件被轮走了,但应用还在往旧文件写,新文件是空的?
程序持有旧文件描述符,改名不影响它继续写。要么在 postrotate 里给进程发重开信号,要么改用 copytruncate。
Q:/var/log/syslog 为什么不按我改的规则轮转?
多个配置文件匹配同一个日志时,后读入的覆盖先读入的。include /etc/logrotate.d 在主配置末尾,/etc/logrotate.d/ 下的文件会覆盖主配置里的同名日志段;先 logrotate -d 看它实际用了哪份配置。
Q:保留多少份合适? 按磁盘预算倒推:单份压缩后体积 × 保留份数 ≈ 你愿意给日志的上限。Text 日志压缩率高,14 份 daily 通常占用不到几百 MB;二进制日志(如某些数据库 redo)压缩率低,份数要保守。
Q:轮转的瞬间会丢日志吗?
postrotate + create 姿势不丢;copytruncate 在复制与截断之间有小概率丢几行。对日志完整性有硬要求的场景(审计、合规),前者是唯一选项。
收尾
logrotate 配置的全部要点可以压成三句话:触发机制靠 systemd timer,规则写在 /etc/logrotate.d/,每次改动先用 -d 演练再 -f 验证。轮转姿势二选一——程序能重开日志就 postrotate + create,不能就 copytruncate。磁盘被日志写满从来不是 logrotate 失职,是它没被配置。