ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux磁盘满排查实战:从syslog失控到logrotate根治

Linux磁盘满排查实战:从syslog失控到logrotate根治 半夜被监控连环报警叫起来看到根分区 100%那一刻的心情相信每个运维都懂。上个月我就实打实踩了一次SSH 登录卡得不行写文件直接 No space left on device连数据库都起不来最后df一敲系统盘就是挂载在/的那块盘可不是装系统的 PE/U 盘已经满了。再往下追最大的文件居然是一个不起眼的/var/log/syslog愣是膨胀到了 6 个多 G。这篇文章就把从发现、定位、处理到根治的完整过程原原本本还原一遍以后再遇到系统盘被日志塞满的情况照着这个思路来能少走很多弯路。1. 故障初见一张 100% 的磁盘1.1 最先看到的表象那天的报警先从监控平台开始磁盘使用率从 80% 一路飙到 99%然后就是各种服务连锁崩溃。我先复现了一下症状SSH 能连上但敲命令明显迟钝因为 shell 要写 history、写临时文件磁盘写不进去自然各种卡touch一个空文件都报错cron 任务刷屏式地往邮件里喷错误。这时候第一反应就是看磁盘两条命令基本是固定动作df -hT df -idf -hT看块设备使用率df -i看 inode 使用率。很多人只盯空间结果有时候 inode 满了同样会报 No space left on device。这次情况比较纯粹/dev/vda1挂载在/Use% 直接 100%inode 倒还正常。有一点得提醒系统盘满了之后千万别迷信重启就好。重启过程中 systemd 要写 PID 文件、journald 要落日志、各种服务的状态文件都要写盘磁盘满的时候重启大概率更加起不来甚至可能把系统搞到需要人工介入的程度。所以第一要务是赶紧定位是谁把盘吃满了。1.2 故障影响面比想象中大磁盘写满不是少点空间这么简单它会把整台机器拖进恶性循环。最直接的是服务起不来MySQL 或者其他数据库要写 WAL、写 redo log写不进去就直接拒绝启动nginx 要写 access log虽然通常打不进主盘但要是临时目录满了同样会出问题。更隐蔽的是日志系统本身journald 一旦写不进日志会一直重试并占着 CPUrsyslog 的队列也会积压。也就是说日志导致磁盘满磁盘满又加剧日志写失败两个环节互相放大。这种时候先腾出空间让系统喘口气比什么都重要。2. 排查定位把大头目录揪出来2.1 按部就班的磁盘排查法确认磁盘满之后排查大文件的路径基本是从根往下逐层剥。我自己习惯用组合命令直接给结果du -x --max-depth1 -h / 2/dev/null | sort -rh | head -20 du -ah /var/log 2/dev/null | sort -rh | head -20 find /var/log -xdev -type f -size 100M -printf %s %p\n 2/dev/null | sort -n简单解释下参数-x是让du不要跨挂载点避免把其他分区也算进来--max-depth1只看一层先定方向sort -rh是按人类可读的数字倒序排。第一次排查别一上来就du -sh /*慢慢扫机器已经很难受了命令要尽量快、尽量少产生磁盘 IO。find /var/log -xdev -type f -size 100M这步是直接找大文件-printf %s %p\n把字节数和路径打出来方便排序。速度比du快而且能直接给出文件清单。2.2 /var/log 里的异常文件我的排查结果很清晰/var/log下面躺着一个 6.4G 的syslog/var/log/messages也有 1.2G其他要么几 M 要么几十 M都是正常体型。这里插一句不同发行版日志路径不一样CentOS/RHEL 系列主力文件是/var/log/messagesDebian/Ubuntu 系列是/var/log/syslog国产的 UOS、麒麟这类系统也大同小异排查思路完全一致只是文件名不同。看到这种巨型单文件先别急着删先用tail看一眼内容搞清楚是谁在往里面写tail -n 200 /var/log/syslog我当时看到的内容全是同一段 Java 堆栈几秒钟就刷上百行一看就是有程序在死循环里疯狂printStackTrace()。这种日志的典型特征是重复内容极多一眼就能认出来。2.3 一个容易忽略的细节被删除但未释放的文件排查磁盘满有个经典坑有时候du扫出来明明没多少大文件df却显示 100%。这种情况十有八九是文件被删了但进程还握着打开的文件描述符fd空间没真正释放。lsof | grep deleted如果输出里有一堆类似rsyslogd ... /var/log/syslog (deleted)的条目恭喜你找到隐藏凶手了。处理方法不是去再删一遍而是要找到持有 fd 的进程让它重新打开文件一般发 HUP 信号重载配置即可或者干脆重启对应服务。这次排查虽然最后确认占用空间的是现存的大文件但这个检查步骤我一直保留着遇到磁盘满必须先跑一次成本低收益大。3. 根因深挖syslog 为什么失控3.1 syslog 到底在记录什么在动手清理之前必须搞清楚 syslog 为什么会变成这样否则清了也是白清明天又会涨满。先说说 syslog 的机制Linux 下最常见的实现是 rsyslog它通过 socket 接收内核、系统服务、第三方应用的日志消息再根据/etc/rsyslog.conf和/etc/rsyslog.d/下的规则写到不同文件。默认配置下几乎所有*.info级别的系统日志都会汇总到/var/log/syslog或/var/log/messages。也就是说这个文件是个大杂烩——内核消息、认证消息、cron 消息、应用通过 syslog 接口打出来的日志全都往里塞。好处是排查问题方便坏处是只要有一个源头刷屏整个文件就跟着爆炸。3.2 日志暴涨的几类典型诱因我这些年处理过不少日志刷屏事故诱因其实就那么几类整理出来方便大家对照诱因特征常见例子硬件错误刷屏kern.log、syslog 里大量相同硬件错误磁盘坏道、CPU MCE 错误、网卡反复 link up/down应用死循环打日志同一段堆栈反复出现时间戳密集Java 异常循环 printStackTrace、PHP/Node 进程 failing loop防火墙/内核日志规则太粗每个连接都被记录频率极高iptables LOG 规则不加限速被扫描时直接爆炸cron 任务报错邮件cron 执行报错且未重定向默认发给 root落入 syslog备份脚本写不进目标盘每小时重试一次集中日志接收被刷远端设备/服务把日志发到本机本机无过滤交换机、路由器把 remote logging 指向本机一台设备故障就刷屏logrotate 失效日志一直在长但轮转没发生配置缺失、daily 窗口错过、postrotate 没通知进程重开文件这里多说一句集中日志的场景。很多环境喜欢用集中式日志服务器把网络设备和各个主机的 syslog 都汇聚到一台机器上有的同学还习惯用 Visual Syslog Server 这类图形工具把 Windows 侧的日志也接进来。听着方便但实际上等于把外部所有刷屏风险都引到了本机。汇聚端一旦没有做来源隔离、没有做文件大小上限某台设备抽风就能把整台日志服务器拖死。我遇到的很多莫名磁盘满其实是被远端日志灌满的。3.3 本次事故的根因还原说回我这次的事故根因是两个问题叠加。第一是应用层一个 Java 服务在异常重试逻辑里写了死循环每次循环都打完整堆栈而且日志框架里同时配了 console 和 syslog 两个 appendersyslog appender 走本机 UDP 514 发给 rsyslog再由 rsyslog 全部落进/var/log/syslog。结果就是一个请求异常能在几分钟内刷出几个 G 的内容。第二是轮转层logrotate 默认的 daily 策略只在cron.daily触发一次日志文件在两次轮转之间从 1G 涨到 6G根本不触发轮转。说来也巧那几天logrotate.timer因为系统时间跳变加机器负载高直接错过了执行窗口于是文件就这么一路涨下去直到把磁盘塞满。这两点合在一起就是教科书式的日志失控。处理顺序也很明确先止血再修轮转最后堵源头。4. 解决实施先止血再根治4.1 第一步安全地清空日志文件很多人一上来就rm /var/log/syslog这是大忌。rsyslogd 还开着这个文件直接删掉后 inode 和空间都不会释放还会导致进程往一个已经删除的 inode 上继续写日志凭空消失空间也没腾出来。正确的做法是截断文件不是删除文件。我最常用的是truncate如果文件已经被进程占用先停掉 rsyslog 再操作更稳妥systemctl stop rsyslog truncate -s 0 /var/log/syslog systemctl start rsyslog如果因为某些原因不想重启 rsyslog也可以给进程发 SIGHUP 让它自己重新打开日志文件然后用cat /dev/null /var/log/syslog清空。但第一次救火我建议干脆停下来清保证干净利落。还有个配套动作清完 syslog 后顺手检查一下同目录的轮转压缩包把过期的.gz清一清因为磁盘满往往不只一个大文件旧的轮转包叠在一起也很占地方ls -lh /var/log/*.gz find /var/log -name *.gz -mtime 30 -delete4.2 第二步修复 logrotate让轮转重新接管腾出几个 G 的空间只是暂时保命真正的关键是让 logrotate 可靠工作。先看默认配置长什么样cat /etc/logrotate.d/rsyslog典型的配置是这样/var/log/syslog { rotate 4 weekly missingok notifempty compress delaycompress sharedscripts postrotate /usr/lib/rsyslog/rsyslog-rotate endscript }这配置最大的问题是只有weekly没有size限制。日志一天涨几 G它也老老实实等一周才轮转一次完全失控。我给所有关键日志统一加了size和maxsize/var/log/syslog { rotate 7 daily maxsize 100M size 100M missingok notifempty compress delaycompress sharedscripts postrotate /usr/lib/rsyslog/rsyslog-rotate endscript }解释一下这两个参数size 100M表示不管哪天只要文件超过 100M 就立刻轮转这是最核心的兜底maxsize 100M则是到点了必须轮转但哪怕没到点超过这个大小也会轮转配合daily双保险。实际效果就是日志最多长到 100M 多一点点就会被切走再也不会出现夜半惊魂。改完配置先干跑一遍验证确认没问题再强制轮转一次logrotate -d /etc/logrotate.d/rsyslog logrotate -f -v /etc/logrotate.d/rsyslog-d是 debug 模式只打印会做什么而不实际执行强烈建议先跑这个-f是强制轮转-v是显示详细过程。执行后再看目录ls -lh /var/log/syslog*正常情况下应该能看到syslog只剩几十 K后面跟着syslog.1、syslog.2.gz这样的历史文件。4.3 第三步给 journald 也戴上紧箍咒日志占用大头除了 rsyslog 的明文文件还有 systemd-journald 的二进制日志位置通常在/var/log/journal。它有个特点默认只受磁盘空间上限约束在服务器上很可能占好几个 G 而不自知。先看一眼现状journalctl --disk-usage如果占用超过几百 M可以立即收缩journalctl --vacuum-size200M journalctl --vacuum-time7d不过做这些只是临时清理关键还是改配置。编辑/etc/systemd/journald.conf[Journal] SystemMaxUse500M SystemMaxFileSize64M MaxRetentionSec14d然后重启 journald 生效systemctl restart systemd-journald这套参数的意思是journal 总量最多 500M单文件最大 64M最多保留 14 天。注意systemctl restart systemd-journald不影响正在跑的服务它是独立守护进程重启很安全。我见过不少环境只清理了 syslog 没管 journal结果盘还得再满一次两个都得处理才算闭环。5. 踩坑记录与问题速查5.1 清空文件后空间没变如果你执行了truncate但df显示使用率纹丝不动先别怀疑人生大概率是文件之前被rm过某个进程还握着已删除的 inode。用lsof | grep deleted找出来确认是哪个进程后要么重启它要么发 HUP 让它重新打开文件。如果是 rsyslog 自己握着执行systemctl restart rsyslog再df -h看空间通常会立刻回来。这个删了还没释放的坑我前几年踩过一次当时折腾了半小时才发现是 auditd 把旧的/var/log/audit/audit.log删掉后一直没重开 fd导致轮转后的新日志全写进了一个幽灵 inode。5.2 logrotate 配置没生效的排查改完 logrotate 配置却一直不轮转按这个顺序排查确认 logrotate 的执行入口还在ls /etc/cron.daily/logrotate另外新版系统用的是 systemd timer检查systemctl list-timers | grep logrotate之前提到过我遇到的 timer 窗口被跳过这是真实发生的。用logrotate -d看它到底匹配了哪些文件、做了什么判断。很多时候是配置路径写错比如实际日志在/var/log/syslog配置里却写的/var/log/messages。确认notifempty和missingok不会误伤。日志为空时不轮转是正常的别以为配置坏了。检查轮转后 rsyslog 有没有重新打开文件。logrotate 跑完postrotate脚本如果只是kill -HUP rsyslogdrsyslog 配置没改的话有时不会重新打开同名文件结果就是新日志继续写进已经被改名为syslog.1的文件里出现轮转了但主日志文件还在涨的怪象。用copytruncate可以规避这个问题但会有轻微丢日志的风险适合那些不关心严格完整性的场景。5.3 高频刷屏日志的快速定位技巧面对几 G 的日志文件别用tail -f等着被刷屏要主动定位刷屏来源。我常用这一招按日志前缀汇总统计谁最可疑一目了然awk {print $5} /var/log/syslog | sort | uniq -c | sort -rn | head$5是 syslog 行里的进程名/标签字段比如java[1234]:。输出的第一行往往就是罪犯。如果刷屏的是某种固定模式也可以数一下重复次数grep -c 特定错误关键字 /var/log/syslog确认来源之后在 rsyslog 里给它单独导流甚至丢弃让主场安静下来。比如把foo程序的日志全部挪到单独文件if $programname foo then { action(typeomfile file/var/log/foo.log) stop }这样它再怎么刷也只刷自己的文件不会拖垮/var/log/syslog。这类过滤规则写在/etc/rsyslog.d/下新建的.conf文件里改完执行systemctl restart rsyslog生效。下面把本节的坑整理成速查表症状可能原因处理方式df 显示满du 找不到大文件被删除但未释放的 fdlsof grep deleted重启对应进程truncate 后空间没变化进程握着旧 inodesystemctl restart rsyslog/auditdlogrotate 一直不轮转timer/cron 未执行、配置路径错查 timer、logrotate -d 验证轮转后主日志还在涨postrotate 没让进程重开 fd改用 copytruncate 或检查 HUPsyslog 反复被刷屏应用死循环/远程日志灌入rsyslog 规则单独导流丢弃6. 长效防护让磁盘不再告急6.1 监控与告警救火之后必须把监控补上否则下次还得半夜爬起来。最基础的是磁盘使用率监控阈值设在 80% 告警、90% 高级告警、95% 紧急别等 100% 才收到消息。我强烈建议再加一项大文件巡检每天定时扫一遍日志目录超过 500M 的文件直接报警。这个脚本十几行就能搞定#!/bin/bash find /var/log -xdev -type f -size 500M -printf %s %p\n 2/dev/null | sort -n配合 Prometheus 的话node_exporter的node_filesystem_avail_bytes指标足够做阈值告警用 Zabbix 的话内置磁盘监控模板也能直接套。关键是粒度要细到单文件不然一堆小文件加起来满了常规磁盘监控报了警也定位不到具体是谁。6.2 日志分区规划预防磁盘满架构上的根治方案是给日志独立分区。生产环境我强烈建议把/var单独挂一块盘条件允许的话干脆/var/log也独立分区这样即使日志彻底炸了也只影响日志分区系统盘和数据库盘不受牵连。下次重装或扩容时用 LVM 挂/var/log后期扩容就是一个lvextend的事。如果是容器化环境还要留意宿主机的/var/lib/docker和容器 stdout 日志Docker 默认的 json-file 日志驱动如果不限制大小一样能把系统盘撑爆。容器编排平台里记得配置log-opts max-size50m max-file3。对于承担集中日志汇聚的服务器rsyslog 的队列和写盘模式也需要优化。接收量大时建议开启磁盘辅助队列并给每种 facility 设置独立的文件大小上限避免某一路日志把自己吃死。6.3 落地成制度最后想说的是日志治理不是装个工具就完事得变成团队共识。我现在会在上线 checklist 里强制这几条应用日志框架必须设置单文件大小和滚动策略禁止无限写单个文件所有写日志的路径必须有独立分区或大小上限logrotate 配置修改必须-d验证后才生效每季度手动执行一次logrotate -f做体检顺手检查 journal 占用。这些事看着琐碎但正是它们把日志把系统盘塞满这种事故从概率事件变成了几乎不可能事件。我个人在实际操作中的体会是处理日志撑爆磁盘最重要的不是清理动作本身而是先确认谁在写、再截断、最后堵源头这个顺序。永远不要在没搞清写入方的情况下删任何日志文件清空用truncate而不是rm根治靠的是可靠的轮转机制和过滤规则而不是一次次手动清盘。把这些习惯固化下来以后再看到报警你会有底气先喝口水再稳稳地一步步定位。
返回列表