生产环境Linux应用目录迁移至LVM独立分区 标准化实战方案

前言:在企业级Linux运维场景中,系统盘与业务数据盘分离是 核心运维规范之一。我们经常会遇到这类场景:业务上线初期未做目录规划,应用数据、上传附件、日志文件直接存放在根分区下的默认目录,随着业务增长,根分区被占满导致系统崩溃、数据备份困难、磁盘扩容受限等问题频发。

本文基于生产环境实战经验,提供一套通用、可复制、零数据丢失、最小化停机窗口 的应用目录迁移方案,通过预同步+增量同步的两步法,将业务停机窗口从数小时压缩至秒级,将正在运行的业务目录无缝迁移至LVM逻辑卷分区,全程遵循Linux标准规范,兼顾稳定性与可操作性,适用于绝大多数内网/公网业务场景。


零数据丢失、秒级停机窗口、可回滚的企业级运维方案

一、适用场景与核心原则

1.1 适用场景

本方案适用于以下所有业务场景,命令可直接适配修改后执行:

  • Web应用的用户上传附件、图片、静态资源目录
  • GitLab、禅道、Jenkins等DevOps工具的核心数据目录
  • 业务系统的日志、报表、临时文件生成目录
  • 数据库的非核心数据文件、备份文件存储目录
  • 所有需要将「根分区下的普通业务目录」转换为「LVM独立分区挂载点」的场景

