Linux 基础体系 · 第 14/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 块设备与存储:分区、LVM、RAID、挂载和在线扩容

在 Linux 中,“磁盘容量变大”并不等于“应用马上可以使用更多空间”。存储通常由多个层次组成:

物理磁盘或虚拟磁盘
        │
        ▼
分区表与分区
        │
        ▼
软件 RAID 或硬件 RAID
        │
        ▼
LVM:PV → VG → LV
        │
        ▼
文件系统:ext4、XFS 等
        │
        ▼
挂载点与目录树
        │
        ▼
应用通过路径读写文件

每一层都有自己的容量、元数据、状态和扩容方式。扩容必须沿着数据路径逐层完成;只扩大底层设备而不扩大上层对象,文件系统和应用仍然只能看到原来的容量。

本文使用现代主流 Linux 发行版中的常见工具,例如 lsblkblkidpartedmdadmlvm2mountresize2fsxfs_growfs。命令的默认行为、包名和某些选项会因 Debian、Ubuntu、RHEL、Rocky Linux、AlmaLinux、SUSE 等发行版而不同。涉及真实磁盘的命令具有破坏性,示例中的 /dev/sdX/dev/nvme0n1 等必须替换为经过确认的设备,不能机械复制。


一、块设备是什么

1.1 块设备与字符设备

块设备是以固定大小的块为基本单位进行读写的设备。磁盘、SSD、虚拟磁盘和软件 RAID 设备通常属于块设备。

字符设备则通常按字节流访问,例如终端、串口和某些特殊设备。二者在 /dev 中都表现为设备文件,但内核处理路径不同。

查看设备类型:

ls -l /dev/sda /dev/tty

典型输出的第一个字符可能是:

brw-rw---- 1 root disk ... /dev/sda
crw-rw-rw- 1 root tty  ... /dev/tty

b 表示块设备,c 表示字符设备。

设备文件本身不是磁盘数据的副本,而是一个供用户态程序引用内核设备对象的入口。设备文件中的主设备号和次设备号用于定位内核中的驱动和具体设备。

1.2 设备、分区与文件系统不是同一个概念

以下对象经常被混淆:

  • /dev/sda:整块磁盘设备。
  • /dev/sda1:第一分区。
  • /dev/mapper/vg-data:LVM 逻辑卷映射设备。
  • /dev/md0:Linux 软件 RAID 设备。
  • ext4XFS:写在某个块设备上的文件系统。
  • /data:文件系统挂载到目录树后的访问路径。

例如,下面的关系可能成立:

/dev/sdb
└── /dev/sdb1
    └── LVM PV
        └── VG vgdata
            └── LV lvdata
                └── XFS
                    └── 挂载到 /data

/data 是目录,不是设备;/dev/mapper/vgdata-lvdata 是设备,不是文件系统;文件系统负责把块编号组织成目录、文件、索引节点和日志。

1.3 内核如何把一次文件读操作变成设备 I/O

应用执行:

read("/data/a.log")

大致经过以下路径:

  1. VFS 根据进程的挂载命名空间和目录项缓存,解析 /data/a.log
  2. 具体文件系统找到文件对应的逻辑块。
  3. 文件系统将逻辑块转换为块设备上的扇区范围。
  4. 块层合并、排序或调度请求。
  5. 设备映射层可能继续转换请求,例如:
    • LVM 将逻辑卷偏移转换为 PV 上的物理区段;
    • md 将 RAID 逻辑条带转换为多个成员设备上的读写;
    • 加密层将请求交给底层设备。
  6. 驱动通过 NVMe、SCSI、virtio 等协议访问硬件或虚拟设备。
  7. 数据返回后,文件系统、页缓存和应用逐层处理。

因此,文件系统看到的“设备容量”来自它直接所在的块设备,而不是机器中所有磁盘的总容量。


二、识别设备、分区和挂载关系

在修改存储之前,第一步不是执行格式化命令,而是确认当前拓扑。

lsblk -e7 -o NAME,PATH,TYPE,SIZE,FSTYPE,FSVER,LABEL,UUID,MOUNTPOINTS

常见输出类似:

NAME              PATH                     TYPE  SIZE FSTYPE      UUID                                 MOUNTPOINTS
sda               /dev/sda                 disk  100G
├─sda1            /dev/sda1                part    1G xfs         ...                                  /boot
└─sda2            /dev/sda2                part   99G LVM2_member ...
  ├─vgroot-root   /dev/mapper/vgroot-root  lvm    40G xfs         ...                                  /
  └─vgroot-var    /dev/mapper/vgroot-var   lvm    20G xfs         ...                                  /var
sdb               /dev/sdb                 disk  500G
└─sdb1            /dev/sdb1                part  500G linux_raid_member ...
  └─md0           /dev/md0                 raid1  500G LVM2_member ...
    └─vgdata-lv    /dev/mapper/vgdata-lv    lvm   400G ext4        ...                                  /data

关键字段含义如下:

  • TYPEdiskpartraid1lvm 等。
  • FSTYPE:文件系统类型或底层成员类型。
  • UUID:文件系统 UUID 或分区、RAID、LVM 元数据中的标识。
  • MOUNTPOINTS:设备当前被挂载到哪些路径。
  • LABEL:文件系统标签,适合人类识别,但必须保持唯一。

