1. 为什么你的根分区说满就满:先搞清楚空间到底被谁吃了
做运维这些年,隔三差五就能收到“根分区使用率 98%”的告警,一开始还会紧张一下,后来发现这几乎是每台服务器走向稳定的必经之路。很多人第一反应是加磁盘、扩分区,但说实话,如果不先搞清楚空间是被谁占掉的,扩完还是会满。根目录不是一瞬间爆掉的,它的使用率通常是在日志堆积、软件缓存、临时文件这些地方一点点被蚕食的。
先看几个最常见的高危目录,你ssh上去之后可以先按这个顺序检查:
/var/log:服务日志、系统日志、nginx/access.log、mysql 慢查询,这些文件正常情况下会按天切割,但一旦某个服务疯狂写错误日志,几天就能把磁盘塞满。我见过最夸张的一次,一个 Java 应用因为死循环打异常堆栈,两天写了 120GB 日志。/tmp:很多程序会把临时文件往这里丢,比如解压中的 tar 包、上传中的临时分片、sort 命令的中间文件。程序异常退出后,这些临时文件不会被自动清理。/var/cache:apt/yum 的软件包缓存,升级过几次系统后,这个目录能到几个 GB 甚至十几个 GB。还有 pip、npm 的缓存目录,虽然默认路径在用户家目录下,但 root 用户的也在 /root 下面。/var/lib/docker:如果你跑 Docker,这个目录是重灾区。overlay2 层、悬空镜像、构建缓存,都是按 GB 计算的空间占用。/usr、/opt:有些部署脚本把 JDK、中间件、数据库数据直接放根分区,而不是单独挂数据盘,这是非常常见的“规划欠债”。/root:用户家目录里不要小看,.cache、.local、.npm、.conda这些隐藏目录,日积月累也不小。
那排查命令怎么用?我的习惯是三层递进:
先看整体格局,执行df -hT看每个挂载点的使用率和文件系统类型,重点盯“/”那一行的 Use% 和 Mounted on。
再看块设备分布,执行lsblk或lsblk -f,确认磁盘是怎么分区的、有没有 LVM、物理卷大小是多少。这一步决定你后面能走哪条扩容路线。
最后定位大文件,执行du -sh /*先把根目录下一级目录的占用列出来,找到大目录再一层层往下钻。这一步有个效率技巧:直接cd /然后再敲du -sh *还是慢?用ncdu这个工具会更直观,它可以交互式浏览目录树,按大小排序,还能直接删除垃圾文件,几秒钟就能定位到“罪魁祸首”。
如果是应急止血,先别急着扩容,把下面这几类东西清一清:
- journald 日志:执行
journalctl --vacuum-size=200M,把 systemd 日志限制在 200MB 内。这个命令比直接删文件安全,它会自动处理正在写入的日志文件。 - apt/yum 缓存:Ubuntu 执行
apt-get clean,CentOS 执行yum clean all,能释放不少空间。 - Docker 悬空镜像:
docker system prune,会清理不再使用的镜像层、容器、网络和构建缓存。注意它默认不会删正在使用的镜像,相对安全。 - core dump 文件:
find / -type f -name "core.*" -size +100M 2>/dev/null找出来直接删掉。 - 旧的系统内核:
uname -r看当前内核版本,再用dpkg --list | grep linux-image(Debian/Ubuntu)或rpm -qa | grep kernel(CentOS)列出所有内核,把旧的删掉。Ubuntu 可以使用apt-get autoremove --purge自动清理旧内核。
这些清理动作频繁做几次,你大概就能总结出自己服务器上“谁在稳定吃空间”。到了这一步,你才有资格去谈扩容——因为扩容只是在给系统“续命”,如果垃圾的源头不堵住,扩容后的空间一样会被吞掉。
2. 扩容前的“体检”:分清你是哪种磁盘布局,选错方案会出大事
不同机器根目录扩容的思路完全不一样,不是一句“执行 resize2fs”就能搞定的。动手之前,至少要确认三件事:磁盘类型是传统 MBR 还是 GPT、根分区所在磁盘上有没有空闲空间、以及核心的——文件系统是 ext4 还是 xfs。
2.1 先认清楚三种常见布局
第一种是传统分区直挂,也就是/dev/sda1直接挂载到/。这种布局下如果想要在线扩容,前提是分区后面还有未分配的空间,或者你能腾出空间来。如果是虚拟机,可以直接调大虚拟磁盘;如果空间已经全部分配完了,那就比较麻烦,需要借助空闲分区或者重建分区表。
第二种是 LVM 逻辑卷管理,/dev/mapper/centos-root挂载到/。这是大多数 CentOS/RHEL 服务器默认的安装方式。LVM 的好处是可以在不影响数据的情况下,把新加入的物理卷空间划给逻辑卷,这就是大多数人说的“扩容”的真正操作对象。国产主流 Linux 发行版和 Ubuntu Server 的默认安装,也大多会把系统根分区放到 LVM 里,区别只是卷组名和逻辑卷名不同。
第三种是云盘直挂,云服务器厂商(比如阿里云、腾讯云、华为云)的控制台操作了“在线扩容”之后,磁盘块设备本身已经有了更大容量,但在系统里还没生效,需要你执行 growpart 或 fdisk 去调整分区表,再扩展文件系统。这种情况不涉及新磁盘,也不需要 LVM,属于“物理盘变大,系统分区还没跟上”。
判断自己属于哪一种,一条命令就能解决:
lsblk输出里如果看到vda1或sda1这类直接挂根目录的,就是传统分区;如果看到类似vg-root、vg-lv_root、ubuntu--vg-ubuntu--lv这种带逻辑卷名称的,就是 LVM;如果只有vda1但你在云控制台已经执行过扩容,那就是云盘场景。
2.2 确认文件系统类型:ext4 和 xfs 的扩容命令完全不同
文件系统类型决定扩容的最后一步用什么命令,这一步出错会导致数据丢失,所以绝对不能跳过。
df -hT /输出结果的第二列会显示文件系统类型:
- 如果是
ext4,最后一步执行resize2fs /dev/xxx,可以无损扩展,而且支持在线扩容。 - 如果是
xfs,最后一步执行xfs_growfs /,注意这个命令的参数是一个挂载点,而不是设备路径,而且 xfs 只能扩大不能缩小。也就是说,如果某个 xfs 分区空间规划过头了,你想把它缩小,那基本只能备份重来。 - 如果你看到是 btrfs,情况又不一样,它自带子卷功能,一般很少通过传统分区方式去扩容,这里不展开讲。
分类讨论完之后,还有一条铁律:无论哪种情况,扩容前先确认有没有空闲空间。用fdisk -l /dev/sda或parted /dev/sda print free查看分区表,看分区后面还有没有未分配的空间。如果没有,那你就别急着动 resize,而是先想办法扩大磁盘本身(虚拟机加盘、云盘扩容),或者考虑挂载新磁盘。
注意:不要在根分区正在满负荷读写、数据库正在高峰期的时候做扩容操作。虽然 LVM 和 resize2fs 都支持在线操作,但文件系统层面的元数据变更,任何一次意外断电都可能导致数据不一致。生产环境强烈建议先做快照,或者至少选择业务低峰期操作。
2.3 我说过很多次的“扩容三不”
第一,不要直接删掉一个分区再重建,哪怕你觉得那个分区没用。分区表操作一旦失误,数据直接找不回来。第二,不要在根分区上执行mkfs,那等于格式化,我见过太多新手把/dev/sda1和/dev/vda1敲反,结果整个系统起不来。第三,不要迷信网上的“一键扩容脚本”,跟自己当前系统内核、文件系统版本、分区表格式都对不上,容易翻车。
3. 虚拟机场景扩容:从虚拟磁盘到文件系统的完整链路
虚拟机和云服务器的扩容操作有相似之处,也有明显的差异。这里我以 KVM/libvirt 虚拟机和 VirtualBox 为例,把从“虚拟磁盘变大”到“系统内生效”的全链路走一遍。
3.1 给虚拟磁盘增加容量:两种常用方式
KVM 的磁盘文件常见格式是 qcow2,可以离线扩容。先关机(必须关机),然后在宿主机上执行:
qemu-img resize /var/lib/libvirt/images/your-disk.qcow2 +50G这个命令就是把虚拟磁盘镜像的容量增加 50G。操作结束后,你用qemu-img info /var/lib/libvirt/images/your-disk.qcow2可以看到虚拟磁盘容量已经变大了。qcow2 是稀疏存储的,扩容后宿主机的物理磁盘不会立刻被占满 50G,只有虚拟机里真正写入数据才会逐步占用,这点不用担心。
VirtualBox 的命令则是:
VBoxManage modifymedium disk "/path/to/your-disk.vdi" --resize 120000注意这个参数单位是 MB,--resize 120000表示把磁盘调整到 120GB。如果虚拟机里已经用了 80GB,想扩到 100GB,那就填 100000 而不是 20000。填错了只会让你多出很多未分配空间,不会损坏数据,但最好还是算清楚。
还有一种更“物理”的方式:直接给虚拟机增加一块新虚拟磁盘。这种方式不需要改原盘的镜像文件,启动之后按新磁盘来挂载、初始化。如果你的目的是给/扩容,那要按“新磁盘加入 LVM 卷组”的路线操作,后面会说。
3.2 开机后识别新空间
磁盘镜像扩容完成后,启动虚拟机,在系统里执行lsblk,你会发现磁盘设备(如/dev/vda)显示的总容量已经变大,但分区(如/dev/vda1)还保持原来的大小。这时操作系统已经看到了“大磁盘”,但分区表和文件系统还没有跟上。
这一步非常关键:分区表操作前,建议先备份分区表信息:
sfdisk -d /dev/vda > /root/partition-table-backup.txt保存这份备份主要是为了出问题时能快速恢复分区布局。然后检查分区表类型:
fdisk -l /dev/vda如果输出里看到 “Disk label type: gpt”,说明是 GPT 分区表,扩容时优先用growpart自动处理,比手工用 fdisk 删了重建要安全得多。如果是 “dos”,那就是经典的 MBR 分区表,也可以用 growpart 处理。
3.3 分区扩展:growpart 的最佳实践
growpart 是一个专门用来扩展分区大小的工具,它比手动画分区表更安全,因为它会保留原有分区的起始位置不变,只扩展结束位置,数据不移动、不出错。
yum install -y cloud-utils-growpart # 或 apt-get install -y cloud-guest-utils安装完成后,确认根分区所在磁盘和分区号。比如根分区是/dev/vda1,那命令就是:
growpart /dev/vda 1注意这里不是写/dev/vda1,而是写成磁盘设备加空格加分区号。执行成功后,再看lsblk,vda1的大小已经延伸到磁盘末尾了。
有些老版本内核或者云镜像系统里没有 growpart,此时可以手动用 parted 处理:
parted /dev/vda print free resizepart 1 100% quit这个操作同样只调整分区终点,不触碰分区起点。执行之后立刻同步分区表:
partprobe /dev/vda注意:有些虚拟机磁盘驱动(比如 virtio-blk)支持直接在线重新读取分区表,但如果是老型号的 IDE 或 SATA 控制器,
partprobe可能不生效,会提示 “Re-reading the partition table failed”。这种只能重启虚拟机,没有别的安全办法。重启前确保把扩容命令准备好,开机后在第一时间把文件系统扩展命令执行掉。
3.4 最后一步:扩展文件系统,区分 ext4 和 xfs
分区变大之后,文件系统还停留在原来的大小,不扩展的话,df -h看到的使用率不会有任何变化。继续执行文件系统扩展命令:
ext4 文件系统:
resize2fs /dev/vda1xfs 文件系统:
xfs_growfs /两个命令的区别要记住:resize2fs 针对的是设备路径,你在/etc/fstab里怎么写就填哪个设备;xfs_growfs 针对的是挂载点,直接写/就行,它会自动找到对应的设备。执行完,用df -hT /验证,根分区容量已经变大。
如果你是用 LVM 装系统的虚拟机,那上面的操作前半段是一样的——先扩大虚拟磁盘,再 growpart 扩展分区。但后半段要对准逻辑卷来操作:
pvresize /dev/vda1 lvextend -l +100%FREE /dev/mapper/centos-root resize2fs /dev/mapper/centos-root # 如果是 xfs: # xfs_growfs /这套命令顺序不能乱:先让物理卷识别新空间,再把空闲空间全部划给逻辑卷,最后扩展文件系统。我见过有人先 lvextend 再 pvresize,结果 lvextend 提示没有足够空闲空间,然后又回头折腾 pvresize,虽然最终能补回来,但白白绕了一圈。
4. 云服务器在线扩容:控制台点完按钮,系统内还要做这几步
云服务器的扩容走的是另一条路线,因为云盘的控制权在厂商控制台里,不在你的系统里。阿里云、腾讯云、华为云虽然界面不同,但逻辑都一样:先在控制台执行“磁盘扩容”,然后在系统内让分区和文件系统感知到新容量。
4.1 控制台扩容后的第一件事
在控制台完成扩容后,回到系统里先执行:
lsblk你会看到磁盘整体容量已经变大了,比如原来 40G 的/dev/vda现在是 100G,但/dev/vda1还是 40G。如果lsblk看到的磁盘容量还是原来的值,说明云盘在系统层还没被重新识别,可以尝试:
echo 1 > /sys/class/block/vda/device/rescan或者对大多数虚拟化平台:
partprobe /dev/vda如果都不生效,那就只能重启实例。好在云服务器重启很快,代价不大。
4.2 分区扩容:两种云盘类型,两种处理方式
云服务器的系统盘绝大多数是 GPT 分区表,而且云厂商通常会预装 growpart 工具。确认根分区是哪个:
df -hT /假设输出显示根是/dev/vda1,执行:
growpart /dev/vda 1执行后立刻验证:
lsblk /dev/vda如果看到分区大小已经变为 100G,说明分区表更新成功。极少数情况下,你看到分区的 SIZE 列已经变大,但cat /sys/class/block/vda1/size还是原来的块数,那可能是分区表信息没有刷新,重启一般能解决。
4.3 文件系统扩展,缺一不可
这一步和虚拟机场景一样,按文件系统类型选择命令。唯一需要注意的是,有些云厂商的系统盘默认是 xfs(比如新版 CentOS、阿里云 Alibaba Cloud Linux),有些是 ext4(比如 Ubuntu、Debian)。执行完文件系统扩展后,df -hT /看到的容量就更新了。
为了降低风险,云服务器扩容前建议打一个“磁盘快照”。别觉得麻烦,快照几分钟就能创建好,却能让你在操作失误时一键回滚,成本极低,收益极高。
4.4 扩容后如果新空间没生效,查这几个地方
df -hT没有变化:检查文件系统扩展命令是否执行成功,可能是命令报错了但你没仔细看输出。- 确认
lvextend是否已经把空间划给根逻辑卷,可能卷组里有空闲空间,但逻辑卷没扩展。 - 确认是否格式化过(千万别):如果你不小心对新分区执行了
mkfs,数据全部清空,只能靠快照恢复。 - 确认是多路径盘还是直通盘:有些云盘在多路径环境下,块设备名不是
/dev/vda,而是/dev/mapper/mpatha,扩容方式完全不同,建议咨询云厂商文档。
5. 非 LVM 且无剩余空间的另一种解法:目录迁移与软链接
有时候根目录所在的磁盘在物理上和分区表层面都已经是“满”了,没有未分配空间可以扩,也没有云控制台可以点。这种情况下,最稳的方案不是强行改分区表,而是把大目录迁移到新磁盘或新分区上,用软链接指回去。这个方案安全、可逆、不需要动根分区,非常适合那些“根分区规划太小但没法在线扩”的老机器。
举个例子,根目录下/home或者/var/lib/mysql占满了根分区,而系统里刚好加了一块新盘/dev/sdb。操作流程:
- 格式化新盘(确认盘符不会写错):
mkfs.ext4 /dev/sdb1- 挂载新盘到临时目录:
mkdir /mnt/newhome mount /dev/sdb1 /mnt/newhome- 同步数据:
rsync -avH /home/ /mnt/newhome/用 rsync 而不是 cp,是因为 rsync 能保留文件属性、硬链接、时空戳,而且可以断点续传。数据同步完成后,校验一下数据完整性:
diff -r /home/ /mnt/newhome/- 把原目录改名做备份,然后挂载新盘到原路径:
mv /home /home.bak mkdir /home mount /dev/sdb1 /home- 验证一切正常后,写入开机自动挂载:
echo "/dev/sdb1 /home ext4 defaults 0 0" >> /etc/fstab- 确认无误后删除旧的
/home.bak,或者先保留几天再清理。
这种方式对很多人来说比扩容根分区更实用:不需要动分区表,不会中途停电翻车,风险集中在数据拷贝阶段,而数据拷贝本身是可以反复校验的。
6. 我踩过的一些坑:扩容时最容易翻车的几个细节
6.1 xfs 只能扩大不能缩小
这是文件系统层面的硬限制,不是 Linux 的 bug。所以在规划分区时就要想清楚:xfs 的根分区给太大,空间浪费;给太小,后面想缩回来几乎不可能。我的建议是,如果用 xfs 做根分区,初始容量最少给 50G,云服务器用户可以直接给 80G 甚至 100G。很多人问“我 xfs 根分区能不能缩小”,答案是不能,除非你备份重做分区。这是踩过坑之后最痛的教训。
6.2 扩容顺序千万别搞反
LVM 扩容的正确顺序永远是:物理卷(pvresize)→ 卷组(自动识别)→ 逻辑卷(lvextend)→ 文件系统(resize2fs/xfs_growfs)。倒过来执行几乎都会提示找不到空间或没有多余空间。网上有些教程把顺序写得模棱两可,跟着实操很容易失败,建议以这个顺序为准则。
6.3 分区表操作前先备份
虽然 growpart 很安全,但你永远不知道手滑会敲出什么命令。在改动分区表前,花十秒钟跑一下sfdisk -d或者parted ... print free留底,必要的时候甚至可以给云盘打个快照。我见过不少同行在分区表操作时翻车,最终只能靠备份恢复,所以这个习惯真的值得养成。
6.4 resizepart 之后一定刷新分区表
不管是 growpart 还是 parted resizepart,操作完都要刷新分区表。部分环境中这个刷新会自动完成,但有些环境不会。如果lsblk显示分区大小已经变了,直接执行文件系统扩展命令即可;如果没变,一定要partprobe或重启,千万不要直接去执行resize2fs,那会得到 “Couldn't find valid filesystem superblock” 之类的报错。
6.5 docker 目录疯狂膨胀
如果是 Docker 服务导致的根分区爆满,最简单的方式是给 docker 的默认存储目录单独挂一个数据盘。你可以通过配置/etc/docker/daemon.json中的>df -hT / lsblk
确认根分区容量已更新、文件系统挂载正常。然后,把mount -a执行一遍,确认/etc/fstab里的挂载项没有语法错误。如果是 LVM 扩容,确认vgs、lvs输出正常,卷组里不要遗留奇怪的“unknown device”。
到这里,扩容操作本身已经完成了。但这段经历真正值得沉淀的是:下次你装机或重装系统时,记得给根分区留足余量,数据目录单独挂数据盘,日志目录单独规划大小,不要把所有鸡蛋放在一个筐里。扩容的“术”很简单,难的是对空间规划的“道”。把这次遇到的情况记录下来,下次告警再来时,你就能从容应对了。