1.2 不适用场景

  • 系统核心目录(//etc/var/usr等)的整体迁移
  • 正在运行的数据库核心数据文件目录(如MySQL的datadir,需额外适配数据库锁表、主从同步等机制)
  • 无业务停机窗口的金融级核心交易系统(需额外做集群切换、流量切分等适配)

1.3 不可突破的4大核心原则(生产环境红线)

  1. 数据先行原则:必须先将全量数据同步至LVM分区,再执行挂载操作,禁止空盘直接挂载至业务目录
  2. 停服写入原则:最终挂载切换前,必须停止业务服务,彻底终止进程对原目录的写入,避免数据分裂、丢失
  3. 权限一致原则:迁移前后必须保证目录/文件的权限、属主属组完全一致,避免业务启动失败、读写异常
  4. 可回滚原则:所有操作必须保留回滚能力,禁止直接删除原目录数据,必须业务验证无误后再清理旧数据

二、前置准备与环境检查

2.0 依赖工具安装

本方案用到的rsynclsofacl工具,在部分极简版Linux镜像(Minimal)、云服务器或容器环境中默认未安装,执行迁移前先完成安装:

bash 复制代码
# CentOS/RHEL/Rocky 系列
yum install rsync lsof acl -y

# Debian/Ubuntu 系列
apt install rsync lsof acl -y

2.1 提前完成LVM分区创建与格式化

本方案默认你已完成LVM逻辑卷的创建,若未创建,可通过以下标准步骤完成(生产环境通用):

bash 复制代码
# 1. 查看新增磁盘,确认磁盘设备名(如/dev/sdb)
fdisk -l

# 2. 创建物理卷PV
pvcreate /dev/sdb

# 3. 创建卷组VG(自定义卷组名,如vg_data)
vgcreate vg_data /dev/sdb

# 4. 创建逻辑卷LV(自定义逻辑卷名,如lv_data,分配100G空间)
lvcreate -L 100G -n lv_data vg_data
# 【生产小贴士】若想将该卷组的所有剩余空间全部分配给逻辑卷,可使用以下命令
# lvcreate -l +100%FREE -n lv_data vg_data

# 5. 格式化逻辑卷(生产环境推荐xfs,ext4也可)
mkfs.xfs /dev/mapper/vg_data-lv_data

# 6. 创建临时挂载点,用于预同步数据
mkdir -p /mnt/lvm_temp

# 7. 临时挂载LVM分区,验证可用性
mount /dev/mapper/vg_data-lv_data /mnt/lvm_temp

# 8. 验证挂载成功
df -h /mnt/lvm_temp

2.2 生产环境必做:全量备份

任何生产环境变更前,必须先做全量备份,这是故障兜底的最后防线

bash 复制代码
# 进入业务目录上级路径
cd /your/app/parent/path

# 全量打包备份,保留所有权限、属主、软链接
tar -zcpvf app_data_full_backup.tar.gz your_data_dir/

2.3 环境信息确认与检查项

执行迁移前,必须完成以下检查,避免踩坑:

  1. 目录大小确认 :确认业务目录总大小,确保LVM分区可用空间 ≥ 目录总大小的1.2倍

    bash 复制代码
    du -sh /your/app/data/path
  2. 服务状态确认:确认业务服务的启停命令、进程状态,确保可正常停止/启动

  3. 权限备份 :备份原目录的权限、属主信息,用于迁移后校验

    bash 复制代码
    # 备份权限信息
    getfacl -R /your/app/data/path > /tmp/app_dir_perm.bak
  4. 进程占用检查 :确认是否有进程正在读写业务目录,避免同步时数据不一致

    bash 复制代码
    lsof /your/app/data/path

三、标准化迁移全流程(核心实战步骤)

本流程将停机窗口压缩至秒级-分钟级,全程分为5个阶段,严格按顺序执行,禁止跳步。

阶段1:不停机预同步全量数据(最小化停机窗口)

本阶段业务服务正常运行,用户无感知,核心是提前将99%的全量数据同步至LVM分区,大幅缩短最终停机同步的时间。

bash 复制代码
# 全量同步原目录数据至LVM临时挂载点,保留所有权限、属主、软链接、硬链接
# -a:归档模式,保留所有属性;-v:显示同步进度;--delete:删除目标端多余文件
rsync -av --delete /your/app/data/path/ /mnt/lvm_temp/
  • 若目录数据量较大(几十G以上),可多次执行此命令,每次只会同步新增/修改的文件,逐步缩小数据差。
  • 同步完成后,可对比原目录与LVM目录的文件大小、数量,确认一致性。

阶段2:停机+增量同步(彻底停止写入,保证数据一致性)

本阶段为唯一停机窗口,核心是彻底终止业务进程的写入,同步最后一次增量数据,保证两端数据100%一致。

bash 复制代码
# 1. 停止业务服务,彻底终止对原目录的所有读写操作
# 示例:systemctl stop your_app.service 或 自定义的服务停止脚本
systemctl stop your_app.service

# 2. 确认服务已停止,无进程占用原目录
lsof /your/app/data/path
ps -ef | grep your_app

# 3. 最后一次增量同步,仅同步预同步后新增/修改的文件,速度极快
rsync -av --delete /your/app/data/path/ /mnt/lvm_temp/

# 4. 可选:权限校验,对比两端权限是否一致
diff <(getfacl -R /your/app/data/path) <(getfacl -R /mnt/lvm_temp)

阶段3:LVM分区挂载至原业务路径

本阶段完成目录切换,核心是将已同步好数据的LVM分区,挂载到业务原来使用的路径上,业务无需修改任何配置

bash 复制代码
# 1. 隔离原目录数据,重命名为备份目录,用于后续回滚与验证
mv /your/app/data/path /your/app/data/path_bak

# 2. 创建与原路径同名的空目录,作为挂载点
mkdir -p /your/app/data/path

# 3. 卸载LVM分区的临时挂载点
umount /mnt/lvm_temp

# 4. 将LVM分区挂载至业务原路径
mount /dev/mapper/vg_data-lv_data /your/app/data/path

# 5. 验证挂载成功,确认数据完整
df -h /your/app/data/path
ls -la /your/app/data/path

阶段4:配置永久挂载(/etc/fstab)

本阶段实现开机自动挂载,避免服务器重启后挂载失效,生产环境必须使用UUID配置,保证唯一性。

bash 复制代码
# 1. 获取LVM分区的UUID
blkid /dev/mapper/vg_data-lv_data

# 2. 编辑/etc/fstab文件,添加永久挂载配置
vim /etc/fstab

# 3. 在文件末尾添加以下配置(替换为你的UUID、挂载路径、文件系统)
# 格式:UUID=你的UUID  挂载路径  文件系统  defaults  0  0
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  /your/app/data/path  xfs  defaults  0  0

# 4. 验证fstab配置是否正确,无报错则配置生效
mount -a

# 5. 再次验证挂载状态
df -h /your/app/data/path

避坑提醒:必须执行mount -a验证配置,若配置错误,会导致服务器重启后无法开机!

阶段5:服务重启与业务验证

本阶段完成业务恢复,确认迁移基础成功

bash 复制代码
# 1. 启动业务服务
systemctl start your_app.service

# 2. 确认服务状态正常
systemctl status your_app.service

# 3. 基础业务验证(必须完成所有项,才能进入后续观察期)
# ✅ 业务页面正常访问,无404/500错误
# ✅ 文件上传/下载功能正常,文件可正常读写
# ✅ 历史数据完整无丢失,附件、图片可正常加载
# ✅ 服务运行无报错,日志输出正常
# ✅ 确认数据实际写入LVM分区,df -h可看到磁盘占用变化

补充提示:CentOS/RedHat/Rocky系列系统,若开启了SELinux(Enforcing模式),新挂载的目录会因安全上下文不匹配导致权限拒绝,可执行restorecon -Rv /your/app/data/path修复安全上下文。


四、应急回滚方案(生产环境必备,100%可兜底)

若挂载后业务异常、数据缺失,可通过以下步骤快速回滚,恢复至迁移前状态,全程有防呆保护,避免误删数据:

bash 复制代码
# 1. 立即停止业务服务,终止所有读写操作
systemctl stop your_app.service

# 2. 尝试卸载LVM分区挂载,若提示设备繁忙,需先确认无进程占用
umount /your/app/data/path

# 3. 【防呆安全操作】仅删除空的挂载点目录
# rmdir仅能删除空目录,若umount失败、目录内仍有数据,会直接报错终止,避免误删数据
rmdir /your/app/data/path

# 4. 恢复原业务目录,回滚至迁移前状态
mv /your/app/data/path_bak /your/app/data/path

# 5. 启动业务服务
systemctl start your_app.service

# 6. 验证业务完全恢复正常
systemctl status your_app.service

极端情况兜底:若/etc/fstab配置错误导致服务器无法开机,可进入单用户模式,注释掉新增的fstab配置项,重启服务器后再排查修复。


五、旧数据安全验证与生命周期管理(生产环境必做)

本章节是生产环境迁移的最后一道安全防线,核心逻辑是:挂载完成后,绝对不能立即删除原目录的备份数据,必须经过完整的业务周期观察,100%确认所有业务读写都已落到新LVM分区后,再安全清理旧数据

这也是多次生产环境迁移沉淀的核心实战经验,避免因挂载异常、配置错误导致数据丢失。

5.1 迁移后的观察周期规范

生产环境必须设置完整的观察周期,建议标准:

  • 非核心业务、低流量业务:至少观察1个完整业务日(覆盖日间业务高峰、夜间定时任务、自动备份任务)
  • 核心业务、高流量业务:至少观察3个完整业务日(覆盖工作日高峰、周末低峰、全量定时任务周期)
  • 金融级核心业务:至少观察7个自然日(覆盖全量业务场景、月结/日结任务、全量备份周期)

5.2 核心验证:确认业务读写100%落到新LVM分区

观察期内,必须完成以下多层级验证,确保没有任何数据写入旧的备份目录/your/app/data/path_bak,所有读写都在新挂载的LVM分区。

验证1:挂载点持续性校验
bash 复制代码
# 1. 验证当前挂载状态正常,LVM分区已正确挂载到业务原路径
df -h /your/app/data/path

# 2. 验证fstab永久挂载配置无异常,无报错则配置生效
mount -a

# 【可选强校验】重启服务器后再次验证挂载状态,确保开机自动挂载正常
# reboot
# df -h /your/app/data/path
验证2:业务写入方向校验(最核心)

模拟业务真实写入场景,确认文件写入新LVM分区,而非旧目录:

bash 复制代码
# 1. 业务端上传一个测试文件(如test_verify.img),或手动创建测试文件
touch /your/app/data/path/test_verify.img

# 2. 验证该文件所在的挂载点,确认输出为新LVM分区,而非根分区
df /your/app/data/path/test_verify.img

# 3. 确认旧备份目录中无该测试文件,无任何新增内容
ls -la /your/app/data/path_bak | grep test_verify.img
验证3:目录大小增长校验(实战最常用、最直观)

这是生产环境最通用的验证方法,核心逻辑:新挂载的业务目录会随着业务运行持续增长,而隔离后的旧备份目录大小永远保持不变

bash 复制代码
# 1. 观察期首日,记录旧备份目录的总大小,保存为基准值
du -sh /your/app/data/path_bak > /tmp/path_bak_size_base.log

# 2. 观察期内,每日/每小时对比旧目录大小,确认与基准值完全一致,无任何增长
diff /tmp/path_bak_size_base.log <(du -sh /your/app/data/path_bak)

# 3. 同步观察新挂载目录的大小,确认随业务运行持续增长,符合业务预期
du -sh /your/app/data/path

实战说明:若旧备份目录大小发生变化,说明仍有进程在向旧目录写入数据,必须立即排查进程,终止写入,否则会出现数据分裂、丢失。

验证4:进程占用校验

确认无任何进程持有旧备份目录的文件句柄,无进程读写旧目录:

bash 复制代码
# 确认无进程占用旧备份目录,无输出则为正常
lsof /your/app/data/path_bak
验证5:全量业务功能回归

观察期内,完成全量业务场景回归,确保:

  • 历史附件、图片、文件可正常访问、下载
  • 新文件上传、写入功能正常,文件可持久化保存
  • 定时任务、报表生成、日志写入等功能正常
  • 服务重启、配置重载后,业务功能无异常

5.3 旧数据安全清理操作

完成所有验证、观察期结束,100%确认业务运行正常后,再执行旧数据清理操作,全程保留兜底能力。

步骤1:最终全量备份(兜底)

清理前,对旧备份目录做最后一次全量归档备份,存放至异地备份服务器或对象存储:

bash 复制代码
# 进入旧目录上级路径
cd /your/app/data/

# 全量打包备份,保留所有权限
tar -zcpvf app_data_final_backup.tar.gz path_bak/
步骤2:安全删除旧数据
bash 复制代码
# 【推荐安全操作1】先将旧目录移动至/tmp目录,观察12-24小时业务无异常后,再最终删除
# 若出现异常,可立即从/tmp恢复,避免直接删除导致数据无法找回
mv /your/app/data/path_bak /tmp/

# 【推荐安全操作2】确认业务完全无异常后,执行最终删除
rm -rf /tmp/path_bak/

# 【禁止操作】严禁在挂载完成后立即执行 rm -rf /your/app/data/path_bak,无任何兜底能力
步骤3:最终磁盘空间校验
bash 复制代码
# 确认根分区空间已释放,LVM分区挂载正常,业务运行无异常
df -h
ls -la /your/app/data/path
systemctl status your_app.service

六、核心原理与高频避坑指南

6.1 为什么不能不停机直接挂载?

核心原因是Linux的mount机制与进程文件句柄的特性

  • mount操作的本质是「用新的文件系统,覆盖在原目录路径上」,原目录的所有数据会被瞬间隐藏,而非删除。
  • 业务进程在运行时,已经持有原目录的文件句柄,这些句柄指向原目录的inode(索引节点),挂载后进程不会自动切换句柄,会继续往被隐藏的原目录写入数据。
  • 最终结果:新数据永久丢失、业务看不到旧文件、直接出现404/文件不存在报错。

6.2 为什么必须先同步数据再挂载?

新创建的LVM分区是空的,mount操作只会把空盘挂载到原路径,不会自动把原目录的数据迁移到新盘里。

  • 若空盘直接挂载,业务会看到一个空目录,所有历史数据瞬间"消失",业务直接崩溃。
  • 只有提前把数据全量同步到LVM分区,挂载后业务才能看到完整的历史数据,正常运行。

6.3 为什么要分预同步+增量同步两步?

  • 预同步:业务正常运行时提前同步绝大多数数据,把停机需要同步的数据量降到最低。
  • 增量同步:停机后仅同步最后一次的增量数据,把停机窗口压缩到秒级,最大程度降低业务影响。

6.4 权限一致性为什么这么重要?

业务进程通常以特定用户(如nobody、www-data、git等)运行,若迁移后目录/文件的属主、权限发生变化,进程会无法读写文件,直接导致服务启动失败、上传功能异常、权限拒绝报错。


七、常见问题排查

7.1 挂载后业务报文件不存在/404

  • 排查方向1:确认LVM分区里已经提前同步了全量数据,不是空盘挂载。
  • 排查方向2:确认挂载路径与业务配置的路径完全一致,无拼写错误。
  • 排查方向3:执行ls -la /your/app/data/path,确认文件确实存在于挂载后的目录中。

7.2 服务启动报权限拒绝错误

  • 排查方向1:对比原目录备份的权限信息,确认迁移后目录的属主、权限完全一致。
  • 排查方向2:执行getfacl -R /your/app/data/path,与备份文件对比,修复异常权限。
  • 排查方向3:确认挂载的文件系统没有加noexecnosuid等限制权限的挂载参数。
  • 排查方向4:CentOS/RedHat/Rocky系列系统,若开启了SELinux,执行restorecon -Rv /your/app/data/path修复安全上下文。

7.3 执行mount -a报错:can't find in /etc/fstab

  • 排查方向1:确认/etc/fstab里的配置格式正确,无拼写错误、多余空格。
  • 排查方向2:确认UUID正确,与blkid查询到的UUID完全一致。
  • 排查方向3:确认挂载点目录已创建,路径正确。

7.4 服务器重启后挂载失效

  • 排查方向1:确认/etc/fstab配置正确,且已执行mount -a验证无报错。
  • 排查方向2:确认使用的是UUID配置,而非/dev/mapper设备名(设备名可能重启后发生变化)。
  • 排查方向3:确认LVM服务开机自启,systemctl enable lvm2-monitor已执行。

7.5 旧备份目录大小持续增长

  • 排查方向1:确认业务服务已完全停止,无残留进程持有旧目录的文件句柄。
  • 排查方向2:确认定时任务、脚本、其他关联服务,没有配置旧目录的绝对路径,仍在向旧目录写入数据。
  • 排查方向3:终止所有向旧目录写入的进程,重新同步增量数据,再次验证写入方向。

迁移总结

  1. 本方案是生产环境下「普通业务目录转LVM独立分区」的标准化方案,全程遵循Linux运维规范,可适配绝大多数业务场景。
  2. 迁移的核心逻辑永远是:先同步数据到LVM → 停服务终止写入 → 挂载切换 → 配置永久生效 → 启服务验证,顺序不可颠倒。
  3. 生产环境变更,永远要把「数据安全、可回滚」放在第一位,禁止在无备份、无回滚方案的情况下执行挂载操作。
  4. 最小化停机窗口的核心,是提前做不停机预同步,把最终停机的操作压缩到最短。
  5. 旧数据清理是迁移的最后一道防线,必须经过完整的业务周期观察与多层级验证,确认无误后再安全清理,严禁提前删除备份数据。

生产环境LVM目录一键迁移自动化脚本

脚本说明

本脚本基于前文SOP编写,具备以下特性:

  • 全自动化执行:从环境检查到最终挂载,一键完成
  • 完整容错机制:每一步都检查执行结果,失败立即终止
  • 自动回滚能力:关键步骤失败自动触发回滚流程,恢复至初始状态
  • 详细日志记录:所有操作都有时间戳日志,便于审计和排查
  • 安全确认机制:停机前二次确认,避免误操作
  • 预同步+增量同步:最小化停机窗口

一键迁移脚本 lvm_migrate.sh

bash 复制代码
#!/bin/bash
set -euo pipefail

# ==========================================
# 【用户配置区域】请根据实际环境修改以下变量
# ==========================================
# 业务原数据目录(需要迁移的目录)
SOURCE_DIR="/your/app/data/path"
# LVM逻辑卷设备路径
LVM_DEV="/dev/mapper/vg_data-lv_data"
# LVM临时挂载点(用于预同步数据)
LVM_TEMP_MOUNT="/mnt/lvm_temp"
# 业务服务名称(systemd服务名)
SERVICE_NAME="your_app.service"
# 文件系统类型(xfs/ext4)
FS_TYPE="xfs"
# 旧数据备份目录后缀(自动生成:${SOURCE_DIR}_${BACKUP_SUFFIX})
BACKUP_SUFFIX="bak_$(date +%Y%m%d_%H%M%S)"
# 日志文件路径
LOG_FILE="/var/log/lvm_migrate_$(date +%Y%m%d_%H%M%S).log"

# ==========================================
# 【全局变量与函数】请勿随意修改
# ==========================================
# 脚本执行状态标记
ROLLBACK_NEEDED=0
STAGE="初始化"
# 备份目录路径(自动生成)
BACKUP_DIR="${SOURCE_DIR}_${BACKUP_SUFFIX}"

# 日志输出函数
log() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "[$timestamp] [$level] [$STAGE] $message" | tee -a "$LOG_FILE"
}

