大数跨境

LVM逻辑卷在线扩容实战:零停机扩展磁盘空间

LVM逻辑卷在线扩容实战:零停机扩展磁盘空间 马哥Linux运维
2026-01-08
16
导读:概述干了这么多年运维,磁盘空间不够用这事儿估计每个人都遇到过。半夜三点被告警电话叫醒,一看是数据库服务器磁盘快满了,这种经历真的是刻骨铭心。

概述

干了这么多年运维,磁盘空间不够用这事儿估计每个人都遇到过。半夜三点被告警电话叫醒,一看是数据库服务器磁盘快满了,这种经历真的是刻骨铭心。早些年没用LVM的时候,扩容意味着停机、备份、重新分区、恢复数据,一套流程下来少说几个小时,业务方那边催得你脑仁疼。

后来全面上了LVM,这种痛苦就少多了。LVM这东西说白了就是在物理磁盘和文件系统之间加了一层抽象,让磁盘空间的管理变得灵活很多。最爽的一点就是可以在线扩容,业务不用停,数据不用动,加完磁盘敲几条命令就搞定。

这篇文章我会把LVM在线扩容的方方面面都讲透,从基础原理到实战操作,从最佳实践到故障排查,都是这些年踩坑总结出来的经验。内容比较长,建议收藏慢慢看。

为什么选择LVM

在聊扩容之前,先说说为什么要用LVM。传统的磁盘分区方式有几个致命问题:

第一,分区大小固定。一旦分区建好,想扩容基本上要动到整个分区表,风险极高。我见过有人直接用fdisk改分区大小,结果把分区表搞坏了,数据全丢。

第二,跨磁盘不方便。如果一个分区需要100G,但是手头只有两块60G的盘,传统分区方式根本没法搞。只能做RAID0或者分成两个挂载点,都不是很优雅的解决方案。

第三,扩容必须停机。这个在生产环境简直是噩梦。现在的互联网业务哪能说停就停,一分钟的停机可能就是几十万的损失。

LVM把这些问题都解决了。它的核心思想是把物理磁盘抽象成物理卷(PV),多个物理卷组成卷组(VG),然后从卷组里划出逻辑卷(LV)给上层使用。这样一来,扩容就变得很简单:加磁盘、创建PV、扩展VG、扩展LV、扩展文件系统,全程不用停服务。

LVM架构基础

在动手之前,先把LVM的几个核心概念搞清楚,不然后面操作的时候容易懵。

物理卷(Physical Volume, PV)

物理卷是LVM的最底层,可以是整块磁盘,也可以是磁盘上的一个分区。一块磁盘要被LVM使用,首先得初始化成物理卷。这个过程会在磁盘开头写入LVM的元数据,标记这块盘已经是LVM的一部分了。

卷组(Volume Group, VG)

卷组是物理卷的集合,你可以把它理解成一个大的存储池。多个物理卷加入同一个卷组后,它们的空间就合并在一起了。后续创建逻辑卷就是从这个池子里划空间出来。

逻辑卷(Logical Volume, LV)

逻辑卷就是最终呈现给上层应用使用的块设备,你可以在上面创建文件系统、挂载使用。逻辑卷的大小可以随时调整,这就是LVM灵活性的核心所在。

物理区块(Physical Extent, PE)

PE是LVM分配空间的最小单位,默认是4MB。当你创建一个100MB的逻辑卷时,实际上是分配了25个PE。这个粒度在绝大多数场景下都够用,但如果你需要更精细的控制,创建卷组的时候可以指定更小的PE大小。

这几个概念的关系可以这么理解:物理磁盘(或分区)→ 物理卷 → 卷组 → 逻辑卷 → 文件系统。扩容的时候就是反过来:添加磁盘 → 创建PV → 扩展VG → 扩展LV → 扩展文件系统。

在线扩容的前提条件

不是所有情况都能在线扩容,有几个前提条件需要满足:

  1. 文件系统支持在线扩容:ext4、xfs这些主流文件系统都支持,但是老的ext2就不行。xfs甚至只能扩大不能缩小,这点要注意。

  2. 内核版本支持:现在主流的Linux发行版内核都没问题,RHEL 7以上、Ubuntu 16.04以上都OK。

  3. 有可用的磁盘空间:这是废话,但确实有人问过我"磁盘满了能不能在线扩容",兄弟,你得先有盘加进来啊。

  4. LV不是快照卷:如果逻辑卷有活跃的快照,扩容可能会有问题。建议先删除快照再扩容。

详细步骤

下面进入实操环节。我会按照实际工作中最常见的场景来讲,就是给虚拟机加一块新磁盘,然后扩容现有的逻辑卷。

场景说明

假设我们有一台CentOS 8的服务器(其实Rocky Linux 9、AlmaLinux 9操作都一样),现有一个100G的逻辑卷挂载在/data目录,现在空间不够用了,需要再加100G。

当前的存储结构是这样的:

  • 物理磁盘:/dev/sdb(100G)
  • 物理卷:/dev/sdb
  • 卷组:data_vg
  • 逻辑卷:data_lv(100G)
  • 挂载点:/data
  • 文件系统:xfs

