Logrotate 日志轮转实战:避免磁盘被日志打爆

Logrotate 是几乎所有 Linux 发行版预装的服务,负责按时间或大小对日志做日志轮转:自动切分、压缩、删除旧日志,防止单个日志无限增长把磁盘打满。很多线上故障并不是代码写错,而是 /var/log 下某个日志悄悄长到几十 GB,把系统盘塞爆。本文从原理到实战,手把手配出一套可靠、可调试、可回滚的日志轮转策略。

为什么必须做日志轮转

一个长期运行的服务,访问日志、错误日志、审计日志会持续累加。如果从不轮转,后果不止”磁盘空间变少”这么简单:日志文件越大,后续写入越慢;磁盘写满后新请求无法落盘,进程开始报错甚至崩溃;更隐蔽的是 inode 也可能先被海量小日志耗尽,导致”磁盘还有空间却无法创建文件”。这和磁盘 inode 耗尽排查、磁盘 IO 被打满是同一类根治问题——而日志轮转,正是最省心的第一道防线。

Logrotate 是怎么被触发的

Logrotate 本身是个一次性命令,真正”每天跑一次”靠的是调度器。传统发行版把它放在 /etc/cron.daily/logrotate,由 cron/anacron 每天触发;使用 systemd 的系统则提供 logrotate.service 与 logrotate.timer,由定时器每日唤起。无论哪种方式,它启动时都会读取主配置 /etc/logrotate.conf,再逐个加载 /etc/logrotate.d/ 目录下的片段——所以绝大多数场景,你只需要在 /etc/logrotate.d/ 里新增一个文件,而不要去动主配置。

# 查看定时器是否启用(systemd 系统)
systemctl status logrotate.timer

# 手动触发一次(生产排障常用)
/usr/sbin/logrotate /etc/logrotate.conf

# 查看今天是否已执行(anacron 记录)
cat /var/lib/logrotate/status

第一个配置:轮转 Nginx 访问日志

以 Nginx 为例,新建 /etc/logrotate.d/nginx。下面这套配置覆盖了 90% 的 Web 服务场景:每天轮转、保留 14 份、压缩旧日志、丢失文件不报错、空文件不轮转,并在轮转后通知 Nginx 重新打开日志文件。它和Nginx 反向代理配置、服务器安全加固共同构成一台对外服务主机的运维基线。

/var/log/nginx/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    create 0640 www-data adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 $(cat /var/run/nginx.pid)
        fi
    endscript
}

这里最关键的是 postrotate 里的 kill -USR1:Nginx 收到 USR1 信号会重新打开日志文件描述符,否则它还会继续往已被重命名的旧文件里写,轮转就失去了意义。用 systemctl reload nginx 也能达到同样效果,但 reload 比 USR1 重一点,高并发场景更推荐 USR1。

关键指令速查

理解下面这张表,就能应对绝大多数轮转需求。指令写在小节的花括号内,对当前小节的所有日志文件生效。

指令作用常见取值
daily / weekly / monthly按时间周期轮转每天 / 每周 / 每月
rotate N保留几份旧日志7 / 14 / 30
size / minsize / maxsize按体积触发(见下文)100M / 50M / 200M
compress / nocompress是否用 gzip 压缩旧日志compress
delaycompress延迟一轮再压缩,兼容正在写的进程delaycompress
copytruncate拷贝后截断原文件,无需重载服务copytruncate
create [mode owner group]轮转后新建日志并指定权限create 0640 root utmp
missingok日志不存在也不报错missingok
notifempty空文件不轮转notifempty
dateext / dateformat用日期而非序号命名旧日志dateext
olddir把旧日志放到别的目录olddir /var/log/archive

按大小还是按时间:size / maxsize / minsize 的区别

这是最容易配错的一组。三者都和体积有关,但触发逻辑完全不同:

# size:只要超过 100M 立刻轮转,不管时间周期
size 100M

# maxsize:超过 100M 或到了 daily 周期,先到先轮
daily
maxsize 100M

# minsize:必须 daily 周期到了,且体积超过 50M 才轮
daily
minsize 50M