# 错误处理与回滚入口
error_exit() {
    log "ERROR" "$1"
    if [ "$ROLLBACK_NEEDED" -eq 1 ]; then
        log "WARN" "检测到关键步骤失败,触发自动回滚流程..."
        auto_rollback
    fi
    log "ERROR" "迁移失败,请查看日志:$LOG_FILE"
    exit 1
}

# 命令执行检查函数
run_cmd() {
    local cmd="$1"
    local desc="$2"
    log "INFO" "正在执行:$desc"
    log "DEBUG" "命令详情:$cmd"
    
    if eval "$cmd" >> "$LOG_FILE" 2>&1; then
        log "INFO" "✅ 成功:$desc"
    else
        error_exit "❌ 失败:$desc"
    fi
}

# 环境检查函数
pre_check() {
    STAGE="环境检查"
    log "INFO" "=========================================="
    log "INFO" "开始执行前置环境检查..."
    log "INFO" "=========================================="

    # 检查是否为root用户
    if [ "$EUID" -ne 0 ]; then
        error_exit "请使用root用户执行此脚本"
    fi

    # 检查源目录是否存在
    if [ ! -d "$SOURCE_DIR" ]; then
        error_exit "源目录不存在:$SOURCE_DIR"
    fi

    # 检查LVM设备是否存在
    if [ ! -b "$LVM_DEV" ]; then
        error_exit "LVM设备不存在:$LVM_DEV"
    fi

    # 检查服务是否存在
    if ! systemctl list-unit-files | grep -q "^${SERVICE_NAME}"; then
        error_exit "systemd服务不存在:$SERVICE_NAME"
    fi

    # 检查依赖工具是否安装
    local tools=("rsync" "lsof" "blkid" "getfacl")
    for tool in "${tools[@]}"; do
        if ! command -v "$tool" &> /dev/null; then
            error_exit "依赖工具未安装:$tool,请先安装依赖"
        fi
    done

    # 检查LVM空间是否足够(源目录大小 * 1.2)
    local source_size=$(du -sb "$SOURCE_DIR" | awk '{print $1}')
    local lvm_size=$(blockdev --getsize64 "$LVM_DEV")
    local required_size=$((source_size * 12 / 10))
    
    log "INFO" "源目录大小:$(numfmt --to=iec $source_size)"
    log "INFO" "LVM分区大小:$(numfmt --to=iec $lvm_size)"
    log "INFO" "建议最小空间:$(numfmt --to=iec $required_size)"
    
    if [ "$lvm_size" -lt "$required_size" ]; then
        error_exit "LVM分区空间不足,建议至少预留源目录1.2倍空间"
    fi

    log "INFO" "=========================================="
    log "INFO" "✅ 所有环境检查通过"
    log "INFO" "=========================================="
}

