1. 从一次告警说起:为什么容器主机的根分区总是不够用
凌晨两点,监控面板上那条代表磁盘使用率的曲线又一次顶到了95%的红线。登录到宿主机上一看,df -h的输出里/dev/mapper/centos-root这个挂载点已经吃掉了将近全部空间,而 Docker 的镜像层、容器可写层、日志文件还在源源不断地往里写。这种场景对跑容器的人来说几乎算是"必修课"——你以为给虚拟机分了200G很宽裕,结果半年不到就被镜像和日志啃得干干净净。
这篇文章要聊的就是这个具体问题:在 CentOS 系的宿主机上,当 Docker 把根分区撑爆之后,怎么给/dev/mapper/centos-root做在线扩容。它不是什么高深技术,但涉及 LVM 逻辑卷、文件系统、分区表这几层概念,任何一层搞错顺序都可能把数据搞没。适合两类人看:一类是刚接手容器运维、第一次遇到根分区告警的新手;另一类是知道大概流程但每次操作都心里发虚、想彻底搞明白"为什么是这个顺序"的从业者。
我先把结论摆出来,方便你对照自己的情况:如果卷组(VG)里还有剩余空间,扩容就是三步——扩逻辑卷、扩文件系统、验证,全程在线不用重启;如果卷组也没空间了,那得先扩物理卷所在的分区或加新磁盘,这一步才是真正容易出事的地方。下面我会把这两种情况拆开讲透,包括每一步背后的原理、命令的每个参数在干什么、以及我自己踩过的几个坑。
在动手之前,有一个前置动作必须做,而且我要把它放在最前面强调:先确认你的文件系统类型。CentOS 7 默认是 XFS,CentOS 6 默认是 ext4,这两者的扩容命令完全不同,XFS 只能扩不能缩,ext4 可扩可缩。用df -T或者lsblk -f一眼就能看出来。我见过有人照着 ext4 的教程去操作 XFS,resize2fs直接报错,还好没造成损坏,但白白折腾了半小时。
提示:任何磁盘操作之前,哪怕是在线扩容这种相对安全的操作,也请先对关键数据做一次快照或备份。LVM 本身很稳,但"稳"不等于"零风险",尤其是涉及分区表修改的时候。
2. 动手前必须搞懂的 LVM 三层结构
很多人扩容失败或者不敢操作,根源在于没理清 LVM 的分层关系。我用一个生活化的类比来讲:把 LVM 想象成一套"积木收纳系统"。
最底层是物理卷(PV,Physical Volume),它可以是整块硬盘,也可以是硬盘上的一个分区。这相当于你手里的一块块积木。往上一层是卷组(VG,Volume Group),它把多个物理卷"合并"成一个大池子,相当于把所有积木倒进一个大收纳箱。最上层是逻辑卷(LV,Logical Volume),它从卷组这个池子里切出一块来用,相当于你从收纳箱里取出一些积木搭成一个具体的造型——/dev/mapper/centos-root就是这么一个逻辑卷。
理解了这个结构,扩容的逻辑就顺理成章了:你要给 root 扩容,本质上是要从卷组这个"池子"里再切一块空间给 root 这个逻辑卷。所以第一步永远是问一句:池子里还有没有剩的空间?
用vgs命令就能看到卷组的剩余空间,输出里的VFree那一列就是答案。如果VFree大于0,恭喜你,扩容非常简单,直接跳到第3节。如果VFree是0,说明池子也空了,你得先往池子里加积木——要么扩底层分区,要么加新硬盘,这就是第4节的内容。
这里有个细节值得说清楚:/dev/mapper/centos-root这个路径里的centos是卷组名,root是逻辑卷名。不同系统上卷组名可能不一样,有的叫cl(CentOS 8 默认),有的叫vg00之类。所以你在敲命令前,先用vgs和lvs确认一下自己系统上的实际名字,别照抄教程里的centos。我见过有人直接复制粘贴,结果报"volume group not found",还以为是命令错了。
另外补充一个判断依据:用lsblk看整个块设备树,能直观地看到磁盘、分区、LVM 之间的父子关系。比如你会看到sda下面有sda2,sda2下面是centos-root和centos-swap。这个树状图对理解"空间从哪来、往哪去"特别有帮助,建议每次操作前都看一眼。
3. 卷组还有余量时的在线扩容全流程
这是最常见也最简单的情况。假设vgs显示VFree还有 50G,你想把这 50G 全给 root,操作分三步走。
3.1 第一步:扩展逻辑卷本身
命令是lvextend。有两种写法,一种是指定要增加多少,一种是指定扩到多大。我个人更推荐"增加多少"的写法,因为不容易算错:
lvextend -L +50G /dev/mapper/centos-root如果你想"把卷组剩余空间全部用上",可以用-l +100%FREE:
lvextend -l +100%FREE /dev/mapper/centos-root这里的-L后面跟的是绝对大小(+50G 表示增加50G),-l后面跟的是逻辑扩展单元(LE)数量,+100%FREE是个百分比写法,意思是把剩余空间全拿走。这两个参数别混用,-L和-l是互斥的。
执行完这一步,逻辑卷的"容器"变大了,但文件系统还没变大。这是新手最容易困惑的点:为什么lvextend成功了,df -h看还是原来的大小?因为逻辑卷和文件系统是两个层次的东西,逻辑卷是"地",文件系统是"地上盖的房子",你只是把地扩了,房子还没跟着扩。
3.2 第二步:扩展文件系统
这一步要分文件系统类型。先确认类型:
df -T /dev/mapper/centos-root如果是 XFS,用xfs_growfs,注意它接的是挂载点而不是设备路径:
xfs_growfs /如果是 ext4,用resize2fs,它接的是设备路径:
resize2fs /dev/mapper/centos-root这两个命令的参数差异是很多人踩的坑。xfs_growfs后面跟/(挂载点),resize2fs后面跟/dev/mapper/centos-root(设备)。记混了就会报错。我自己的记忆方法是:XFS 的growfs里带个fs,它关心的是"文件系统挂在哪",所以给挂载点;resize2fs关心的是"哪个设备",所以给设备路径。
好消息是,这两个命令都支持在线扩容,不需要卸载文件系统,不需要重启,业务无感知。这也是 LVM + XFS/ext4 组合在生产环境受欢迎的核心原因。
3.3 第三步:验证结果
扩容完一定要验证,别嫌麻烦:
df -h / lvsdf -h看文件系统层面是否真的变大了,lvs看逻辑卷层面的大小。两个数字应该对得上。如果df没变,八成是第二步命令用错了或者没执行。
注意:
xfs_growfs默认会把文件系统扩到逻辑卷的当前大小,不需要指定目标大小。如果你只想扩一部分,得用-D参数指定块数,但生产上几乎没人这么干,都是直接扩满。
4. 卷组也满了:扩分区还是加硬盘
如果vgs显示VFree为0,说明卷组这个池子也空了。这时候有两条路:扩底层分区(如果分区后面还有未分配空间)或者加新硬盘。前者风险高,后者更稳妥。
4.1 先判断底层分区后面有没有空闲空间
用fdisk -l /dev/sda看分区表。重点看sda2的结束扇区是不是紧挨着磁盘末尾。如果sda2后面还有未分配的空闲扇区,理论上可以扩分区。但这里有个大坑:如果sda2是 LVM 分区,扩它需要先删掉再重建分区表项,而删除分区这个动作本身有风险。
具体来说,流程是:fdisk /dev/sda进入交互,用d删除sda2,再用n新建一个分区,起始扇区必须和原来完全一致,结束扇区设到磁盘末尾,最后把分区类型改回8e(Linux LVM)。这个操作只要起始扇区填错一位,整个 LVM 就废了。所以我一般不建议新手在生产环境这么干,除非你确认有完整备份。
扩完分区后,还要让 LVM 重新识别:pvresize /dev/sda2,然后vgs就能看到VFree有空间了,接着回到第3节的流程。
4.2 加新硬盘是更稳的选择
如果宿主机还能挂新盘(虚拟化环境直接加一块虚拟磁盘就行),这是最省心的方案。假设新盘是/dev/sdb:
pvcreate /dev/sdb vgextend centos /dev/sdb第一行把新盘初始化成物理卷,第二行把它加入centos这个卷组。执行完vgs就能看到卷组变大了,VFree有空间了。然后照旧走lvextend+xfs_growfs的流程。
这个方案的好处是全程不碰现有分区表,风险极低。坏处是需要有物理或虚拟磁盘资源。在云主机上,很多平台支持在线扩容云盘,扩完之后在系统里用growpart把分区撑大,再pvresize,也能达到同样效果。
4.3 两种方案的风险对比
| 方案 | 操作复杂度 | 风险等级 | 是否需要停机 | 适用场景 |
|---|---|---|---|---|
| 扩底层分区 | 高 | 高 | 否(但风险大) | 分区后有连续空闲空间且无备份条件 |
| 加新硬盘 | 低 | 低 | 否 | 有可用磁盘资源,推荐首选 |
| 云盘在线扩容 | 中 | 低 | 否 | 云主机环境 |
我个人的排序是:加新盘 > 云盘扩容 > 扩分区。扩分区永远是最后选项,因为它对操作精度要求太高,一个扇区填错就是灾难。
5. 那些教程不会告诉你的坑
这一节是我这些年实际操作中攒下来的经验,很多是文档里不会写的。
第一个坑:lvextend成功但df没变,别慌。这是正常的,因为你只扩了逻辑卷没扩文件系统。新手经常在这里以为操作失败了,然后反复执行lvextend,结果把空间重复分配。记住:lvextend和xfs_growfs(或resize2fs)是两步,缺一不可。
第二个坑:XFS 不能缩小。如果你哪天想给 root 缩容,XFS 是做不到的,只能备份数据、重建文件系统。所以扩容时别一次性扩太猛,虽然扩多了不浪费(LVM 层面可以再调整),但 XFS 的"只增不减"特性意味着你扩出去的空间很难收回来。ext4 虽然支持缩容,但也必须先卸载文件系统,在线缩容是不存在的。
第三个坑:/dev/mapper/centos-root和/dev/centos/root是同一个东西。前者是设备映射器路径,后者是 LVM 的符号链接路径,指向同一个逻辑卷。用哪个都行,但脚本里建议统一用/dev/mapper/开头的,更直观。
第四个坑:Docker 的日志和镜像才是空间杀手。扩容只是治标,治本要配置 Docker 的日志轮转。默认情况下,容器的 json 日志文件会无限增长,一个跑久了的容器日志能吃掉几十G。在/etc/docker/daemon.json里加上:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }这样每个容器日志最多占 300M,能省下大量空间。改完要重启 Docker 服务,且只对新创建的容器生效,老容器得重建。
第五个坑:pvresize之后vgs没变化。有时候扩完分区执行pvresize,vgs里的VFree还是0。这通常是因为分区表变了但内核没重新读取。用partprobe /dev/sda强制内核重读分区表,再执行pvresize就好了。
第六个坑:操作顺序不能乱。正确的顺序永远是:先扩底层(分区/物理卷)→ 再扩卷组 → 再扩逻辑卷 → 最后扩文件系统。从下往上,一层一层来。反过来操作必然失败。这个顺序背后的逻辑是:上层空间来自下层,下层没空间上层就无从谈起。
6. 一套可以抄作业的完整命令清单
最后我把整个流程整理成可以直接复制的命令序列,分两种场景。执行前请把centos和/dev/sda2替换成你自己系统上的实际名称,用vgs和lsblk确认。
场景A:卷组有余量(最常见)
# 1. 确认文件系统类型和卷组余量 df -T / vgs # 2. 扩展逻辑卷(增加50G,或全用剩余空间) lvextend -L +50G /dev/mapper/centos-root # 或 lvextend -l +100%FREE /dev/mapper/centos-root # 3. 扩展文件系统(二选一,看类型) xfs_growfs / # XFS resize2fs /dev/mapper/centos-root # ext4 # 4. 验证 df -h / lvs场景B:卷组也满了,加新硬盘
# 1. 确认新盘 lsblk # 2. 创建物理卷并加入卷组 pvcreate /dev/sdb vgextend centos /dev/sdb # 3. 确认卷组已扩大 vgs # 4. 后续同场景A的第2、3、4步 lvextend -l +100%FREE /dev/mapper/centos-root xfs_growfs / df -h /场景C:扩底层分区(高风险,谨慎)
# 1. 查看分区表和空闲空间 fdisk -l /dev/sda # 2. 交互式操作:d删除sda2,n新建(起始扇区必须一致),t改类型为8e,w保存 fdisk /dev/sda # 3. 让内核重读分区表 partprobe /dev/sda # 4. 扩展物理卷 pvresize /dev/sda2 # 5. 后续同场景A提示:场景C的第2步是整个流程里唯一可能造成数据丢失的操作。如果你对
fdisk交互不熟,强烈建议先用一台测试机练手,或者直接选场景B。生产环境里,能用加盘解决的就别动分区表。
我个人在实际操作中的体会是,LVM 这套东西设计得其实很优雅,扩容本身并不难,难的是搞清楚当前处于哪一层、哪一层还有空间。把vgs、lvs、lsblk、df -T这四个命令用熟,每次操作前先看一眼,基本就不会出问题。真正需要警惕的永远是分区表那一层,其他步骤都是可以放心在线执行的。另外,扩容只是应急,把 Docker 日志轮转和镜像清理做成定期任务,才是让根分区不再告警的根本办法。