1. 日志只增不减,服务器是怎么悄悄被拖垮的
先说一个我早年接手的真实故障。某个业务模块跑了一周之后,突然告警说磁盘占用率到了 98%,SSH 登录都卡得不行,应用日志开始疯狂报错“No space left on device”。上去一看,/var/log/ 底下躺着几个十多个 GB 的日志文件,个别文件大到连 tail 都要卡几秒。当时第一反应是“谁没事写了死循环打日志”,查了一圈发现根本没有,就是正常的访问日志、错误日志日积月累,一旦没人管,它能把你整块磁盘都吃光。
这个场景你应该不陌生。Linux 下几乎所有服务都会写日志,Nginx 的 access.log、MySQL 的 error.log、Java 应用的 stdout 重定向文件、系统自身的 syslog,只要有流量有请求,日志文件就会一直膨胀。很多新手以为“日志嘛,存着就行,反正磁盘大”,但磁盘再大也有用完的一天,而且日志文件过大带来的问题远不止磁盘告警这么简单——排查问题时 grep 一个几十 GB 的文件,慢到你怀疑人生;日志文件占用磁盘 inode 到上限,其他进程连创建临时文件都失败;老日志没有任何清理机制,历史遗留的敏感信息长期躺在磁盘上,审计的时候都是雷。
logrotate 就是专门解决这个问题的。它的名字拆开看就是“log”(日志)+“rotate”(轮转),核心职责是:到了一定条件(比如每天、或者文件长到多大),把当前正在写的日志文件改名归档,再新建一个新文件让服务继续写,同时按照策略删除或压缩最老的归档,整个过程不需要你手动操作,也不需要重启服务。
我见过不少运维做了两三年,日志管理还是靠“写个 shell 脚本每天 crontab 跑一下”。这个思路没错,但 script 脚本要考虑的事情太多了:怎么处理服务正在占用文件描述符的问题、归档文件怎么命名、压缩时机怎么选、超过多少个归档之后要删除、如果服务挂了你还要不要继续轮转……这些边界情况 logrotate 全部已经处理好了。它不是什么新潮工具,几乎跟着 Linux 发行版捆绑安装了几十年,稳定、简单、无处不在,但真正用得好的人并不算多。
这篇文章我会从 logrotate 的执行机制讲起,逐步拆解它的配置语法,再给几组真正可以直接落地的配置模板,最后重点聊聊我踩过的那些坑——有些坑排查了好几个小时,最后发现就是一个配置项的锅。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 刨根问底:logrotate 的调度机制与执行原理
2.1 它到底靠什么被触发
很多人的误区是:我把配置写进 /etc/logrotate.d/ 了,logrotate 就会自动轮转。不对,logrotate 本身不是一个常驻后台的服务,它是一个命令行工具,必须由外部机制定时调用它才会执行。绝大多数发行版把这个定时任务放在了 cron 里。
打开 cron 配置看一眼:
code复制/etc/cron.daily/logrotate
脚本内容大致是:
bash复制#!/bin/sh
/usr/sbin/logrotate /etc/logrotate.conf
这个脚本由 cron 每天执行一次(具体时间取决于发行版,通常在凌晨 3 点到 5 点之间)。也就是说,logrotate 的默认调度粒度是“天”。你给某个日志文件配置了 daily,但它实际执行的点是在 crontab 任务运行的那一瞬间,不是凌晨零点整,不要在时间精度上较真。
另外,新一点的 systemd 发行版(比如 CentOS 7+、Ubuntu 16.04+)还会通过 systemd-timers 来触发。你执行 systemctl list-timers 可能会看到 logrotate.timer,它的触发逻辑和 cron 大同小异,不用过度纠结用哪个,反正最终都是执行 /usr/sbin/logrotate /etc/logrotate.conf。
2.2 主配置与子配置的加载关系
logrotate 执行时先读取主配置文件 /etc/logrotate.conf,然后在主配置里通过 include 指令加载 /etc/logrotate.d/ 目录下的所有子配置。用 man logrotate 里的话说,主配置负责全局默认行为,子配置负责单个软件的专属规则。
看一下默认主配置的关键几行:
text复制weekly
rotate 4
create
include /etc/logrotate.d
/var/log/wtmp {
monthly
create 0664 root utmp
missingok
rotate 1
}
第一行 weekly 是全局默认轮转周期,如果你给某个日志文件没有单独指定频率,就按每周一次处理。rotate 4 是全局保留四份归档,超出后删除最旧的。create 表示轮转后创建新日志文件。include 把 /etc/logrotate.d/ 下的文件全部加载进来,相当于把多个配置拼成一个大配置。
你在 /etc/logrotate.d/ 里写的每个配置块,可以覆盖这些全局默认值。比如 Nginx 的配置里指定了 daily,那么它的日志就是每天轮转,不受全局 weekly 影响。
2.3 状态文件和轮转判定的依据
logrotate 如何决定“我今天到底要不要转这个文件”?靠的是状态文件。默认位置在 /var/lib/logrotate.status(老版本是 /var/lib/logrotate/logrotate.status)。
状态文件的内容大致长这样:
text复制logrotate state -- version 2
"/var/log/nginx/access.log" 2025-5-20-3:0:0
"/var/log/mysql/error.log" 2025-5-16-3:25:0
一行对应一个日志文件,记录的是它最后一次轮转的时间。当 logrotate 判断 daily 规则时,它会看当前日期是否已经过了上次记录日期的一天以上。对于 size 规则,它会调 stat() 看文件大小是否超过了阈值。
这里面有一个关键逻辑我提醒你注意:不满足轮转条件时,logrotate 不会去重命名文件,但状态文件里的时间也不更新。 也就是说,如果日志文件一直很小,可能连续十几天都不会碰它一下,这是正常现象,不是配置失效了。
2.4 轮转一个正在被写入的日志文件,凭什么不丢日志
这是 logrotate 最核心的设计,也是很多人看不懂的地方。
进程写日志时,打开的是文件描述符,指向 inode。你执行 mv access.log access.log.1,仅仅是把目录项换了名字,inode 还是同一个,正在写的进程持有的是旧 inode,它会继续往这个已改名的文件里写内容。所以 logrotate 重命名完老日志之后,必须主动让进程打开一个新的日志文件,否则后续日志还是全部写进了 access.log.1。
logrotate 解决这个问题靠的是 create 指令和 postrotate 脚本的配合。
code复制create` 指令:轮转后立刻以指定权限创建一个新文件,比如 `create 0644 nginx adm`,它就帮你 `touch` 出一个新的 access.log。但此时进程还持有旧文件描述符,如果进程不重新打开文件,新文件就永远是空文件。
`postrotate` 脚本:轮转动作完成后执行的脚本。最常见的动作是给进程发信号,让它重新打开日志文件。Nginx 的做法是 `kill -USR1 $(cat /var/run/nginx.pid)