# 全量备份函数
full_backup() {
    STAGE="全量备份"
    log "INFO" "=========================================="
    log "INFO" "开始执行全量备份(生产环境必做)..."
    log "INFO" "=========================================="

    local backup_tar="/opt/lvm_migrate_full_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
    log "INFO" "备份文件将保存至:$backup_tar"
    
    # 进入源目录上级路径打包
    local parent_dir=$(dirname "$SOURCE_DIR")
    local base_dir=$(basename "$SOURCE_DIR")
    
    run_cmd "cd $parent_dir && tar -zcpvf $backup_tar $base_dir/" \
            "全量打包备份源目录"
    
    log "INFO" "✅ 全量备份完成:$backup_tar"
    log "INFO" "【重要】请妥善保管此备份文件,这是故障兜底的最后防线"
}

# 预同步数据函数(不停机)
pre_sync() {
    STAGE="数据预同步"
    log "INFO" "=========================================="
    log "INFO" "开始执行数据预同步(业务不停机)..."
    log "INFO" "=========================================="

    # 创建临时挂载点
    if [ ! -d "$LVM_TEMP_MOUNT" ]; then
        run_cmd "mkdir -p $LVM_TEMP_MOUNT" "创建LVM临时挂载点"
    fi

    # 挂载LVM到临时目录
    if ! mountpoint -q "$LVM_TEMP_MOUNT"; then
        run_cmd "mount $LVM_DEV $LVM_TEMP_MOUNT" "挂载LVM到临时目录"
    fi

    # 第一次全量预同步
    log "INFO" "开始第一次全量预同步,数据量较大时可能需要较长时间..."
    run_cmd "rsync -av --delete $SOURCE_DIR/ $LVM_TEMP_MOUNT/" \
            "第一次全量预同步数据"
    
    log "INFO" "=========================================="
    log "INFO" "✅ 数据预同步完成"
    log "INFO" "【提示】若数据量巨大,可再次运行此脚本进行多次预同步,逐步缩小数据差"
    log "INFO" "=========================================="
}

