Logrotate 实战:日志自动轮转与切割压缩

服务器上的应用日志如果不加管理,几天就能把一个磁盘写满,甚至触发监控告警、拖垮整个节点。Logrotate 是 Linux 发行版自带的标准日志轮转(log rotation)工具,能在日志达到时间或大小阈值时自动切割、压缩、删除旧文件,并平滑地让程序继续往新文件写入。本文用 Nginx、Docker 等真实场景,把它的配置语法、核心指令和最容易踩的坑一次讲透。

一、为什么必须做日志轮转

很多新手以为”日志多占点空间没关系”,但生产环境的真实情况要残酷得多。第一,单个文件持续追加会无限膨胀,几 GB 的日志文件既难用 grep 检索,也难用 tail 跟踪。第二,Linux 删除被进程占用的文件并不会立即释放磁盘空间,只有进程关闭句柄或重启后空间才回收,这会让 df 与 du 显示的结果严重不一致。第三,磁盘写满后,数据库、消息队列这类强依赖磁盘的服务会直接拒绝写入,引发雪崩。

日志轮转的本质,是在”可检索的历史”和”可控的磁盘占用”之间做平衡:把旧日志改名压缩、保留 N 份、超期删除,同时保证应用进程无感地切到新文件。Logrotate 就是干这件事的事实标准,它通常挂在系统的 /etc/cron.daily 里每天跑一次,也可以通过 anacron 或 systemd timer 触发。

二、Logrotate 的工作机制与目录约定

2.1 主配置与 /etc/logrotate.d

Logrotate 有两个入口:全局配置 /etc/logrotate.conf 定义默认策略,而具体的服务轮转规则放在 /etc/logrotate.d/ 目录下,每个文件对应一个应用。主配置里常见的全局设置包括 weekly、rotate 4、create、compress 等,但更推荐把每个服务的规则独立成文件,便于维护和排障。

2.2 一个最小可用配置

# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0640 www-data www-data
}

这段配置的含义是:每天轮转一次、保留 7 份、压缩旧日志、延迟一轮再压缩(delaycompress,给还在写的进程留缓冲)、日志不存在也不报错、空文件不轮转、轮转后新建 0640 权限的文件。下一节我们把这些指令逐一拆开。

三、核心指令速查

Logrotate 的指令很多,但日常 90% 的场景只需要下面这一组。理解它们各自的默认值和副作用,比死记硬背更重要。

指令作用常见取值注意点
daily / weekly / monthly按时间触发轮转三选一基于文件 mtime,配合 size 可变成”或”关系
size达到指定大小才轮转100M / 1G与 daily 是”或”逻辑,满足任一即轮转
rotate N保留几份历史7 / 30超过 N 份的最旧文件被删除
compress / nocompress是否 gzip 压缩默认关闭压缩后旧日志变成 .gz
delaycompress延迟一轮再压缩—必须与 compress 同用,避免压缩正在写的文件
copytruncate拷贝后截断原文件—不重命名,适合无法重载的服务,有极小数据丢失窗口
create mode owner group轮转后新建文件0640 user group与 copytruncate 互斥
postrotate / endscript轮转后执行脚本reload 服务常用于让服务重新打开日志文件

四、实战一:Nginx 访问日志按大小轮转

Nginx 默认会把访问日志和错误日志持续追加到同一个文件,高流量站点一天就能产生数 GB。下面这个配置让它在”每天”或”超过 200M”时任一条件满足就轮转,避免磁盘被瞬间打满。

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    size 200M
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 $(cat /var/run/nginx.pid)
        fi
    endscript
}

这里的 kill -USR1 是 Nginx 官方推荐的”重新打开日志文件”信号,相当于平滑重载日志,不会断连接。如果你的 Nginx 是通过反向代理对外提供服务的,保持访问日志可控同样重要,否则一次流量高峰就可能把代理节点写挂。

五、实战二:Docker 容器日志轮转

Docker 容器默认使用 json-file 日志驱动,日志会原样写进 /var/lib/docker/containers/<id>/ 下的 JSON 文件,而且 Docker 本身不会帮你轮转。正确做法是先在 daemon.json 里给容器日志设上限,再用 Logrotate 兜底。

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  }
}