检查文件系统和 UUID:

blkid
findmnt
findmnt -T /data

findmnt -T /data 会回答“访问 /data 时实际落在哪个挂载点和设备上”。这比只看目录名更可靠,因为目录可能位于另一个挂载点的下面,也可能被 bind mount 或容器挂载覆盖。

查看块设备的内核信息:

cat /sys/block/sda/queue/logical_block_size
cat /sys/block/sda/queue/physical_block_size
cat /sys/block/sda/queue/discard_max_bytes

逻辑扇区大小是内核寻址使用的单位,物理扇区大小反映设备更适合的写入粒度。现代设备常见 512e 或 4Kn 形态。分区起始位置不对齐时,可能导致一次上层 I/O 跨越更多物理块,产生额外读改写。


三、分区:在一块磁盘上划分独立的扇区范围

3.1 分区表的作用

分区表记录磁盘上若干段连续扇区的起始位置、结束位置、类型和属性。它不等于文件系统,也不负责保存目录和文件。

两类常见分区表:

  • MBR,也称 DOS 分区表:
    • 结构较老;
    • 主分区数量有限;
    • 传统地址模式通常受约 2 TiB 磁盘容量限制;
    • 可通过扩展分区容纳更多逻辑分区。
  • GPT:
    • 现代系统的常见选择;
    • 支持大量分区;
    • 对大容量磁盘更合适;
    • 通常与 UEFI 启动配合,但 GPT 本身不等于 UEFI。

查看分区表:

sudo parted /dev/sdb print
sudo fdisk -l /dev/sdb

典型 parted 输出包含:

Model: ...
Disk /dev/sdb: 500GB
Sector size (logical/physical): 512B/4096B
Partition Table: gpt
Number  Start   End     Size    File system  Name  Flags
 1      1049kB  500GB   500GB                data

分区类型标识主要用于告诉操作系统或管理工具该分区的用途,例如 Linux filesystem、Linux LVM、Linux RAID。类型标识本身不会把分区变成 LVM 或 RAID;真正的 LVM、RAID 元数据仍需由 pvcreatemdadm 写入。

3.2 分区边界和对齐

分区的基本条件是:

0 ≤ 起始扇区 < 结束扇区 < 磁盘总扇区数

为了避免物理设备的读改写,通常让分区起始位置按 1 MiB 对齐。若逻辑扇区是 512 字节,则:

1 MiB / 512 B = 2048 个逻辑扇区

所以常见起始扇区是 2048 或其倍数。

这只是对齐建议,不是所有设备的硬性规范。现代 partedfdisk 通常会给出合理的默认起点,但从旧系统迁移而来的分区仍应检查。

3.3 创建分区的安全方式

下面示例假设 /dev/sdb 是一块新磁盘,磁盘上没有需要保留的数据:

sudo parted --script /dev/sdb \
  mklabel gpt \
  mkpart primary 1MiB 100%

sudo partprobe /dev/sdb
lsblk /dev/sdb

每一步的含义:

  1. mklabel gpt 创建新的 GPT,会覆盖原有分区表
  2. mkpart 创建从 1 MiB 到磁盘末尾的分区。
  3. partprobe 请求内核重新读取分区表。
  4. lsblk 验证内核是否出现了 /dev/sdb1

如果设备正在使用,内核可能拒绝重新读取分区表,或者分区设备节点暂时未更新。此时可检查:

dmesg | tail -n 50
udevadm settle
lsblk

不要仅凭分区工具显示“成功”就继续写入上层元数据;必须确认内核实际看到了新边界。

3.4 分区扩容不是文件系统扩容

假设原先 /dev/sdb1 只有 100 GiB,后来底层磁盘变成 200 GiB。将分区末尾扩大到 200 GiB 只完成了第一层:

磁盘:200 GiB
分区:200 GiB
文件系统:仍可能只有 100 GiB

如果分区中是普通文件系统,还需要执行对应文件系统的扩容命令。如果分区中是 LVM PV,还需要执行 pvresize。如果分区中是 RAID 成员,还要先处理 RAID 阵列。


四、文件系统:把块组织成文件和目录

分区、RAID 和 LVM 都只提供某种形式的块地址空间。只有文件系统才能定义目录、文件、权限、索引节点、空闲空间和一致性规则。

4.1 创建文件系统

例如,在已经确认为空的逻辑卷上创建 XFS:

sudo mkfs.xfs /dev/vgdata/lvdata

在 ext4 上创建文件系统:

sudo mkfs.ext4 /dev/vgdata/lvdata

mkfs 会写入超级块、空闲空间管理结构等元数据。对已有文件系统执行 mkfs 通常会使原有数据不可用,除非明确知道自己在做什么,否则不能把它当作“初始化挂载”。

查看文件系统参数:

sudo tune2fs -l /dev/vgdata/lvdata        # ext4
sudo xfs_info /dev/vgdata/lvdata          # XFS,某些信息也可在挂载后查看

4.2 ext4 与 XFS 的扩容边界

常见关系如下:

文件系统 在线扩大 在线缩小 典型工具
ext4 支持 不支持,通常需卸载 resize2fs
XFS 支持 不支持 xfs_growfs

“在线”指文件系统已经挂载并被使用时完成扩大。在线扩容仍需先扩大文件系统所在的块设备。

ext4 扩大示例:

sudo resize2fs /dev/vgdata/lvdata

如果没有指定大小,resize2fs 会尝试把文件系统扩大到设备当前可见的最大空间。

XFS 扩大示例:

sudo xfs_growfs /data

XFS 命令通常以挂载点作为目标,因为它通过已挂载的文件系统接口执行扩容。

验证:

df -hT /data
lsblk -f

lsblk 看到的 SIZE 是块设备容量,df 看到的是文件系统可用空间。二者不一致并不一定是错误:文件系统需要保存元数据,部分空间可能被预留、分配给日志或快照结构。

4.3 文件系统扩大与缩小的非对称性

扩大通常只需要向空闲块范围延伸元数据和空间管理结构,因此 ext4 和 XFS 可以在满足条件时在线完成。

缩小则需要先确认文件系统末端没有活动数据,并移动或重排这些数据。XFS 没有通用的原地缩小操作;ext4 通常需要:

  1. 卸载文件系统;
  2. 使用 e2fsck 检查;
  3. resize2fs 缩小文件系统;
  4. 再缩小底层 LV 或分区;
  5. 重新挂载并验证。

顺序不能反过来。若先缩小 LV,再让文件系统访问原来的末端,文件系统会访问越界块,可能造成严重损坏。


五、挂载:把文件系统接入目录树

5.1 挂载点不是“磁盘目录”

Linux 使用统一目录树。挂载操作将一个文件系统的根目录附加到已有目录,例如:

sudo mkdir -p /data
sudo mount /dev/vgdata/lvdata /data

挂载前,/data 只是根文件系统中的普通目录;挂载后,通过 /data 访问的是 lvdata 文件系统。挂载点原来目录中的内容不会被删除,但会被挂载覆盖,直到卸载后才重新可见。

查看挂载关系:

findmnt /data
mount | grep ' /data '
df -hT /data

5.2 mount 的核心条件

挂载成功通常要求:

  1. 设备存在且可读;
  2. 设备上有内核支持的文件系统;
  3. 挂载点存在并且权限允许;
  4. 文件系统没有因损坏而被拒绝挂载;
  5. 当前挂载命名空间中没有冲突;
  6. 执行者具有 CAP_SYS_ADMIN,普通用户只有在 /etc/fstab 明确允许时才能使用某些挂载配置。

内核先读取文件系统超级块,确认类型和关键参数,然后创建 VFS 挂载对象,将其接到目录树。一个进程看到的挂载关系还受 mount namespace 影响,因此容器内外可能看到不同的挂载结果。

5.3 使用 UUID 配置 /etc/fstab

临时挂载只在当前启动周期有效。开机自动挂载通常配置在 /etc/fstab

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  /data  ext4  defaults,nofail  0  2

查看准确 UUID:

blkid /dev/vgdata/lvdata

字段含义:

设备标识   挂载点   文件系统类型   挂载选项   dump   fsck 顺序

使用 UUID 而不是 /dev/sdb1,是因为设备名可能因硬件枚举顺序、虚拟机配置或热插拔而变化。UUID 必须唯一;复制磁盘或文件系统时可能产生重复 UUID,届时不能盲目同时使用。

修改后验证:

sudo mount -a
findmnt /data

mount -a 会尝试挂载 fstab 中尚未挂载且未标记 noauto 的条目。生产环境修改根文件系统、/boot 或远程系统的 fstab 时,错误可能导致系统无法正常启动。nofail 可以避免某些非关键数据盘故障阻塞启动,但也可能掩盖存储故障,是否使用取决于该文件系统是否是业务必需的。

卸载:

sudo umount /data

若出现:

target is busy

说明仍有进程打开文件、当前工作目录位于该挂载点,或存在子挂载。诊断:

findmnt -R /data
sudo lsof +f -- /data
sudo fuser -vm /data

应先停止相关服务、退出工作目录并处理子挂载,而不是直接使用强制卸载。对网络文件系统,umount -f 的行为和风险又不同,不能将其作为普通本地文件系统的通用解决办法。


六、LVM:把物理空间抽象成可调整的逻辑卷

LVM(Logical Volume Manager)位于块设备和文件系统之间,核心对象是:

PV(Physical Volume,物理卷)
        │
        ▼
VG(Volume Group,卷组)
        │
        ▼
LV(Logical Volume,逻辑卷)

6.1 PV、VG 和 LV 的职责

PV 是 LVM 可以管理的块设备,例如:

  • /dev/sdb1
  • /dev/nvme1n1p1
  • /dev/md0

pvcreate 会在设备上写入 LVM 标签和元数据。

VG 是一个空间池,由一个或多个 PV 组成。VG 将空间分割为固定大小的 Physical Extent(PE)。LV 使用其中的 extents。

若 VG 的 extent 大小为 E,某 LV 分配了 N 个 extent,则其近似容量为:

LV 容量 = N × E