# 停机切换主函数
stop_and_switch() {
    STAGE="停机切换"
    log "INFO" "=========================================="
    log "INFO" "⚠️  ⚠️  ⚠️  即将进入停机切换阶段 ⚠️  ⚠️  ⚠️"
    log "INFO" "此阶段业务将短暂停止,请确认已通知相关人员"
    log "INFO" "=========================================="

    # 二次确认
    read -p "请输入 YES 确认开始停机切换(输入其他内容将终止脚本):" confirm
    if [ "$confirm" != "YES" ]; then
        log "INFO" "用户取消操作,脚本终止"
        exit 0
    fi

    # 标记:后续步骤失败需要回滚
    ROLLBACK_NEEDED=1

    # 1. 停止业务服务
    log "WARN" "正在停止业务服务:$SERVICE_NAME"
    run_cmd "systemctl stop $SERVICE_NAME" "停止业务服务"
    
    # 确认服务已停止
    local count=0
    while systemctl is-active --quiet "$SERVICE_NAME" && [ $count -lt 30 ]; do
        log "INFO" "等待服务完全停止... ($count/30)"
        sleep 1
        count=$((count + 1))
    done
    
    if systemctl is-active --quiet "$SERVICE_NAME"; then
        error_exit "服务无法在30秒内停止,请手动检查"
    fi

    # 确认无进程占用源目录
    if lsof "$SOURCE_DIR" >> "$LOG_FILE" 2>&1; then
        error_exit "检测到仍有进程占用源目录,请手动清理后重试"
    fi

    # 2. 最后一次增量同步
    log "INFO" "开始最后一次增量同步(仅同步差异数据,速度极快)..."
    run_cmd "rsync -av --delete $SOURCE_DIR/ $LVM_TEMP_MOUNT/" \
            "最后一次增量同步数据"

    # 3. 备份权限信息
    run_cmd "getfacl -R $SOURCE_DIR > /tmp/lvm_migrate_perm_$(date +%Y%m%d_%H%M%S).bak" \
            "备份源目录权限信息"

    # 4. 隔离原目录
    log "INFO" "正在隔离原目录:$SOURCE_DIR -> $BACKUP_DIR"
    run_cmd "mv $SOURCE_DIR $BACKUP_DIR" "重命名原目录为备份目录"

    # 5. 创建新挂载点
    run_cmd "mkdir -p $SOURCE_DIR" "创建新的空挂载点目录"

    # 6. 卸载临时挂载
    run_cmd "umount $LVM_TEMP_MOUNT" "卸载LVM临时挂载"

    # 7. 挂载LVM到原路径
    run_cmd "mount $LVM_DEV $SOURCE_DIR" "挂载LVM到业务原路径"

    # 8. 验证挂载
    if ! mountpoint -q "$SOURCE_DIR"; then
        error_exit "LVM挂载失败,请检查"
    fi
    
    local mount_dev=$(df "$SOURCE_DIR" | tail -n 1 | awk '{print $1}')
    if [ "$mount_dev" != "$LVM_DEV" ]; then
        error_exit "挂载设备不匹配,期望:$LVM_DEV,实际:$mount_dev"
    fi

    log "INFO" "✅ LVM挂载成功"
}