现在要添加一块新磁盘/dev/sdc(100G),把/data扩到200G。

第一步:确认新磁盘已识别

在虚拟化环境下加完磁盘后,有时候系统不会自动识别,需要手动扫描一下。

# 查看当前磁盘
lsblk

# 如果看不到新磁盘,扫描SCSI总线
echo "- - -" > /sys/class/scsi_host/host0/scan
echo "- - -" > /sys/class/scsi_host/host1/scan
echo "- - -" > /sys/class/scsi_host/host2/scan

# 或者用这个更暴力的方式,扫描所有host
for host in /sys/class/scsi_host/host*; do
    echo "- - -" > ${host}/scan
done

# 再次确认
lsblk

正常情况下你应该能看到新加的/dev/sdc了:

NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda               8:0    0   50G  0 disk
├─sda1            8:1    0    1G  0 part /boot
└─sda2            8:2    0   49G  0 part
  ├─centos-root 253:0    0   45G  0 lvm  /
  └─centos-swap 253:1    0    4G  0 lvm  [SWAP]
sdb               8:16   0  100G  0 disk
└─data_vg-data_lv
                253:2    0  100G  0 lvm  /data
sdc               8:32   0  100G  0 disk

这里sdc就是我们新加的磁盘,还没有任何分区。

第二步:创建物理卷

把新磁盘初始化为LVM物理卷:

# 创建物理卷
pvcreate /dev/sdc

# 输出类似这样:
# Physical volume "/dev/sdc" successfully created.

# 确认物理卷创建成功
pvs

# 输出:
#   PV         VG      Fmt  Attr PSize   PFree
#   /dev/sda2  centos  lvm2 a--  <49.00g      0
#   /dev/sdb   data_vg lvm2 a--  <100.00g     0
#   /dev/sdc           lvm2 a--  <100.00g <100.00g

# 查看详细信息
pvdisplay /dev/sdc

pvdisplay会输出更详细的信息:

  "/dev/sdc" is a new physical volume of "<100.00 GiB"
  --- NEW Physical volume ---
  PV Name               /dev/sdc
  VG Name
  PV Size               <100.00 GiB
  Allocatable           NO
  PE Size               0
  Total PE              0
  Free PE               0
  Allocated PE          0
  PV UUID               xxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxxxx

注意看VG Name那里是空的,说明这个物理卷还没加入任何卷组。

第三步:扩展卷组

把新创建的物理卷加入到现有的卷组:

# 扩展卷组
vgextend data_vg /dev/sdc

# 输出:
# Volume group "data_vg" successfully extended

# 确认卷组扩展成功
vgs

# 输出:
#   VG      #PV #LV #SN Attr   VSize   VFree
#   centos    1   2   0 wz--n- <49.00g       0
#   data_vg   2   1   0 wz--n- 199.99g <100.00g

# 查看详细信息
vgdisplay data_vg

vgdisplay输出:

  --- Volume group ---
  VG Name               data_vg
  System ID
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  3
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                1
  Open LV               1
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               199.99 GiB
  PE Size               4.00 MiB
  Total PE              51198
  Alloc PE / Size       25599 / <100.00 GiB
  Free  PE / Size       25599 / <100.00 GiB
  VG UUID               xxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxxxx

可以看到VG Size已经变成200G了,Free PE / Size显示有100G的可用空间。

第四步:扩展逻辑卷

现在卷组有了空闲空间,可以扩展逻辑卷了。这一步有几种不同的写法:

# 方式一:指定要扩展的大小
lvextend -L +100G /dev/data_vg/data_lv

# 方式二:指定扩展后的总大小
lvextend -L 200G /dev/data_vg/data_lv

# 方式三:使用卷组中所有剩余空间
lvextend -l +100%FREE /dev/data_vg/data_lv

# 方式四:扩展到卷组的某个百分比
lvextend -l 100%VG /dev/data_vg/data_lv

我个人比较喜欢用方式三,简单直接,不用算具体数字。执行后输出:

  Size of logical volume data_vg/data_lv changed from <100.00 GiB (25599 extents) to 199.99 GiB (51198 extents).
  Logical volume data_vg/data_lv successfully resized.

确认一下:

lvs

# 输出:
#   LV      VG      Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
#   root    centos  -wi-ao---- <45.00g
#   swap    centos  -wi-ao----   4.00g
#   data_lv data_vg -wi-ao---- 199.99g

逻辑卷已经扩到200G了,但这时候文件系统还是100G,还需要最后一步。

第五步:扩展文件系统

这一步根据文件系统类型不同,用的命令也不一样。

对于XFS文件系统:

# xfs用xfs_growfs,后面跟挂载点
xfs_growfs /data