由于容量按 extent 分配,LV 的实际大小可能按 extent 粒度取整。

LV 是呈现给上层的虚拟块设备。文件系统、交换分区、数据库原始设备或加密层都可以建立在 LV 上。

6.2 创建一组 LVM 对象

假设 /dev/sdb1 是空分区:

sudo pvcreate /dev/sdb1
sudo vgcreate vgdata /dev/sdb1
sudo lvcreate -n lvdata -L 200G vgdata
sudo mkfs.ext4 /dev/vgdata/lvdata
sudo mkdir -p /data
sudo mount /dev/vgdata/lvdata /data

逐步检查:

sudo pvs
sudo vgs
sudo lvs -a -o +devices
lsblk -f

典型状态是:

PV         VG      PSize    PFree
/dev/sdb1  vgdata  499.99g  299.99g

LV      VG      LSize
lvdata  vgdata  200.00g

这里的 300 GiB 空闲空间仍属于 VG,不属于 lvdata。应用无法使用它,直到管理员扩大 LV,并扩大其上的文件系统。

6.3 LVM 的映射关系和数据流

对于一个简单线性 LV,逻辑块地址 L 会被映射到某个 PV 的物理 extent:

LV 逻辑地址 L
    ↓
LV extent 编号
    ↓
VG 中的物理 extent
    ↓
PV 上的块地址

如果 LV 使用条带化、镜像或 RAID 类型,映射关系会更复杂。查看 LV 类型:

sudo lvs -a -o lv_name,vg_name,lv_attr,lv_size,segtype,devices

linear 表示线性映射;raid1raid5thin 等表示不同的分配和冗余机制。不能看到一个 /dev/mapper/... 就推断它一定是简单线性 LV。

6.4 LVM 扩容

如果 VG 中已有空闲空间:

sudo lvextend -L +100G /dev/vgdata/lvdata

+100G 表示增加 100 GiB 左右的容量;-L 300G 表示设置最终容量为 300 GiB。具体显示会受十进制单位和二进制单位影响,应以 lvs 为准。

然后扩展文件系统。

ext4:

sudo resize2fs /dev/vgdata/lvdata

XFS:

sudo xfs_growfs /data

完整验证:

sudo lvs /dev/vgdata/lvdata
lsblk
df -hT /data

某些发行版支持:

sudo lvextend -r -L +100G /dev/vgdata/lvdata

-r 请求同时调整文件系统,但其实际调用的文件系统工具和对 XFS 的支持取决于 lvm2fsadm 及发行版版本。生产变更中可以分开执行 lvextend 和文件系统扩容,这样每一步的状态更明确、错误更容易定位。

6.5 LVM 扩容的反例

错误顺序:

先扩大文件系统
再扩大 LV

文件系统不能访问尚不存在的块,通常会失败。

更危险的错误顺序:

先缩小 LV
再缩小文件系统

这会截断文件系统实际使用的块,可能破坏超级块、日志、目录或文件数据。

6.6 LVM 快照和精简卷的边界

LVM snapshot 使用写时复制(copy-on-write):创建快照后,当原 LV 的某个块即将被修改,旧内容先被保存到快照空间。若快照空间耗尽,快照可能失效;它不是无限期备份。

LVM thin provisioning 允许逻辑容量大于当前实际分配的物理空间:

逻辑可见容量 > 已分配物理容量

这要求持续监控 thin pool。thin pool 满后,写入可能失败,即使 df 仍显示文件系统有空闲空间。它适合明确管理的虚拟化或测试环境,不应把“超分配”误解成真实容量增加。


七、RAID:在多个设备之间分布数据和冗余

RAID(Redundant Array of Independent Disks)将多个设备组织为一个逻辑块设备,以获得性能、容量或故障容忍能力。

RAID 不是文件系统,也不是备份。删除文件、错误写入、勒索软件和应用逻辑错误通常会被同步复制到所有副本。

7.1 常见 RAID 级别

设有 N 个成员设备,每个设备容量为 C,忽略元数据和不同设备大小造成的损失:

  • RAID0:
    • 数据条带化;
    • 可用容量约为 N × C
    • 任意一个成员故障,整个阵列通常不可用;
    • 只有性能和容量收益,没有冗余。
  • RAID1:
    • 数据镜像;
    • 两盘时可用容量约为 C
    • 可承受一个成员故障;
    • 多盘 RAID1 的可用容量和故障容忍取决于具体布局。
  • RAID5:
    • 条带化加单个分布式校验;
    • 可用容量约为 (N - 1) × C
    • 可承受一个成员故障;
    • 重建期间再次故障可能导致阵列丢失。
  • RAID6:
    • 双校验;
    • 可用容量约为 (N - 2) × C
    • 可承受两个成员故障;
    • 写入和重建开销通常高于 RAID5。
  • RAID10:
    • 镜像与条带化组合;
    • 常见布局可用容量约为 floor(N / 2) × C
    • 实际可容忍哪些成员同时故障取决于故障是否发生在不同镜像副本中。

若成员容量不同,很多实现会按最小成员容量截取每个成员,不能简单把所有磁盘容量相加。

7.2 软件 RAID 与硬件 RAID