# 配置永久挂载
config_fstab() {
    STAGE="配置永久挂载"
    log "INFO" "=========================================="
    log "INFO" "开始配置/etc/fstab永久挂载..."
    log "INFO" "=========================================="

    # 获取UUID
    local uuid=$(blkid -s UUID -o value "$LVM_DEV")
    if [ -z "$uuid" ]; then
        error_exit "无法获取LVM设备的UUID,请检查设备是否正确"
    fi
    
    log "INFO" "LVM设备UUID:$uuid"

    # 检查是否已存在相同配置
    if grep -q "$uuid" /etc/fstab; then
        log "WARN" "检测到fstab中已存在该UUID配置,跳过添加"
    else
        # 备份原fstab
        run_cmd "cp -a /etc/fstab /etc/fstab.lvm_migrate_bak_$(date +%Y%m%d_%H%M%S)" \
                "备份/etc/fstab"
        
        # 添加新配置
        local fstab_line="UUID=$uuid  $SOURCE_DIR  $FS_TYPE  defaults  0  0"
        run_cmd "echo '$fstab_line' >> /etc/fstab" "添加永久挂载配置到/etc/fstab"
    fi

    # 验证fstab配置
    log "INFO" "正在验证fstab配置(mount -a)..."
    run_cmd "mount -a" "验证fstab配置正确性"

    log "INFO" "✅ 永久挂载配置完成"
}

# 启动服务与验证
start_and_verify() {
    STAGE="服务启动与验证"
    log "INFO" "=========================================="
    log "INFO" "开始启动业务服务并验证..."
    log "INFO" "=========================================="

    # 启动服务
    run_cmd "systemctl start $SERVICE_NAME" "启动业务服务"

    # 等待服务启动
    local count=0
    while ! systemctl is-active --quiet "$SERVICE_NAME" && [ $count -lt 60 ]; do
        log "INFO" "等待服务启动... ($count/60)"
        sleep 1
        count=$((count + 1))
    done

    if ! systemctl is-active --quiet "$SERVICE_NAME"; then
        error_exit "服务启动失败,请检查服务日志"
    fi

    # 基础验证
    log "INFO" "✅ 服务启动成功"
    log "INFO" "=========================================="
    log "INFO" "📋 请手动完成以下验证项:"
    log "INFO" "  1. 业务页面正常访问,无404/500错误"
    log "INFO" "  2. 文件上传/下载功能正常"
    log "INFO" "  3. 历史数据完整无丢失"
    log "INFO" "  4. 执行 'df -h $SOURCE_DIR' 确认数据写入LVM"
    log "INFO" "  5. 旧备份目录:$BACKUP_DIR"
    log "INFO" "  6. 迁移日志:$LOG_FILE"
    log "INFO" "=========================================="
    log "INFO" "【重要】观察期结束并确认无误后,再手动删除旧备份目录"
    log "INFO" "=========================================="
}

