服务器用久之后,最容易变得混乱的地方通常不是系统本身,而是数据放置方式:站点文件在一个目录,数据库在另一个目录,Docker 数据和日志又在系统盘里。等到系统盘快满、云硬盘需要扩容或更换时,才发现迁移边界并不清楚。
这篇文章记录一种比较稳妥的数据盘迁移方法:把站点文件、MySQL 数据、Docker 数据和 Docker 日志统一放到新的数据盘上,系统盘尽量只保留系统、软件包和配置文件。
目标
迁移后的结构大致是这样:
/
├── var
│ ├── lib
│ │ ├── mysql -> 绑定到数据盘
│ │ └── docker -> 绑定到数据盘
│ └── www
│ └── example.com -> 指向数据盘里的站点目录
/mnt/data
├── www
│ └── example.com
└── system-data
├── mysql
└── docker这样做有几个好处:
- 系统盘更干净,重装或扩容时风险更小。
- MySQL、Docker、站点文件都在数据盘,备份和迁移边界更清晰。
- Docker 的容器日志也跟随 Docker 数据目录放到数据盘,避免系统盘被日志写满。
- 以后更换数据盘时,只需要围绕一个挂载点处理。
迁移前先盘点
不要一上来就复制文件。先确认当前磁盘、挂载点、服务和主要数据目录。
lsblk -f
df -hT
findmnt
systemctl --failed看 MySQL 和 Docker 当前数据目录:
mysql -NBe "SELECT @@datadir;"
docker info --format 'DockerRootDir={{.DockerRootDir}} Driver={{.Driver}}'看几个关键目录的大小:
du -sh /var/lib/mysql /var/lib/docker /var/www /mnt/data 2>/dev/null如果 Docker 里有 bind mount,也要提前列出来:
docker ps -q | xargs -r docker inspect \
--format '{{.Name}} {{range .Mounts}}{{.Source}}=>{{.Destination}} {{end}}'这一步的目标是搞清楚:哪些是运行时数据,哪些只是旧备份、旧应用或可以删除的历史文件。
准备新数据盘
假设新盘是 /dev/vdb,旧数据盘挂载在 /mnt/data,临时挂载点使用 /mnt/data-new。实际操作时请替换成自己的设备名。
先确认新盘没有挂错:
lsblk -f然后分区并格式化。下面只是示例,执行前一定要确认设备名:
parted /dev/vdb --script mklabel gpt
parted /dev/vdb --script mkpart primary ext4 0% 100%
mkfs.ext4 -L data-disk /dev/vdb1创建临时挂载点并挂载:
mkdir -p /mnt/data-new
mount /dev/vdb1 /mnt/data-new先做在线预同步
真正停服务前,可以先把大部分静态数据同步过去,减少最终停机时间。
rsync -aHAX --numeric-ids --info=stats2 /mnt/data/ /mnt/data-new/如果 MySQL 和 Docker 还在系统盘,可以先在新数据盘上准备目录:
mkdir -p /mnt/data-new/system-data/mysql
mkdir -p /mnt/data-new/system-data/docker在线预同步一次:
rsync -aHAX --numeric-ids --delete /var/lib/mysql/ /mnt/data-new/system-data/mysql/
rsync -aHAX --numeric-ids --delete /var/lib/docker/ /mnt/data-new/system-data/docker/这里的预同步只是为了节省时间,不能当作最终一致的数据。MySQL 和 Docker 运行时仍然会写文件,所以最后还需要停服务再同步一次。
进入维护窗口
最终切换时,需要短暂停掉会写入数据的服务。常见包括:
systemctl stop cron nginx php8.2-fpm docker mysql如果机器上服务名不同,例如 PHP 版本不是 8.2,按实际情况替换。
停服务后做最终同步
先同步旧数据盘内容到新数据盘:
rsync -aHAX --numeric-ids --delete /mnt/data/ /mnt/data-new/再同步 MySQL 和 Docker 数据:
rsync -aHAX --numeric-ids --delete /var/lib/mysql/ /mnt/data-new/system-data/mysql/
rsync -aHAX --numeric-ids --delete /var/lib/docker/ /mnt/data-new/system-data/docker/几个参数含义:
-a:归档模式,保留权限、时间等信息。-HAX:尽量保留硬链接、ACL、扩展属性。--numeric-ids:按数字 UID/GID 同步,避免用户名映射差异。--delete:删除目标端多余文件,让目标端和源端一致。
更新挂载关系
先查新盘 UUID:
blkid /dev/vdb1备份 /etc/fstab:
cp /etc/fstab /etc/fstab.bak.$(date +%Y%m%d%H%M%S)然后把数据盘挂载改成新 UUID。示例:
UUID=<NEW_DATA_DISK_UUID> /mnt/data ext4 defaults 0 1如果希望 MySQL 和 Docker 继续使用原来的路径,可以使用 bind mount。这样配置文件和服务不需要感知真实数据位置变化:
/mnt/data/system-data/mysql /var/lib/mysql none bind,nofail 0 0
/mnt/data/system-data/docker /var/lib/docker none bind,nofail 0 0切换挂载点
为了保留回滚机会,可以先把旧目录改名,而不是直接删除:
mv /var/lib/mysql /var/lib/mysql.before-data-disk
mkdir -p /var/lib/mysql
chown mysql:mysql /var/lib/mysql
chmod 750 /var/lib/mysql
mv /var/lib/docker /var/lib/docker.before-data-disk
mkdir -p /var/lib/docker
chmod 711 /var/lib/docker卸载旧挂载点和临时挂载点,再按新的 fstab 挂载:
umount /mnt/data
umount /mnt/data-new
mount /mnt/data
mount /var/lib/mysql
mount /var/lib/docker确认挂载结果:
findmnt /mnt/data
findmnt /var/lib/mysql
findmnt /var/lib/docker
df -hT /mnt/data /var/lib/mysql /var/lib/docker启动服务并验证
服务启动顺序可以按依赖来:
systemctl start mysql docker php8.2-fpm nginx cron验证服务状态:
systemctl is-active mysql docker php8.2-fpm nginx cron
systemctl --failed验证 MySQL:
mysqladmin ping验证 Docker:
docker info --format 'DockerRootDir={{.DockerRootDir}}'
docker ps如果是 WordPress,可以用 PHP 直接加载一次站点:
php -r "require '/mnt/data/www/example.com/wp-load.php'; echo get_bloginfo('name'), PHP_EOL;"最后从 HTTP 层验证:
curl -I https://example.com/控制 Docker 日志大小
迁移 Docker 数据目录后,顺手应该把 Docker 日志大小控制住。否则 json-file 日志可能持续增长。
编辑 /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
},
"live-restore": true
}这个配置对新创建的容器生效。已经存在的容器,可以再用 logrotate 兜底:
/var/lib/docker/containers/*/*-json.log {
rotate 5
daily
size 20M
compress
missingok
delaycompress
copytruncate
notifempty
}这样新容器有 Docker 原生日志限制,旧容器也不会无限写大日志文件。
旧盘怎么处理
确认新盘运行正常后,旧盘先不要急着格式化。建议至少保留一段观察期:
- 服务都能启动。
- 网站能访问。
- 数据库能读写。
- Docker 容器状态正常。
- 重启后挂载关系仍然正确。
确认没有回滚需求后,再到云厂商控制台分离旧数据盘。注意:系统里的 umount 只是卸载文件系统,云厂商控制台里的“卸载/分离硬盘”是另一步。
回滚思路
如果切换后发现问题,回滚的关键是:旧数据不要立刻删除,旧盘不要立刻释放。
常见回滚方式:
- 停止相关服务。
- 恢复
/etc/fstab备份。 - 重新挂载旧数据盘。
- 把
/var/lib/mysql、/var/lib/docker指回旧数据。 - 启动服务并验证。
等新盘稳定运行后,再删除系统盘上的迁移备份目录,释放空间。
总结
更换数据盘最重要的不是某一条命令,而是迁移边界要清楚:哪些数据必须一致,哪些服务会写入,哪些路径需要保持兼容。
我比较推荐的结构是:系统盘只放系统和软件,数据盘承载站点文件、MySQL、Docker 数据和 Docker 日志。通过 fstab 和 bind mount 保持原路径不变,既减少配置改动,也让后续备份、扩容和迁移更容易。