ARTICLE DETAIL

资讯详情

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

Ubuntu根目录空间不足?rsyslogd日志膨胀排查与清理实战

Ubuntu根目录空间不足?rsyslogd日志膨胀排查与清理实战 晚上十一点手机弹出一条磁盘告警根目录使用率 98%。我最开始以为是有人往 /home 里拖了大文件ssh 进去后先 df -h结果发现 / 挂载点只剩几百 MB而 du 扫 /home 才用了不到 5G。真正的大头在 /var/log/syslog一个文件已经涨到 11Grsyslogd 进程还在不紧不慢地往里写文本。遇到 “Ubuntu 根目录空间不足” 的时候先别急着扩容差不多有一半情况是日志把根目录的空间给吃了而这其中 rsyslogd 的文本日志又是最常见的元凶。这篇文章我按排障路径来讲先教你怎么快速判断是不是 rsyslogd 在膨胀再把日志机制的来龙去脉拆开最后给出能落地的清理和治理方案。整个过程不复杂但里面有不少坑尤其是“日志文件被进程占用导致空间不释放”的那个搞懂之后你以后再遇到类似问题五分钟就能定位。1. 症状与第一反应根目录写满时系统在闹什么1.1 先看现象再碰命令根目录满不是“你什么都做不了”的一瞬间而是各种诡异问题的连环爆发。你可能会看到 SSH 能连上但 shell 一进去就报/home/xxx/.bashrc: No space left on deviceapt 安装任何包都会失败systemd 服务 restart 的时候卡住甚至 cron 每晚跑的任务都在静默失败。它的本质是根文件系统没有了可分配块日志、临时文件、PID 文件都写不进去连带所有依赖这些文件的服务一起抽风。我那次遇到的情况是messagebus 起不来、rsyslogd 也在反复重启正是因为 rsyslogd 写日志写不进去连它自己的 pid 文件也创建不了。然后系统陷入“越写不了越重启、越重启越快把剩余空间写满”的恶性循环。第一反应一定不是急着删文件而是先搞清楚两件事空间是“块空间”不够还是“inode”不够。绝大多数人把df -h跑完就完事了但你还需要跑一条df -i /看看 inode 使用率。inode 相当于仓库里的货架编号每个文件都要占一个编号如果 inode 满而块空间还有富余那说明是小文件堆积不是大文件的问题得把根目录下所有目录整体扫一遍看哪里有大量零碎文件。1.2 别急着删文件df -h 与 df -i 的关系用一个简单的类比磁盘空间是一个仓库块是仓库里的货架面积inode 是货架编号。你往仓库里放一件大货占的面积大但只占一个编号你放一万件指甲盖大小的货面积没多少但编号先耗光了。所以df -h看面积df -i看编号两者都要看。如果df -i /显示 100%那你先别折腾日志去/var/spool/postfix/maildrop、/tmp、/var/tmp、docker 容器目录这些容易堆小文件的地方找找。在 Ubuntu 上最常见的是 maildrop 里堆积了大量发不出去的邮件每封邮件是一个文件瞬间能把 inode 吃光。而 rsyslogd 这种写大日志的进程消耗的主要是块空间inode 一般不会先爆。所以排障的第一步是打开终端依次执行df -h / df -i /两条都看。只有块空间使用率高、inode 正常才进入下一步去找大文件。这一步虽然基础但我见过太多人下载了扩容工具咔咔加虚拟磁盘最后发现根因只是日志扩容完日志继续涨下次照旧。1.3 第一轮盘查根目录下哪些目录最可疑空间是块空间的问题那就用du从根目录往下扫。我习惯用这个sudo du -x --max-depth1 -h / 2/dev/null | sort -hr-x参数的意思是不要跨文件系统统计。很多人的根目录里还挂着独立分区的/home或/data如果不加-xdu 会把其他挂载点的内容也算进来造成“根目录根本没这么大”的错觉。2/dev/null是过滤权限报错有 sudo 其实不太需要但某些特殊目录还是会刷屏。跑完之后你会看到类似这样的输出11G /var 3.2G /usr 1.8G /home 1.2G /snap ...真正的大头在/var下面已经是常规操作了。继续往下缩圈sudo du -x --max-depth1 -h /var 2/dev/null | sort -hr如果看到/var/log占了 9G 甚至更多那基本可以锁定日志问题了。从根目录一路缩到/var/log再缩到具体文件整个过程通常不超过一分钟。下面是我常用的候选清单目录容易堆积的原因优先级/var/logsyslog、kern.log、journald 日志最高/var/cache/apt软件包缓存不清理中/var/lib/docker镜像、容器层、日志 json 文件高装了 docker 的话/tmp /var/tmp临时文件或 core dump中/home 用户目录下载、缓存、容器镜像不一定/snapsnap 旧版本保留低注意当你锁定了/var/log以后不要直接执行rm -rf /var/log/*收工。日志文件删起来比多数人想象的更容易踩坑尤其是 rsyslogd 还活着的时候你删了的文件不会立刻从磁盘上消失。这背后的文件句柄问题我放到第 3 章和第 4 章详细讲。2. rsyslogd 为什么能把根目录“吃”掉十几个G2.1 syslog 生态科普内核日志不只有 kmsg很多人对 rsyslogd 的认知停留在“它是系统的日志服务”但到底谁在给它喂日志、它写出来的东西去了哪里并不清楚。现代 Ubuntu 采用 systemd 之后日志链路已经变成这样内核日志先进入/dev/kmsgsystemd-journald 从那里读走然后按来源分别保存成 journal 二进制文件rsyslogd 作为传统 syslog 实现又把 journal 收到的内容转换回纯文本追加写进/var/log下的几个文本文件。为什么要搞两套因为 journald 的目标是方便 systemd 生态做结构化查询二进制格式机器友好但传统运维和第三方工具还是更习惯文本日志rsyslogd 承担了这个“翻译和分发”的角色。它做的事包括把全局日志写到/var/log/syslog、内核日志写到/var/log/kern.log、认证日志写到/var/log/auth.log还可以根据 facility 和 priority 做过滤甚至转发到远程日志服务器。这两套日志同时落盘的结果就是同样的内容在/var/log/journal/下存了一份在/var/log/syslog等文本文件里又存了一份。平时感觉不到一旦系统持续输出某类错误两个目录会一起膨胀而且因为 rsyslogd 写的是纯文本文件大小非常直观动辄单文件好几个 GB。2.2 默认配置与增长模型rsyslogd 的配置在/etc/rsyslog.conf和/etc/rsyslog.d/下Ubuntu 的默认规则在/etc/rsyslog.d/50-default.conf。默认情况下你会看到类似这些规则auth,authpriv.* /var/log/auth.log *.*;auth,authpriv.none -/var/log/syslog kern.* -/var/log/kern.log daemon.* -/var/log/daemon.log ...注意*.*到 syslog 这一条几乎所有非认证、非第三方过滤的日志都会进 syslog这个文件就是增长最快的候选。而 kern.log 只在内核产生消息时写入如果机器上有驱动在反复报错它也会以非常快的速度膨胀。默认的 rsyslog 规则本身没有任何大小限制。日志增长逻辑完全依赖 logrotate 的每日轮转每天切分一次默认保留 7 份压缩存储。如果 logrotate 正常执行单个文本文件的大小会被控制在一个合理的范围但如果 logrotate 因为某种原因没跑或者轮转后旧文件没被压缩清理syslog 就会无限增长从几个 G 到几十个 G 只需要一两个月。我遇到过一种情况特别常见系统装好后一直没设置过任何东西logrotate 也确实在跑但某一个应用或内核驱动持续刷错误导致当天生成的 syslog 在一天之内就写满了几 GB。logrotate 再勤劳面对“一天几百 MB 到几 GB”的写入速度也无济于事因为它是按天切分不是按大小切分。2.3 真实案例一次驱动报错如何在两天内写满磁盘这里说一个真实案例。有一台 Ubuntu 机器外接了某个 USB 设备驱动不稳隔几秒就断连一次。每次断连内核都会打印一坨包含寄存器、栈回溯在内的错误信息几十行起步。那段时间我刚好在折腾别的没注意日志两天后根目录就满了。打开/var/log/kern.log一看文件已经 8 个 G。grep -c error能扫出几十万行。再算一笔账一次报错 1KB每 5 秒一次一天就有86400 / 5 * 1KB大约 17MB看起来不多。但如果驱动频繁断连每秒就报好几次每次 4-5KB一天就能冲到 1-2G两天 3-4G。再加上 syslog 里同样的内容再存一遍磁盘就被快速吃满了。这类问题的特点是 rsyslogd 本身的 CPU 和内存占用不高你top看进程排行根本发现不了它因为它的 CPU 可能只有百分之零点几内存只有几 MB。真正的问题不在 rsyslogd 进程本身而在于它忠实地把一条条重复日志写进了文件。就像一台不断打印的打印机打印机本身没问题问题是有人不停地按打印键。这就是为什么遇到“Ubuntu 根目录空间不足”时只盯着大文件是不够的还要回头看看是哪个应用在频繁产生日志不然清了空间第二天又会满了。2.4 为什么 rsyslogd 能一直写而不爆内存还有一个值得解释的细节rsyslogd 持续写入十几 G 的文件为什么内存吃不消原因是文件写入并不是每次 write 都直接落盘。rsyslogd 以追加模式打开日志文件写入时内核先把数据放进页缓存page cache攒到一定程度再刷到磁盘。所以从 rsyslogd 进程的角度看它只是不停地往内核里递数据本身并不保存一份完整副本占用的内存永远是那么一点。页缓存虽然会占内存但遇到内存压力可以被回收所以你看free -h也不会有明显异常。但是持续的页缓存脏页确实会造成磁盘 IO 压力尤其在机械硬盘上日志刷盘过程会影响其他进程的 IO。这时候你用iostat看会发现写盘设备利用率很高但 CPU 使用率很一般。这也是日志型故障常见的“假性瓶颈”看起来是磁盘忙实际是日志在拖慢整个系统。3. 定位过程一条命令一条命令把真凶挖出来3.1 逐层缩圈先找目录再找文件回到我的那次排障。执行完du -x --max-depth1 -h /之后明确/var是 11G。往下sudo du -x --max-depth1 -h /var 2/dev/null | sort -hr很快看到/var/log占了 10G 多。再往下sudo du --max-depth1 -h /var/log 2/dev/null | sort -hr输出里最显眼的就是syslog这个文件11G 左右。ls -lh /var/log/syslog也能直接看到文件大小和时间戳都很新说明 rsyslogd 正在持续写入。如果du的层级太多也可以用ncdu /var/log这种交互式工具它是文本界面可以方便地切进目录看明细。Ubuntu 上安装sudo apt install ncdu sudo ncdu /var/log有一定经验的人用纯命令也能搞定但 ncdu 在排查大目录时效率明显更高。这里我不建议大家直接ls -lh /var/log/syslog*就下结论因为删除文件后空间不释放的问题经常出现需要结合下一步的 lsof 确认。3.2 用 lsof L1 抓“已经被删但还占用空间”的文件这是我排障时最喜欢用的一招。很多人在日志挤爆磁盘后第一时间想到的就是删文件。如果你先执行了rm /var/log/syslog正常情况下 rsyslogd 会继续往这个已删除文件的 inode 上写入数据磁盘空间并不会释放。这时候你再怎么df空间使用率还是满的因为文件虽然从目录里看不到了但文件句柄还挂在 rsyslogd 上。这种“孤儿文件”不是用 du 能看到的目录扫描已经找不到它了但块空间被它占着。正确做法是用 lsofsudo lsof L1L1的意思是列出所有 link count 为 0 的已删除文件。如果 rsyslogd 正拿着一个已被删除的日志文件你会看到类似这样的输出COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME rsyslogd 31782 root 8w REG 8,2 1149356032 717671 /var/log/syslog (deleted)看到(deleted)就实锤了。这种情况下单纯清空目录里的文件没有意义要么让 rsyslogd 重启把旧句柄关掉要么在删除前先把进程停掉。从我的经验看遇到根目录空间不足lsof L1这条命令应该和df -h一样成为标配。顺序应该是先看 df锁定了 / 分区后立刻跑一遍 lsof L1排除“已删除但未释放”的情况再用 du 去定位还挂在目录树上的大文件。否则很容易白忙活。3.3 journaldrsyslogd 的同伙有时候你处理完 syslog 和 kern.logdf 显示空间恢复了但没两天又满了。这时候要怀疑 journald。systemd-journald 默认把日志保存在/var/log/journal/目录下某些版本默认在/run/log/journal/重启后丢失。在 Ubuntu 上journal 默认占用空间受SystemMaxUse控制通常是所在文件系统大小的 10% 左右具体上限不同版本略有差异。也就是说你只清理 rsyslogd 的文本日志却不处理 journal/var/log依然可能被 journal 文件占去好几个 G。查看 journal 占用journalctl --disk-usage输出类似Archived and active journals take up 3.2G in the file system.这就说明了 journal 的大小。如果你发现 syslog、kern.log 加起来才 1G而 journals 占 3G那真相其实是 journald 在膨胀。rsyslogd 和 journald 经常同时变大原因是同一个事件会被两套系统各存一份很多人只处理了一个另一个隔一阵子再冒出来会误导排查方向。3.4 确认 rsyslogd 相关日志文件及增长频率为了确认“谁在疯狂产生日志”我会在清空前做一个小实验记录当前时间下几个关键日志的大小等上 2 分钟再看一次。sudo ls -lh /var/log/syslog /var/log/kern.log /var/log/auth.log sleep 120 sudo ls -lh /var/log/syslog /var/log/kern.log /var/log/auth.log如果 2 分钟里 syslog 涨了几十 MB说明日志写入速度非常可怕。这时候再去看日志内容sudo tail -n 100 /var/log/kern.log sudo journalctl -f -n 50找到刷屏源头。常见的有内核反复报 USB 设备错误通常与驱动或硬件不稳有关显卡驱动报错特别是新装驱动和内核版本不匹配时sshd 反复认证失败属于 auth.log 膨胀某些应用每隔几秒写一条 warning把 syslog 灌满。定位到具体的报错内容才能做下一步的根治否则清理只是暂时续命。顺带说一句dmesg -T也能看内核日志但现代 Ubuntu 上我更推荐用journalctl -k -f实时观察内核层面的输出时间戳更直观还能和 syslog 的内容对照。4. 存量清理正确的“急救收尾”动作4.1 先停服务再清空避免写一半在已删的 inode 上存量清理要遵守一个原则先停 rsyslogd再清空日志文件最后启动服务。顺序错了清理效果会打折扣。我常用的完整步骤sudo systemctl stop rsyslog然后清空主要日志文件。这里我强调一下是“清空”不是“删除”。删除文件会导致 rsyslogd 重新创建新文件这一来一回本身没有问题但如果你忘了重启 rsyslogd日志会写到旧 inode 上表现为空间没释放。清空则不同文件还在rsyslogd 的文件句柄依然有效你清多少释放多少不会出现孤儿文件。sudo truncate -s 0 /var/log/syslog sudo truncate -s 0 /var/log/kern.log sudo truncate -s 0 /var/log/auth.log sudo truncate -s 0 /var/log/daemon.log如果想更全面地把所有 rsyslog 相关文件都清一遍可以这样sudo find /var/log -type f \ \( -name *.log -o -name syslog* -o -name kern.log* \ -o -name auth.log* -o -name daemon.log* \) \ -exec truncate -s 0 {} 但注意find这种方式会连一些不该清的文件一起清空比如/var/log/btmp、/var/log/wtmp这类登录记录。不想误伤的话就手动列出来清别贪图“一条命令全清”。清完之后启动 rsyslogdsudo systemctl start rsyslog启动后等一下确认/var/log/syslog正常写入没有报错。如果发现 rsyslogd 反复重启多半是还有文件权限或磁盘空间的问题没解决。4.2 一个清空命令组实际操作时我会把上面几条合成一个可重复执行的小命令组排障时直接复制粘贴sudo systemctl stop rsyslog sudo truncate -s 0 /var/log/syslog /var/log/kern.log /var/log/auth.log /var/log/daemon.log 2/dev/null sudo systemctl start rsyslog这条命令组只清理最常见的几个文件不涉及 wtmp 之类的登录历史。如果还有/var/log/messages这种自定义文件自行把路径加进去。journald 的部分单独处理sudo journalctl --vacuum-size200M sudo journalctl --vacuum-time3d第一条把整个 journal 压缩到 200M 以内第二条删除 3 天以前的 journal。两者可以同时用先按时间删再按体积兜底保险起见我都写上。如果 journal 目录占用巨大这两个参数能一次释放好几个 G。如果 journald 的配置还没调整清理完是可以立刻生效的不用重启 journald。但如果想让它以后不再涨回来需要改/etc/systemd/journald.conf这个放在第 5 章讲。4.3 空间回收验证清理完成后跑最后一次验证df -h / df -i / sudo du -sh /var/log正常情况下你会看到根目录使用率从 98% 回到 30% 左右/var/log也从 10G 变成几百 MB。如果 df 显示的空间没有变化不用慌优先查lsof L1 | grep deleted看是不是有进程还拿着旧文件句柄。出现过很多次的情况是logrotate 之前已经把某个日志改名成syslog.1但 rsyslogd 的重启没有生效导致它一直往syslog.1这个已不在预期位置的文件里写。此时可以强制让 rsyslogd 重开文件sudo systemctl restart rsyslog重启之后再看lsof L1句柄会消失空间才会真正释放。有时候要处理的不只是 rsyslogd比如某些 Java 进程、Nginx、Docker 容器也可能把日志文件开到一半又删掉lsof L1会把它们全列出来定位到对应进程后重启该进程即可。4.4 这里容易翻车的路日志文件被进程占用时的删除顺序我见过最典型的翻车案例是这样的同事 A 发现 syslog 太大直接rm -f /var/log/syslog然后一看 df 还是满的又去扩容同事 B 加入发现没用又把/var/log整个删了一遍还是满的最后重启 rsyslogd空间瞬间释放。整个过程不复杂但因为没有理解“删除并不释放被占用文件的块”这个机制多折腾了俩小时。所以这里特意强调如果日志是被某个打开它的进程持续持有的删除不是第一选择清空才是。如果你确实想删掉旧文件让系统重建也可以先停服务再删删除后立刻启动服务让服务重新创建新文件。但用 truncate 清理更符合“保证服务最小中断”的原则这也是我在生产环境里的默认做法。对根目录空间不足这种问题救急和根治是两件事。救急用 truncate journal vacuum根治则要调整 logrotate、journald 配置并解决真正的日志刷屏源头。第 5 章讲这些。5. 从源头治理让 rsyslogd 以后不再无限膨胀5.1 logrotate 是根治核心每天轮转 压缩 大小上限清理只是急救真正的根治得靠 logrotate 好好配置。Ubuntu 上 rsyslog 的日志轮转配置一般在/etc/logrotate.d/rsyslog。默认配置大致是/var/log/syslog { rotate 7 daily missingok notifempty delaycompress compress postrotate /usr/lib/rsyslog/rsyslog-rotate endscript }含义很清晰每天轮转一次保留 7 份轮转后压缩并且延迟一天压缩。delaycompress是因为某些服务在轮转后仍然会短暂地写上一份文件等一天再压缩更稳妥。但默认配置有一个漏洞它没有大小限制。如果某天 syslog 一天写了 10G第二天 logrotate 会把 10G 的 syslog 变成 syslog.1然后新的 syslog 又接着写结果可能是你有 8 个 10G 的压缩包磁盘照样很快满。所以我会在配置里加上maxsize/var/log/syslog { rotate 7 daily maxsize 100M missingok notifempty delaycompress compress postrotate /usr/lib/rsyslog/rsyslog-rotate endscript }maxsize 100M的意思是就算没到轮转时间只要 syslog 增长到 100M也立刻轮转。这样即使某天异常日志暴涨单个日志文件的体积也被限制住顶多是轮转次数变多不会出现单文件十几 G 的恐怖情况。建议把 100M 作为一个起步值日志量大可以调到 200M、500M但最好不要超过 1G。改完配置后可以验证一下sudo logrotate -d /etc/logrotate.d/rsyslog sudo logrotate -f /etc/logrotate.d/rsyslog-d是 debug 模式只打印执行计划不真正执行-f是强制执行。两条配合使用可以确认配置没有语法错误还可以立刻触发一次轮转。5.2 控制日志内容与级别不是关掉是收敛如果某个源头在持续刷日志logrotate 只是被动地限制文件大小治标不治本。更主动的做法是在 rsyslogd 层面过滤掉低价值日志或者把某些来源的日志级别调高。比如/etc/rsyslog.d/50-default.conf默认把内核日志全部写到/var/log/kern.logkern.* -/var/log/kern.log如果内核反复报 debug 级别的消息你可以改成kern.warn -/var/log/kern.log意思是只有 warn 及以上级别才写进 kern.log。这样大部分正常工作的调试信息就不会落盘。注意这里的warn是 syslog 的严重级别不是业务里的“警告”概念别搞混。对 syslog 也可以做类似收敛。默认*.*把所有消息都写进 syslog你可以显式排除某个来源。比如某个应用疯狂产生 notice 级别日志你想把它丢弃:programname, isequal, yourapp ~这一行的意思是把 programname 为 yourapp 的消息全部丢弃~是 discard。但要小心过滤规则写错会把关键日志丢掉排障时很难回头找。我的原则是先确认哪些日志是垃圾再加过滤规则尽量用“丢弃特定程序名”而不是“全局降低日志级别”。另外如果你确定内核总是向控制台打印大量信息也可以把 console loglevel 调低sudo sysctl -w kernel.printk3 4 1 3kernel.printk控制内核日志中送往控制台和 /dev/kmsg 的细节等级。数字越小输出越少。调低之后内核日志的落盘量和控制台输出都会明显减少。这个参数多用在嵌入式或频繁报错的内核上普通桌面或服务器不建议为了省空间盲目调因为会影响到紧急内核消息的可见性。5.3 给 journald 也戴上紧箍咒rsyslogd 只解决文本日志journald 也不能放任不管。打开/etc/systemd/journald.conf[Journal] SystemMaxUse500M SystemMaxFileSize50M MaxRetentionSec1weekSystemMaxUse500Mjournal 目录总体积上限 500M超过后自动清理最旧日志。SystemMaxFileSize50M单个 journal 文件最大 50M防止单文件过大影响读取效率。MaxRetentionSec1week只保留最近 7 天的日志配合体积限制双保险。注意SystemMaxFileSize是新建文件后才生效已有的大文件需要 vacuum 一次才能压下来所以改完配置后最好再执行sudo journalctl --vacuum-size500M sudo systemctl restart systemd-journald有些人会问把 journal 限制得太小排障时日志不够用怎么办。我的建议是保留 500M 到 1G 的 journal 空间足够回溯最近几天的系统事件深究历史日志时再临时把限制调大或者依赖 rsyslogd 的文本日志。两套日志的侧重点不同journald 负责短期快速检索rsyslog 负责长期归档两边都要有但不能让任何一边无限膨胀。5.4 防患于未然十分钟一次的巡检脚本雏形最后给一个偷懒的方案用脚本定期检查磁盘使用率达到阈值就提示避免每次都是空间满了才后知后觉。我自己的服务器上放了一个简单的脚本每 10 分钟跑一次#!/bin/bash THRESHOLD85 EMAILyouexample.com USAGE$(df / | awk NR2 {print $5} | tr -d %) JOURNAL_SIZE$(journalctl --disk-usage | awk /take up/ {print $(NF-2)}) if [ $USAGE -gt $THRESHOLD ]; then echo 根目录使用率${USAGE}%journal${JOURNAL_SIZE} | mail -s Disk alert on $(hostname) $EMAIL fi这里只做告警不做自动清理。自动清理的风险是误删重要日志尤其在生产环境我主张“告警 人工确认 快速处理”。如果你连告警都不想要也可以用 systemd timer 每天在低峰期自动跑一次logrotate -f /etc/logrotate.d/rsyslog和journalctl --vacuum-size但我个人更喜欢留一个人工检查的窗口。脚本本身不复杂关键是“周期性执行”这点不能省。很多人是磁盘满了一次清理完就继续跑结果下个月再满一次周而复始。只有把巡检变成例行行为才能真正避免反复踩坑。最后分享一点我个人的体会这些东西排查多了就会发现“根目录空间不足”本身不难难的是别把手段当成目的。清空日志、调 logrotate、限制 journald都是在跟日志增长的规律做朋友而不是战胜它。只要系统还在跑日志就会继续产生区别只是你能不能让它处在一个可控的范围内。如果你现在的机器已经出现了类似的警报不妨先跑一遍df -h、lsof L1、du -x --max-depth1 -h /把三个命令的结果放在一起看大概率十分钟内就能定位到答案。工具谁都会装会定位问题的思路才是真正值钱的。
返回列表