# 自动回滚函数
auto_rollback() {
    STAGE="自动回滚"
    log "ERROR" "=========================================="
    log "ERROR" "⚠️  触发自动回滚流程 ⚠️"
    log "ERROR" "=========================================="

    # 回滚逻辑:根据当前执行阶段判断回滚范围
    log "WARN" "正在尝试回滚..."

    # 1. 确保服务已停止
    log "INFO" "回滚步骤1:确保业务服务已停止"
    systemctl stop "$SERVICE_NAME" >> "$LOG_FILE" 2>&1 || true

    # 2. 尝试卸载LVM(如果已挂载到原路径)
    log "INFO" "回滚步骤2:检查并卸载LVM挂载"
    if mountpoint -q "$SOURCE_DIR"; then
        umount "$SOURCE_DIR" >> "$LOG_FILE" 2>&1 || true
    fi
    
    if mountpoint -q "$LVM_TEMP_MOUNT"; then
        umount "$LVM_TEMP_MOUNT" >> "$LOG_FILE" 2>&1 || true
    fi

    # 3. 恢复原目录(如果已重命名)
    log "INFO" "回滚步骤3:检查并恢复原业务目录"
    if [ -d "$BACKUP_DIR" ] && [ ! -d "$SOURCE_DIR" ]; then
        mv "$BACKUP_DIR" "$SOURCE_DIR" >> "$LOG_FILE" 2>&1 || true
    elif [ -d "$BACKUP_DIR" ] && [ -d "$SOURCE_DIR" ]; then
        # 如果SOURCE_DIR已创建但为空,删除后再恢复
        if [ -z "$(ls -A $SOURCE_DIR)" ]; then
            rmdir "$SOURCE_DIR" >> "$LOG_FILE" 2>&1 || true
            mv "$BACKUP_DIR" "$SOURCE_DIR" >> "$LOG_FILE" 2>&1 || true
        fi
    fi

    # 4. 恢复fstab(如果已修改)
    log "INFO" "回滚步骤4:检查并恢复/etc/fstab"
    local fstab_bak=$(ls -t /etc/fstab.lvm_migrate_bak_* 2>/dev/null | head -1)
    if [ -n "$fstab_bak" ]; then
        cp -a "$fstab_bak" /etc/fstab >> "$LOG_FILE" 2>&1 || true
        mount -a >> "$LOG_FILE" 2>&1 || true
    fi

    # 5. 尝试启动服务
    log "INFO" "回滚步骤5:尝试启动业务服务"
    systemctl start "$SERVICE_NAME" >> "$LOG_FILE" 2>&1 || true

    log "WARN" "=========================================="
    log "WARN" "自动回滚流程执行完毕"
    log "WARN" "请手动验证业务是否已恢复正常"
    log "WARN" "旧备份目录:$BACKUP_DIR"
    log "WARN" "迁移日志:$LOG_FILE"
    log "WARN" "=========================================="
}

# 主流程
main() {
    log "INFO" "=========================================="
    log "INFO" "生产环境LVM目录一键迁移脚本启动"
    log "INFO" "日志文件:$LOG_FILE"
    log "INFO" "=========================================="

    # 执行流程
    pre_check
    full_backup
    pre_sync
    
    # 询问是否继续执行停机切换
    echo ""
    log "INFO" "预同步阶段已完成,数据已同步至LVM临时目录"
    read -p "是否继续执行停机切换?(YES/NO):" continue_switch
    
    if [ "$continue_switch" = "YES" ]; then
        stop_and_switch
        config_fstab
        start_and_verify
        
        log "INFO" "=========================================="
        log "INFO" "🎉 迁移脚本执行完毕!"
        log "INFO" "请务必完成手动验证,并保留旧备份目录至观察期结束"
        log "INFO" "=========================================="
    else
        log "INFO" "用户选择暂停,停机切换阶段未执行"
        log "INFO" "数据已预同步至:$LVM_TEMP_MOUNT"
        log "INFO" "可稍后重新运行此脚本继续执行"
    fi
}

# 启动主流程
main

脚本使用说明

1. 脚本配置

使用前请修改脚本顶部的**【用户配置区域】**:

bash 复制代码
# 示例配置
SOURCE_DIR="/opt/zbox"              # 你的业务数据目录
LVM_DEV="/dev/mapper/vg_data-lv_data" # 你的LVM设备路径
SERVICE_NAME="zentao.service"       # 你的业务服务名
FS_TYPE="xfs"                        # 文件系统类型

2. 脚本执行

bash 复制代码
# 1. 赋予执行权限
chmod +x lvm_migrate.sh

# 2. 使用root用户执行
sudo ./lvm_migrate.sh

3. 脚本执行流程

  1. 环境检查:自动检查所有前置条件
  2. 全量备份:自动打包备份源目录(生产必做)
  3. 数据预同步:不停机同步数据至LVM临时目录
  4. 人工确认:询问是否继续停机切换
  5. 停机切换:停止服务 → 增量同步 → 挂载切换
  6. 配置永久挂载 :自动配置/etc/fstab
  7. 启动验证:启动服务并输出验证清单

4. 回滚机制

  • 脚本在关键步骤失败时会自动触发回滚
  • 回滚会尝试:停止服务 → 卸载LVM → 恢复原目录 → 恢复fstab → 启动服务
  • 所有操作都有详细日志,便于排查

