记录一次真实的服务器迁移:把 New API、Nginx 反代、生图调度、画布产品四个容器从旧机迁到新机,DNS 切换后端到端验证全绿。过程比想象中多踩了不少「环境差异」的坑,一并整理在这里。
背景
旧机 155.103.156.11 → 新机 38.175.195.160(Debian 12)。计划是用 rsync 搬数据、docker save/load 搬本地构建的镜像,在 1 分钟停服窗口内同步数据库,再切 DNS、重签证书。
八个真实踩坑
- PowerShell 管道传 tar 会损坏文件:Windows PowerShell 的管道不是字节保真的二进制通道,把 tar 经管道传给远端会损坏归档,必须落盘后再用文件方式传输。
bash -s的 stdin 被内层 ssh 吃掉:多层 ssh 嵌套时,外层传给远程bash -s的脚本内容会被内层 ssh 抢占 stdin,需要显式分隔标准输入。rsync -n失效:在部分远程 shell 组合下 dry-run 的预测和实际不一致,不能只靠-n判断会传哪些文件。- Debian 缺 AppArmor:部分容器运行依赖 AppArmor 配置,Debian 12 默认未启用,会导致相关容器起不来或告警。
- 挂载文件的 gid 漂移(本次最典型):
nginx官方镜像里 nginx 组的数字 ID 从旧机容器里的 999 变成了新版镜像的 101。迁移后挂载的.htpasswd-canvas权限失效,直接导致 canvas 全站 500。修复:chown 0:101+chmod 640。 - 本机代理 7897 排障假象:排查”远端连不上”时,本机的代理端口会让你误判成远端网络问题,先排除本地代理再下结论。
- nginx 镜像日志软链阻塞:官方 nginx 镜像把 access/error log 软链到 stdout,如果把挂载目录覆盖到日志路径,会阻塞容器输出。
grep -c的退出码:grep -c在无匹配时返回 1,在set -e脚本里会误触发退出,写脚本要小心它的退出码语义。
经验小结
迁移的关键不在「搬得动」,而在「环境差异的隐性假设」。数据库同步用 sha256 校验一致性、镜像用 docker save/load 保持本地构建版本、证书从手动改自动续期,这几件事比搬运本身更决定成败。
完整过程与回滚矩阵记录在个人知识库 60-sources/2026-08-03-服务器迁移执行记录.md。