新服务器上架,插了 12 块盘,你要做的第一件事不是 mkfs,而是搞清楚:哪个 /dev/sdX 是哪块物理盘?重启之后它还叫这个名字吗?扇区是 512 还是 4K,分区对齐了吗?将来容量不够了能不能不停机扩容?这一课回答这些问题。
学完你能用 lsblk、blkid、udevadm 摸清一台机器上的所有块设备,理解为什么生产环境必须用 /dev/disk/by-id 引用磁盘,用 parted 建出 4K 对齐的 GPT 分区,并用 LVM 完成创建、在线扩容、精简配置和快照。全部实验都在 loop 设备上完成,不会碰到你的真实磁盘。
什么是块设备
块设备(Block Device) 是一种以固定大小的块为单位、可以按地址随机读写的设备。磁盘、分区、RAID 阵列、LVM 逻辑卷、loop 设备、iSCSI LUN,在内核看来都是块设备。它和字符设备(终端、串口)的区别在于:块设备可以随机寻址,内核会为它提供缓存和 I/O 调度。
ls -l /dev/vda 输出的首字母 b 表示块设备(字符设备是 c),紧跟的 253, 0 是主设备号(驱动)和次设备号(实例)。更完整的信息在 /sys/block/ 下,每个设备一个目录,上一课看到的 queue/、device/ 都在这里。
设备名从哪来
| 名字 | 驱动 / 来源 | 说明 |
|---|---|---|
sda、sdb… | SCSI 子系统(SATA、SAS、USB、iSCSI 都走这里) | 按发现顺序分配字母,分区是 sda1 |
nvme0n1 | NVMe | 控制器 0 的命名空间 1,分区是 nvme0n1p1 |
vda、vdb… | virtio-blk(KVM 虚拟机) | 分区是 vda1 |
loop0… | loop 驱动,把文件当成盘 | 分区是 loop0p1(需要 -P) |
dm-0… | device mapper(LVM、dm-crypt、多路径) | 通常通过 /dev/mapper/<名字> 访问 |
md0… | Linux 软 RAID | 见RAID 与 mdadm |
lsblk:一眼看清设备树
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 63.9M 1 loop /snap/core20/2318
loop1 7:1 0 87M 1 loop /snap/lxd/29351
vda 253:0 0 40G 0 disk
├─vda1 253:1 0 1M 0 part
├─vda2 253:2 0 2G 0 part /boot
└─vda3 253:3 0 38G 0 part
└─ubuntu--vg-ubuntu--lv 252:0 0 19G 0 lvm /
vdb 253:16 0 20G 0 disk这是一台典型的 Ubuntu 24.04 虚拟机:vda3 是 LVM 物理卷,上面切出了根分区的逻辑卷;loop0、loop1 被 snap 占用(后面做实验时要注意别撞上)。常用的列组合:
lsblk -f # 文件系统类型、标签、UUID、挂载点
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS,SERIAL,WWN
lsblk -d -o NAME,SIZE,ROTA,TRAN,LOG-SEC,PHY-SEC,MODEL # 只看整盘blkid 从设备上读出文件系统签名,是查 UUID 最直接的方式:
$ sudo blkid /dev/vda2
/dev/vda2: UUID="2f4c6b3e-8a1d-4f0e-9c7b-1d2e3f4a5b6c" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="7a9b1c2d-..."稳定命名:生产环境必须用 by-id
sdX 为什么不可靠
sda、sdb 的字母是内核按探测顺序分配的。控制器初始化快慢、某块盘上电晚了半秒、换了一块盘、加了一张 HBA 卡、插了个 U 盘,都可能让字母整体错位。上次重启时的 sdc,这次可能变成了 sdd。
udev 与 /dev/disk/by-*
设备出现时,内核发出 uevent,用户态的 udev 根据规则读取设备的型号、序列号、WWN、所在总线路径等属性,在 /dev/disk/ 下创建一组指向真实设备的符号链接:
| 目录 | 依据 | 稳定性 | 典型用途 |
|---|---|---|---|
by-id/ | 型号 + 序列号,或 WWN / EUI | 跟着物理盘走,换槽位也不变 | 引用整盘:建 RAID、Ceph OSD、GPFS NSD |
by-path/ | PCI 地址 + 端口 / 槽位 | 跟着槽位走,换了盘名字不变 | 按槽位定位、自动化换盘 |
by-uuid/ | 文件系统 UUID | 跟着文件系统走,重新 mkfs 会变 | /etc/fstab 挂载 |
by-partuuid/ | GPT 分区 UUID | 跟着分区走 | 引导配置、无文件系统的分区 |
by-label/ | 文件系统标签 | 标签可能重复 | 人工可读的临时挂载 |
$ ls -l /dev/disk/by-id/ | grep -v part
lrwxrwxrwx 1 root root 9 Sep 24 09:12 ata-ST20000NM007D-3DJ103_ZVT0XXXX -> ../../sda
lrwxrwxrwx 1 root root 13 Sep 24 09:12 nvme-SAMSUNG_MZQL23T8HCLS-00A07_S64HNXXXX -> ../../nvme0n1
lrwxrwxrwx 1 root root 13 Sep 24 09:12 nvme-eui.36344830526021230025384500000001 -> ../../nvme0n1
lrwxrwxrwx 1 root root 9 Sep 24 09:12 wwn-0x5000c500d1234567 -> ../../sda同一块盘常有多个 by-id 链接。wwn-/nvme-eui. 是全球唯一标识,最稳;ata-型号_序列号 可读性最好,序列号和盘上贴纸一致,方便机房人员找盘。
想知道某个设备的全部 udev 属性:
$ udevadm info --query=property --name=/dev/sda | grep -E '^(ID_MODEL|ID_SERIAL_SHORT|ID_WWN|ID_PATH|DEVLINKS)='
ID_MODEL=ST20000NM007D-3DJ103
ID_SERIAL_SHORT=ZVT0XXXX
ID_WWN=0x5000c500d1234567
ID_PATH=pci-0000:3b:00.0-sas-phy4-lun-0
DEVLINKS=/dev/disk/by-id/wwn-0x5000c500d1234567 /dev/disk/by-path/pci-0000:3b:00.0-sas-phy4-lun-0 /dev/disk/by-id/ata-ST20000NM007D-3DJ103_ZVT0XXXX扇区大小与 4K 对齐
逻辑扇区与物理扇区
上一课讲过,很多盘是 512e:对外报 512 字节逻辑扇区,内部是 4K 物理扇区。内核通过 sysfs 暴露这几个关键值:
$ grep . /sys/block/sda/queue/{logical_block_size,physical_block_size,minimum_io_size,optimal_io_size}
/sys/block/sda/queue/logical_block_size:512
/sys/block/sda/queue/physical_block_size:4096
/sys/block/sda/queue/minimum_io_size:4096
/sys/block/sda/queue/optimal_io_size:0logical_block_size:可寻址的最小单位,I/O 必须是它的整数倍。physical_block_size:真正的读写单位,小于它的写要做读-改-写。optimal_io_size:RAID 阵列、部分 SSD 会报告条带宽度,文件系统据此对齐分配。
为什么要对齐
如果分区从第 63 个扇区开始(老式 MBR 工具的默认值),分区里的每个 4K 块都会横跨两个物理扇区:
物理扇区(4K) | P0 | P1 | P2 |
未对齐分区 | ↘ 4K 块 A ↘ 4K 块 B ↘ ... 每次写一个块要改两个物理扇区
起点 = 63 × 512 = 32256 字节,不是 4096 的整数倍
对齐分区 | 4K 块 A | 4K 块 B | 4K 块 C | 一一对应
起点 = 2048 × 512 = 1 MiB现代工具(parted、fdisk、sgdisk)默认都把第一个分区放在 1 MiB 处。1 MiB 是 4K、64K、256K、1M 等常见条带和擦除块的公倍数,能同时满足 HDD、SSD 和 RAID 的对齐需求。经验法则:分区起点和大小都用 MiB 为单位来写,就不会出错。
GPT 分区与 parted
MBR 还是 GPT
| MBR | GPT | |
|---|---|---|
| 最大磁盘 | 2 TiB(512 字节扇区时) | 实际无上限(8 ZiB) |
| 分区数 | 4 个主分区(或扩展分区套逻辑分区) | 默认 128 个 |
| 冗余 | 无,分区表只在第 0 扇区 | 头部和尾部各一份,带 CRC 校验 |
| 分区标识 | 1 字节类型码 | 类型 GUID + 每个分区唯一的 PARTUUID + 名字 |
没有理由再用 MBR。今天的数据盘动辄 20 TB,MBR 根本放不下。
准备实验用的 loop 设备
用 parted 建 GPT 分区
parted 的 -s(script)模式适合写进脚本;交互模式下每条命令会立即写盘,没有"保存"这一步。
sudo parted -s $D1 mklabel gpt
sudo parted -s $D1 mkpart data1 1MiB 50%
sudo parted -s $D1 mkpart data2 50% 100%
sudo parted -s $D1 unit s print
sudo parted -s $D1 align-check optimal 1Model: Loopback device (loopback)
Disk /dev/loop8: 4194304s
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
1 2048s 2097151s 2095104s data1
2 2097152s 4192255s 2095104s data2
1 alignedGPT 里 mkpart 的第一个参数是分区名字(写入分区表的 PARTLABEL),不是文件系统类型。起点 2048 扇区正好是 1 MiB。注意磁盘最后留下的一小段:GPT 在尾部存放了备份分区表。
$ lsblk -o NAME,SIZE,PARTLABEL,PARTUUID $D1
NAME SIZE PARTLABEL PARTUUID
loop8 2G
├─loop8p1 1023M data1 3c5d7e9f-1a2b-4c3d-8e9f-0a1b2c3d4e5f
└─loop8p2 1023M data2 8f7e6d5c-4b3a-4291-a0b1-c2d3e4f5a6b7其他常用操作:
sudo parted -s $D1 rm 2 # 删除 2 号分区
sudo partprobe $D1 # 分区表被其他工具改动后,通知内核重新读取
sudo wipefs $D1 # 只列出签名;wipefs -a 才会擦除(回收旧盘的标准动作,破坏性)模拟 4Kn 盘
loop 设备可以指定逻辑扇区大小,用来观察 4Kn 盘上的行为:
$ sudo truncate -s 1G /var/lib/lab/disk4k.img
$ D4K=$(sudo losetup -fP --show -b 4096 /var/lib/lab/disk4k.img)
$ lsblk -o NAME,LOG-SEC,PHY-SEC $D4K
NAME LOG-SEC PHY-SEC
loop11 4096 4096
$ sudo losetup -d $D4KLVM:让容量可以流动
为什么需要 LVM
直接在分区上建文件系统,容量在分区那一刻就定死了:想扩容,要么后面恰好有空闲空间,要么停机搬数据。LVM(Logical Volume Manager) 在物理盘和文件系统之间加了一层池化:
文件系统 ext4 (/data) xfs (/logs)
│ │
LV 逻辑卷 ┌────┴─────┐ ┌─────┴────┐
│ labvg/data│ │labvg/logs│ ← 从 VG 中按需切出,可在线扩容
└────┬─────┘ └─────┬────┘
VG 卷组 ┌────┴───────────────────┴─────────────┐
│ labvg:PE 池(默认每个 PE 4 MiB) │ ← 把多块 PV 的空间汇成一个池
└────┬───────────────────┬─────────────┘
PV 物理卷 /dev/loop9 /dev/loop10 ← 整盘、分区、RAID 阵列都可以- PV(Physical Volume):被 LVM 初始化过的块设备,头部写有 LVM 元数据。
- VG(Volume Group):一个或多个 PV 组成的存储池,空间以 PE(Physical Extent) 为单位管理。
- LV(Logical Volume):从 VG 中分配的虚拟块设备,由若干 PE 组成,可以跨越多个 PV。
LVM 构建在内核的 device mapper 之上,所以 LV 在 lsblk 里显示为 dm-N,在 /dev/mapper/labvg-data 和 /dev/labvg/data 两处都能访问。
创建 PV、VG、LV
sudo apt install -y lvm2
sudo pvcreate $D2 $D3
sudo vgcreate labvg $D2 # 先只用一块,留 D3 演示扩容
sudo lvcreate -n data -L 1G labvg
sudo mkfs.ext4 -q /dev/labvg/data
sudo mkdir -p /mnt/lvdata && sudo mount /dev/labvg/data /mnt/lvdata$ sudo pvs
PV VG Fmt Attr PSize PFree
/dev/loop10 lvm2 --- 2.00g 2.00g
/dev/loop9 labvg lvm2 a-- <2.00g 1020.00m
$ sudo vgs
VG #PV #LV #SN Attr VSize VFree
labvg 1 1 0 wz--n- <2.00g 1020.00m
$ sudo lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
data labvg -wi-ao---- 1.00g<2.00g 是因为 PV 头部的元数据占掉了一点空间,剩余不足整 2 GiB。pvdisplay、vgdisplay、lvdisplay 给出更详细的信息,包括 PE 大小和总数。
在线扩容
LVM 最常用的功能:业务不停,文件系统在挂载状态下直接变大。
# 1) VG 空间不够时,先加一块盘进来
sudo vgextend labvg $D3
# 2) 扩 LV,-r 表示同时扩展上面的文件系统(ext4 调 resize2fs,XFS 调 xfs_growfs)
sudo lvextend -r -L +1.5G labvg/data
df -h /mnt/lvdataFilesystem Size Used Avail Use% Mounted on
/dev/mapper/labvg-data 2.4G 24K 2.3G 1% /mnt/lvdata-L +1.5G 是增加 1.5G,-L 3G 是扩到 3G,-l +100%FREE 是吃掉 VG 里全部剩余空间。
LVM 还能在线迁移数据:pvmove /dev/老盘 /dev/新盘 会在不停业务的情况下把 PE 搬到新盘,然后 vgreduce 把老盘移出 VG。这是在不支持热换的老旧存储上换盘的常用手段。
精简配置(Thin Provisioning)
普通 LV 创建时就占满了声明的容量(厚置备)。精简配置先建一个精简池(thin pool),再从池里创建"虚拟大小"任意的精简卷,真正写入时才分配空间,因此可以超额分配(overcommit)。
sudo lvcreate --type thin-pool -L 1G -n tpool labvg
sudo lvcreate -V 5G --thin -n thin1 labvg/tpool
sudo lvcreate -V 5G --thin -n thin2 labvg/tpool WARNING: Sum of all thin volume sizes (10.00 GiB) exceeds the size of thin pool labvg/tpool and the size of whole volume group (<3.99 GiB).
WARNING: You have not turned on protection against thin pools running out of space.
WARNING: Set activation/thin_pool_autoextend_threshold below 100 to trigger automatic extension of thin pools before they get full.
Logical volume "thin2" created.1G 的池子声明出了两个 5G 的卷,LVM 已经在警告你了。写一点数据看看池的使用率:
$ sudo mkfs.ext4 -q /dev/labvg/thin1
$ sudo mkdir -p /mnt/thin1 && sudo mount /dev/labvg/thin1 /mnt/thin1
$ sudo dd if=/dev/urandom of=/mnt/thin1/blob bs=1M count=300 status=none && sync
$ sudo lvs -o lv_name,lv_size,pool_lv,data_percent,metadata_percent labvg
LV LSize Pool Data% Meta%
data 2.50g
thin1 5.00g tpool 6.44
thin2 5.00g tpool 0.00
tpool 1.00g 32.23 11.23精简配置的另一个好处是配合 discard:文件系统删除文件后执行 fstrim /mnt/thin1,池里的空间会被真正归还。
LVM 快照
快照让你得到某个 LV 在某一时刻的只读(或可写)视图,常用于"升级前留个后悔药"或"在一致的时间点上做备份"。
传统快照(COW)
echo "important v1" | sudo tee /mnt/lvdata/config.txt
sudo lvcreate -s -n data-snap -L 256M labvg/data # 给快照分配 256M 的 COW 空间
sudo rm /mnt/lvdata/config.txt # 手滑删掉了
sudo mkdir -p /mnt/snap
sudo mount -o ro /dev/labvg/data-snap /mnt/snap
cat /mnt/snap/config.txt # important v1,找回来了传统快照的原理是写时复制(Copy-on-Write):快照刚建好时不占空间,之后源卷每修改一个块,LVM 先把旧内容复制到快照的 COW 区,再写新数据。这带来两个后果:
- 源卷上的每次首次写都变成"读旧块 + 写 COW + 写新块",性能明显下降,快照越多越慢。
- COW 区写满后快照直接失效(
lvs里Data%到 100% 后 Attr 显示I),只能删除。
lvs 的 Data% 列就是 COW 区的使用率。确认要回滚时,可以把快照合并回源卷:
sudo umount /mnt/snap /mnt/lvdata
sudo lvconvert --merge labvg/data-snap # 源卷回到快照时刻,快照随之消失
sudo mount /dev/labvg/data /mnt/lvdata && cat /mnt/lvdata/config.txt如果源卷正在使用中无法卸载(比如根分区),合并会推迟到下次激活该 LV 时(通常是重启)。
精简快照
精简卷的快照不需要预先指定大小,直接共享池里的数据块,性能损耗远小于传统快照,也可以对快照再做快照:
sudo lvcreate -s -n thin1-snap labvg/thin1
sudo lvchange -ay -K labvg/thin1-snap # 精简快照默认带"跳过激活"标志,-K 忽略它清理实验环境
sudo umount /mnt/snap /mnt/lvdata /mnt/thin1 2>/dev/null
sudo vgremove -y labvg # 删除 VG 以及其中所有 LV
sudo pvremove $D2 $D3
sudo losetup -d $D1 $D2 $D3
sudo rm -f /var/lib/lab/disk*.img动手练习
- 在你的机器上执行
ls -l /dev/disk/by-id/ /dev/disk/by-path/ /dev/disk/by-uuid/,为每块整盘找出它的所有稳定名字,并用udevadm info --query=property核对序列号。 - 用
sudo parted -s -a none $D1 mklabel msdos mkpart primary 63s 100%故意建一个从第 63 扇区开始的 MBR 分区(-a none关闭自动对齐),然后执行align-check optimal 1,观察结果并解释原因;再改回 GPT、1MiB 起点的正确分区。 - 按本课流程建
labvg/data,在里面持续写入(while true; do date >> /mnt/lvdata/log; sleep 1; done)的同时执行vgextend与lvextend -r,确认写入没有中断。 - 创建一个 200M 的传统快照,然后往源卷写入 300M 数据,观察
lvs中快照Data%的变化以及快照最终的状态。 - 把精简池设置为自动扩展(修改
lvm.conf中的两个参数),往thin1持续写数据直到超过池的初始大小,观察池是否自动长大。
自测
为什么脚本里格式化或组阵列时不能写 /dev/sdb,而要写 /dev/disk/by-id/...?
sdX 按内核探测顺序分配,重启、换盘、加卡、插 U 盘都可能改变顺序,同一个名字可能指向另一块装有数据的盘。by-id 基于盘的序列号或 WWN,跟着物理盘走,不会因为探测顺序变化而改变,破坏性操作用它才安全。
一块 512e 硬盘上,分区从第 63 个扇区开始,会有什么问题?
起点 63 × 512 = 32256 字节不是 4096 的整数倍,分区内每个 4K 文件系统块都会横跨两个物理扇区。每次写一个 4K 块,盘都要对两个物理扇区做读-改-写,写性能大幅下降。现代工具默认从 1 MiB(第 2048 个扇区)开始即可避免。
PV、VG、LV、PE 分别是什么?一个 LV 能否跨越多块物理盘?
PV 是被 LVM 初始化的块设备;VG 是由一个或多个 PV 组成的存储池;PE 是 VG 中分配空间的最小单位(默认 4 MiB);LV 是从 VG 中分配若干 PE 组成的虚拟块设备。LV 可以由来自多个 PV 的 PE 组成,所以能跨越多块物理盘(默认线性拼接,这时任何一块盘坏都会影响该 LV)。
精简配置有什么风险?生产中如何防范?
精简配置允许卷的虚拟容量总和超过池的实际容量。一旦池的数据或元数据空间写满,池中所有精简卷的写入都会失败或挂起,文件系统可能损坏。防范措施:监控 data_percent 和 metadata_percent 并告警;配置 thin_pool_autoextend_threshold 和 thin_pool_autoextend_percent 自动扩展,并保证 VG 中有余量;定期 fstrim 回收已删除的空间。
传统 LVM 快照为什么会拖慢源卷?它的 COW 空间写满会怎样?
快照建立后,源卷上每个块第一次被修改前,LVM 都要先读出旧数据复制到快照的 COW 区,再写入新数据,一次写变成了多次 I/O,快照越多越慢。COW 区写满后快照立即失效,无法再使用,只能删除。