1. SELinux 自动处理

在"挂载LVM到原路径"后,自动检测并修复SELinux上下文:

bash 复制代码
# 在脚本的【stop_and_switch】函数中,挂载LVM后添加:

# 自动检测并处理SELinux
if command -v getenforce &> /dev/null; then
    local selinux_status=$(getenforce)
    if [ "$selinux_status" = "Enforcing" ] || [ "$selinux_status" = "Permissive" ]; then
        log "INFO" "检测到SELinux已启用,正在修复安全上下文..."
        run_cmd "restorecon -Rv $SOURCE_DIR" "修复SELinux安全上下文"
    fi
fi

2. 备份文件自动管理(保留最近3次)

在"全量备份"函数后,添加自动清理旧备份的逻辑:

bash 复制代码
# 在脚本的【full_backup】函数后新增:

cleanup_old_backups() {
    STAGE="旧备份清理"
    log "INFO" "=========================================="
    log "INFO" "开始清理旧备份文件(保留最近3次)..."
    log "INFO" "=========================================="

    # 备份文件存储目录(与full_backup中一致)
    local backup_dir="/opt"
    local backup_pattern="lvm_migrate_full_backup_*.tar.gz"
    
    # 列出所有备份文件,按时间排序,保留最近3个,删除其余
    local old_backups=$(ls -t "$backup_dir"/$backup_pattern 2>/dev/null | tail -n +4)
    
    if [ -n "$old_backups" ]; then
        while IFS= read -r backup; do
            run_cmd "rm -f $backup" "删除旧备份:$backup"
        done <<< "$old_backups"
        log "INFO" "✅ 旧备份清理完成"
    else
        log "INFO" "暂无需要清理的旧备份"
    fi
}

# 在【main】函数中,full_backup后调用:
# full_backup
# cleanup_old_backups

3. 文件系统健康检查

在挂载LVM前,添加文件系统检测:

bash 复制代码
# 在【pre_sync】函数挂载LVM前,或【stop_and_switch】函数最终挂载前添加:

# 文件系统健康检查(仅对ext4/xfs等本地文件系统有效)
if [ "$FS_TYPE" = "ext4" ] || [ "$FS_TYPE" = "ext3" ]; then
    log "INFO" "正在执行ext4文件系统健康检查..."
    run_cmd "e2fsck -p $LVM_DEV" "ext4文件系统检测与修复"
elif [ "$FS_TYPE" = "xfs" ]; then
    log "INFO" "XFS文件系统将在挂载后由xfs_repair自动处理,跳过预检查"
fi

4. 网络文件系统(NFS)连接验证

若涉及网络存储,添加网络连通性与挂载验证:

bash 复制代码
# 在【pre_check】函数中新增:

# 网络存储验证(可选,若LVM_DEV为网络设备时启用)
verify_network_storage() {
    # 示例:若LVM_DEV是NFS路径(如nfs_server:/export/lv)
    if [[ "$LVM_DEV" == *":"* ]]; then
        local nfs_server=$(echo "$LVM_DEV" | cut -d: -f1)
        log "INFO" "检测到网络存储,正在验证网络连通性..."
        
        # 验证NFS服务器可连通
        if ! ping -c 3 "$nfs_server" >> "$LOG_FILE" 2>&1; then
            error_exit "网络存储服务器不可达:$nfs_server"
        fi
        
        # 验证NFS导出可用
        if command -v showmount &> /dev/null; then
            run_cmd "showmount -e $nfs_server" "验证NFS导出配置"
        fi
    fi
}

# 在【pre_check】函数末尾调用:
# verify_network_storage

5. Logrotate 配置示例

针对"日志轮转"建议,可单独创建一个 Logrotate 配置文件 /etc/logrotate.d/lvm_migrate

bash 复制代码
/var/log/lvm_migrate_*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0644 root root
    dateext
    dateformat -%Y%m%d
}

注意事项

  1. 测试环境先行:请先在测试环境充分验证脚本,再在生产环境使用
  2. 保留备份:脚本会自动做全量备份,请务必妥善保管,勿立即删除旧备份目录。
  3. SELinux :CentOS/RedHat系统若开启SELinux,挂载后可能需要手动执行restorecon修复上下文
相关推荐
feasibility.3 小时前
SSH Agent Forwarding 与 tmux 排障笔记
linux·运维·服务器·经验分享·笔记·ssh
ShawnLiaoking4 小时前
Linux 会话窗口常开
linux·运维·服务器
230万光年的思念4 小时前
向日葵远程控制ubuntu24一直显示连接中
linux
CDN3604 小时前
中小团队加速 + 防护方案:360CDN+SDK 游戏盾实测
运维·游戏·网络安全
今晚务必早点睡5 小时前
Nginx 从入门到精通:一篇讲透原理、功能、配置与实战场景
运维·nginx·负载均衡
IMPYLH5 小时前
Linux 的 dir 命令
linux·运维·服务器·数据库
fanged5 小时前
操作系统番外1(Linux的测试体系)(TODO)
linux·运维·服务器
成为你的宁宁6 小时前
【Docker 与 Docker-Compose 实战:从零开始容器化部署若依项目,从单容器分步运行到 Compose 一键编排】
运维·docker·容器·docker-compose
123过去7 小时前
pixiewps使用教程
linux·网络·测试工具·算法·哈希算法