流程示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
┌──────────────────────────────┐
│ 旧服务器 (源) │
│ 正在运行的 Docker 容器 │
└──────────────┬───────────────┘

│ 执行

┌──────────────────────────────┐
│ docker-smart-migrate.sh │
│ - 收集容器镜像、配置、数据卷 │
│ - 打包 docker_migrate_bundle │
│ - scp 传输到新服务器 │
└──────────────┬───────────────┘

│ 生成迁移包并传输

┌──────────────────────────────┐
│ 新服务器 (目标) │
│ 空环境 / 已安装 Docker │
└──────────────┬───────────────┘

│ 解包并恢复

┌──────────────────────────────┐
│ docker-restore.sh │
│ - 导入私有镜像 (docker load) │
│ - 拉取公共镜像 (docker pull) │
│ - 解压数据卷 │
└──────────────┬───────────────┘

│ 生成编排文件

┌──────────────────────────────┐
│ gen-compose.sh │
│ - 读取容器 JSON 配置 │
│ - 生成 docker-compose.yml │
└──────────────┬───────────────┘

│ 启动容器

┌──────────────────────────────┐
│ docker-compose up -d │
│ - 按生成的 compose 一键启动 │
└──────────────┬───────────────┘

│ 验证服务

┌──────────────────────────────┐
│ check-containers.sh │
│ - 检查容器状态/健康检查 │
│ - 确认迁移是否成功 │
└──────────────────────────────┘

1.迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/bin/bash
# 一键迁移 Docker 容器到新服务器
# 用法: ./docker-smart-migrate.sh <目标服务器用户> <目标服务器IP> <目标目录>

USER=$1
HOST=$2
DEST=$3

if [ -z "$USER" ] || [ -z "$HOST" ] || [ -z "$DEST" ]; then
echo "❌ 参数不足"
echo "用法: $0 <目标服务器用户> <目标服务器IP> <目标目录>"
exit 1
fi

mkdir -p migrate_tmp/images migrate_tmp/volumes migrate_tmp/config

echo ">>> 收集容器信息"
docker ps --format '{{.ID}} {{.Names}}' | while read cid cname; do
echo ">>> 处理容器: $cname"
img=$(docker inspect --format '{{.Config.Image}}' $cid)
echo " 镜像: $img"

# 公共镜像跳过,只保存私有镜像
if [[ "$img" == *"spg-registry"* || "$img" == *"gitlab"* ]]; then
echo " ⏳ 保存镜像: $img"
docker save -o migrate_tmp/images/${cname}.tar $img
else
echo " ✅ 公共镜像, 新服务器上直接 pull: $img"
echo "$img" >> migrate_tmp/images/public_images.txt
fi

# 保存容器配置
docker inspect $cid > migrate_tmp/config/${cname}.json

# 保存挂载卷数据
for vol in $(docker inspect --format '{{range .Mounts}}{{.Source}} {{end}}' $cid); do
if [ -d "$vol" ]; then
vname=${cname}_$(basename $vol)
echo " 打包数据卷: $vol"
tar czf migrate_tmp/volumes/${vname}.tgz -C $(dirname $vol) $(basename $vol)
fi
done
done

echo ">>> 打包迁移文件"
tar czf docker_migrate_bundle.tgz migrate_tmp
rm -rf migrate_tmp

echo ">>> 传输到目标服务器: $HOST"
ssh $USER@$HOST "mkdir -p $DEST"
scp docker_migrate_bundle.tgz $USER@$HOST:$DEST/

echo ">>> 在目标服务器上解压"
ssh $USER@$HOST "cd $DEST && tar xzf docker_migrate_bundle.tgz"

echo "✅ 迁移完成"
echo "下一步请在新服务器执行: ./docker-restore.sh $DEST/migrate_tmp"

2.恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash
# 用法: ./docker-restore.sh <迁移目录>
# 示例: ./docker-restore.sh /opt/docker-migrate/migrate_tmp

SRC=$1
if [ -z "$SRC" ]; then
echo "❌ 参数不足"
echo "用法: $0 <迁移目录>"
exit 1
fi

cd $SRC || exit 1

echo ">>> 1. 导入私有镜像"
for img in images/*.tar; do
[ -f "$img" ] || continue
echo " 加载镜像: $img"
docker load -i $img
done

echo ">>> 2. 拉取公共镜像"
if [ -f images/public_images.txt ]; then
while read img; do
echo " 拉取: $img"
docker pull $img
done < images/public_images.txt
fi

echo ">>> 3. 恢复数据卷"
for vol in volumes/*.tgz; do
[ -f "$vol" ] || continue
echo " 解压数据卷: $vol"
tar xzf $vol -C /
done

echo "✅ 数据和镜像已恢复"
echo "下一步: ./gen-compose.sh $SRC/config > docker-compose.yml && docker-compose up -d"

3.启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/bash
# 用法: ./gen-compose.sh <config目录> > docker-compose.yml

CFG_DIR=$1
if [ -z "$CFG_DIR" ]; then
echo "❌ 参数不足"
echo "用法: $0 <config目录>"
exit 1
fi

echo "version: '3.8'"
echo "services:"

for cfg in $CFG_DIR/*.json; do
cname=$(basename $cfg .json)
image=$(jq -r '.[0].Config.Image' $cfg)
cmd=$(jq -r '.[0].Path + " " + (.[0].Args|join(" "))' $cfg)

echo " $cname:"
echo " image: $image"

# 端口
ports=$(jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "- \(.value[0].HostPort):\(.key)"' $cfg)
if [ -n "$ports" ]; then
echo " ports:"
echo "$ports" | sed 's/^/ /'
fi

# 卷
mounts=$(jq -r '.[0].Mounts[]? | "- \(.Source):\(.Destination)"' $cfg)
if [ -n "$mounts" ]; then
echo " volumes:"
echo "$mounts" | sed 's/^/ /'
fi

# 环境变量
envs=$(jq -r '.[0].Config.Env[]? | "- \(. )"' $cfg)
if [ -n "$envs" ]; then
echo " environment:"
echo "$envs" | sed 's/^/ /'
fi

# 启动命令
if [ "$cmd" != " " ]; then
echo " command: $cmd"
fi

echo
done