前言:磁盘扩容的痛点
对于运维和开发人员来说,云服务器或虚拟机(VMware/ESXi/PVE)磁盘空间告急是家常便饭。通常我们在云控制台或虚拟机设置里把硬盘从 50G 调整到了 100G,但进入系统执行 df -h 一看,可用空间依然没变。
传统的扩容步骤繁琐且容易出错,你需要:
fdisk或parted修改分区表(如果不小心删错了分区,系统直接挂掉)。pvresize刷新物理卷(如果是 LVM)。lvextend扩展逻辑卷。resize2fs或xfs_growfs刷新文件系统。
只要中间一步敲错,可能就得去恢复快照了。为了解决这个问题,我编写了一个全自动、健壮的 Shell 脚本,能够自动识别 LVM 架构、自动处理 NVMe 硬盘命名,实现“一键无损扩容”。
为什么普通脚本会失败?
在编写这个脚本的过程中,我们踩过一个大坑(也是很多网上的脚本失效的原因):分区号的自动探测。
早期的脚本通常使用 lsblk 解析树状结构来寻找根目录对应的物理分区。但在 LVM 架构下,逻辑极其复杂,脚本很容易在提取 /dev/sda3 这种路径时,错误地提取为空值,导致 growpart 报错:
# 典型报错
must supply partition-number
[Error] growpart 执行失败。为了解决这个问题,V3 版脚本引入了更暴力的 pvs 直接探测法和正则匹配逻辑,完美兼容以下场景:
- 普通分区:直接挂载在
/dev/sda1上的根目录。 - LVM 架构:标准的 Ubuntu LVM 配置。
- NVMe 硬盘:识别
/dev/nvme0n1p1这种带p的特殊命名格式。
🛠️ 终极版扩容脚本 (resize_disk.sh)
⚠️ 高危操作警告:虽然脚本已经过多次验证且包含错误检查,但在生产环境执行任何磁盘操作前,请务必对服务器进行快照备份!
将以下内容保存为 resize_disk.sh:
#!/bin/bash
# =========================================================
# Ubuntu 自动磁盘扩容工具 V3 (Fix LVM detection & NVMe)
# =========================================================
# 定义颜色
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
echo -e "${CYAN}==============================================${NC}"
echo -e "${CYAN} Ubuntu Auto Disk Resize Utility V3 ${NC}"
echo -e "${CYAN}==============================================${NC}"
# --- 1. 权限检查 ---
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}[Error] 请使用 sudo 或 root 用户运行此脚本!${NC}"
exit 1
fi
# --- 2. 工具检查 (growpart) ---
echo -e "${GREEN}[1/6] 检查必要工具 (cloud-guest-utils)...${NC}"
if ! command -v growpart &> /dev/null; then
echo "正在安装 growpart..."
apt-get update -y > /dev/null 2>&1
apt-get install -y cloud-guest-utils > /dev/null 2>&1
else
echo "工具已安装。"
fi
# --- 3. 智能探测分区信息 ---
echo -e "${GREEN}[2/6] 正在探测磁盘架构...${NC}"
# 获取根挂载点
ROOT_MOUNT=$(findmnt -n -o SOURCE /)
echo "根分区设备: $ROOT_MOUNT"
IS_LVM=0
TARGET_PART=""
# 判断是否为 LVM
if [[ "$ROOT_MOUNT" == *"/mapper/"* ]] || lvs "$ROOT_MOUNT" >/dev/null 2>&1; then
IS_LVM=1
echo -e "${YELLOW}>> 检测到 LVM 架构${NC}"
# 获取 LVM 对应的物理卷 (PV)
# 使用 pvs 直接获取,排除干扰
TARGET_PART=$(pvs --noheadings -o pv_name | head -n 1 | tr -d ' ')
if [ -z "$TARGET_PART" ]; then
echo -e "${RED}[Error] 无法找到 LVM 对应的物理卷!请手动检查 'pvs' 命令输出。${NC}"
exit 1
fi
else
echo -e "${YELLOW}>> 检测到普通分区架构${NC}"
TARGET_PART=$ROOT_MOUNT
fi
echo "目标物理分区: $TARGET_PART"
# --- 4. 分离 磁盘路径 和 分区号 ---
# 兼容 /dev/sda3 和 /dev/nvme0n1p3 两种格式
# 提取数字结尾作为分区号
PART_NUM=$(echo "$TARGET_PART" | grep -o '[0-9]*$')
# 去掉结尾数字作为磁盘部分
DISK_DEV=${TARGET_PART%$PART_NUM}
# 特殊处理 NVMe (如果磁盘以 p 结尾,如 nvme0n1p,去掉那个 p)
if [[ "$DISK_DEV" == *"p" ]] && [[ "$DISK_DEV" == *"/nvme"* ]]; then
DISK_DEV=${DISK_DEV%p}
fi
echo -e ">> 解析结果: 磁盘=[${CYAN}$DISK_DEV${NC}] 分区号=[${CYAN}$PART_NUM${NC}]"
if [ -z "$DISK_DEV" ] || [ -z "$PART_NUM" ]; then
echo -e "${RED}[Error] 无法解析磁盘或分区号,脚本停止以保护数据。${NC}"
exit 1
fi
# --- 5. 执行物理扩容 (Growpart) ---
echo -e "${GREEN}[3/6] 扩展物理分区表 (growpart)...${NC}"
growpart "$DISK_DEV" "$PART_NUM"
GP_CODE=$?
if [ $GP_CODE -eq 0 ]; then
echo "分区表更新成功。"
elif [ $GP_CODE -eq 1 ]; then
echo -e "${YELLOW}[Info] 无需更新 (可能是已经扩容过了),继续尝试调整文件系统...${NC}"
else
echo -e "${RED}[Error] growpart 失败!返回码: $GP_CODE${NC}"
exit 1
fi
# --- 6. 扩展 LVM (如果是) ---
if [ $IS_LVM -eq 1 ]; then
echo -e "${GREEN}[4/6] 刷新 LVM 物理卷 (pvresize)...${NC}"
pvresize "$TARGET_PART"
echo -e "${GREEN}[5/6] 扩展 LVM 逻辑卷 (lvextend)...${NC}"
# 使用 -r 参数尝试同时调整文件系统
lvextend -l +100%FREE -r "$ROOT_MOUNT"
LVE_CODE=$?
if [ $LVE_CODE -eq 0 ]; then
echo "LVM 扩容完成。"
else
echo -e "${YELLOW}[Warning] lvextend 自动刷新文件系统失败,尝试手动刷新...${NC}"
fi
else
echo -e "${GREEN}[4/6] 非 LVM,跳过 PV/LV 操作。${NC}"
echo -e "${GREEN}[5/6] 准备文件系统扩容...${NC}"
fi
# --- 7. 再次确认文件系统 (兜底) ---
echo -e "${GREEN}[6/6] 验证并刷新文件系统...${NC}"
FS_TYPE=$(df -T / | tail -n 1 | awk '{print $2}')
echo "文件系统类型: $FS_TYPE"
case $FS_TYPE in
ext4|ext3|ext2)
resize2fs "$ROOT_MOUNT"
;;
xfs)
xfs_growfs /
;;
*)
echo -e "${YELLOW}[Info] 未知或已处理的文件系统,跳过。${NC}"
;;
esac
echo -e "${CYAN}==============================================${NC}"
echo -e "${GREEN} 所有操作完成! ${NC}"
echo -e "${CYAN}==============================================${NC}"
df -h /脚本核心原理解析
这个脚本做了六件事,确保全流程无死角:
- 环境自检:自动检测是否安装了核心工具
cloud-guest-utils(内含growpart),如果没有则自动安装。 - LVM 识别:不依赖猜测,而是通过检查根目录挂载点是否包含
/mapper/以及使用pvs命令来精准判断是否启用了 LVM。 智能参数解析:
- 这是最难的一步。脚本会自动将
/dev/sda3拆解为 磁盘=/dev/sda和 分区号=3。 - 针对 NVMe 硬盘(如
/dev/nvme0n1p1),脚本有专门的逻辑去除中间的p,确保growpart能正确识别设备路径。
- 这是最难的一步。脚本会自动将
- 物理层扩容:调用
growpart修改分区表,将云厂商分配的新空间划入分区。 - 逻辑层扩容:如果是 LVM,依次执行
pvresize(让物理卷变大)和lvextend(让逻辑卷变大)。 - 文件系统层扩容:最后根据
ext4或xfs类型,自动调用resize2fs或xfs_growfs,让 OS 真正看到这部分空间。
如何使用
1. 准备工作
首先,请确保你已经在云控制台(阿里云/腾讯云/AWS)或虚拟机管理界面(VMware/ESXi)将硬盘容量调大了。
2. 执行脚本
登录到 Ubuntu 服务器:
# 创建脚本
vim resize_disk.sh
# (粘贴上面的代码并保存退出)
# 赋予执行权限
chmod +x resize_disk.sh
# 运行 (需要 root 权限)
sudo ./resize_disk.sh3. 查看结果
脚本运行结束后,会自动输出 df -h / 的结果,你应该能看到根目录空间已经成功增加了。
总结
磁盘扩容本应是一件简单的事。通过这个脚本,我们将原本需要输入 4-5 条复杂命令且还要心惊胆战防止输错参数的过程,简化成了一行 ./resize_disk.sh。
版权属于:soarli
本文链接:https://blog.soarli.top/archives/781.html
转载时须注明出处及本声明。