改完配置需要 systemctl restart docker 才能对新容器生效(已存在的容器要重建)。但 json-file 的 max-size 是按容器单文件算的,多个容器叠加仍可能占满磁盘,所以再补一条 Logrotate 规则清掉磁盘上的 JSON 残留:

# /etc/logrotate.d/docker-containers
/var/lib/docker/containers/*/*.log {
    daily
    rotate 7
    compress
    delaycompress
    copytruncate
    missingok
}

这里用 copytruncate 而不是 create,是因为 Docker 守护进程握着这些 JSON 文件的句柄,重命名会失效。关于 Docker 的更多生产实践,可以参考多环境部署那篇。

六、实战三:postrotate 平滑重载服务

很多程序(Nginx、rsyslog、MySQL)打开的是文件描述符而非文件名。Logrotate 默认用 rename(mv)把旧日志改名,但进程还在往旧的文件描述符写,结果就是:磁盘上看到 access.log.1,可日志依旧写进那个已经被改名的文件里,轮转形同虚设。解决办法就是在 postrotate 里让服务重新打开文件。

# /etc/logrotate.d/rsyslog
/var/log/syslog {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}

HUP 信号让 rsyslog 重新打开日志文件,是这类”按文件名持有”服务的标准做法。同样地,当你在服务器上用Fail2ban 阻断暴力破解时,它的日志也需要被正确轮转,否则被封禁记录查无可查。

七、delaycompress 与 copytruncate 的坑

这两个指令是新手最常踩坑的地方,用错会直接导致磁盘空间不释放或日志丢失。下表把典型误区列清楚。

误区现象正确做法
开了 compress 却没开 delaycompress当天的 .1 文件被立即压缩,postrotate 里用文件名 reload 的服务找不到文件需要 postrotate 读旧文件时,务必配合 delaycompress
对无法 reload 的服务用 create进程仍写旧文件,新文件是空的,日志”消失”改用 copytruncate,牺牲极小窗口换取无感
size 与 daily 误以为是”与”关系以为”每天且超过 200M 才转”,实际任一满足就转想要”且”语义需写自定义脚本判断
忘记 sharedscripts每个匹配文件都执行一次 postrotate,reload 多次多文件场景加 sharedscripts 只执行一次
# copytruncate 示例:Tomcat 这类不支持信号重载的服务
/opt/tomcat/logs/catalina.out {
    daily
    size 300M
    rotate 10
    compress
    copytruncate
    missingok
    notifempty
}

八、调试与强制执行

写好配置后千万别直接等它自动跑,先用 debug 模式验证一遍,确认轮转逻辑符合预期再放手。

# 只模拟执行,不真正轮转,能看到每一步决策
logrotate -d /etc/logrotate.d/nginx

# 强制立即轮转一次(排障或测试用)
logrotate -f /etc/logrotate.d/nginx

# 指定 state 文件,避免污染系统默认状态
logrotate -f -s /tmp/lr.state /etc/logrotate.d/nginx

# 结合 verbose 看详细信息
logrotate -dv /etc/logrotate.d/nginx

-d 是”dry run”,能帮你发现配置语法错误或逻辑冲突而不动真实文件,强烈建议每次新增规则都先跑一遍。如果某条规则一直不生效,多半是权限问题或被 state 文件里的”上一次轮转时间”挡住了——用 -s 指定独立 state 可以排除干扰。

九、落地检查清单

  • 确认 /etc/cron.daily/logrotate 或 systemd timer 在正常运行(systemctl status logrotate.timer)。
  • 每个新增服务都在 /etc/logrotate.d/ 下有独立规则,不堆在主配置里。
  • 需要服务重新打开日志的,务必配 postrotate + sharedscripts。
  • 无法信号重载的服务用 copytruncate,但要知道它有极小的丢日志窗口。
  • 上线前用 logrotate -d 验证,用 -f 做一次真实演练。
  • 把磁盘用量接入Grafana 监控和Prometheus,设置”剩余空间 < 20%”告警,别等写满才发现。

日志轮转看似是个小配置,却是运维稳定性的第一道防线。把它和监控、告警串起来,才能在你睡觉的时候真正放心。

上一篇 CQRS 实战:读写分离架构与事件投影落地
下一篇 Java Record 实战:用 record 关键字写出不可变数据类
最新评论
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 […]