Appearance
08|磁盘管理
服务跑着跑着突然报错"no space left on device"——磁盘满了。加了块新盘想用,却找不到在哪。扩容后空间还是没变。这些是磁盘运维的高频问题。
磁盘相关的概念有层次——从底层的物理设备到上层的挂载点,中间可能还有 LVM、RAID。理不清这些层次,排查磁盘问题时容易思路混乱。本篇按从底层到上层的顺序梳理:设备 → 分区 → 文件系统 → 挂载点,再讲空间占用排查和扩容。
一、概念层级
排查磁盘问题时,以下四个概念容易混淆,先明确区分:
| 概念 | 含义 | 示例 |
|---|---|---|
| 设备(device) | 系统识别到的物理或虚拟磁盘 | /dev/sdb |
| 分区(partition) | 在磁盘上划分出的独立区域 | /dev/sdb1 |
| 文件系统(filesystem) | 组织数据的方式,决定文件如何存储与查找 | xfs、ext4、btrfs |
| 挂载点(mountpoint) | 文件系统接入目录树的位置 | /data、/mnt/backup |
排查磁盘问题时的标准顺序是从下往上:系统有没有识别到这块盘 → 分区表是否正确 → 文件系统是否创建 → 是否已挂载。任何一层断开,上层都失去意义。
查看块设备与挂载关系
bash
lsblk # 块设备树:设备→分区→挂载点,最直观的总览
blkid # 查看分区的 UUID 和文件系统类型
df -h # 各挂载点的空间使用情况
df -i # 各挂载点的 inode 使用情况lsblk 是磁盘总览最好的工具,一条命令展示设备、分区、挂载点三层关系:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 100G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 99G 0 part
├─vg-root 253:0 0 50G 0 lvm /
└─vg-data 253:1 0 49G 0 lvm /data
sdb 8:16 0 200G 0 disk
└─sdb1 8:17 0 200G 0 part /mnt/backupinode 耗尽:空间未满却写不进文件
df -h 看的是数据块空间,df -i 看的是 inode(文件元数据)的使用情况。两者是独立的资源——空间和 inode 都可能单独耗尽。
一个经典故障:df -h 显示某分区还有大量空闲空间,但应用却报"No space left on device"无法写入文件。这种情况几乎都是 inode 用尽:
bash
$ df -h /var
Filesystem Size Used Avail Use% Mounted on
/dev/vg-data 50G 20G 30G 40% /var
$ df -i /var
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vg-data 3.2M 3.2M 0 100% /var ← inode 100% 是问题所在通常的原因是某个目录积累了几百万个小文件——每个文件占用一个 inode,空间没用多少但 inode 耗尽。常见场景:
- session 文件、缓存文件未清理
- mail 队列、错误日志按文件粒度堆积
- 大量小图片、小日志文件
定位 inode 占用大户:
bash
# 找出 /var 下文件数最多的几个子目录
find /var -xdev -type f | awk -F/ '{print $1"/"$2"/"$3}' | sort | uniq -c | sort -nr | head二、分区
MBR 与 GPT
分区表记录磁盘上每个分区的起止位置,有两种格式:
| 格式 | 容量上限 | 分区数上限 | 备份分区表 |
|---|---|---|---|
| MBR(DOS) | 2 TB | 4 个主分区(或 3 主 + 多个逻辑) | 无 |
| GPT | EB 级 | 默认 128 个 | 有(主分区表损坏时可从备份恢复) |
新部署的服务器与大容量磁盘都使用 GPT。MBR 仅在维护历史系统时还会遇到。
查看分区
bash
fdisk -l # 查看所有磁盘的分区(同时支持 MBR 和 GPT)
parted -l # 信息更详细,操作 GPT 更方便给新磁盘分区的标准流程
以 parted 为例,把一块新磁盘 /dev/sdb 划分为一个占满整盘的 GPT 分区:
bash
# 第 1 步:确认设备名(防止操作错磁盘)
lsblk
# 第 2 步:进入 parted 交互模式
parted /dev/sdb在 parted 提示符下:
(parted) mklabel gpt # 创建 GPT 分区表(注意:会清空原有分区表)
(parted) mkpart primary 0% 100% # 创建占满整盘的主分区
(parted) print # 查看当前分区表,确认结果
(parted) quit通知内核重新扫描分区表
分区操作完成后,内核可能尚未感知新的分区。手动通知内核重新读取:
bash
partprobe /dev/sdb
lsblk # 确认 /dev/sdb1 已出现云环境的设备名陷阱
某些云主机的设备名在重启后可能发生变化——本次启动是 /dev/sdb,重启后可能变成 /dev/sdc。因此挂载配置不应当依赖设备名,而应当使用 UUID。UUID 是文件系统格式化时生成的唯一标识,与设备名解耦,不会因为设备顺序变化而失效。这也是 /etc/fstab 推荐使用 UUID 的根本原因。
三、格式化:创建文件系统
在分区上创建文件系统:
bash
mkfs.xfs /dev/sdb1
mkfs.ext4 /dev/sdb1文件系统的选择
xfs 与 ext4 是两个最主流的选择:
| 文件系统 | 特点 | 适用场景 |
|---|---|---|
| xfs | RHEL 7+ 默认,大文件性能优秀,仅支持在线扩容,不支持缩容 | 大文件、日志、数据库存储 |
| ext4 | Debian / Ubuntu 默认,通用性强,支持在线扩容、卸载后可缩容 | 通用场景 |
| btrfs | 支持快照、子卷,功能丰富 | 对快照、压缩有需求的场景 |
| zfs | 强大的卷管理与数据完整性,但 Linux 上需额外模块 | 存储服务器 |
格式化操作的不可逆性
mkfs 会清空分区上的全部数据,该操作不可逆。在生产环境执行 mkfs 之前,必须再次确认目标设备:
bash
# 1. 查看当前文件系统类型与挂载状态
lsblk -f
# 2. 确认目标分区未被挂载
mount | grep sdb1
# 3. 确认无误后执行
mkfs.xfs /dev/sdb1"把正在跑业务的盘 mkfs 了导致数据全部丢失"是真实发生过的事故。多花几秒钟确认设备路径,远比事后数据恢复成本低。
四、挂载与 /etc/fstab
手动挂载
bash
mkdir -p /data
mount /dev/sdb1 /data
df -h /data手动 mount 在重启后失效——分区需要在每次启动时自动挂载,必须写入 /etc/fstab。
持久化挂载:/etc/fstab
获取分区的 UUID:
bash
$ blkid /dev/sdb1
/dev/sdb1: UUID="a1b2c3d4-5678-90ab-cdef-1234567890ab" TYPE="xfs"在 /etc/fstab 中添加一行:
UUID=a1b2c3d4-5678-90ab-cdef-1234567890ab /data xfs defaults 0 0六列字段含义:
| 列 | 字段 | 说明 |
|---|---|---|
| 1 | 设备标识 | 推荐使用 UUID,而非 /dev/sdb1 设备路径 |
| 2 | 挂载点 | 目标目录 |
| 3 | 文件系统类型 | xfs、ext4 等 |
| 4 | 挂载选项 | defaults 是一组默认参数;也可用 noatime,nodiratime 等 |
| 5 | dump 备份标志 | 通常为 0(已废弃) |
| 6 | fsck 检查顺序 | 根分区为 1,其他分区为 2,0 表示跳过 |
写完 fstab 必须先用 mount -a 测试
修改 /etc/fstab 后,务必先执行 mount -a 验证语法:
bash
mount -amount -a 会按 fstab 挂载所有未挂载的条目。如果 fstab 中存在语法错误,这条命令会立即报错,可以当场修改。
跳过测试直接重启的风险:fstab 写错可能导致系统进入 emergency mode(紧急模式),正常登录都无法进行,必须通过控制台或救援盘修复。这一步测试只需几秒钟,但能避免一次严重故障。
验证挂载与持久化
bash
mount -a # 验证 fstab 语法
df -h /data # 确认挂载成功
cat /proc/mounts | grep /data # 通过 /proc 验证内核视角的挂载五、SWAP:磁盘充当应急内存
SWAP 是磁盘上划出的一块区域,当物理内存不足时,内核会把不活跃的内存页换出到 SWAP,腾出物理内存给更需要的进程。本质上是用磁盘充当"溢出的内存",由于磁盘速度比物理内存慢几个数量级,SWAP 只能用于应急,不能作为常态资源依赖。
查看 SWAP 状态
bash
free -h # 查看内存与 SWAP 使用情况
swapon --show # 查看当前所有 SWAP 设备输出示例:
$ free -h
total used free shared buff/cache available
Mem: 7.7G 3.1G 2.4G 100M 2.2G 4.2G
Swap: 2.0G 100M 1.9G创建 SWAP 文件
bash
# 第 1 步:创建 2GB 的文件
fallocate -l 2G /swapfile
# 第 2 步:权限收紧到 600(SWAP 文件可能包含敏感的内存内容)
chmod 600 /swapfile
# 第 3 步:格式化为 SWAP
mkswap /swapfile
# 第 4 步:启用
swapon /swapfile
# 第 5 步:验证
swapon --show
free -h持久化:写入 /etc/fstab
/swapfile none swap sw 0 0数据库服务器的 SWAP 策略
MySQL、PostgreSQL、Redis 这类数据库服务通常不希望依赖 SWAP。数据库一旦开始大量换出页面到 SWAP,性能会断崖式下跌——本来毫秒级的查询会变成秒级,业务无法承受。
偶尔有少量页面被换出是正常的,但如果观察到 SWAP 在持续频繁活动(thrashing),说明物理内存严重不足,正确的应对是增加物理内存或优化应用配置(如调小 InnoDB buffer pool),而不是放任 SWAP 缓解。
可以通过 vmstat 观察 SWAP 活动:
bash
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 100M 2.4G 200M 2.2G 0 0 0 5 150 300 5 2 92 0 1si(swap in)与 so(swap out)持续大于 0 表示 SWAP 正在被频繁读写。
调整 swappiness
vm.swappiness 控制内核倾向于使用 SWAP 的程度,值越大越倾向于换出。数据库服务器通常调低这一参数:
bash
# 查看当前值(默认 60)
cat /proc/sys/vm/swappiness
# 临时设为 10
sysctl vm.swappiness=10
# 持久化
echo "vm.swappiness=10" >> /etc/sysctl.d/99-swappiness.conf六、LVM:逻辑卷管理
LVM(Logical Volume Manager)在物理磁盘和文件系统之间引入了一层抽象。最大的价值是支持动态扩容——可以在不停止服务、不卸载文件系统的前提下扩展存储空间。在物理服务器环境(云环境的云盘本身就支持在线扩容)中,LVM 几乎是标配。
三层结构
LVM 由三层抽象构成:
物理磁盘/分区 → PV(Physical Volume,物理卷)
↓
VG(Volume Group,卷组) ← 多个 PV 组成的存储池
↓
LV(Logical Volume,逻辑卷) ← 从 VG 中划出的逻辑空间
↓
文件系统 ← 建立在 LV 上创建 LVM 的完整流程
bash
# 第 1 步:把分区初始化为物理卷
pvcreate /dev/sdb1
# 第 2 步:创建卷组 vg_data,包含 /dev/sdb1
vgcreate vg_data /dev/sdb1
# 第 3 步:从 vg_data 中创建逻辑卷 lv_data,占用全部剩余空间
lvcreate -n lv_data -l 100%FREE vg_data
# 第 4 步:在逻辑卷上创建文件系统
mkfs.xfs /dev/vg_data/lv_data
# 第 5 步:挂载
mkdir /data
mount /dev/vg_data/lv_data /dataLVM 在线扩容流程
假设业务运行一段时间后存储不足,加了一块新磁盘 /dev/sdc,要给 /data 扩容:
bash
# 第 1 步:分区(可选,也可以直接对整盘做 pvcreate)
parted /dev/sdc mklabel gpt mkpart primary 0% 100%
# 第 2 步:把新分区初始化为 PV
pvcreate /dev/sdc1
# 第 3 步:把新 PV 加入卷组(VG 容量增加)
vgextend vg_data /dev/sdc1
# 第 4 步:扩展 LV,并同步扩展文件系统
lvextend -r -l +100%FREE /dev/vg_data/lv_data-r 参数极其关键——它会在扩展逻辑卷的同时,自动调用对应文件系统的扩容工具(xfs 用 xfs_growfs,ext4 用 resize2fs)。漏掉 -r 的后果是:逻辑卷确实扩大了,但文件系统仍然停留在原大小,df -h 看到的空间没有任何变化。
如果漏掉了 -r,事后单独扩文件系统:
bash
# XFS
xfs_growfs /data
# ext4
resize2fs /dev/vg_data/lv_dataLVM 状态查看
bash
pvs # 查看所有物理卷
vgs # 查看所有卷组
lvs # 查看所有逻辑卷
pvdisplay # 物理卷的详细信息
vgdisplay # 卷组的详细信息
lvdisplay # 逻辑卷的详细信息LVM 缩容的困难
LVM 扩容方便,缩容麻烦。原因在于:
- xfs 完全不支持缩容
- ext4 支持缩容,但必须先卸载文件系统(
umount),业务必须停服
因此规划存储时,"以后不够再扩"是合理假设,"以后过大再缩"不是。空间宁可一开始规划宽松,也不要预留过紧导致后期被迫做缩容操作。
七、RAID 简介
RAID(Redundant Array of Independent Disks)用多块磁盘组合成逻辑存储,提供性能提升或数据冗余。常见级别:
| 级别 | 特点 | 容量利用率 | 适用场景 |
|---|---|---|---|
| RAID 0 | 条带化(数据分散到多块盘) | 100% | 性能优先,无冗余,坏一块盘数据全失 |
| RAID 1 | 镜像(两块盘内容完全一致) | 50% | 系统盘、重要数据 |
| RAID 5 | 分布式奇偶校验,可坏 1 块盘 | (N-1)/N | 一般业务存储,需 ≥3 块盘 |
| RAID 10 | 先镜像再条带化 | 50% | 高性能 + 高可靠,需 ≥4 块盘 |
Linux 软件 RAID
Linux 通过 mdadm 管理软件 RAID:
bash
cat /proc/mdstat # 查看当前 RAID 状态
mdadm --detail /dev/md0 # 查看指定 RAID 设备的详细信息cat /proc/mdstat 输出示例:
md0 : active raid1 sdb1[0] sdc1[1]
209715200 blocks super 1.2 [2/2] [UU][UU] 表示两块盘都健康,如果出现 [U_] 则表示某块盘故障(降级状态)。
云环境通常无需自建 RAID
在公有云环境中,通常不需要在虚拟机层面再做 RAID。云平台底层的块存储已经做了冗余:
- AWS EBS、阿里云 ESSD 等使用三副本或纠删码
- 块设备故障由云平台底层处理,对用户透明
在云盘上再叠加一层软件 RAID,既不能解决底层物理故障(因为已经被云平台处理),又会增加复杂度和管理成本。RAID 主要应用于物理服务器与本地存储场景。
八、空间占用排查
磁盘告警的标准排查流程分两步:定位高占用目录 → 找到具体大文件。
bash
# 第 1 步:确认是哪个挂载点空间不足
df -h
# 第 2 步:从根目录开始,定位占用最多的子目录
du -sh /* 2>/dev/null | sort -hr | head -10
# 第 3 步:逐层深入(假设定位到 /var)
du -sh /var/* 2>/dev/null | sort -hr | head -10
du -sh /var/log/* 2>/dev/null | sort -hr | head -10
# 第 4 步:直接列出大于 1G 的文件
find /var -type f -size +1G -exec ls -lh {} \; 2>/dev/nulldu 与 df 数值不一致
有时候 du -sh /var 与 df 显示的 /var 占用相差很大,常见原因:
- 被删除但仍被进程持有的文件(下面详述)
- 跨文件系统的子目录——
du -s /var不会跨越文件系统,但df显示的是整个文件系统的占用 - 挂载点被其他文件系统遮蔽——某个挂载点下原本有文件,挂载新文件系统后被遮蔽,
du看不到但仍占用空间
第 3 种情况的验证:
bash
# 临时挂载到其他目录看一眼原内容
mkdir /mnt/check
mount --bind / /mnt/check
du -sh /mnt/check/var
umount /mnt/check已删除文件但空间未释放
典型现象:已经执行了 rm 删除一个大文件,df -h 显示空间没有任何减少。
原因:该文件仍被某个进程持有文件描述符。文件名从目录树中消失了,但 inode 引用计数未归零(因为进程还在引用),文件系统不会真正释放空间。
定位:lsof 中带 (deleted) 标记的文件即为这种情况:
bash
$ lsof | grep deleted
nginx 12345 www-data 3w REG 8,1 10737418240 789012 /var/log/nginx/access.log (deleted)输出显示 nginx(PID 12345)仍以写模式持有 /var/log/nginx/access.log 这个已删除的文件,占用 10GB。
解决方法:
bash
# 方法 1:让进程重新打开日志文件(nginx 支持 reload 触发日志重打开)
systemctl reload nginx
# 方法 2:重启占用文件的进程
systemctl restart some-service
# 方法 3:如果无法重启服务,可以通过 /proc 截断文件
# 注意:这只是把文件大小置零,进程仍可继续写,但已删除的旧数据空间会释放
: > /proc/12345/fd/3正确的预防方式是先清空再删除——> /var/log/nginx/access.log 比 rm 更安全,因为不会让 inode 失去名称。或者通过 logrotate 的 copytruncate 配置避免这一问题(详见 第 11 讲 日志管理)。