经验法则:访问日志用 daily + maxsize 最稳,既不会因突发流量撑爆磁盘,也不会因清淡期无意义地天天切;审计类低频日志用 size 更合适,只有真的写大了才动它。

应用没有”重开日志”信号怎么办:copytruncate

有些老程序或第三方组件不会响应 USR1,也不会在配置 reload 后重开日志。这种情况用 copytruncate:先复制当前日志内容到轮转文件,再把原文件截断为 0 字节。进程始终写同一个文件描述符,不需要任何信号,代价是复制与截断之间可能丢失极少量日志(毫秒级)。

/var/log/myapp/app.log {
    daily
    rotate 10
    size 200M
    copytruncate
    compress
    missingok
    notifempty
}

用 postrotate 正确重载服务

sharedscripts 表示只有当本小节至少有一个文件被轮转时,才执行一次 postrotate(避免每个文件都 reload 一次服务)。下面以 Java 服务为例,用 systemd 重新打开日志:

/opt/myapp/logs/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
    sharedscripts
    postrotate
        systemctl reload myapp.service > /dev/null 2>&1 || true
    endscript
}

让旧日志带日期:dateext

默认轮转出的文件叫 nginx.log.1、nginx.log.2.gz,序号不直观。加上 dateext 后会变成 nginx.log-20261102.gz,一眼能看出是哪天产生的,也方便和备份策略对齐。

/var/log/app/*.log {
    daily
    rotate 30
    dateext
    dateformat -%Y%m%d
    compress
    delaycompress
    missingok
    notifempty
    create 0640 app app
}

调试:先 dry-run 再强制

千万别直接改完配置就等明天自动跑——先用 -d 做”干跑”,它会完整模拟轮转过程并打印每一步,但不真正改动任何文件,是上线前必做的验证。

# 干跑:只打印将要做什么,不修改文件
logrotate -d /etc/logrotate.d/nginx

# 强制立即执行一次(verbose 看明细),用于验证效果
logrotate -vf /etc/logrotate.d/nginx

# 只跑某一个,不影响其它配置
logrotate -vf /etc/logrotate.conf

-d 输出里重点看两处:是否”considering log”后进入了轮转分支,以及 postrotate 脚本是否被正确解析。若看到 error: 则说明配置语法有问题,需要先修。

常见排障

现象原因解决办法
日志从没被轮转配置语法错或文件权限不对logrotate -d 看 error;确认 /etc/logrotate.d/ 下文件权限 644
磁盘还是被写满用了 size 但阈值太大,或没配 compress调小 maxsize、确认 compress 生效
轮转后服务不写新日志忘了 postrotate 重载,或用了 copytruncate 之外却截断加 kill -USR1 或 systemctl reload;评估 copytruncate
旧日志权限错误create 的 owner/group 写错用 create 指定正确的属主,或加 su 指令
SELinux 下轮转失败新文件安全上下文不对用 semanage 修正类型,或配合 olddir 同分区

和监控、容器配合

轮转解决”历史日志堆积”,但”实时磁盘水位”仍需监控兜底。把磁盘使用率接入 Prometheus + Grafana 监控面板,在 80% 水位就报警,比等写满再救火从容得多。容器场景下,日志最好交给 Docker 的 logging driver 或节点级 logrotate 统一接管,避免容器内部自己写盘、又没人轮转的”双重失控”。

总结

Logrotate 看似只是个”切日志”的小工具,却是 Linux 服务器长期稳定运行的隐形基石:用 /etc/logrotate.d/ 下的片段化管理配置,靠 daily + maxsize + compress + delaycompress 兜住体积,用 postrotate 信号或 copytruncate 适配不同服务,再用 logrotate -d 干跑验证后再上线。把它和 inode 监控、磁盘 IO 监控、Prometheus 告警串起来,日志就再也成不了压垮服务器的那根稻草。下次部署新服务时,记得把”配好日志轮转”写进你的上线检查清单。

上一篇 Mamba 状态空间模型:线性复杂度序列建模
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]