# 输出类似:
# meta-data=/dev/mapper/data_vg-data_lv isize=512    agcount=4, agsize=6553344 blks
#          =                       sectsz=512   attr=2, projid32bit=1
#          =                       crc=1        finobt=1, sparse=1, rmapbt=0
#          =                       reflink=1
# data     =                       bsize=4096   blocks=26213376, imaxpct=25
#          =                       sunit=0      swidth=0 blks
# naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
# log      =internal log           bsize=4096   blocks=12799, version=2
#          =                       sectsz=512   sunit=0 blks, lazy-count=1
# realtime =none                   extsz=4096   blocks=0, rtextents=0
# data blocks changed from 26213376 to 52426752

对于EXT4文件系统:

# ext4用resize2fs,后面跟设备路径
resize2fs /dev/data_vg/data_lv

# 输出类似:
# resize2fs 1.45.6 (20-Mar-2020)
# Filesystem at /dev/data_vg/data_lv is mounted on /data; on-line resizing required
# old_desc_blocks = 13, new_desc_blocks = 25
# The filesystem on /dev/data_vg/data_lv is now 52426752 (4k) blocks long.

验证扩容结果:

df -h /data

# 输出:
# Filesystem                  Size  Used Avail Use% Mounted on
# /dev/mapper/data_vg-data_lv 200G   50G  150G  25% /data

完美,/data已经变成200G了,整个过程业务完全无感知。

一条命令搞定扩容

其实从2.02.98版本开始,lvextend命令支持一个-r参数,可以在扩展逻辑卷的同时自动扩展文件系统,省去了单独执行resize2fs或xfs_growfs的步骤:

# 扩展逻辑卷的同时扩展文件系统
lvextend -r -l +100%FREE /dev/data_vg/data_lv

# 或者
lvextend --resizefs -l +100%FREE /dev/data_vg/data_lv

这个-r参数会自动检测文件系统类型并调用对应的扩容命令,非常方便。现在我基本上都用这种方式,能少敲一条命令就少敲一条。

更多实战场景

上面讲的是最基础的场景,实际工作中还会遇到各种变体情况,下面一一说明。

场景一:扩展现有磁盘而非添加新磁盘

在虚拟化环境中,有时候不是添加新磁盘,而是直接扩展现有磁盘的大小。比如把原来100G的虚拟磁盘扩到200G。这种情况下操作稍微不一样。

# 首先让系统识别到磁盘大小变化
# 对于SCSI磁盘
echo 1 > /sys/block/sdb/device/rescan

# 确认磁盘大小已更新
lsblk
# 应该能看到sdb变成200G了

# 扩展物理卷
pvresize /dev/sdb

# 输出:
# Physical volume "/dev/sdb" changed
# 1 physical volume(s) resized or updated / 0 physical volume(s) not resized

# 确认
pvs
# 应该能看到sdb的大小已经变成200G

# 后续步骤和前面一样,扩展LV和文件系统
lvextend -r -l +100%FREE /dev/data_vg/data_lv

这种方式比添加新磁盘更干净,物理卷数量不会增加。但要注意,pvresize默认会使用磁盘上所有可用空间,如果你只想用一部分,需要用--setphysicalvolumesize参数指定大小。

场景二:磁盘有分区的情况

有些环境下,物理卷不是直接建在整块磁盘上,而是建在分区上。这种情况扩容就复杂一些。

假设/dev/sdb分成了一个分区/dev/sdb1,物理卷建在sdb1上。现在把sdb从100G扩到200G。

# 让系统识别磁盘大小变化
echo 1 > /sys/block/sdb/device/rescan

# 查看分区情况
fdisk -l /dev/sdb
# 会看到sdb是200G,但sdb1还是100G

# 这时候需要扩展分区
# 使用growpart工具(需要安装cloud-utils-growpart包)
growpart /dev/sdb 1

# 或者用parted
parted /dev/sdb
# 进入parted后
(parted) resizepart 1 100%
(parted) quit

# 刷新分区表
partprobe /dev/sdb

# 确认分区已扩展
lsblk
# sdb1应该变成200G了

# 扩展物理卷
pvresize /dev/sdb1

# 后续步骤一样
lvextend -r -l +100%FREE /dev/data_vg/data_lv

说实话,我个人不太喜欢在分区上建PV,直接用整块磁盘更简单。但有些老系统就是这么设计的,你也没办法。

场景三:根分区扩容

根分区扩容是最常见也最让人紧张的场景。因为根分区不能卸载,操作不当可能导致系统无法启动。

好消息是,如果根分区是ext4或xfs,在线扩容是完全支持的。

# 假设根分区是 /dev/centos/root

# 添加新磁盘/dev/sdc后
pvcreate /dev/sdc
vgextend centos /dev/sdc
lvextend -r -l +100%FREE /dev/centos/root

# 就这么简单

不过要注意几点:

  1. 在RHEL/CentOS 7和8上,如果根分区是xfs,用xfs_growfs扩容时后面要跟"/"而不是设备路径。
  2. 某些系统根分区可能用了LVM thin provisioning,扩容方式略有不同。
  3. 如果根分区空间已经100%满了,可能连lvextend命令都执行不了(因为LVM操作需要临时空间)。这时候需要先清理一些空间出来。

场景四:LVM thin provisioning扩容

