Docker 游戏服务器看起来很好迁移:保存镜像、复制配置和存档,再到另一台机器上重新启动容器即可。
真正实践之后会发现,事情远没有这么简单。
一个真正合格的迁移包,需要回答的并不是:
文件有没有备份?
而是:
如果原服务器彻底不存在,只剩下这个备份包,能不能在另一台 Linux + Docker 主机上重新启动服务器,并继续读取原来的世界和存档?
这两者之间存在很大差别。
本文以 Don’t Starve Together(DST) 和 Palworld 两套 Docker 游戏服务器为例,介绍一套相对可靠的 Docker 游戏服务器迁移方法。
所有涉及实际服务器路径、用户名、端口、域名、镜像摘要、世界 ID、时间戳等信息均已脱敏。
一、首先明确什么叫“可迁移备份”
普通备份可能只包含:
docker-compose.yml
配置文件
游戏存档
这样的备份当然有价值,但还不能证明它可以恢复。
完整迁移至少涉及四个部分:
Docker image
+
容器运行配置
+
持久化数据
+
恢复说明
进一步说,一个理想的迁移包应该具备:
migration-package/
├── docker/
│ └── images/
├── config/
├── data/
├── metadata/
├── README-MIGRATION.md
└── MANIFEST.sha256
其中最关键的是:
不能依赖原服务器上仍然存在的东西。
例如,如果恢复过程中仍然需要:
docker pull some-image:latest
那么这并不是严格意义上的自包含迁移。
因为几年之后:
- 镜像可能被删除;
latest可能已经指向另一个版本;- Registry 可能不存在;
- 上游镜像可能发生不兼容变化。
因此,Docker image 本身也应该成为备份的一部分。
二、先搞清楚数据到底在哪里
Docker 应用的数据并不一定存储在 Docker volume 中。
实际部署中常见三种情况。
1. Named Volume
例如:
volumes:
- game-data:/data
这种情况下,需要单独备份 Docker volume。
2. Bind Mount
例如:
volumes:
- ./data:/data
- ./mods:/mods
- ./logs:/logs
这时真正的数据其实就在宿主机目录里。
DST 案例正属于这种情况。
其主要内容包括:
data
runtime
mods
logs
因此迁移重点不是 Docker volume,而是整个应用目录的完整冷备份。
3. 容器 Writable Layer
这是最容易被忽略的一层。
即使 Compose 中已经配置了 volume 或 bind mount,仍然不能直接假设:
所有重要数据一定都在挂载目录里。
如果过去曾经:
docker exec
进入容器并手工修改过文件,那么这些修改可能只存在于容器 writable layer 中。
因此正式迁移前最好检查:
docker diff <container>
如果容器已经不存在,则至少需要确认历史运行方式没有依赖容器内部的临时修改。
三、备份 Docker image,而不是只记录镜像名称
例如某个 Compose 文件写着:
image: vendor/game-server:latest
这并不意味着未来执行:
docker pull vendor/game-server:latest
就能得到现在正在使用的版本。
latest 只是标签,不是不可变版本。
实际迁移中应该记录:
Image ID
RepoDigest
Architecture
OS
Entrypoint
Cmd
Environment
WorkingDir
User
Volumes
ExposedPorts
Healthcheck
然后导出镜像:
docker image save \
-o game-image.tar \
vendor/game-server:latest
最终恢复时:
docker load -i game-image.tar
这样即使上游镜像已经发生变化,仍然能够重新得到当时使用的镜像。
四、docker save 并不一定马上开始写文件
这次 DST 迁移过程中出现了一个比较有意思的现象。
执行:
docker image save -o game-image.tar <image>
之后,很长一段时间看不到正式的:
game-image.tar
反而出现类似:
.tmp-game-image.tarXXXXXXXX
这样的临时文件。
开始时它甚至可能是:
0 bytes
与此同时磁盘却持续进行大量 I/O。
这并不一定意味着命令已经失败。
Docker CLI 在使用 -o 输出时,可能先创建临时文件,随后等待 Docker daemon 准备 image export。
内部可能需要:
解析 image metadata
读取 layer
遍历 content store
准备 tar 数据流
只有 daemon 真正开始向客户端发送数据之后,临时文件才开始增长。
因此实际观察到的过程可能是:
.tmp file = 0 B
↓
磁盘持续大量读取
↓
等待数分钟
↓
.tmp file 开始快速增加
↓
数 GB
↓
导出完成
↓
.tmp 原子重命名为正式 .tar
判断是否真正卡死时,不应该只依赖硬盘声音。
更好的观察方式是:
watch -n 2 'ls -lh .tmp-game-image*'
如果文件大小开始增长,说明镜像数据流已经正常输出。
五、docker save 和 docker export 完全不是一回事
这是 Docker 迁移中非常重要的区别。
docker save
针对:
image
保存:
layer
image metadata
tag
manifest
config
恢复使用:
docker load
docker export
针对:
container filesystem
得到的是一个 root filesystem:
docker export <container>
它不包含完整 Docker image metadata。
因此不能直接:
docker load
恢复。
两者关系大致是:
docker save
↓
完整 Docker image archive
↓
docker load
docker export
↓
root filesystem tar
↓
需要 docker import / 重建配置
因此如果目标是完整迁移,优先使用 docker save。
docker export 更适合作为灾难恢复的备用资产,而不是主要恢复方案。
六、为什么只打包还不够
即使已经拥有:
Docker image
Compose
配置
世界存档
mods
仍然不能直接宣布:
Backup: PASS
因为文件存在,并不代表它们组合起来能够工作。
可靠的迁移必须进行一次:
独立恢复测试。
关键原则是:
测试不能使用原始正式目录。
假设正式数据位于:
/srv/game
恢复测试不能直接:
volumes:
- /srv/game:/data
否则即使容器启动成功,也只能证明原服务器上的数据仍然可用。
正确方法应该是:
正式目录
↓
cold copy
↓
migration staging
↓
测试恢复目录
↓
临时容器
恢复测试必须只读取迁移包中的副本。
七、DST:Master 和 Caves 必须同时验证
DST 的 Dedicated Server 通常不是一个单独实例。
典型结构包括:
Master
Caves
因此只看到:
Master container: running
并不足以认为恢复成功。
至少需要验证:
Master 正常启动
Caves 正常启动
Master / Caves 都使用预期镜像
cluster 被识别
原世界被识别
save 被识别
modoverrides.lua 被读取
Workshop mods 被加载
还必须特别关注:
有没有生成一个新的世界。
如果服务器因为找不到原来的存档而自动初始化了新世界,Docker 本身仍然可能显示:
running
但迁移实际上已经失败。
因此日志验证非常重要。
真正有效的证据通常是:
读取到了以前存在的 world/session
识别到了已有 save version
识别到了现有 mods
没有出现 fresh-world initialization
这才说明:
迁移后的服务器真的接着原来的世界继续运行。
八、Palworld:存档识别比“容器启动成功”更重要
Palworld 同样如此。
成功恢复至少要确认:
服务器进程正常启动
原 SaveGames 目录存在
Level.sav 被识别
LevelMeta.sav 被识别
Config 被读取
没有重新初始化新世界
可以进一步对关键存档做 SHA-256:
sha256sum Level.sav
然后对比:
正式数据 hash
最终备份 hash
如果一致,就能够进一步证明最终归档中的存档与原始服务器一致。
九、为什么恢复测试后还要重新做一次 Cold Copy
游戏服务器在启动过程中通常会修改:
save
logs
runtime
metadata
因此,如果直接拿恢复测试使用过的数据作为最终备份,就可能把测试行为写进迁移包。
更稳妥的流程是:
原正式数据
↓
第一次 cold copy
↓
恢复测试
↓
验证 PASS
↓
删除测试数据
↓
再次从正式目录 cold copy
↓
制作最终迁移包
这样最终归档保存的仍然是正式服务器停止时的原始状态。
十、迁移包必须有内部完整性清单
最终目录应该生成:
MANIFEST.sha256
例如:
find . -type f \
! -name MANIFEST.sha256 \
-print0 |
sort -z |
xargs -0 sha256sum \
> MANIFEST.sha256
随后验证:
sha256sum -c MANIFEST.sha256
这样可以检测:
文件损坏
不完整复制
意外修改
磁盘错误
长期存储 bit rot
十一、最终归档还需要一个外部 SHA-256
内部:
MANIFEST.sha256
保护包里面的文件。
而最终压缩包本身还应该有:
game-migration.tar.zst.sha256
例如:
sha256sum game-migration.tar.zst \
> game-migration.tar.zst.sha256
迁移到另一台机器之后首先执行:
sha256sum -c game-migration.tar.zst.sha256
只有 PASS 才开始解压。
这样形成两层完整性保护:
外层 archive SHA-256
↓
验证压缩包
内部 MANIFEST.sha256
↓
验证所有文件
十二、最终真正需要保留什么
完成迁移以后,长期保存其实并不需要很多文件。
每套游戏只需要:
game-migration.tar.zst
game-migration.tar.zst.sha256
因为迁移包内部已经包含:
Docker image
Compose
配置
游戏存档
mods
metadata
恢复说明
MANIFEST
打包过程产生的:
staging directory
单独的 image tar
临时 rootfs
restore-test directory
旧失败归档
审计中间文件
在确认最终 migration archive 完整以后都可以清理。
十三、README-MIGRATION 才是长期维护的关键
一个五年以后仍然能够恢复的备份,不能依赖维护者还记得当年的操作。
迁移包中应该包含类似:
README-MIGRATION.md
内容至少说明:
支持的 Linux / CPU 架构
Docker 环境要求
镜像导入方法
应用目录恢复方法
权限要求
Compose 启动方法
需要开放的端口
Master/Caves 关系
存档检查方法
日志检查方法
恢复成功判断方法
一个理想的 README 应该做到:
即使原管理员已经完全忘记部署细节,只看这个文件也能重新启动服务。
十四、端口也应该属于迁移资料的一部分
很多迁移最后失败,不是 Docker 问题,而是:
容器正常
进程正常
游戏世界正常
但客户端连接不上
原因通常只是目标服务器没有开放正确端口。
因此最终迁移资料应该根据实际配置提取:
game port
query port
Master port
Caves port
authentication port
而不是根据网上默认值猜测。
例如应该从:
Compose
server.ini
cluster.ini
游戏自身配置
读取实际配置。
这样迁移到新的服务器之后,只需要按照 README 配置:
host firewall
router/NAT
cloud security group
即可恢复外部访问。
十五、迁移成功的真正验收标准
最终不能只看:
docker load: PASS
也不能只看:
docker compose up: PASS
真正的验收标准应该是:
Docker image PASS
配置 PASS
数据 PASS
权限 PASS
容器启动 PASS
游戏进程 PASS
原世界识别 PASS
原存档识别 PASS
Mods PASS
内部 MANIFEST PASS
最终 archive SHA-256 PASS
恢复说明 PASS
只有这些条件真正成立,才可以说:
这个游戏服务器已经脱离原服务器,可以独立迁移。
十六、这次迁移实践带来的几个经验
1. Docker 并不自动等于可迁移
Docker 解决的是运行环境封装问题。
数据、配置、镜像版本、外部网络和恢复流程仍然需要人为管理。
2. latest 不应该成为备份策略
长期运行的服务器可能多年没有更新 image。
当前机器上的:
some-image:latest
和今天 Registry 中的:
some-image:latest
可能已经完全不是同一个东西。
因此必须保存真实 image。
3. docker export 是备用方案,不是首选方案
真正需要的是:
docker save
+
docker load
这样才能保留完整 image metadata。
4. 只有恢复过的备份才值得信任
未经恢复测试的备份只能叫:
已复制的数据。
实际启动并读取原世界以后,才有资格叫:
已验证的迁移备份。
5. 备份和退役应该是两个任务
一个常见错误是把:
备份
迁移验证
删除旧环境
放在一次操作里。
更安全的方法是:
第一阶段
审计
第二阶段
打包
第三阶段
独立恢复验证
第四阶段
确认最终归档
第五阶段
另行决定是否退役原环境
这样即使清理计划后来改变,也不会影响已经做好的迁移包。
结语
Docker 游戏服务器迁移真正需要保存的,并不是某一个目录,也不是某一个容器。
需要保存的是一套完整的运行状态:
镜像
+
配置
+
存档
+
依赖
+
恢复方法
而判断它是否完整的唯一可靠方法,是:
从备份本身真正恢复一次。
当 DST 的 Master 与 Caves 能从迁移副本重新加载原世界,当 Palworld 能重新识别已有 SaveGames,当镜像可以离线导入、配置完整、最终归档通过哈希校验时,这份备份才真正完成了从“文件副本”到“可迁移系统”的转变。