Linux 软件 RAID 通常由 mdadm 管理,阵列设备如 /dev/md0。硬件 RAID 则由控制器向操作系统呈现一个逻辑磁盘,操作系统可能看不到底层成员盘。

二者的诊断边界不同:

  • 软件 RAID:使用 mdadm --detail/proc/mdstat
  • 硬件 RAID:必须使用对应控制器工具查看物理盘和缓存状态;
  • /dev/sdX 的数量不能证明是否存在硬件 RAID;
  • RAID 控制器的“逻辑盘健康”也不等于每个物理盘都健康。

7.3 创建 Linux 软件 RAID1

假设 /dev/sdb1/dev/sdc1 是两个大小相同、确认为空的分区:

sudo mdadm --create /dev/md0 \
  --level=1 \
  --raid-devices=2 \
  /dev/sdb1 /dev/sdc1

查看状态:

cat /proc/mdstat
sudo mdadm --detail /dev/md0

创建后可能看到:

md0 : active raid1 sdc1[1] sdb1[0]
      488... blocks [2/2] [UU]
      [====>................]  recovery = ...

[UU] 表示两个成员都在线;若出现 [_U],表示一个成员缺失或故障。创建或更换成员后,阵列可能正在同步,期间性能和故障风险与稳定运行状态不同。

阵列设备通常可以作为 LVM PV:

sudo pvcreate /dev/md0
sudo vgcreate vgdata /dev/md0
sudo lvcreate -n lvdata -L 400G vgdata

此时层次是:

成员分区 → md RAID → LVM PV → VG → LV → 文件系统

7.4 RAID 故障与恢复路径

查看阵列:

cat /proc/mdstat
sudo mdadm --detail /dev/md0

故障成员可能显示为 faultyremoved。更换磁盘后,正确流程通常是:

sudo mdadm /dev/md0 --fail /dev/sdb1
sudo mdadm /dev/md0 --remove /dev/sdb1

# 新磁盘上创建大小和对齐合适的新分区 /dev/sdd1
sudo mdadm /dev/md0 --add /dev/sdd1
watch -n 2 cat /proc/mdstat

命令中的设备必须经过确认。将仍在工作的成员错误标记为故障,会主动降低冗余。

重建时,RAID 会从仍然有效的成员读取数据和校验,并向新成员写入。重建速度受设备、内核参数、业务 I/O 和阵列级别影响;不能给出脱离环境的固定时间。RAID5/6 的校验重建还可能发现潜在介质错误,因此阵列有冗余不代表所有数据都已被验证。

7.5 RAID 写洞与一致性

对于带校验的 RAID,更新一个小块通常涉及:

  1. 读取旧数据;
  2. 读取旧校验;
  3. 计算新校验;
  4. 写入新数据;
  5. 写入新校验。

如果系统在第 4、5 步之间断电,数据和校验可能不一致,这类问题常被称为 write hole。现代实现可能通过写日志、条带缓存、控制器非易失缓存或其他机制降低风险,但具体保证取决于 RAID 实现、内核版本、控制器和电源保护。

文件系统日志解决的是文件系统元数据一致性问题,不自动解决底层 RAID 校验一致性,也不等于应用数据已经持久化。


八、RAID、LVM 与文件系统的组合方式

常见架构有三种。

8.1 RAID 在 LVM 下方

磁盘分区 → RAID → LVM → 文件系统

优点:

  • RAID 对多个磁盘提供统一冗余;
  • LVM 在阵列之上灵活分配逻辑卷;
  • 文件系统只看到一个稳定的逻辑设备。

扩容顺序通常是:

扩展磁盘或添加成员
→ 扩展分区
→ 扩展 RAID
→ pvresize
→ 扩展 LV
→ 扩展文件系统

8.2 LVM 在磁盘上,RAID 由 LVM 提供

LVM 可以创建 raid1raid5raid6 等类型的 LV:

多个 PV → LVM RAID LV → 文件系统

这种方案由 LVM 维护冗余,管理命令、状态字段和故障处理方式不同于 mdadm。不能把 mdadm --detail 用在 LVM RAID LV 上,也不能把 lvs 当作完整的 md RAID 状态工具。

8.3 文件系统自身提供冗余

某些文件系统具有自身的多设备和校验机制,但这属于文件系统层能力,不能与 md RAID 或 LVM RAID 混为一谈。不同层叠加冗余可能增加管理复杂度、写放大和故障定位难度。

选择层次时应先明确:

  • 需要的是设备级故障容忍,还是文件系统级校验;
  • 是否需要在线扩容或缩容;
  • 是否需要快照;
  • 是否需要跨主机复制;
  • 故障时由哪一层负责重建和报警。

九、在线扩容:容量如何逐层向上流动

在线扩容的本质是让每一层都确认“下面出现了更多可用块”。

完整路径:

云盘或物理磁盘扩容
        ↓
内核重新识别设备容量
        ↓
分区扩大
        ↓
RAID 扩大(如果存在)
        ↓
PV 扩大
        ↓
VG 出现空闲 extents
        ↓
LV 扩大
        ↓
文件系统扩大
        ↓
df 显示更多空间

任意一步没有完成,上层都不能使用新增容量。