Thin provisioning是LVM的高级特性,可以实现存储空间的超额分配。理解起来就是你可以创建一个1TB的逻辑卷,但实际只占用10G空间,随着数据写入动态增长。

thin provisioning涉及thin pool和thin volume两层,扩容也是两步:

# 假设thin pool是 data_vg/data_pool
# thin volume是 data_vg/data_thin_lv

# 先扩展thin pool
lvextend -l +100%FREE data_vg/data_pool

# 再扩展thin volume(这一步其实是修改元数据,很快)
lvextend -L 500G data_vg/data_thin_lv

# 最后扩展文件系统
# xfs
xfs_growfs /data
# 或 ext4
resize2fs /dev/data_vg/data_thin_lv

thin provisioning的坑比较多,尤其是thin pool满了之后的处理很麻烦,这个以后可以单独写一篇。

场景五:使用系统自带磁盘的剩余空间

有些虚拟机模板默认只用了磁盘的一部分空间做LVM,剩余空间没有使用。这种情况不需要加新盘,直接用现有磁盘的剩余空间就行。

# 查看磁盘分区情况
fdisk -l /dev/sda
# 假设sda有100G,但只有sda1和sda2用了50G

# 创建新分区
fdisk /dev/sda
# 输入n创建新分区,选择默认值使用所有剩余空间
# 输入t修改分区类型为8e(Linux LVM)
# 输入w保存退出

# 刷新分区表
partprobe /dev/sda

# 创建物理卷
pvcreate /dev/sda3

# 扩展卷组
vgextend centos /dev/sda3

# 扩展逻辑卷
lvextend -r -l +100%FREE /dev/centos/root

这种方式在云环境下特别常见,因为很多云镜像默认就是这么做的。

实战案例

讲了这么多理论,来几个真实的生产案例。

案例一:MySQL数据目录扩容

这是我遇到过最多的场景。数据库跑着跑着数据量上来了,/var/lib/mysql挂载的分区快满了。

环境信息:

  • OS: Rocky Linux 9.2
  • MySQL: 8.0.35
  • 当前/var/lib/mysql:500G,使用率85%
  • 需要扩到1TB

操作记录:

# 1. 查看当前状态
df -h /var/lib/mysql
# Filesystem                    Size  Used Avail Use% Mounted on
# /dev/mapper/mysql_vg-mysql_lv 500G  425G   75G  85% /var/lib/mysql

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
#   ├─rl-root             253:0    0   50G  0 lvm  /
#   └─rl-swap             253:1    0    4G  0 lvm  [SWAP]
# sdb                       8:16   0  500G  0 disk
# └─mysql_vg-mysql_lv     253:2    0  500G  0 lvm  /var/lib/mysql

# 2. 虚拟化平台添加500G新磁盘

# 3. 扫描新磁盘
for host in /sys/class/scsi_host/host*; do
    echo "- - -" > ${host}/scan
done

lsblk
# 确认sdc出现

# 4. 执行扩容
pvcreate /dev/sdc
vgextend mysql_vg /dev/sdc
lvextend -r -l +100%FREE /dev/mysql_vg/mysql_lv

# 5. 验证
df -h /var/lib/mysql
# Filesystem                    Size  Used Avail Use% Mounted on
# /dev/mapper/mysql_vg-mysql_lv 1.0T  425G  575G  43% /var/lib/mysql

整个过程MySQL一直在正常运行,没有任何感知。我当时还在跑着一个大查询,完全没受影响。

案例二:批量扩容脚本

当你管理几百台服务器的时候,手动一台台扩容太慢了。这是我写的一个批量扩容脚本:

#!/bin/bash
#
# LVM在线扩容脚本
# 用法: lvm_extend.sh <设备> <卷组> <逻辑卷>
# 例如: lvm_extend.sh /dev/sdc data_vg data_lv
#

set -e

NEW_DISK=$1
VG_NAME=$2
LV_NAME=$3

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 参数检查
if [ -z "$NEW_DISK" ] || [ -z "$VG_NAME" ] || [ -z "$LV_NAME" ]; then
    echo "用法: $0 <新磁盘设备> <卷组名> <逻辑卷名>"
    echo "例如: $0 /dev/sdc data_vg data_lv"
    exit 1
fi

# 检查磁盘是否存在
if [ ! -b "$NEW_DISK" ]; then
    log_error "设备 $NEW_DISK 不存在"
    exit 1
fi

# 检查磁盘是否已被使用
if pvs "$NEW_DISK" &>/dev/null; then
    log_error "设备 $NEW_DISK 已经是物理卷"
    exit 1
fi

if mount | grep -q "$NEW_DISK"then
    log_error "设备 $NEW_DISK 已经被挂载"
    exit 1
fi

# 检查卷组是否存在
if ! vgs "$VG_NAME" &>/dev/null; then
    log_error "卷组 $VG_NAME 不存在"
    exit 1
fi

# 检查逻辑卷是否存在
if ! lvs "$VG_NAME/$LV_NAME" &>/dev/null; then
    log_error "逻辑卷 $VG_NAME/$LV_NAME 不存在"
    exit 1
fi

