为什么日志管理会成为运维的“隐形杀手”
运维中最容易被忽视的隐患是日志。一个每天产生500MB日志的Java应用,如果不做轮转,两周就能撑满100GB磁盘。后果是服务写入失败、监控告警轰炸,甚至系统只读挂载。而logrotate是Linux下最正统的日志轮转工具——它由rsyslog或syslog-ng触发,但配置文件却常常因为权限、路径错误或轮转策略不当而失效。
本文直接聚焦两个实操问题:如何正确配置logrotate让日志健康轮转,以及当磁盘突然爆满时,如何快速定位并处理。每个命令都有安全提醒,避免你手滑删错文件。
logrotate如何工作:从触发到执行
logrotate的核心是一个cron定时任务,通常位于/etc/cron.daily/logrotate。系统每天按时执行它,扫描/etc/logrotate.conf和/etc/logrotate.d/目录下的所有配置文件。每个配置段定义一个日志文件的轮转策略:何时轮转、保留多少个归档、是否压缩、是否执行脚本等。
关键触发条件有两种:时间周期(daily/weekly/monthly)和 文件大小(size 100M)。注意:如果同时设置,logrotate会先检查时间。若日志增长异常,仅靠daily可能不够,建议混合使用。
风险点:logrotate默认以root运行,但目标日志文件可能属于其他用户(如nginx、mysql)。如果不做权限处理,轮转后新日志文件可能由root创建,导致原进程无法写入。解决方案在下面配置部分。
配置文件详解:一段配置看懂所有参数
一个典型配置示例(以Nginx access日志为例):
/var/log/nginx/access.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 nginx adm
sharedscripts
postrotate
/bin/kill -USR1 `cat /var/run/nginx.pid 2>/dev/null` 2>/dev/null || true
endscript
}
逐行解释:
- daily:每天轮转一次。也可用size 100M替代
- rotate 7:保留7个旧归档,超过则删除最旧的
- compress:用gzip压缩归档,节省空间(但会消耗CPU)
- delaycompress:推迟一次轮转再压缩。避免刚轮转的文件立即被压缩,导致nginx仍在写入的旧句柄丢失
- missingok:日志文件不存在时不报错
- notifempty:如果日志为空,跳过轮转
- create 0640 nginx adm:轮转后创建新日志文件,权限0640,属主nginx,属组adm。这是解决权限问题的关键——确保轮转后新文件依然能被nginx写入
- sharedscripts:当配置里写了多个日志路径时,只执行一次prerotate/postrotate脚本,而不是每个文件都执行
- postrotate:轮转完成后执行的命令。这里向nginx发送USR1信号,让nginx重新打开日志文件句柄。注意用
|| true防止nginx未运行时脚本报错
常见踩坑:忘记create参数,或权限写错,新日志文件属主变成root,nginx无法写入。排查时先看新文件的所有者。
高级配置:多文件匹配与Script安全
如果你的应用日志分散在多个子目录,可以利用通配符:
/var/log/myapp/*.log {
weekly
rotate 4
compress
maxage 90
dateext
dateformat -%Y%m%d
missingok
postrotate
systemctl reload myapp || true
endscript
}
dateext让归档文件名包含日期而非序号,便于回溯。maxage 90删除90天以上的归档,即使rotate数量未到。这是避免长期积累的好习惯。
脚本安全提示:postrotate和prerotate中尽量避免调用复杂的脚本。如果必须调用外部脚本,确保脚本中不产生大量输出(会阻塞logrotate),并且使用绝对路径。推荐用一个轮转前的检查:
prerotate
if [ ! -f /var/run/myapp.pid ]; then
exit 0
fi
endscript
这样如果应用未运行,logrotate会跳过该日志(配合missingok)。
测试与验证:不等到磁盘爆满才发现配置无效
配置完不能只靠等。现场验证步骤:
- 模拟执行:
sudo logrotate -d /etc/logrotate.d/nginx。这会打印详细调试信息,但不实际轮转。检查参数是否被正确解析。 - 强制立即轮转:
sudo logrotate -f /etc/logrotate.d/nginx。强制轮转(忽略时间和大小条件)。确认新文件生成、旧文件被归档。 - 检查结果:看 /var/log/nginx/ 下是否有
access.log.1或access.log-20250324.gz。轮转后access.log应重新创建且为空,属主为nginx。 - 验证应用日志写入:启动nginx,刷新页面,确认新日志写入 access.log,而不是写入到归档文件中。
如果轮转后应用日志写入失败,立即检查/var/log/messages或journalctl -u logrotate,常见错误:failed to rotate /var/log/nginx/access.log: No such file or directory(路径不对)或 failed to create new log file: Permission denied。
磁盘突然爆满:三分钟排查流程
当告警显示磁盘使用率超过95%,保持冷静,按以下顺序操作:
- 定位大目录:
cd / && du -sh * | sort -rh | head -20。快速找出/var/log是否在列。 - 深度扫描日志:
du -sh /var/log/* | sort -rh | head -10。如果某个日志异常大(例如几GB),看它是否被轮转:sudo ls -lh /var/log/nginx/。若只有一个access.log且非常大,说明logrotate从未成功轮转。 - 查看轮转日志本身:
cat /var/lib/logrotate/status或检查lastrotate时间戳。如果显示“never rotated”,立刻执行强制轮转。 - 如果轮转卡住(异常大的日志文件,轮转时耗时长),先不删原文件,而是截断:
sudo truncate -s 0 /path/to/huge.log。这会迅速释放磁盘空间,且不影响仍在写入的进程(只要进程持有旧句柄,截断后新内容会从0开始写,但原句柄位置不变——注意,仅适用于日志进程在截断后会自动续写的场景。对于nginx、apache等按行写入的程序,安全。但对于某些二进制日志程序可能引起混乱,最好先停止写操作)。 - 删掉较旧的归档:如果归档很多,保留最近3个,删除更早的:
sudo find /var/log/nginx/ -name '*.gz' -mtime +30 -delete。注意确认后缀。
回滚方案:若误删了当前正在写入的日志文件,进程句柄不会释放,空间也不会释放。此时需要:sudo lsof | grep deleted 找到被删除但仍在占空间的进程,重启该进程或使用 /proc/pid/fd/ 下的文件描述符来恢复(不推荐新手操作)。最稳妥做法:删之前用 cp /dev/null 方式清空,而不是直接rm。
长期预防:监控logrotate执行状态
避免下次再爆,加入以下机制:
- 添加cron定时检查:每周执行脚本,检查 /var/log 下是否存在一周未轮转的日志文件,并报警。
- 使用 systemd timer 替代cron:很多现代系统用
systemd-tmpfiles清理临时文件,但logrotate仍依赖timer。检查systemctl list-timers | grep logrotate,确保它最近执行过。 - 设置磁盘使用率阈值:
df -h | grep /dev/sda,编写简单脚本在超过80%时发邮件。 - 对于关键应用,考虑中央日志系统(如ELK、Loki),本地日志按天轮转并同步到远端,本地只保留3天。日志轮转本身也可以配合
copytruncate参数(不移动文件,直接复制后截断),适用于无法发送USR1信号的程序(如java进程)。但注意copytruncate存在竞态风险:复制和截断之间可能丢失少量日志行。
总结:让logrotate真正为你工作
日志轮转配置本身不复杂,容易踩坑的点集中在权限、路径和脚本失败后的恢复。每次修改配置后,务必用-d参数调试,用-f强制执行并验证。磁盘空间排查要由大到小,先定位大文件,再决定截断还是删除。记住:不要直接rm正在写入的日志文件,使用truncate -s 0更安全。
一份健康的生产环境,logrotate应配置为:每天或达到指定大小轮转,保留30天以内,压缩归档,设置正确的文件权限,并用dateext命名方便检索。 你的磁盘不会突然爆满,监控告警也不会在凌晨把你叫醒。
延伸阅读