9.1 示例:无 RAID 时扩展分区、PV、LV 和 ext4

假设当前结构为:

/dev/sdb1 → PV → vgdata → lvdata → ext4 → /data

云平台或虚拟化平台已将 /dev/sdb 从 500 GiB 扩大到 800 GiB。

第一步:确认内核看到新磁盘容量

lsblk /dev/sdb
sudo blockdev --getsize64 /dev/sdb

如果内核没有自动识别,可以针对 SCSI 设备执行重新扫描;不同虚拟化环境的操作不同。不要直接假设所有设备都支持同一个 sysfs 路径。

第二步:扩大分区

使用 growpart 的发行版可能需要安装 cloud-guest-utils 或同类软件包:

sudo growpart /dev/sdb 1
sudo partprobe /dev/sdb
lsblk /dev/sdb

这里 1 是分区号,不是容量。

也可以使用 parted

sudo parted /dev/sdb
(parted) print
(parted) resizepart 1 100%
(parted) quit
sudo partprobe /dev/sdb

resizepart 只改变分区边界,不移动文件系统数据。前提是分区末尾后方存在连续未使用空间,并且该操作确实针对正确的分区。

第三步:扩大 PV

sudo pvresize /dev/sdb1
sudo pvs
sudo vgs

预期结果是:

vgdata 的 PFree 增加

如果 pvresize 报告没有可扩展空间,常见原因是:

  • 分区实际上没有扩大;
  • 内核仍使用旧分区表;
  • 指定了错误的设备;
  • PV 并不在该分区上;
  • 底层还有 RAID、加密或其他映射层没有扩容。

第四步:扩大 LV

例如将 LV 再增加 200 GiB:

sudo lvextend -L +200G /dev/vgdata/lvdata
sudo lvs /dev/vgdata/lvdata

第五步:扩大 ext4 文件系统

sudo resize2fs /dev/vgdata/lvdata
df -hT /data

只有最后一步完成后,df 才会显示文件系统空间增加。

9.2 示例:XFS 的在线扩容

lvdata 上是 XFS,前四步相同,但第五步使用挂载点:

sudo xfs_growfs /data
df -hT /data

XFS 的扩容要求文件系统已挂载。若 /data 没有正确挂载,命令可能失败,或者操作者误以为扩大了目标文件系统,因此应先验证:

findmnt -T /data

9.3 示例:RAID 位于 LVM 下方

假设结构是:

/dev/sdb1 + /dev/sdc1 → /dev/md0 → PV → VG → LV → XFS

现在为 RAID1 添加一块新磁盘 /dev/sdd1,并希望扩大阵列容量。简化流程是:

sudo mdadm /dev/md0 --add /dev/sdd1
sudo mdadm --detail /dev/md0
cat /proc/mdstat

对于 RAID1,从两成员扩展到三成员时,加入新成员通常先触发数据同步,但阵列的可用容量是否增加、如何改变镜像布局,必须根据目标 RAID 级别和 mdadm --grow 支持方式确认。不能把“添加成员”自动等同于“容量扩展”。

对于需要增加可用容量的阵列,通常还需要类似:

sudo mdadm --grow /dev/md0 --raid-devices=3

具体参数必须以当前 RAID 级别、阵列布局和 mdadm 版本的手册为准。执行前应保存:

sudo mdadm --detail --scan
sudo mdadm --detail /dev/md0

并确认已存在可恢复的备份和维护窗口。

当 RAID 设备真正扩大后,再执行:

sudo pvresize /dev/md0
sudo lvextend -L +200G /dev/vgdata/lvdata
sudo xfs_growfs /data

顺序中不能跳过 pvresize。LVM 不会因为 /dev/md0 变大就自动增加 VG 的空闲 extents。

9.4 分区、RAID 和 LVM 的扩容顺序为什么不能颠倒

设文件系统需要块地址区间:

[0, F)

它位于 LV 的地址区间:

[0, L)

LV 位于 VG 分配的 extents:

[0, V)

底层设备提供的实际范围是:

[0, D)

必须满足:

F ≤ L ≤ V ≤ D

扩容时,先增大 D,再依次增大 VLF。若先令 L > V,LV 没有足够 extents;若先令 F > L,文件系统访问的块超过设备边界。缩容时则相反,必须先减少 F,再减少 LV,而且每一步都要验证数据没有越过新的末端。


十、生产环境中的容量、并发和一致性问题

10.1 dfdulsblk 为什么可能不一致

三者观察的是不同层次:

  • lsblk:块设备容量;
  • df:文件系统的块和 inode 使用情况;
  • du:目录树中仍可通过路径遍历到的文件大小。

当进程打开了一个已经删除的文件时:

目录项已删除,但文件描述符仍然打开

df 仍会统计它占用的块,而 du 无法通过目录遍历找到它。诊断:

sudo lsof +L1

文件系统还可能保留预留空间、日志空间、快照空间或稀疏文件的未实际分配区域。不要仅凭 du 的结果判断块设备是否快满。

10.2 文件系统满、inode 满和底层空间满

ext4 等文件系统不仅有数据块,还有 inode。检查:

df -hT /data
df -ih /data