# 获取当前大小
CURRENT_SIZE=$(lvs --noheadings -o lv_size "$VG_NAME/$LV_NAME" | tr -d ' ')
NEW_DISK_SIZE=$(lsblk -b -d -n -o SIZE "$NEW_DISK" | numfmt --to=iec)

log_info "开始扩容操作"
log_info "新磁盘: $NEW_DISK ($NEW_DISK_SIZE)"
log_info "目标逻辑卷: $VG_NAME/$LV_NAME (当前: $CURRENT_SIZE)"

# 确认操作
read -p "确认执行扩容? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    log_warn "操作已取消"
    exit 0
fi

# 创建物理卷
log_info "创建物理卷..."
pvcreate "$NEW_DISK"

# 扩展卷组
log_info "扩展卷组..."
vgextend "$VG_NAME" "$NEW_DISK"

# 扩展逻辑卷
log_info "扩展逻辑卷和文件系统..."
lvextend -r -l +100%FREE "/dev/$VG_NAME/$LV_NAME"

# 验证结果
NEW_SIZE=$(lvs --noheadings -o lv_size "$VG_NAME/$LV_NAME" | tr -d ' ')
log_info "扩容完成!"
log_info "逻辑卷大小: $CURRENT_SIZE -> $NEW_SIZE"

# 显示最终状态
echo ""
echo "=== 扩容后状态 ==="
pvs
echo ""
vgs "$VG_NAME"
echo ""
lvs "$VG_NAME/$LV_NAME"
echo ""
df -h "/dev/mapper/$VG_NAME-$LV_NAME"

使用方法:

chmod +x lvm_extend.sh
./lvm_extend.sh /dev/sdc data_vg data_lv

案例三:Ansible自动化扩容

对于大规模环境,用Ansible做自动化扩容更方便:

---
# lvm_extend.yml
# 用法: ansible-playbook -i inventory lvm_extend.yml -e "new_disk=/dev/sdc vg_name=data_vg lv_name=data_lv"

- name: LVM在线扩容
  hosts: all
  become: yes
  vars:
    new_disk: "{{ new_disk }}"
    vg_name: "{{ vg_name }}"
    lv_name: "{{ lv_name }}"

  tasks:
    - name: 扫描SCSI总线识别新磁盘
      shell: |
        for host in /sys/class/scsi_host/host*; do
          echo "- - -" > ${host}/scan
        done
      changed_when: false

    - name: 等待磁盘识别
      wait_for:
        path: "{{ new_disk }}"
        state: present
        timeout: 30

    - name: 获取扩容前状态
      command: lvs --noheadings -o lv_size {{ vg_name }}/{{ lv_name }}
      register: lv_size_before
      changed_when: false

    - name: 创建物理卷
      lvg:
        vg: "{{ vg_name }}"
        pvs: "{{ new_disk }}"
        state: present
      register: pv_result

    - name: 扩展卷组
      lvg:
        vg: "{{ vg_name }}"
        pvs: "{{ lookup('pipe', 'pvs --noheadings -o pv_name -S vg_name=' + vg_name + ' 2>/dev/null | tr -d \" \" | tr \"\\n\" \",\"') }}{{ new_disk }}"
        state: present
      when: pv_result.changed

    - name: 扩展逻辑卷
      lvol:
        vg: "{{ vg_name }}"
        lv: "{{ lv_name }}"
        size: +100%FREE
        resizefs: yes

    - name: 获取扩容后状态
      command: lvs --noheadings -o lv_size {{ vg_name }}/{{ lv_name }}
      register: lv_size_after
      changed_when: false

    - name: 显示扩容结果
      debug:
        msg: "逻辑卷 {{ vg_name }}/{{ lv_name }} 从 {{ lv_size_before.stdout | trim }} 扩展到 {{ lv_size_after.stdout | trim }}"

用Ansible的好处是可以并行在多台机器上执行,而且有幂等性保证,即使磁盘已经添加过了也不会出错。

案例四:Kubernetes PVC扩容

现在越来越多的环境用Kubernetes,PVC扩容也很常见。如果后端存储是LVM-based的(比如用了local-path-provisioner或者TopoLVM),扩容流程是这样的:

# 首先确保StorageClass允许扩容
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-lvm
provisioner: kubernetes.io/no-provisioner
allowVolumeExpansion: true  # 这个必须设置为true
volumeBindingMode: WaitForFirstConsumer
# 修改PVC的大小
kubectl patch pvc mysql-data -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

# 等待扩容完成
kubectl get pvc mysql-data -w

# 某些CSI驱动需要重启Pod才能完成扩容
kubectl delete pod mysql-0

如果用的是TopoLVM,它会自动处理底层的LVM扩容操作,非常方便。

最佳实践

这些年踩的坑总结成了一些最佳实践,希望能帮你少走弯路。

容量规划

  1. 预留buffer空间:永远不要等到磁盘快满了才扩容。我的经验是使用率超过70%就该考虑扩容了,超过85%就必须立即行动。磁盘满了再扩容压力很大,万一出问题连救的余地都没有。

  2. 监控告警:配置好磁盘空间告警,阈值设置在70%、80%、90%三档。Prometheus + Alertmanager或者Zabbix都可以。

  3. 扩容幅度:每次扩容不要太小气,一次扩个50%到100%是比较合理的。频繁扩容也是运维负担。

