一台 20G 的 VPS 被日志吃掉一半:我按这个顺序查了二十分钟

6 阅读6分钟

周一早上七点,手机弹了条磁盘告警:/var 使用率 92%。

登上去看谁在长:

du -sh /var/log/* | sort -rh | head -5 
# 9.1G /var/log/nginx

/etc/logrotate.d/nginx 是有的,cat 出来也正常,daily、rotate 14、compress 都写着。配置在,日志还在涨,说明中间断了一环。

我按下面的顺序查,二十分钟定位到根因。

一、先确认它到底跑没跑

很多人直接改配置,其实第一步该看的是执行记录:

systemctl list-timers --all | grep -i logrotate 
tail -5 /var/lib/logrotate/status

status 文件里是一条条记录,格式是「日志路径 + 上次轮转的日期」。如果某条日志后面跟着一个很旧的日期,说明它被跳过、或者压根没被匹配到。这个文件比日志本身更能说明问题,因为它记的是 logrotate 自己的判断。

顺带看一眼执行日志,有没有报错:

journalctl -u logrotate --since "-7 days" --no-pager | tail -20

二、size 会把 daily 盖掉

这是最常被写错的一处。三个指令的语义不一样:

写法触发条件
只写 daily每天执行一次,不看大小
写 size 100M只看大小,时间条件被忽略
写 minsize 100M时间到了、且大于这个值才转

size 和 daily 同时写,生效的是 size,daily 被忽略——但 size 又只在 logrotate 被调用时判断,所以表现成「我设了每天转,它偏不转」。要的是「每天一次、太小就不转」,正确写法是 daily + minsize。

三、配置文件的权限会被直接忽略

ls -l /etc/logrotate.d/ 
# 正确 -rw-r--r-- 1 root root 
# 危险 -rw-rw-rw- 或 -rw-rw-r--

组可写或其他人可写的配置文件,logrotate 会跳过并提示 Ignoring ... because of bad file mode。这个提示在 logrotate -d 的输出中间,不在末尾,所以只看最后一行的人会漏掉。

四、同一个日志被两份配置匹配

grep -rn "access.log" /etc/logrotate.d/

两份都匹配同一个路径时,后读到的覆盖前面的,不是合并。/etc/logrotate.conf 里 include /etc/logrotate.d 是按文件名顺序读的,所以「z-nginx」会盖掉「nginx」。表现就是:改了配置没反应,因为改的那份被别人盖了。

五、nginx 的日志要发信号,不然白转

nginx 的日志文件是被 master 进程一直开着的。文件改名了,进程还往旧 inode 里写,du 看不到新文件变大,df 也不减——空间被一个「已经改名」的文件占着。

标准写法是这样:

/var/log/nginx/*.log { 
    daily 
    rotate 14 
    missingok 
    notifempty 
    compress 
    delaycompress 
    sharedscripts 
    postrotate 
        [ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid) 
    endscript 
}

postrotate 那段是核心,sharedscripts 保证多个日志只发一次信号。delaycompress 让最新一份不压缩,方便 tail 正在写的那个。

另一条路是 copytruncate:复制一份再清空原文件,不需要信号。代价是复制和清空之间那一瞬的写入会丢,审计类日志不要用。

六、dateext 撞名会静默跳过

用 dateext 时文件名是 access.log-20251104。同一天手动 logrotate -f 第二次,目标文件已存在,它直接跳过,不报错。所以「我强制跑了一次,怎么没变化」多半是这个原因。要么按小时加后缀,要么别反复 -f。

这台机器就是这一条:我前一天调试时 -f 过两次,第二次之后 status 里的日期停在三天前。

日志涨起来那几天,我是开着极目Linux桌面连上去看哪个文件在长,比对着几个目录 tail 快;也仅此而已,轮转规则照样得自己一行行写。

七、被删掉的日志还在占空间

lsof +L1 | grep -i log

进程还持有已删除文件的句柄,空间就不释放。这不属于 logrotate 的问题,但表现完全一样:文件列表里没有大文件,df 却下不去。

八、验证顺序

logrotate -d 看配置解析 → logrotate -f 强制跑一次 → ls -l 看新文件属主 → df -h 看空间 → tail /var/lib/logrotate/status 看时间戳有没有更新。

-d 是空跑,只打印将要做什么,不改任何文件,但它会把「权限不对」「重复定义」「找不到文件」全列出来。养成先 -d 再 -f 的习惯,能省掉很多「跑完才发现配错」的时间。

九、长期怎么盯

日志这件事的难点不在配置,在「配置好了没人看」。容量慢慢涨、单个文件不超限,等告警来的时候往往已经 90%。

要细粒度指标就装 agent 采集,每台机器一个常驻进程、一个监听端口,还得跟着版本升级。不想装的话,另一条路是不装采集 agent,直接通过 SSH 读日志目录、inode 占用和 status 文件——这类「无 agent 监控」的界面在本地渲染、只走现成的 SSH 通道,服务器侧不装 Web 服务、也不新增端口。

选不选它,我的判断只有一条:看它是否在服务器上新增对外端口。 端口多一个,长期要盯的东西就多一样。

FAQ

Q1:logrotate 必须机器连续开机 24 小时才会触发 daily 吗?

不需要连续开机。/etc/cron.daily 由 anacron 驱动时,关机错过的那次会在下次开机后补跑;纯 cron 驱动的机器不补。常年开机的云主机基本不涉及这条,容器里则通常没有 cron.daily,得自己在外面挂一个定时任务调用 logrotate /etc/logrotate.conf。

Q2:轮转后新文件属主变成 root,写不进去怎么办?

用 create 指定模式和属主:create 0640 www-data adm。不写 create 时,logrotate 按原文件属性重建;写了 create 但不带参数,就是默认的 0644 root root,服务进程立刻失去写权限。这是「轮转之后网站 502」的常见原因。

Q3:copytruncate 会丢日志吗?

会。复制完成到清空之间写入的行会丢,窗口通常在毫秒级,但高写入场景一天能丢几十行,而且丢得毫无痕迹。计费、审计、需要取证的日志不要用它,改用信号让进程重开文件。

Q4:多个配置文件匹配同一个日志,以哪个为准?

以最后被读到的为准,是覆盖不是合并。include 目录按文件名顺序读取,所以名字靠后的文件生效。改完不生效,先 grep -rn 看有没有第二份配置在管同一个路径,再确认 include 的顺序。

Q5:改完配置需要重启什么服务吗?

不需要。logrotate 是一次性执行的程序,由 timer 或 cron 触发,每次执行都重新读配置。改完用 logrotate -d /etc/logrotate.conf 验证解析结果就行,没有常驻进程要重启。

Q6:日志保留 30 天,磁盘该留多大?

按日增量乘天数再乘压缩比。文本日志 gzip 后大约是原来的 1/8 到 1/12。日增 300M、留 30 天、开压缩,实际占用 1G 出头;不开压缩就是 9G 左右。规划时按算出来的两倍留余量,因为峰值日往往不是平均值。