前言:在企业级Linux运维场景中,系统盘与业务数据盘分离是 核心运维规范之一。我们经常会遇到这类场景:业务上线初期未做目录规划,应用数据、上传附件、日志文件直接存放在根分区下的默认目录,随着业务增长,根分区被占满导致系统崩溃、数据备份困难、磁盘扩容受限等问题频发。
本文基于生产环境实战经验,提供一套通用、可复制、零数据丢失、最小化停机窗口 的应用目录迁移方案,通过预同步+增量同步的两步法,将业务停机窗口从数小时压缩至秒级,将正在运行的业务目录无缝迁移至LVM逻辑卷分区,全程遵循Linux标准规范,兼顾稳定性与可操作性,适用于绝大多数内网/公网业务场景。
零数据丢失、秒级停机窗口、可回滚的企业级运维方案
一、适用场景与核心原则
1.1 适用场景
本方案适用于以下所有业务场景,命令可直接适配修改后执行:
- Web应用的用户上传附件、图片、静态资源目录
- GitLab、禅道、Jenkins等DevOps工具的核心数据目录
- 业务系统的日志、报表、临时文件生成目录
- 数据库的非核心数据文件、备份文件存储目录
- 所有需要将「根分区下的普通业务目录」转换为「LVM独立分区挂载点」的场景
1.2 不适用场景
- 系统核心目录(
/、/etc、/var、/usr等)的整体迁移 - 正在运行的数据库核心数据文件目录(如MySQL的datadir,需额外适配数据库锁表、主从同步等机制)
- 无业务停机窗口的金融级核心交易系统(需额外做集群切换、流量切分等适配)
1.3 不可突破的4大核心原则(生产环境红线)
- 数据先行原则:必须先将全量数据同步至LVM分区,再执行挂载操作,禁止空盘直接挂载至业务目录
- 停服写入原则:最终挂载切换前,必须停止业务服务,彻底终止进程对原目录的写入,避免数据分裂、丢失
- 权限一致原则:迁移前后必须保证目录/文件的权限、属主属组完全一致,避免业务启动失败、读写异常
- 可回滚原则:所有操作必须保留回滚能力,禁止直接删除原目录数据,必须业务验证无误后再清理旧数据
二、前置准备与环境检查
2.0 依赖工具安装
本方案用到的rsync、lsof、acl工具,在部分极简版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 环境信息确认与检查项
执行迁移前,必须完成以下检查,避免踩坑:
-
目录大小确认 :确认业务目录总大小,确保LVM分区可用空间 ≥ 目录总大小的1.2倍
bashdu -sh /your/app/data/path -
服务状态确认:确认业务服务的启停命令、进程状态,确保可正常停止/启动
-
权限备份 :备份原目录的权限、属主信息,用于迁移后校验
bash# 备份权限信息 getfacl -R /your/app/data/path > /tmp/app_dir_perm.bak -
进程占用检查 :确认是否有进程正在读写业务目录,避免同步时数据不一致
bashlsof /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:确认挂载的文件系统没有加
noexec、nosuid等限制权限的挂载参数。 - 排查方向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:终止所有向旧目录写入的进程,重新同步增量数据,再次验证写入方向。
迁移总结
- 本方案是生产环境下「普通业务目录转LVM独立分区」的标准化方案,全程遵循Linux运维规范,可适配绝大多数业务场景。
- 迁移的核心逻辑永远是:先同步数据到LVM → 停服务终止写入 → 挂载切换 → 配置永久生效 → 启服务验证,顺序不可颠倒。
- 生产环境变更,永远要把「数据安全、可回滚」放在第一位,禁止在无备份、无回滚方案的情况下执行挂载操作。
- 最小化停机窗口的核心,是提前做不停机预同步,把最终停机的操作压缩到最短。
- 旧数据清理是迁移的最后一道防线,必须经过完整的业务周期观察与多层级验证,确认无误后再安全清理,严禁提前删除备份数据。
生产环境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. 脚本执行流程
- 环境检查:自动检查所有前置条件
- 全量备份:自动打包备份源目录(生产必做)
- 数据预同步:不停机同步数据至LVM临时目录
- 人工确认:询问是否继续停机切换
- 停机切换:停止服务 → 增量同步 → 挂载切换
- 配置永久挂载 :自动配置
/etc/fstab - 启动验证:启动服务并输出验证清单
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
}
注意事项
- 测试环境先行:请先在测试环境充分验证脚本,再在生产环境使用
- 保留备份:脚本会自动做全量备份,请务必妥善保管,勿立即删除旧备份目录。
- SELinux :CentOS/RedHat系统若开启SELinux,挂载后可能需要手动执行
restorecon修复上下文