操作规范

  1. 操作前备份:虽然在线扩容理论上很安全,但对于重要系统还是建议操作前做个快照或备份。

  2. 选择合适的时间:虽然可以在线扩容,但如果不是紧急情况,还是选择业务低峰期操作比较好。万一出问题影响面小。

  3. 记录操作日志:把所有执行的命令和输出都记录下来,出问题时方便排查。用script命令录制终端会话是个好办法:

    script /var/log/lvm_extend_$(date +%Y%m%d_%H%M%S).log
    # 执行操作
    exit
  4. 逐步确认:每一步操作完都确认一下结果,不要一口气执行完才发现中间某步出错了。

架构设计

  1. 分离数据目录:数据目录单独做逻辑卷,不要和系统盘混在一起。这样扩容更方便,也更安全。

  2. 卷组命名规范:用有意义的名字,比如mysql_vg、mongodb_vg,别用vg01、vg02这种。时间长了你自己都不知道是干什么的。

  3. 预留VG空间:创建LV时不要把VG空间用满,留10-20%的buffer。这样可以快速响应紧急扩容需求,不用等新磁盘到位。

  4. 使用thin provisioning要谨慎:thin provisioning是把双刃剑,用好了可以提高存储利用率,用不好可能导致thin pool满了整个LV不可用。如果用,一定要监控thin pool使用率。

多路径和RAID环境

  1. 多路径环境:在SAN存储多路径环境下,一定要在multipath设备上操作(/dev/mapper/mpatha),不要在单个路径上操作(/dev/sda)。

  2. 硬件RAID:如果底层是硬件RAID,扩容需要先在RAID卡上添加磁盘、扩展RAID组,然后才能在OS层面扩容。

  3. 软RAID:如果用的是mdadm软RAID,需要先扩展md设备,再pvresize。

云环境特殊考虑

  1. EBS/云盘扩容:AWS EBS、阿里云云盘这些可以直接在控制台扩容,但扩容后有冷却时间(通常6小时),期间不能再次扩容。规划好一次扩够。

  2. 热迁移影响:某些虚拟化平台在热迁移过程中不允许磁盘操作,如果扩容卡住了检查一下是不是在迁移。

  3. NVMe磁盘:NVMe磁盘的扫描命令不一样:

    # NVMe设备重新扫描
    nvme ns-rescan /dev/nvme0n1
    # 或者
    echo 1 > /sys/class/nvme/nvme0/rescan_controller

故障排查

即使是再简单的操作也可能出问题,下面是常见问题和解决方法。

问题一:新磁盘识别不到

症状:虚拟化平台已经加了磁盘,但lsblk看不到。

排查步骤

# 1. 确认SCSI扫描已执行
for host in /sys/class/scsi_host/host*; do
    echo "- - -" > ${host}/scan
done

# 2. 检查dmesg有没有磁盘识别信息
dmesg | tail -50 | grep -i sd

# 3. 检查/dev下有没有新设备
ls -la /dev/sd*

# 4. 如果是virtio磁盘,看vd设备
ls -la /dev/vd*

# 5. 如果是NVMe
ls -la /dev/nvme*

# 6. 还是不行,试试重新加载存储驱动
modprobe -r mptspi && modprobe mptspi  # 具体驱动看你的环境

常见原因

  • SCSI控制器类型配置错误
  • 虚拟机配置没有保存
  • 需要重启虚拟机(某些老版本vmware)

问题二:pvcreate报错Device excluded by a filter

症状

Device /dev/sdc excluded by a filter.

解决方法

# 1. 检查磁盘是否有分区表或文件系统
wipefs /dev/sdc

# 2. 如果有,清除(确保这块盘真的是空的!)
wipefs -a /dev/sdc

# 3. 或者强制创建(不推荐,先确认磁盘是对的)
pvcreate -f /dev/sdc

# 4. 检查lvm.conf的filter配置
grep -E "filter|global_filter" /etc/lvm/lvm.conf | grep -v "^#"

常见原因

  • 磁盘上有残留的分区表或文件系统元数据
  • LVM的filter配置排除了这个设备
  • 磁盘被multipath管理了应该用/dev/mapper/xxx

问题三:vgextend报错Physical volume already in volume group

症状

Physical volume '/dev/sdc' is already in volume group 'xxx'

解决方法: 这说明PV已经加入VG了,用pvs确认一下。如果确实已经在目标VG里,直接跳过这步执行lvextend就行。

问题四:lvextend后文件系统大小没变

症状:lvs显示LV已经扩大了,但df -h看到的大小还是原来的。

解决方法: 忘记扩展文件系统了。

# xfs
xfs_growfs /挂载点

# ext4
resize2fs /dev/mapper/vgname-lvname

问题五:resize2fs报错on-line resizing required

症状

resize2fs: On-line shrinking not supported

