1. LVM自动化管理脚本开发背景
在Linux服务器运维工作中,LVM(Logical Volume Manager)是存储管理的核心工具之一。它提供了比传统分区更灵活的磁盘管理方式,包括在线扩容、快照、条带化等高级功能。然而在实际操作中,LVM的配置和管理往往涉及多个步骤和命令,容易出错且效率低下。
1.1 运维人员的实际痛点
典型的LVM扩容操作需要执行以下步骤:
- 对新磁盘进行分区(fdisk/parted)
- 创建物理卷(pvcreate)
- 扩展卷组(vgextend)
- 扩展逻辑卷(lvextend)
- 调整文件系统大小(resize2fs/xfs_growfs)
每个步骤都可能遇到各种问题:
- 分区表格式不匹配(MBR vs GPT)
- 磁盘扩容后GPT备份表位置错误
- 文件系统类型识别错误
- 挂载点定位困难
1.2 脚本设计目标
基于这些痛点,我们决定开发一个全自动化的LVM管理脚本,主要实现两个核心功能:
- Extend模式:在现有磁盘的剩余空间创建新分区,并扩容指定的逻辑卷
- New模式:在新磁盘上完整配置LVM环境(分区→PV→VG→LV→格式化→挂载)
2. 脚本开发中的关键技术挑战
2.1 函数返回值污染问题
问题现象:早期版本中,当执行pvcreate $(create_partition /dev/sdb)时频繁报错"Device not found"。
根本原因:create_partition函数中混用了stdout输出日志和返回数据:
bash复制create_partition() {
echo "正在创建分区" # 日志输出到stdout
echo "/dev/sdb2" # 返回值也输出到stdout
}
导致pvcreate实际接收到的是"正在创建分区 /dev/sdb2"这样的非法参数。
解决方案:严格区分stdout和stderr的使用:
bash复制create_partition() {
echo "创建分区..." >&2 # 日志转到stderr
echo "/dev/sdb2" # 仅返回数据到stdout
}
经验:在Shell脚本中,所有函数应该只通过stdout返回数据,日志和错误信息必须输出到stderr。这是编写可靠脚本的基本原则。
2.2 GPT分区表损坏处理
典型场景:在虚拟化环境中对磁盘进行扩容后,原有GPT备份表位置不再正确。
错误表现:
code复制Error: The backup GPT table is not at the end of the disk.
自动化修复方案:
bash复制fix_gpt_table() {
local DISK=$1
if parted -s $DISK print 2>&1 | grep -q "fix the GPT"; then
warn "检测到GPT分区表损坏,正在修复..." >&2
if command -v sgdisk &>/dev/null; then
# 使用sgdisk修复备份表位置
if ! sgdisk -e $DISK >/dev/null 2>&1; then
warn "尝试gdisk交互式修复..."
printf "r\ne\nw\nY\n" | gdisk $DISK >/dev/null 2>&1
fi
else
error "需要手动修复GPT: parted $DISK print → 输入fix"
fi
partprobe $DISK
sleep 2
fi
}
关键点:
- 优先使用
sgdisk -e自动修复 - 备选方案是通过gdisk交互命令修复
- 必须执行
partprobe让内核重新读取分区表
2.3 文件系统扩容的兼容性处理
不同文件系统类型需要不同的扩容命令:
bash复制local FS=$(blkid -o value -s TYPE $LV_PATH)
case $FS in
xfs)
local MNT=$(findmnt -n -o TARGET --source $LV_PATH 2>/dev/null ||
df $LV_PATH 2>/dev/null | tail -1 | awk '{print $NF}')
[[ -n "$MNT" ]] && xfs_growfs $MNT || warn "无法找到挂载点"
;;
ext4|ext3)
resize2fs $LV_PATH
;;
*)
warn "不支持的文件系统: $FS"
;;
esac
特别注意:XFS文件系统必须在挂载状态下才能扩容,且需要传递挂载点而非设备路径。
3. 脚本完整实现解析
3.1 核心函数:智能分区创建
bash复制create_partition_smart() {
local DISK=$1
local SIZE=$(get_disk_size_mb $DISK)
local LAST=$(get_last_end $DISK)
[[ -z "$LAST" ]] && LAST=0
local FREE=$((SIZE - LAST))
[[ $FREE -lt 1024 ]] && { echo "ERROR:剩余空间不足" >&2; return 1; }
local NUM=$(get_next_num $DISK)
local PART="${DISK}${NUM}"
[[ -b "$PART" ]] && { echo "ERROR:分区已存在" >&2; return 1; }
# 自动修复GPT问题
fix_gpt_table $DISK
# 初始化分区表(如果需要)
[[ $LAST -eq 0 ]] && parted -s $DISK mklabel gpt >/dev/null 2>&1
# 创建新分区
parted -s $DISK mkpart primary ${LAST}MB 100% >/dev/null 2>&1 || return 1
parted -s $DISK set $NUM lvm on >/dev/null 2>&1
partprobe $DISK 2>/dev/null
sleep 2
# 返回分区设备路径
[[ -b "$PART" ]] && echo "$PART" || return 1
}
关键功能:
- 自动计算磁盘剩余空间
- 智能确定下一个分区编号
- 自动处理GPT分区表问题
- 设置LVM标志位
3.2 Extend模式实现
bash复制extend_lv() {
local DISK=$1
local LV_PATH=$2
[[ -b "$LV_PATH" ]] || error "LV不存在"
local VG=$(lvs --noheadings -o vg_name $LV_PATH 2>/dev/null | awk '{print $1}')
[[ -n "$VG" ]] || error "无法获取VG名称"
log "目标卷组: $VG"
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT $DISK
local PART=$(create_partition_smart $DISK) || error "分区创建失败!"
log "成功创建: $PART"
pvcreate $PART
vgextend $VG $PART
lvextend -l +100%FREE $LV_PATH
# 文件系统扩容(前文已展示)
resize_filesystem $LV_PATH
log "扩展完成!"
df -h $LV_PATH
}
3.3 New模式实现
bash复制new_config() {
local DISK=$1
local VG=$2
local LV=$3
local MNT=$4
log "警告: 将格式化 $DISK"
read -p "确认? (yes/no): " CONFIRM
[[ "$CONFIRM" == "yes" ]] || exit 0
local PART=$(create_partition_smart $DISK) || error "分区创建失败!"
log "成功创建: $PART"
pvcreate $PART
if vgdisplay $VG &>/dev/null; then
vgextend $VG $PART
else
vgcreate $VG $PART
fi
local LV_PATH="/dev/$VG/$LV"
lvcreate -l 100%FREE -n $LV $VG
log "文件系统 (1:XFS 2:EXT4 3:自定义)?"
read -p "选择 (默认1): " CHOICE
CHOICE=${CHOICE:-1}
case $CHOICE in
1) mkfs.xfs $LV_PATH ;;
2) mkfs.ext4 $LV_PATH ;;
3) read -p "输入类型: " TYPE; mkfs.$TYPE $LV_PATH ;;
*) error "无效选择" ;;
esac
[[ -d "$MNT" ]] || mkdir -p $MNT
mount $LV_PATH $MNT
blkid -o value -s UUID $LV_PATH | xargs -I {} echo "UUID={} $MNT $(blkid -o value -s TYPE $LV_PATH) defaults 0 2" >> /etc/fstab
log "完成!"
df -h $MNT
}
4. 使用指南与最佳实践
4.1 典型使用场景
场景1:扩展现有逻辑卷
bash复制./lvm_manager.sh extend /dev/sdb /dev/centos/root
场景2:新磁盘初始化
bash复制./lvm_manager.sh new /dev/sdc vg_data data /data
4.2 日志管理技巧
由于所有日志输出到stderr,可以通过以下方式管理输出:
bash复制# 仅查看错误(过滤info日志)
./lvm_manager.sh extend /dev/sdb /dev/centos/root 2> >(grep -v INFO)
# 保存完整日志到文件
./lvm_manager.sh new /dev/sdc vg_data data /data 2> lvm.log
# 实时显示日志同时保存到文件
./lvm_manager.sh extend /dev/sdb /dev/centos/root 2>&1 | tee lvm.log
4.3 安全注意事项
- 重要数据备份:虽然脚本有确认提示,但操作前仍建议备份关键数据
- 生产环境测试:首次使用建议在测试环境验证
- 磁盘选择确认:确保操作的是正确的磁盘设备
- 并发操作避免:不要同时对同一磁盘执行多个管理操作
5. 脚本优化方向
5.1 待增强功能
- 快照支持:集成LVM快照创建/恢复功能
- 多磁盘VG:支持一次操作添加多个磁盘到卷组
- 精简配置:支持thin-provisioned逻辑卷
- 加密支持:集成LUKS加密卷创建
5.2 性能优化点
- 并行操作:对多磁盘操作可采用并行处理
- 进度显示:长时间操作增加进度提示
- 预检功能:执行前全面检查系统环境
这个脚本的开发过程展示了从简单需求到生产级工具的演进路径。通过解决实际问题中遇到的各种边界情况,最终形成了一个健壮可靠的运维工具。