导读:本站GoAccess篇刚讲完"Nginx日志是金矿",但金矿有个阴暗面:它会长个儿,直到把磁盘吃穿。cron篇提过journalctl清理,systemd篇跑服务时也总担心应用日志失控,这篇把日志治理正面讲透:logrotate的配置结构和运行机制、核心参数rotate/daily/compress怎么取舍、Nginx日志切割后必须reload的原理、Docker容器日志的max-size治理、journald的体积上限、自己的应用怎么接入切割,以及最重要的dry-run验证习惯。读完这篇,你的磁盘告警会安静很久。
一、日志问题的爆发方式:从来不是渐进的
磁盘被日志吃满的方式很刁钻:平时占个百分之十几岁月静好,某天被扫描器盯上、被爬虫打爆、或某个应用进入报错循环,单日日志量翻十倍,一周内磁盘从"健康"直接跳到"100%"。而磁盘满的连锁反应比日志本身严重得多:数据库写不进WAL直接停摆(本站Swap篇讲过资源耗尽的连锁灾难,这是同款剧情)、Nginx开始返回500、SSH都登不上去——因为登录也要写日志。日志治理的本质不是省磁盘,是给最坏情况装一个上限:无论应用怎么疯狂输出,磁盘都保得住。
二、logrotate的运行机制:cron在背后推
先破除一个常见误解:logrotate不是常驻进程,它是cron触发的一次性任务。Debian系在/etc/cron.daily/logrotate,由systemd的logrotate.timer或每日cron驱动,每天跑一次,检查所有配置,把到期的日志按规则切割。配置文件分两层:/etc/logrotate.conf是全局默认,/etc/logrotate.d/目录下每个服务一个文件——nginx、mysql、apt都自带,你装服务时包管理器顺手放好的。这套结构和cron篇讲的/etc/cron.d设计思路一致:主配置管全局,碎片文件管个案,升级不冲突。
三、核心配置逐行拆:一份Nginx的切割规则
看懂这一份,所有服务的配置都能看懂:
/var/log/nginx/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
sharedscripts
postrotate
systemctl reload nginx > /dev/null 2>&1 || true
endscript
}
逐行翻译:daily每天切一次;rotate 14最多保留14份历史,旧的自动删(这是磁盘上限的关键参数);missingok日志不存在不报错(服务没跑很正常);notifempty空文件不切(切个寂寞没意义);compress历史日志gzip压缩(文本日志压缩比通常10:1,从几百兆变几十兆);delaycompress最新的那份先不压(刚切下来的文件可能还有进程在写尾部);dateext历史文件带日期后缀,一眼看出新旧;sharedscripts通配符匹配多个文件时脚本只跑一次。切完的目录长这样:access.log(今天的)、access.log-20260901.gz、access.log-20260831.gz……按日期排队,超过14份的自动消失。
四、Nginx必须reload的原理:文件句柄的坑
postrotate里那句reload不是仪式,是刚需。原理:Nginx启动时打开access.log拿到文件句柄,之后所有写入都通过句柄直达那个inode。logrotate切割用的是重命名——mv access.log access.log-20260901,inode没变,Nginx的句柄还指着老文件继续写。不reload的话,新access.log永远是空的,所有日志默默写进那个"切割后"的压缩文件里,等它被压缩时才真相大白——丢的这几天日志,GoAccess篇的金矿直接变成废矿。reload让Nginx重新打开文件拿新句柄,一切归位。凡是"进程长期持有日志句柄"的服务(Nginx、大多数自研应用)都要这一步;而syslog类每次写都重新打开文件的服务反而不需要。判断依据不是服务类型,是它写日志的方式。
五、Docker容器日志:另一套体系,同一个问题
容器标准输出落的/var/lib/docker/containers/*/*-json.log不归logrotate管,Docker有自己的治理开关,daemon.json里配置(本站Docker篇讲过的同一个文件):
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
单个容器日志上限10MB、最多3个文件轮转,单容器磁盘占用封顶30MB。这个配置只对新建容器生效,老容器要重建——Compose栈docker compose up -d --force-recreate一遍。它防的是"单个容器无限输出",和logrotate防的"宿主机日志无限增长"是两个战壕,两边都要设。见过太多个例:容器跑了半年,一个debug日志没关,json.log膨胀到几十GB,把宿主机磁盘顶爆——max-size一行配置就能免疫的灾难。
六、journald的体积上限:systemd家的日志要单独管
systemd全家桶的日志走journald,也不归logrotate管,它有自己的开关,/etc/systemd/journald.conf:
SystemMaxUse=200M SystemMaxKeepFree=500M
前者是journal总体积上限,后者是给磁盘保留的余量。改完systemctl restart systemd-journald生效。临时手动清理用cron篇提过的journalctl --vacuum-size=200M,立刻瘦到目标值。journald默认在磁盘充裕时会用掉很大空间(某些发行版上限是磁盘的10%),小盘VPS上这个默认值很危险,200M对个人服务器绰绰有余——毕竟journal的定位是"近期系统事件的黑匣子",不是长期档案库,长期要留的日志应该进ELK类方案或至少落成文件,那是另一个量级的话题。
七、自己的应用接入logrotate:一个配置文件的事
自研服务(本站systemd篇教你注册的那些)输出的日志文件,接入logrotate只需在/etc/logrotate.d/放一个配置。以/home/myapp/app.log为例:
/home/myapp/app.log {
weekly
rotate 8
compress
missingok
notifempty
copytruncate
}
亮点是copytruncate:先把文件复制一份作为历史,再把原文件清空。它绕开了第四节的句柄问题——应用持有的句柄指向的inode从头到尾没变,应用无感知,不需要reload。代价是复制和清空之间有个极小的窗口可能丢几行日志,对绝大多数应用无伤大雅。对比方案是postrotate里给应用发信号让它重开文件,严谨但要求应用支持,两者按应用能力选。更省心的路线是让应用直接输出到stdout交给journald,容量治理就归第六节管了——但日志要给GoAccess这类工具吃的话,还是落文件省事。
八、验证与排障:永远先dry-run
改完配置别赌运气,先演习:
logrotate -d /etc/logrotate.d/myapp
-d是debug模式,完整模拟一遍会切哪个文件、会跑什么脚本、会删哪个历史,但什么都不真做。输出会明确告诉你"would rotate"或"not rotating because",配置错误当场暴露。真实执行用-f强制:logrotate -f /etc/logrotate.d/myapp,立刻看切割效果。日常排障查两点:切割没发生,先看cron那趟跑了没有(journalctl -u logrotate.timer或ls /var/lib/logrotate/status的修改时间);切割发生了但磁盘还在涨,多半是rotate份数给太多、或某类日志根本没被任何配置覆盖——find / -name "*.log" -size +100M扫一遍,大文件自己会现形,这招专治"不知道谁在写日志"的悬案。
九、容量预算:给日志一个明白数
治理完做个简单的算术题:Nginx日志压缩后单份多大乘以保留14份,journald封顶200M,容器每实例30MB乘以实例数,加上应用日志的预算——四项加起来就是日志的总磁盘承诺。把这个数字和df -h的总量对一下,确认它占比合理(个人VPS控制在10%以内很从容)。有了预算,"磁盘告警"就从突发事故变成可预期的容量规划问题,监控篇的哪吒面板再配上磁盘占用告警线,日志这个变量就彻底进入你的掌控——从"不知道什么时候爆"变成"永远差得远"。
结语
日志治理的心法一句话:每一路日志都要有上限、有压缩、有退出机制,没有这三样的日志就是定时炸弹。logrotate管文件日志,max-size管容器日志,SystemMaxUse管journal——三套机制各管一段,配齐之后磁盘满了这个词就和你无关了。这些配置全部是一次性的:配好一次,往后只在你部署新服务时追加一条。这是运维里少有的"投入十分钟、受益数年"的买卖,趁今天顺手做掉。


