soarli

Ubuntu 服务器硬盘一键无损扩容:终极自动化脚本 (支持 LVM & NVMe)
前言:磁盘扩容的痛点对于运维和开发人员来说,云服务器或虚拟机(VMware/ESXi/PVE)磁盘空间告急是家常便...
扫描右侧二维码阅读全文
30
2026/01

Ubuntu 服务器硬盘一键无损扩容:终极自动化脚本 (支持 LVM & NVMe)

前言:磁盘扩容的痛点

对于运维和开发人员来说,云服务器或虚拟机(VMware/ESXi/PVE)磁盘空间告急是家常便饭。通常我们在云控制台或虚拟机设置里把硬盘从 50G 调整到了 100G,但进入系统执行 df -h 一看,可用空间依然没变。

传统的扩容步骤繁琐且容易出错,你需要:

  1. fdiskparted 修改分区表(如果不小心删错了分区,系统直接挂掉)。
  2. pvresize 刷新物理卷(如果是 LVM)。
  3. lvextend 扩展逻辑卷。
  4. resize2fsxfs_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 /

脚本核心原理解析

这个脚本做了六件事,确保全流程无死角:

  1. 环境自检:自动检测是否安装了核心工具 cloud-guest-utils(内含 growpart),如果没有则自动安装。
  2. LVM 识别:不依赖猜测,而是通过检查根目录挂载点是否包含 /mapper/ 以及使用 pvs 命令来精准判断是否启用了 LVM。
  3. 智能参数解析

    • 这是最难的一步。脚本会自动将 /dev/sda3 拆解为 磁盘=/dev/sda 和 分区号=3
    • 针对 NVMe 硬盘(如 /dev/nvme0n1p1),脚本有专门的逻辑去除中间的 p,确保 growpart 能正确识别设备路径。
  4. 物理层扩容:调用 growpart 修改分区表,将云厂商分配的新空间划入分区。
  5. 逻辑层扩容:如果是 LVM,依次执行 pvresize(让物理卷变大)和 lvextend(让逻辑卷变大)。
  6. 文件系统层扩容:最后根据 ext4xfs 类型,自动调用 resize2fsxfs_growfs,让 OS 真正看到这部分空间。

如何使用

1. 准备工作

首先,请确保你已经在云控制台(阿里云/腾讯云/AWS)或虚拟机管理界面(VMware/ESXi)将硬盘容量调大了。

2. 执行脚本

登录到 Ubuntu 服务器:

# 创建脚本
vim resize_disk.sh
# (粘贴上面的代码并保存退出)

# 赋予执行权限
chmod +x resize_disk.sh

# 运行 (需要 root 权限)
sudo ./resize_disk.sh

3. 查看结果

脚本运行结束后,会自动输出 df -h / 的结果,你应该能看到根目录空间已经成功增加了。


总结

磁盘扩容本应是一件简单的事。通过这个脚本,我们将原本需要输入 4-5 条复杂命令且还要心惊胆战防止输错参数的过程,简化成了一行 ./resize_disk.sh

最后修改:2026 年 01 月 30 日 02 : 53 PM

发表评论