logrotate 配置详解:日志把磁盘写满之前,把轮转规则配对

logrotate 配置详解:日志把磁盘写满之前,把轮转规则配对

logrotate日志UbuntuLinux运维
📶 入门 🧩 logrotate · Ubuntu

先看一个熟悉的场景:服务器跑了几个月没重启,某天部署新服务时 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 失职,是它没被配置。