1. 项目概述
在企业的IT基础设施运维中,经常会遇到需要将整套Docker应用环境从一个服务器迁移到另一个服务器的场景。更棘手的是,目标服务器可能完全隔离在无网络环境中,无法访问外部镜像仓库。这种情况下,传统的docker pull方式完全失效,需要一套完整的离线迁移方案。
我最近刚完成一个金融系统的迁移项目,源环境包含12个相互依赖的微服务,涉及MySQL、Redis、Nginx等多个组件,目标机房则严格禁止外网连接。通过实践总结出这套"零网络依赖"的迁移方案,成功将原本需要2天的工作压缩到2小时内完成。
2. 核心迁移流程设计
2.1 整体迁移架构
整个迁移过程可以分为三个关键阶段:
-
源服务器准备阶段:
- 镜像打包:将运行中的容器镜像导出为离线文件
- 配置备份:保存docker-compose.yml等编排文件
- 数据归档:对数据库等持久化数据进行一致性备份
-
物理传输阶段:
- 使用移动硬盘/U盘等介质传输备份文件
- 大文件传输建议使用exFAT格式的SSD移动硬盘
-
目标服务器恢复阶段:
- Docker环境初始化
- 镜像加载与配置恢复
- 数据解压与权限修复
- 服务启动与验证
2.2 为什么选择这种方案?
相比常见的迁移方法,这套方案有三大优势:
- 完全离线操作:不依赖任何网络连接,适合严格隔离的环境
- 原子性迁移:所有组件作为一个整体迁移,避免版本不一致问题
- 时间窗口短:实际切换时间可控制在30分钟内,对业务影响小
3. 详细实施步骤
3.1 源服务器准备工作
3.1.1 镜像打包技巧
bash复制# 获取所有正在运行的容器镜像(去重)
ACTIVE_IMAGES=$(docker ps --format "{{.Image}}" | sort -u)
# 获取所有已停止但可能需要的容器镜像
STOPPED_IMAGES=$(docker ps -a --format "{{.Image}}" | sort -u)
# 合并并去重
ALL_IMAGES=$(echo "$ACTIVE_IMAGES $STOPPED_IMAGES" | tr ' ' '\n' | sort -u)
# 导出为单个tar包(保留原始tag信息)
docker save -o /backup/full_images.tar $ALL_IMAGES
# 验证导出文件完整性
if file /backup/full_images.tar | grep -q 'POSIX tar archive'; then
echo "镜像导出成功,文件大小: $(du -sh /backup/full_images.tar | cut -f1)"
else
echo "镜像导出失败!" >&2
exit 1
fi
关键点:使用
docker save而非docker export,前者会保留完整的镜像分层结构和元数据,后者只保存容器文件系统快照。
3.1.2 配置备份最佳实践
bash复制# 创建备份目录
BACKUP_DIR="/backup/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
# 备份所有docker-compose文件(递归搜索)
find / -name "docker-compose*.yml" -exec cp {} $BACKUP_DIR \;
# 备份环境变量文件
find / -name ".env" -exec cp {} $BACKUP_DIR \;
# 打包整个Docker配置目录(包含daemon.json等)
tar czf $BACKUP_DIR/docker_config.tar.gz /etc/docker
# 记录Docker版本信息
docker version > $BACKUP_DIR/docker_version.txt
3.1.3 数据备份的注意事项
对于数据库类应用,必须确保备份时的事务一致性:
bash复制# MySQL热备份方案
docker exec mysql sh -c 'exec mysqldump --all-databases --single-transaction -uroot -p"$MYSQL_ROOT_PASSWORD"' > $BACKUP_DIR/mysql_full.sql
# PostgreSQL备份方案
docker exec postgres pg_dumpall -U postgres > $BACKUP_DIR/postgres_full.sql
# Redis RDB文件备份
docker exec redis redis-cli save
cp $(docker inspect redis --format '{{.GraphDriver.Data.Mountpoint}}')/dump.rdb $BACKUP_DIR/
对于普通文件数据:
bash复制# 备份命名卷(保留权限和属性)
docker run --rm -v app_data:/volume -v $BACKUP_DIR:/backup alpine \
tar -czf /backup/app_data.tar.gz -C /volume .
# 备份绑定挂载目录
tar --same-owner -czf $BACKUP_DIR/static_files.tar.gz /path/to/mounted/dir
3.2 物理介质传输
3.2.1 文件组织建议
推荐按以下结构组织备份文件:
code复制/迁移备份_20230815/
├── images/
│ └── full_images.tar
├── configs/
│ ├── docker-compose.yml
│ ├── .env
│ └── docker_config.tar.gz
├── data/
│ ├── mysql_full.sql
│ ├── app_data.tar.gz
│ └── static_files.tar.gz
└── meta/
├── docker_version.txt
└── checksums.sha256
3.2.2 生成校验文件
bash复制# 生成所有文件的SHA256校验码
find /backup -type f -exec sha256sum {} \; > /backup/checksums.sha256
# 验证示例(在目标服务器执行)
sha256sum -c /backup/checksums.sha256
3.3 目标服务器部署
3.3.1 环境准备
bash复制# 离线安装Docker(以Ubuntu为例)
sudo dpkg -i docker-ce_20.10.24~ubuntu-focal_amd64.deb \
docker-ce-cli_20.10.24~ubuntu-focal_amd64.deb \
containerd.io_1.6.6-1_amd64.deb
# 配置用户权限
sudo usermod -aG docker $USER
newgrp docker # 立即生效无需注销
# 验证安装
docker --version
docker run hello-world # 测试基础功能
3.3.2 镜像和配置恢复
bash复制# 加载所有Docker镜像
docker load -i /backup/images/full_images.tar
# 检查已加载镜像
docker images
# 恢复Docker配置
sudo tar xzf /backup/configs/docker_config.tar.gz -C /
# 重启Docker服务使配置生效
sudo systemctl restart docker
3.3.3 数据恢复关键步骤
bash复制# 创建所有需要的命名卷(提前创建可避免权限问题)
docker volume create app_data
docker volume create db_data
# 恢复命名卷数据
docker run --rm -v app_data:/target -v /backup/data:/backup alpine \
tar xzf /backup/app_data.tar.gz -C /target
# 恢复绑定挂载目录
sudo mkdir -p /path/to/mounted/dir
sudo tar --same-owner -xzf /backup/data/static_files.tar.gz -C /
3.3.4 服务启动与验证
bash复制# 进入配置目录
cd /backup/configs
# 启动所有服务
docker compose up -d
# 监控启动过程
watch 'docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
# 验证服务健康状态
for container in $(docker ps -q); do
echo "Checking $container..."
docker inspect --format='{{.State.Health.Status}}' $container
done
4. 高级技巧与问题排查
4.1 跨架构迁移方案
当源服务器和目标服务器CPU架构不同时(如x86到ARM):
bash复制# 在源服务器上构建多架构镜像
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:multiarch .
# 导出特定架构的镜像
docker save -o myapp_arm64.tar --platform linux/arm64 myapp:multiarch
4.2 空间不足的解决方法
如果目标服务器磁盘空间紧张:
bash复制# 1. 导出时压缩镜像(节省30%-50%空间)
docker save myapp:latest | gzip > myapp.tar.gz
# 2. 在目标服务器上边加载边解压
gzip -dc myapp.tar.gz | docker load
# 3. 加载后立即清理临时文件
docker load -i myapp.tar && rm -f myapp.tar
4.3 常见错误排查
问题1:docker load时报"no space left on device"
bash复制# 检查Docker存储驱动空间
docker system df
# 清理无用资源
docker system prune -a -f
# 或者临时修改存储位置
sudo systemctl stop docker
sudo rsync -a /var/lib/docker /new/location
sudo ln -s /new/location/docker /var/lib/docker
sudo systemctl start docker
问题2:服务启动后端口冲突
bash复制# 查看端口占用情况
sudo netstat -tulnp
# 修改compose文件中的端口映射
services:
webapp:
ports:
- "8080:80" # 改为未被占用的端口
问题3:数据库服务启动失败
bash复制# 查看日志定位问题
docker logs -f mysql_container
# 常见解决方案:
# 1. 检查数据目录权限
# 2. 验证数据库文件完整性
# 3. 调整内存参数(如innodb_buffer_pool_size)
5. 迁移后验证清单
为确保迁移完全成功,建议执行以下验证步骤:
-
基础检查:
bash复制# 所有容器应处于运行状态 docker ps --format "table {{.Names}}\t{{.Status}}" | grep -v Up && echo "有容器未运行" # 检查关键端口监听 sudo netstat -tuln | grep -E '80|443|3306|6379' -
服务连通性测试:
bash复制# HTTP服务测试 curl -I http://localhost:8080/api/health # 数据库连接测试 docker exec mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW DATABASES;" -
数据一致性验证:
bash复制# 对比关键文件的MD5值 docker run -v app_data:/data alpine sh -c "find /data -type f -exec md5sum {} \;" > /tmp/new.md5 diff /backup/meta/original.md5 /tmp/new.md5 -
性能基准测试:
bash复制# 数据库性能测试 docker exec mysql mysqlslap -uroot -p"$MYSQL_ROOT_PASSWORD" --concurrency=50 --iterations=10 --auto-generate-sql # Web应用压力测试 docker run --rm alpine/ab -n 1000 -c 100 http://webapp:8080/ -
日志错误扫描:
bash复制# 扫描最近1小时内的错误日志 for container in $(docker ps -q); do echo "=== $container ===" docker logs --since 1h $container | grep -i -E 'error|fail|exception' done
6. 实战经验分享
在多次实施这类迁移后,我总结了几个关键经验:
-
镜像瘦身技巧:
- 迁移前运行
docker image prune -a清理无用镜像 - 多阶段构建的镜像通常比普通镜像小50%以上
- 考虑使用alpine等精简基础镜像
- 迁移前运行
-
数据库备份优化:
bash复制# 使用pigz加速压缩(多线程gzip) docker exec mysql mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases | pigz > backup.sql.gz # 备份时排除不必要的数据 mysqldump --ignore-table=mysql.general_log --ignore-table=mysql.slow_log ... -
增量迁移策略:
- 首次全量迁移后,后续可通过rsync同步变化的数据文件
- 对大型数据库,先迁移结构再迁移数据
-
回滚方案设计:
- 保留最后一次成功的备份
- 编写回滚脚本并提前测试
- 关键业务建议在迁移前创建数据库从库
-
权限问题预防:
bash复制# 数据恢复后统一修复权限 docker run --rm -v app_data:/data alpine chown -R 1000:1000 /data # 或者使用与源服务器相同的UID/GID
这套方案已经在金融、医疗等多个行业的生产环境中验证过,最复杂的案例涉及50多个相互依赖的微服务,数据量超过2TB。通过合理的分阶段实施和充分的验证,可以实现零停机的平滑迁移。