解决方法: 这个报错通常是因为你在尝试缩小逻辑卷。ext4支持在线扩大但不支持在线缩小,如果要缩小必须先卸载。xfs根本不支持缩小。

问题六:xfs_growfs报错is not a mounted XFS filesystem

症状

xfs_growfs: /dev/mapper/data_vg-data_lv is not a mounted XFS filesystem

解决方法: xfs_growfs后面要跟挂载点,不是设备路径。

# 错误
xfs_growfs /dev/mapper/data_vg-data_lv

# 正确
xfs_growfs /data  # /data是挂载点

问题七:操作过程中IO错误

症状:执行LVM命令时报Input/output error。

排查步骤

# 1. 检查dmesg
dmesg | tail -100 | grep -i error

# 2. 检查磁盘健康状态
smartctl -a /dev/sdc

# 3. 检查多路径状态(如果用了多路径)
multipath -ll

# 4. 检查存储端日志

常见原因

  • 磁盘故障
  • 光纤链路问题
  • 存储阵列故障
  • HBA卡问题

这种情况先别动,排查清楚故障原因再说。

问题八:扩容后数据丢失

这是最严重的问题,不过正常的在线扩容不会导致数据丢失。如果真的发生了,可能的原因:

  1. 操作了错误的磁盘
  2. 执行了减少逻辑卷大小的操作(缩容时没有先缩文件系统)
  3. 文件系统本身有问题,扩容触发了隐藏的bug

预防措施

  • 操作前三次确认设备名
  • 操作前备份或快照
  • 使用脚本而非手动输入,减少人为错误

问题九:系统启动时LVM卷激活失败

症状:扩容后重启系统,发现LVM卷无法激活。

排查步骤

# 1. 进入救援模式

# 2. 扫描PV
pvscan

# 3. 激活所有VG
vgchange -ay

# 4. 检查VG状态
vgs

# 5. 如果VG显示partial,说明有PV丢失
vgchange -ay --partial vgname

# 6. 查看哪个PV有问题
pvs -a

常见原因

  • 新加的磁盘UUID与系统里已存在的冲突
  • fstab配置错误
  • initramfs没有更新

问题十:LVM元数据损坏

症状

Couldn't find device with uuid xxxxx.
VG data_vg is missing PV xxxxx.

解决方法

# 1. 尝试从备份恢复元数据
vgcfgrestore -l data_vg  # 列出可用备份
vgcfgrestore -f /etc/lvm/archive/data_vg_00042-xxxxxxxx.vg data_vg

# 2. 如果PV真的丢失了,减少VG
vgreduce --removemissing --force data_vg

LVM元数据损坏是很严重的问题,修复之前一定要先备份现有状态。

高级话题

LVM缓存

LVM支持使用SSD作为HDD的缓存,可以显著提升IO性能。扩容时要注意缓存配置:

# 查看是否有缓存配置
lvs -a -o +devices,cache_mode

# 如果有缓存,扩容时可能需要同时扩展缓存

LVM镜像

如果使用了LVM镜像(类似RAID1),扩容需要特殊处理:

# 查看镜像配置
lvs -a -o +devices

# 扩展镜像LV时,新空间也会自动镜像
lvextend -l +100%FREE /dev/vg/mirrored_lv

VDO压缩卷

RHEL 8/9支持VDO(Virtual Data Optimizer),可以做数据去重和压缩。VDO上的LVM扩容稍有不同:

# 查看VDO状态
vdostats --human-readable

# 扩展VDO卷(先扩VDO再扩LVM)
vdo growLogical --name=vdo_data --vdoLogicalSize=2T
lvextend -r -L 2T /dev/vg/lv_on_vdo

Stratis存储

Stratis是Red Hat主推的下一代存储管理方案,比传统LVM更简单。不过目前还不太成熟,生产环境用的不多。

# Stratis扩容
stratis pool add-data my_pool /dev/sdc

监控和自动化

Prometheus监控LVM

用node_exporter可以监控LVM状态:

# prometheus告警规则
groups:
- name: lvm
  rules:
  - alert: LVMVolumeHighUsage
    expr: |
      100 - (node_filesystem_avail_bytes{mountpoint=~"/data.*"} /
             node_filesystem_size_bytes{mountpoint=~"/data.*"} * 100) > 80
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "LVM卷使用率过高"
      description: "{{ $labels.mountpoint }} 使用率超过80%,当前 {{ $value | printf \"%.1f\" }}%"

  - alert: LVMVolumeAlmostFull
    expr: |
      100 - (node_filesystem_avail_bytes{mountpoint=~"/data.*"} /
             node_filesystem_size_bytes{mountpoint=~"/data.*"} * 100) > 90
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "LVM卷即将满"
      description: "{{ $labels.mountpoint }} 使用率超过90%,当前 {{ $value | printf \"%.1f\" }}%,请立即扩容"

自动扩容

在某些场景下可以实现自动扩容,但要谨慎:

#!/bin/bash
# 自动扩容脚本示例(仅供参考,生产环境慎用)