可能出现:

  • 数据块使用率 100%,但 inode 尚有空间;
  • inode 使用率 100%,但数据块尚有空间;
  • 文件系统尚有空间,但 LVM thin pool 已满;
  • 文件系统和 LV 尚有空间,但 RAID 阵列处于降级状态;
  • 路径所在挂载点不是管理员以为的那个设备。

所以诊断应同时看:

findmnt -T /data
df -hT /data
df -ih /data
lsblk -f
sudo lvs -a -o +devices
cat /proc/mdstat

10.3 缓存、写回和持久化

Linux 页缓存可能让写系统调用很快返回,但数据仍在内存中,尚未真正写入稳定存储。应用可使用 fsync()fdatasync() 或合适的打开选项请求持久化;文件系统和块设备还可能有自己的缓存与写屏障机制。

sync 可以请求系统写回脏数据:

sync

但它不是数据库一致性协议,也不能修复已经发生的逻辑错误。断电保护、设备缓存是否掉电安全、RAID 控制器是否有电池或非易失缓存,都会影响真正的持久化语义。


十一、故障诊断:先定位在哪一层

存储故障通常不是“一个命令失败”,而是某一层的状态没有满足上层要求。可以按自下而上的顺序检查。

11.1 设备层

lsblk
dmesg -T | tail -n 100
sudo smartctl -a /dev/sdb       # SATA/SAS 常用,需 smartmontools
sudo nvme smart-log /dev/nvme0  # NVMe 设备

关注:

  • I/O error;
  • medium error;
  • reset、timeout;
  • 链路掉线;
  • 设备容量是否变化;
  • SMART/NVMe 健康信息。

SMART 信息并非所有虚拟磁盘都可用,虚拟机中看到的设备可能由宿主机或云平台管理。

11.2 分区层

sudo parted /dev/sdb print
sudo fdisk -l /dev/sdb
lsblk /dev/sdb

关注:

  • 分区末端是否真的到达新磁盘末端;
  • 起始扇区是否发生意外变化;
  • 是否使用了正确的磁盘;
  • 内核是否重新读取了分区表。

调整分区时,通常只能安全地扩大分区末端;改变起始位置可能要求移动全部上层数据,风险远高于普通扩容。

11.3 RAID 层

cat /proc/mdstat
sudo mdadm --detail /dev/md0

关注:

  • active 还是 inactive
  • [UU] 还是缺失成员;
  • 是否正在 recovery 或 reshape;
  • 成员序列号、事件计数和设备状态是否匹配。

不要在阵列仍处于重建或重塑时随意重启、拔盘或重复创建阵列。mdadm --create 在已有阵列数据的设备上可能覆盖关键元数据。

11.4 LVM 层

sudo pvs -o pv_name,pv_size,pv_free,pv_attr
sudo vgs
sudo lvs -a -o lv_name,vg_name,lv_attr,lv_size,segtype,devices

常见问题:

  • PV 还没有 pvresize
  • VG 没有空闲空间;
  • LV 属性显示非活动、只读或快照异常;
  • thin pool 满;
  • LVM 元数据备份或恢复状态异常。

LVM 元数据和文件系统数据不是一回事。恢复了 VG 配置不代表文件系统内容已经恢复。

11.5 文件系统层

未挂载的文件系统才能安全执行常规一致性检查:

ext4:

sudo umount /data
sudo e2fsck -f /dev/vgdata/lvdata

XFS:

sudo umount /data
sudo xfs_repair /dev/vgdata/lvdata

fsck 不是一个万能修复命令;它通常根据文件系统类型调用具体工具。对已挂载且正在写入的文件系统运行修复工具,可能进一步破坏元数据。XFS 的 xfs_repair 也不应在正常挂载状态下运行。

如果怀疑硬件或 RAID 正在返回错误,先解决底层 I/O 问题,再做文件系统修复。否则修复过程可能把设备错误误当成文件系统损坏。


十二、在线扩容的风险控制和验证

在线扩容通常不需要停机,但“不停机”不等于“无风险”。

12.1 变更前必须确认的状态

findmnt -T /data
lsblk -f
sudo pvs
sudo vgs
sudo lvs -a -o +devices
cat /proc/mdstat
df -hT /data

需要明确:

  • 目标路径实际使用的设备;
  • 文件系统类型;
  • 是否存在 RAID、LVM、加密或 thin pool;
  • 新增容量已经在哪一层可见;
  • 是否有可恢复的备份;
  • 是否存在正在进行的 RAID reshape 或同步。

12.2 每一步都保存状态

扩容过程不应写成一条未经验证的长命令。更可靠的方式是:

lsblk
# 扩大分区

lsblk
# 扩大 RAID(如有)

cat /proc/mdstat
# 扩大 PV

pvs
vgs
# 扩大 LV

lvs
# 扩大文件系统

df -hT

每一步的输出都是下一步的前置证据。若失败,应停留在当前层诊断,而不是继续执行后续命令。

12.3 资源竞争

文件系统在线扩容时,业务仍可能读写文件。大多数现代 ext4 和 XFS 扩容操作会通过文件系统自身的锁和同步机制维护元数据一致性,但高并发 I/O 可能增加延迟,RAID 重建也会竞争设备带宽。

