ARTICLE DETAIL

资讯详情

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

Linux根分区空间不足排查与扩容:从df到resize2fs的完整实践

Linux根分区空间不足排查与扩容:从df到resize2fs的完整实践 先说个几天前刚遇到过的事。一块 RK3568 开发板SD 卡里烧了 Ubuntu 根文件系统启动倒是很顺利结果一执行df -h挂载根/的那个分区可用空间只剩 400 多 MB而系统本身才刚装上不到三天。然后我想往/opt里放一个交叉编译工具链直接提示“no space left on device”。这就是典型的“挂载根的磁盘空间太小”——不是说扩容按钮在哪里而是你得先搞清楚根文件系统被挂载到了哪个分区、这个分区到底有多大、空间是被什么吃掉的以及在不重新烧写的情况下还能不能救得回来。这篇文章我从实际排查顺序出发把根分区空间不足的场景拆开揉碎覆盖嵌入式开发板、虚拟机、服务器三类最常见情况。内容会包含具体命令、判断逻辑和基于常见实践的扩容方法。适合正在被/100% 占满吓到的新手也适合想系统整理根分区管理思路的运维或开发。1. 判断挂载根之前先回答三个关键问题很多人一看到根分区没空间第一反应就是rm -rf一堆日志缓存。但在我经历过几次“删完还是满”或者“删错东西系统崩了”之后现在我遇到这类问题永远先花五分钟回答下面三个问题再决定动不动刀。1.1 挂载根“根”的是哪个设备“根”字在 Linux 系统里不是抽象概念它必然对应一个具体的块设备、一个分区或者一个网络文件系统。最简单的判断方式是用findmnt /它会直接告诉我们根挂载点对应的源、文件系统类型和挂载选项。findmnt /如果是本地分区你会看到类似/dev/mmcblk0p2或者/dev/sda3如果是开发板通过 NFS 启动源可能是192.168.1.100:/srv/nfs/rootfs。这一步的意义在于如果你连根到底落在哪个存储介质上都没确认后面所有清理和扩容都是盲打。与此同时我还习惯再看一眼lsblk把整个块设备拓扑拉出来lsblk这个输出能直接看出分区表结构。比如 SD 卡的mmcblk0下面有几个分区哪个分区挂载成了/哪个是/boot哪些分区还没有挂载。虚拟机上则是/dev/vda或/dev/sda下面划分了vda1、vda2逻辑卷则会有/dev/mapper/ubuntu--vg-ubuntu--lv这种名字。1.2 别把 tmpfs 和 overlay 误当成“真实的根”这里有个很容易踩的坑某些容器环境或者特殊开发板里/可能是 overlay 文件系统或者 tmpfs。df -h /会显示一个看起来占用率很高的虚拟设备比如overlay或者tmpfs但它的后端可能是一个宿主机目录也可能是内存。df -h /如果Filesystem那一列显示overlay说明你看到的“根”实际上是多个下层目录叠加出来的视图真正的磁盘消耗是在宿主机端的 upperdir。如果显示tmpfs那你的根文件系统就是内存盘重启后所有改动都会消失空间当然也取决于内存大小而不是磁盘。搞清楚这一点才不会出现“在容器里清了半天、宿主机空间没变化”的尴尬。1.3 确认是空间不足还是 inode 不足df -h只显示数据块使用率但还有一种情况磁盘明明还有几十 GB却依然报“No space left on device”。这个时候要看df -i /。df -i /inode 用尽意味着文件系统里已经没有任何空闲的文件索引节点了就算有空间也建不了新文件。这种事经常发生在需要创建海量小文件的应用场景比如邮件队列、临时目录、容器镜像层。我见过一个案例根分区空间用了不到 30%但/var/tmp里堆了几十万个几 KB 大小的临时文件把 inode 全部吃光服务全部报磁盘满。所以在根分区不够用的排查链路里df -h和df -i必须成对查看只查一个一定会漏判。2. 空间被谁吃掉了根分区爆满的真实原因排查当确认根确实落在某个磁盘分区上之后下一步不是立刻删文件而是弄清楚空间去哪了。根分区的数据通常是分层结构的系统本身、软件包缓存、日志、临时文件、用户数据以及容器和虚拟机可能产生的镜像层。每一层都可能成为真正的空间杀手。2.1 日志膨胀最常见也最容易被忽略的元凶系统日志是根分区爆满的“首犯”尤其是systemd-journald。默认情况下journal 日志在/var/log/journal下累积如果没配限制它可以一路涨到占用根分区相当大的比例。我见过一块 8GB 根分区的板子光 journal 日志就占了 5GB 多原因是有个服务每天疯狂报错。快速看一下日志占用journalctl --disk-usage如果占用异常可以立即清理到指定大小以下journalctl --vacuum-size200M这条命令会把归档日志压缩清理让所有 journal 文件的总量控制在 200MB 附近。需要注意的是--vacuum-size只作用于已归档日志当前活跃的 journal 不会被删掉。所以更稳妥的做法是直接在配置里写死上限# /etc/systemd/journald.conf SystemMaxUse200M改完执行systemctl restart systemd-journald才真正生效。除了 journal传统的 syslog、应用日志比如 Nginx、Tomcat、Python 服务也往往堆在/var/log下。可以用du -sh /var/log/*快速扫一遍看到哪个文件上了 GB 级别再决定是 truncate 还是配 logrotate。这里我特别提醒一句千万不要直接rm正在被进程写入的日志文件因为文件描述符还握着空间不会真正释放后面单独讲。2.2 包管理缓存与软件残留平常用apt或yum安装软件下载的.deb、.rpm包都会缓存到本地。嵌入式板子和云服务器上都一样时间一长这些缓存能攒到几个 GB。Ubuntu/Debian 系清理很简单apt clean apt autocleanapt clean会清掉/var/cache/apt/archives下所有已下载的安装包autoclean则只清除已无法下载的旧包。对于 CentOS/RHEL 系则是yum clean all另一个容易被忽略的是/var/cache下的其他临时文件以及/tmp里的历史残留。嵌入式环境长期不重启的话/tmp甚至可能藏着上次构建的临时产物。清理前先du -sh /tmp/*看清大小再动手。2.3 文件已删除但空间未释放这个问题特别隐蔽。你明明删掉了一个 3GB 的日志文件df -h却纹丝不动。原因是有进程仍然持有这个文件的文件描述符Linux 只有在所有引用都关闭后才会真正释放空间。我自己的排查习惯是lsof L1这个命令会列出所有已被删除但仍被进程打开的文件。输出里SIZE列对应的就是它们占用的空间大小。找到 PID 之后要么重启对应服务要么让进程重新加载配置关闭旧日志文件。如果这个文件属于某个关键服务不能随便重启可以考虑先 truncate 它: /proc/PID/fd/FD编号但更标准的做法还是解决进程引用的来源比如应用本身写了日志但没断开句柄那就把日志轮转配置修好再重启一次服务空间才会真正回来。2.4 容器镜像层与 overlay 堆积如果你的“根”出现在一台跑 Docker 的机器上或者你的开发板用容器方式跑应用那么/var/lib/docker往往是隐藏的空间大户。镜像、容器层、构建缓存、挂载卷都可能占用大量根分区块。最直接的是先看 Docker 自己报的占用docker system df我的习惯是清理掉所有不再使用的悬空镜像和构建缓存docker system prune -a --volumes但这条命令很激进-a会清除所有没有被容器使用的镜像--volumes会删除未挂载到任何容器的卷。如果是在生产环境建议先docker system df看清楚各类对象占用再手动清理更稳妥。对开发板这种存储紧张的环境我通常还会检查/var/lib/docker/overlay2里是否有异常大的层目录然后通过docker image lsdocker rmi精准清理。2.5 根分区自身的保留空间reserved blocksext4 文件系统默认会预留 5% 的 blocks给 root 用户和紧急恢复用。对于几十 GB 的服务器这 5% 可能有好几个 GB但对于只有 4GB 的 SD 卡根分区5% 就是 200MB非常可观。这不算“空间被吃掉”但它确实导致可用空间比实际容量少。可以用tune2fs -l查看tune2fs -l /dev/mmcblk0p2 | grep -i reserved如果这是个人开发板或测试虚拟机不需要那么高的安全余量可以调低保留比例tune2fs -m 1 /dev/mmcblk0p2但生产环境不建议动这个值。它存在的意义是防止 root 用户在磁盘满时连登录系统、清理空间都做不到——一旦没预留空间连mount、touch这种操作都可能失败。3. 清理操作的正确顺序与验证手段空间分析做完了清理的顺序很重要。很多人上来就把/usr、/opt里的“不常用”文件删了结果系统直接起不来。我下面的顺序是从“安全系数高”到“风险系数高”排列的前两步基本不会出问题后两步则需要你清楚在删什么。3.1 先动日志与临时文件第一步永远是清理日志、包缓存和/tmp。这一步风险最小收益却常常最大。推荐按这套来journalctl --vacuum-size200M apt clean # 或 yum clean all rm -rf /tmp/* # 注意不要在生产环境直接删先检查是否有服务依赖/tmp下如果有正在使用的 socket 文件删除可能导致服务异常。正确做法是重启后再清或者只清超过一定时间的文件find /tmp -type f -atime 7 -delete。3.2 用 du 找出真实占空间的大目录为了不被零散文件迷惑我喜欢用du从根目录一层层往下定位。一次性扫完整根分区可能慢但最直观du -x -h --max-depth1 / 2/dev/null | sort -h-x表示不要跨越文件系统边界否则会把/proc、/sys、其他挂载点全算进来结果就会非常混乱。看到占用最高的几个目录后再进入下一层重复执行直到锁定具体文件。比如du -x -h --max-depth1 /var 2/dev/null | sort -h du -x -h --max-depth1 /var/log 2/dev/null | sort -h这一套组合拳打完根分区的空间分布基本就清楚了。之后该删哪个文件心里才有底。3.3 处理已删除但未释放的文件如果df -h显示使用率还是高执行一次lsof L1找到仍然持有着已删除文件的进程。注意lsof L1的输出可能有大量行最好配合sort -k7 -n按大小排序查看。根据我的经验最常出问题的是数据库、Nginx、Python 日志以及嵌入式设备上常驻的采集程序。对这类问题的处理如果你能接受短暂重启服务直接systemctl restart对应服务是最快的。不能重启的话就用 proc 文件系统将其 truncate: /proc/PID/fd/FD编号这样相当于把那个已经删除但仍被占用的文件截断为空空间立即释放。实际操作时要仔细核对 PID 和 FD 编号别把标准输出或标准错误给截了。3.4 清理完成后的验证清理不是df -h看着下降了就完事。我会再做三件事sync df -h / df -i /sync是把缓存中的脏数据写回磁盘防止显示数据和实际数据不一致。然后重新确认空间与 inode 都在健康范围。最后如果是因为日志膨胀引起的我还会检查一下 journal 配置是否已经限了大小避免过几天又满。4. 真正解决根分区空间太小从 SD 卡到虚拟机的扩容实操清理只是急救扩容才是根治。根分区不够用本质上是因为创建系统时没有给/预留出未来的增长空间。不同环境的扩容手段差异很大下面按“嵌入式开发板 SD 卡/板载存储”“VMware/QEMU 虚拟机”“LVM 逻辑卷”三个场景展开。4.1 嵌入式开发板SD 卡根文件系统分区扩展RK3568、树莓派这类开发板根分区空间太小最典型。用写卡工具烧录镜像时通常只会烧一个和镜像文件大小一致的分区不会自动扩展到整张 SD 卡。比如镜像里的根分区只有 2GB但你的卡是 32GB开机后/就是 2GB剩下的空间是未分配的。处理方式分两步。第一步用fdisk或parted调整分区表把根分区扩展到未分配区域第二步用resize2fs扩展文件系统。注意根分区本身正在被挂载不能直接在系统运行中卸载。安全做法是在开发板上下电取出 SD 卡插到另外一台 Linux 机器上操作或者使用一个独立的急救系统启动再操作 SD 卡。以fdisk为例目标是把/dev/mmcblk0p2扩展到整张卡剩余空间sudo fdisk /dev/mmcblk0 # 进入交互模式 p # 打印分区表记下根分区的起始扇区这个值不能变 d # 删除要扩展的分区注意别删错 n # 新建分区默认起始扇区必须和之前一致 p # 确认新分区 w # 写入分区表分区表改完后立即让内核重新读取分区表然后扩展文件系统sudo partprobe /dev/mmcblk0 sudo e2fsck -f /dev/mmcblk0p2 sudo resize2fs /dev/mmcblk0p2一定要先跑e2fsck再resize2fs不然文件系统元数据不干净扩展过程可能会报错。这套流程也可以套用在板载 eMMC 上只是设备名可能是/dev/mmcblk1之类的清理操作前一定先lsblk确认。4.2 虚拟机VMware / QEMU 扩展虚拟磁盘与分区虚拟机内部看到的是虚拟磁盘扩容前需要先在宿主机上把虚拟磁盘文件加大再进虚拟机系统里分区和文件系统跟着变。以前我在 VMware 上扩容过程跟 Windows 下“扩展磁盘容量”类似QEMU 环境则用 qemu-img 直接调整镜像容量。qemu-img resize ubuntu.qcow2 20G虚拟磁盘增大后进入虚拟机执行lsblk会看到磁盘整体变大了但分区还是原来的大小。此时再用growpart扩展分区。Ubuntu 系一般自带cloud-guest-utils或growpart工具sudo growpart /dev/vda 1 sudo resize2fs /dev/vda1growpart的参数分别是磁盘设备和分区号它会把该分区扩展到磁盘末尾。紧接着扩展文件系统一条resize2fs就能完成。如果是 xfs则用xfs_growfs /。4.3 LVM 根分区在线扩容服务器上很多根分区本身是 LVM 逻辑卷这种结构扩容最方便不用重启。先看逻辑卷名lvdisplay假设卷组叫ubuntu-vg逻辑卷叫ubuntu-lv把根分区从 50GB 扩到 80GB用lvextend扩展逻辑卷。前提是卷组有足够剩余空间。如果没有先加一块新物理磁盘pvcreate后vgextend加入卷组再lvextend。扩展逻辑卷后同样要扩展文件系统sudo lvextend -L 80G /dev/ubuntu-vg/ubuntu-lv sudo resize2fs /dev/ubuntu-vg/ubuntu-lv线上操作 LVM 时我最谨慎的点在于-L指定最终大小还是增量大小容易混淆。-L 80G是最终 80GB-L 30G是增加 30GB。写错一个符号结果天差地别。如果文件系统是 xfs执行的是xfs_growfs而且 xfs 只能扩大不能缩小扩容前不要动缩小的念头。4.4 云服务器与 NAS 挂载类扩容云服务器如 Ubuntu 虚拟机在公有云上的情况通常是控制台里扩了云盘但系统里根分区没感知到。这跟虚拟机原理一致但部分云平台需要先执行一条命令让内核重新读盘sudo partprobe /dev/vda或者某些云环境需要sudo growpart /dev/vda 1后使用resize2fs。另外热词里还有“linux 挂载 nas 存储 csdn”这类情况如果根分区不够是因为想要挂载 NAS 却写到本地根分区下那就得先理解NFS 挂载只是把一个远程目录挂到本地挂载点它本身不占用本地根分区磁盘空间但挂载点目录如果不存在或创建在了根分区里那只是目录占一点点空间不能解决“本地根分区分区空间小”的问题。真要让 NAS 缓解根分区空间得把大文件迁移到 NAS 目录再通过 bind mount 或者符号链接引到原来的路径上。这属于存储布局优化不是扩容。5. 挂载根时的“空间假象”NFS、bind mount 与 overlay 的边界排查过程中我发现很多人说的“挂载根的磁盘空间太小”其实并不是根分区真的满了而是对挂载机制产生了混淆。这一节把几个典型假象拆开讲。5.1 NFS 根文件系统的空间取决于服务端开发板通过 NFS 挂载根文件系统时执行df -h /显示的“Filesystem”是192.168.x.x:/path这个空间来自 NFS 服务端所在机器的磁盘。如果服务端挂载点是某个小分区那开发板的“根空间”自然就小。热词里“rk3568 nfs 根文件”指的就是这种场景。这种情况下你清理开发板上的文件毫无意义因为最终影响的是服务端那个目录。正确做法是在宿主机上确认/srv/nfs/rootfs所在磁盘分区的容量如果需要扩大直接在宿主机上扩建它所在分区如果想限制 rootfs 目录占用用quota或单独的挂载点来控制。我之前犯过的错就是疯狂清理开发板根文件系统里的文件结果宿主机根分区还是越来越满最后发现 NFS 导出目录处在宿主机的/分区里该扩容的是宿主机。5.2 bind mount 把别处的空间“挪”了过来mount --bind不会创建新的存储空间它只是把 A 路径挂到 B 路径上。比如mount --bind /data /var/lib/docker执行后/var/lib/docker里的内容在逻辑上是/data里的内容实际占用的是/data所在分区的空间。如果此时执行df -h /var/lib/docker看到的是/data所在分区的大小而不是根分区的大小。这本身是一个非常好的策略当根分区塞满容器数据时把/var/lib/docker挪到大分区去。但很多新手会误以为这是“把空间变大了”于是再往/var/lib/docker里写东西实际写进了/data。想彻底解决可以把/data放到更大分区或者干脆从根上迁移docker-root配置。5.3 overlay 根分区与宿主机空间的关系容器里的根文件系统是 overlay 挂载df -h /显示的是 overlay 层的总容量这个容量通常继承自宿主机根分区。也就是说在容器里看到“根分区空间太小”要扩容的是宿主机根分区。但如果宿主机根分区本身并不小容器里却显示空间小那就可能是 Docker 的 storage driver 配置或运行容器时指定的--storage-opt size...限制。检查 Docker daemon 配置里的存储驱动选项以及容器创建时的 size 参数就能定位。开发板场景中如果用的是podman或containerd逻辑也类似容器的可写层落在宿主机的某个目录下根空间背后就是那块磁盘。5.4 挂载点目录被一个“看不见”的分区挡着还有一种情况你想把一个新分区挂载到/mnt/data但/mnt本身在根分区上而/mnt/data这个目录创建时只占了几个 KB挂载成功后df -h /mnt/data显示的是新分区的容量。看起来没问题但如果挂载失败或者挂载到的是/mnt/data-other此时/mnt/data仍然属于根分区空间自然不够。排查时我会用mount | grep 挂载点确认挂载是否真的生效而不是只看df的结果。曾经有个朋友说根分区被/mnt/data占满了我一查发现他的/mnt/data根本是空的只是挂载新分区的操作失败后续数据全写进了根分区下的同名目录。这种情况只要调整/etc/fstab里的挂载项再重新 mount 一次就能解决问题。6. 防止根分区再次爆掉的实际经验扩容完成不代表一劳永逸如果没有配合有效的监控和日志限制下一次爆满只是时间问题。下面这些习惯是我踩过不少坑之后固定下来的。6.1 日志轮转和 journal 限额一定要配置好不要依赖系统默认。默认的 journald 虽然也有一定策略但很多时候它会累积到较大容量。建议在/etc/systemd/journald.conf里显式设置SystemMaxUse300M MaxRetentionSec7day重启后检查journalctl --disk-usage是否回落到目标值以内。对于应用日志写好/etc/logrotate.d/配置按天轮转最多保留 7 份并且compress压缩旧日志。这一条在嵌入式设备、开发板、生产服务器上都适用。6.2 根分区容量监控喂到耳朵边上根分区空间是那种“平时不看没事一旦满了立即出事”的指标。服务器上我用简单的脚本配合钉钉/邮件告警当/使用率超过 80% 时触发通知超过 90% 时执行整理操作。其实不用太复杂的工具一个 cron 加一条命令就行df -h / | awk NR2 {gsub(%,,$5); if ($5 80) print $5} | mail -s rootfs usage high adminexample.com开发板上资源紧张我用的是开机自启的一个 shell 检测每 10 分钟检查一次超过阈值就把最老的 journal 和/tmp文件清掉。虽然粗暴但很救命。6.3 给根分区以外的大目录单独分区如果系统盘只有 16GB但你预期/var或/home会长到几十 GB那从一开始就别让它们长在根分区里。SD 卡上规划分区时分一个p2给根文件系统再分一个p3挂载/data或/var让大量数据落到独立分区。这样即使数据塞满独立分区系统的/还能正常启动与登录方便救援。服务器上同理/home、/var/lib/docker、/opt这类易膨胀目录尽量独立分区或者用 LVM 单独建逻辑卷。6.4 定期检查 inode 和碎片化文件除了空间还要监控df -i。开发板上长时间跑数据采集特别容易产生海量小文件把 inode 吃光。如果确实遇到“空间很多但没法写”的怪事多半就是 inode 满了。解决方案是找出小文件堆积的目录清掉或者考虑更换文件系统类型。比如嵌入式场景改用 ext4 或 f2fs小文件场景下 inode 规划更合理。最后说一个我个人的实操体会处理根分区空间不足最忌讳的就是“头痛医头”。每次遇到这个问题我都强迫自己从分区表、日志、镜像层、inode 四个维度完整走一遍而不是随手删了某个大文件就收工。因为根分区不像数据分区它承载的是整个系统的运行底座一次的敷衍往往换来未来更贵的故障。把上面这套流程沉淀成一个检查清单等你下次在开发板上挂载根、在虚拟机里扩容、或者被生产服务器上“根分区 100%”告警吓到的时候把清单照着走一遍大概率十分钟内就能定位问题剩下的就是按需扩容。
返回列表