THRESHOLD=85
VG_NAME="data_vg"
LV_NAME="data_lv"
MOUNT_POINT="/data"

# 获取当前使用率
USAGE=$(df -h "$MOUNT_POINT" | awk 'NR==2 {print $5}' | tr -d '%')

if [ "$USAGE" -gt "$THRESHOLD" ]; then
    # 检查VG是否有剩余空间
    FREE_PE=$(vgs --noheadings -o vg_free_count "$VG_NAME" | tr -d ' ')

    if [ "$FREE_PE" -gt 0 ]; then
        logger "自动扩容: $VG_NAME/$LV_NAME 使用率 ${USAGE}%,执行扩容"
        lvextend -r -l +50%FREE "/dev/$VG_NAME/$LV_NAME"

        # 发送通知
        # send_alert "LVM自动扩容" "$LV_NAME 已自动扩容"
    else
        logger "警告: $VG_NAME 没有剩余空间,无法自动扩容"
        # send_alert "LVM空间不足" "$VG_NAME 没有剩余空间,需要添加磁盘"
    fi
fi

可以把这个脚本加入crontab定期执行,但我个人不太推荐完全自动化扩容,万一脚本有bug可能造成更大问题。我的做法是自动检测+告警,然后人工确认执行扩容。

性能考量

在线扩容对性能的影响很小,但还是有几点需要注意:

  1. 元数据更新:LVM元数据更新是原子操作,通常在毫秒级完成,对业务基本无感知。

  2. 文件系统扩展:xfs_growfs和resize2fs在扩展文件系统时会有短暂的IO增加,但不会锁定文件系统。

  3. 大容量扩展:扩展几TB的逻辑卷和扩展几十GB的逻辑卷,对LVM来说没什么区别,时间都很短。但文件系统扩展时间和容量相关,ext4扩展大容量时可能需要几分钟。

  4. thin pool扩展:thin pool扩展会稍慢一些,因为需要更新更多元数据。

与传统方式对比

最后对比一下LVM在线扩容和传统扩容方式的区别:

对比项
LVM在线扩容
传统分区扩容
停机时间
0
通常1-4小时
数据风险
极低
较高
操作复杂度
低(3-5条命令)
高(需要备份恢复)
跨磁盘扩展
支持
不支持
缩容支持
支持(ext4需离线)
基本不支持
快照功能
支持
不支持
学习成本
中等

总结

LVM在线扩容是每个Linux运维工程师必须掌握的技能。这篇文章从基础概念讲起,覆盖了各种实战场景、最佳实践、故障排查,应该能应对工作中绝大多数的扩容需求。

核心要点回顾:

  1. 扩容流程:添加磁盘 → pvcreate → vgextend → lvextend -r,就这四步。

  2. 记住-r参数:lvextend -r可以同时扩展文件系统,省一条命令。

  3. 不同文件系统不同命令:xfs用xfs_growfs跟挂载点,ext4用resize2fs跟设备路径。

  4. 做好监控:磁盘使用率70%就该考虑扩容了,别等到满了才着急。

  5. 操作前确认:设备名一定要核对清楚,操作错了后果很严重。

  6. 记录日志:所有操作都留痕,出问题好排查。

LVM还有很多高级功能这篇文章没有展开讲,比如快照、镜像、条带化、缓存等,以后有机会再单独写。有问题欢迎留言讨论。


本文基于Rocky Linux 9.3/AlmaLinux 9.3环境编写,LVM2版本2.03.23,适用于RHEL 8/9及其衍生版本,Debian/Ubuntu系列操作类似但包名可能不同。

文末福利


网络监控是保障网络系统和数据安全的重要手段,能够帮助运维人员及时发现并应对各种问题,及时发现并解决,从而确保网络的顺畅运行。

谢谢一路支持,给大家分享6款开源免费的网络监控工具,并准备了对应的资料文档,建议运维工程师收藏(文末一键领取)。

图片
备注:【监控合集】

图片

100%免费领取


一、zabbix

图片
图片

二、Prometheus


内容较多,6款常用网络监控工具(zabbix、Prometheus、Cacti、Grafana、OpenNMS、Nagios不再一一介绍, 需要的朋友扫码备注【监控合集】,即可100%免费领取。

图片

 以上所有资料获取请扫码

备注:【监控合集】

图片

100%免费领取

(后台不再回复,扫码一键领取)


【声明】内容源于网络
0
0
马哥Linux运维
马哥教育创办于2009年,国内高端IT培训品牌,毕业学员薪资12K+以上,累计培养数万人。有Linux云计算运维、Python全栈、自动化、数据分析、人工智能、Go高并发架构等高薪就业课程。凭借高品质课程和良好口碑,与多家互联网建立人才合作
内容 3174
粉丝 0
马哥Linux运维 马哥教育创办于2009年,国内高端IT培训品牌,毕业学员薪资12K+以上,累计培养数万人。有Linux云计算运维、Python全栈、自动化、数据分析、人工智能、Go高并发架构等高薪就业课程。凭借高品质课程和良好口碑,与多家互联网建立人才合作
总阅读799
粉丝0
内容3.2k