扩容命令成功只说明元数据操作被接受,不等于:

  • RAID 已完成重建;
  • 云盘后端已经完成物理迁移;
  • 应用性能没有变化;
  • 数据已经自动备份;
  • 以后不会发生设备故障。

十三、几个容易导致事故的误解

误解一:挂载就是格式化

错误。mount 只把已有文件系统接入目录树;mkfs 才是创建文件系统并可能破坏原有内容的操作。

误解二:RAID 等于备份

错误。RAID 主要应对部分设备故障。误删、错误脚本、恶意加密和应用写坏数据会同步影响镜像或校验阵列。备份需要独立的时间点、介质或故障域,并且必须验证恢复。

误解三:VG 有空闲空间,文件系统就已经扩大

错误。VG 的 PFree 只是 LVM 空间池中的未分配 extents。必须先分配给 LV,再扩大文件系统。

误解四:扩大分区后 df 就会增加

错误。分区只是扩大了块设备边界。若其上有 ext4、XFS、LVM 或 RAID,还要继续处理相应层。

误解五:/dev/sdb 永远代表同一块盘

错误。传统设备名可能受到枚举顺序影响。持久化配置通常使用文件系统 UUID、分区 PARTUUID 或由发行版管理的稳定路径,并在写入前确认其当前指向。

误解六:在线扩容和在线缩容是对称的

错误。在线扩大通常得到广泛支持;在线缩小通常不支持,尤其是 XFS。缩小必须先处理上层数据布局,再处理下层容量。

误解七:文件系统日志可以恢复所有损坏

错误。日志通常用于恢复文件系统元数据操作的一致状态,不保证恢复已经被覆盖的数据,也不能替代备份、RAID 校验或应用级恢复。


十四、一个完整的安全实验:使用 loop 设备模拟磁盘

如果只是学习流程,不应直接拿生产盘实验。可以用普通文件模拟块设备:

truncate -s 2G /tmp/disk-a.img
truncate -s 2G /tmp/disk-b.img

sudo losetup --find --show /tmp/disk-a.img
sudo losetup --find --show /tmp/disk-b.img

假设输出为:

/dev/loop10
/dev/loop11

创建 GPT 和分区:

sudo parted --script /dev/loop10 mklabel gpt mkpart primary 1MiB 100%
sudo parted --script /dev/loop11 mklabel gpt mkpart primary 1MiB 100%
sudo partprobe /dev/loop10
sudo partprobe /dev/loop11

检查设备:

lsblk -o NAME,PATH,TYPE,SIZE,FSTYPE

不同内核和 losetup 版本对 loop 设备分区节点的创建方式可能不同。若没有出现 /dev/loop10p1,可以使用 kpartx,或直接使用整块 loop 设备进行不带分区的实验。不要把以下路径当成必然存在。

如果分区节点存在,可以创建 RAID1:

sudo mdadm --create /dev/md100 \
  --level=1 \
  --raid-devices=2 \
  /dev/loop10p1 /dev/loop11p1

watch -n 1 cat /proc/mdstat

同步完成后创建 LVM 和 ext4:

sudo pvcreate /dev/md100
sudo vgcreate vgtest /dev/md100
sudo lvcreate -n lvtest -L 1G vgtest
sudo mkfs.ext4 /dev/vgtest/lvtest

sudo mkdir -p /mnt/test-storage
sudo mount /dev/vgtest/lvtest /mnt/test-storage
findmnt /mnt/test-storage
df -hT /mnt/test-storage

清理实验环境:

sudo umount /mnt/test-storage
sudo lvremove -y /dev/vgtest/lvtest
sudo vgremove -y vgtest
sudo pvremove -y /dev/md100
sudo mdadm --stop /dev/md100
sudo losetup -d /dev/loop10
sudo losetup -d /dev/loop11
rm -f /tmp/disk-a.img /tmp/disk-b.img

这个实验展示了:

loop 设备
→ 分区
→ md RAID1
→ PV
→ VG
→ LV
→ ext4
→ 挂载点

真实生产系统只是在底层设备、故障模型、容量和性能上更加复杂,并不会改变“逐层建立、逐层验证、逐层扩容”的基本规律。


十五、最终检查模型

处理任何块设备问题时,可以把状态写成一条链:

物理容量 D
  ≥ RAID 可见容量 R
    ≥ PV 可见容量 P
      ≥ VG 已分配空间 V
        ≥ LV 容量 L
          ≥ 文件系统容量 F

对于扩容:

先增加底层可见空间
→ 让下一层重新识别
→ 扩大下一层
→ 验证
→ 继续向上

对于缩容:

先缩小文件系统并确认数据边界
→ 缩小 LV
→ 缩小 PV 或分区
→ 最后处理底层设备

对于故障:

先判断设备是否可靠
→ 再判断 RAID 是否完整
→ 再判断 LVM 映射是否正常
→ 再判断文件系统是否一致
→ 最后检查挂载和应用

掌握这两条顺序,就能避免大多数“磁盘已经扩容但系统看不到”“误把 RAID 当备份”“先缩设备导致文件系统损坏”以及“修复了错误层次”的存储事故。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。