迁移一台服务器,真正要切换的是什么?
一次多服务迁移教会我的写入边界、验证顺序与回滚方法
一次迁移能否成功,不能由“文件复制完了”或“新机返回 200”决定。真正要交接的是写入权、数据状态、用户流量和日常运维职责。
一台小型服务器上跑着网站、API、数据库、缓存、反向代理和监控,看起来仍然叫“迁移一台机器”。等真的开始做,问题却一个接一个:复制数据库时,旧服务会不会还在写?新机的证书能否在 DNS 切换前验证?DNS 改了以后,为什么还有人访问旧 IP?旧机留着,是不是就能随时一键回滚?
这次经历让我不再把迁移理解成“搬家”。它更像一次主权交接:旧系统什么时候失去写入权,新系统什么时候取得写入权,用户请求在交接前后究竟走向哪里。只要把这些问题说清,复杂操作就有了顺序。
同一套系统,有四条不同的“迁移进度”
| 进度 | 要迁走什么 | 常见误判 |
|---|---|---|
| 程序 | 镜像、代码、运行参数、网络与端口 | 容器启动了,就以为业务可用 |
| 状态 | 数据库、缓存持久化、上传/录制文件、内容目录 | 数据库复制了,就以为所有数据都在 |
| 入口 | HTTPS、反向代理、DNS、仍缓存旧地址的客户端 | 改完 DNS,就以为所有用户瞬间到了新机 |
| 运维 | 备份、证书续期、定时任务、CI/CD、告警 | 今天能访问,就以为未来也会自动维护 |
这四条进度不同时完成。可以让新机程序先跑起来,而它的数据仍是昨天的;可以让 DNS 已经更新,而一部分客户端仍访问旧地址;也可以让网站正常打开,但下一次证书续期因任务未迁移而失败。迁移清单应围绕这些差异制定,而不是只按“装 Docker、复制目录、改域名”排列。
先盘点实际运行状态,再决定搬什么
仓库里的 Compose 文件只能说明计划运行什么。真正要看的,是此刻运行的容器、实际使用的镜像版本、挂载卷、目录、网络、入口路由和定时任务。尤其要追问每一块数据:它写在哪里?是谁在写?复制它时能否保持不变?恢复后怎样验证?
我会先把组件分成三类。应用层包括网页、API 和后台任务,可能直接或间接产生写入;状态层包括数据库、缓存和文件;入口与运维层包括反向代理、证书、DNS、备份及部署流水线。即使一个网页容器自身不存数据,它仍可能把请求送到会写数据的 API,所以切换时不能仅凭“这是前端”就让它继续接流量。
版本也要盘。一个值得记住的坑是 latest:新机重新拉取同名镜像时,拿到的可能已经是新主版本。这样一来,迁移同时变成升级,出了问题很难判断原因。用镜像 digest 对齐运行版本,把计划外变化排除出去,通常比“新机顺便升一升”可靠。
预演可以用旧备份,正式切换不能
预演的目的是回答:“目标机器能不能启动并恢复?”它不需要先承接真实流量。把一份现有备份恢复到新机,检查数据库能否打开、容器能否连接、证书和路由能否工作,能在维护窗口前发现大量问题。
但预演备份天然会落后。哪怕它的每个文件都传输完整,只要旧系统仍在服务用户,它就不是生产的最终状态。正确的做法是把预演结果明确标为临时数据,在正式切换时停止旧系统的写入源,再制作最终备份或同步最后一段增量,并覆盖预演状态。
这里有两种经常被混为一谈的保证:
- 传输完整性: 新旧两边文件的 SHA-256 一致,说明传输后的文件字节相同。
- 状态一致性: 取得最终数据时,相关应用、任务和其他写入源已经停止;数据库、缓存和文件处于可一起恢复的业务状态。
校验和只能证明第一件事。它无法替你证明“复制开始后再没有新订单、新消息或新文件”。对多种存储并存的小型系统,短维护窗口内暂停写入,再取最终状态,往往比试图在线拼出跨数据库、缓存和文件的同步快照更容易解释和验证。
DNS 没切之前,就应该验收新机
HTTPS 证书验证的是域名,不是服务器的固定公网 IP。只要新机拥有正确的证书与对应私钥,反向代理加载了它们,且公网 443 真正可达,就能在公开 DNS 仍指向旧机时验证新入口。
一种方法是让单次测试请求仍使用原域名,却强制连接到新 IP,例如 curl --resolve。这样,HTTP Host 和 TLS 的域名校验仍按真实访问场景工作,只有“域名解析到哪里”被临时替换。它能提前找出外层防火墙、证书、反向代理或容器路由的问题,却不会把普通用户提前送去新机。
验收要逐层向上,而不是停在一个绿色的健康检查:
- 机器与容器: 目标系统有足够的内存和磁盘,容器运行,依赖能连接。
- 协议与入口: 从机器外部验证 HTTPS 证书、域名路由和预期响应。
- 状态与业务: 核对关键数据,并完成真实的写入、读取、权限拒绝和清理闭环。
- 运维链路: 确认证书续期、定时备份、部署目标与监控指向新机;尚未实际跑过的任务要标为待验证。
健康检查通过只说明它所检查的东西健康。一次注册、读取、注销的闭环,能证明的业务范围比“首页 200”大得多;但也不能因此推断付费接口、所有后台任务和下一次自动部署都已通过。结论应停在证据到达的那一层。
切换顺序:让旧机先停止写,让新机后开始写
正式维护窗口的关键不是“同时操作两台机器”,而是守住单一写入源。可把过程理解成六个关卡:
新机预演可用
↓
停旧机和新机的应用写入,暂停相关任务
↓
从旧机取最终数据,在新机校验、恢复
↓
新机启动,完成协议与业务验收
↓
旧机先成为通往新机的临时桥
↓
最后修改 DNS,并持续观察
为什么旧机还要做“桥”?DNS 有缓存。即使权威记录已经指向新 IP,先前查询过域名的客户端仍可能拿着旧 IP。若旧机直接下线,这批用户会遇到连接失败;若旧机的反向代理暂时把请求转到新机,他们至少还有一条可用路径。这个桥必须在改 DNS 之前配置并测试。它也不是永久高可用架构:旧机仍产生费用,多绕一跳增加延迟,最终仍要在合适时机撤除。
这一顺序通常意味着一段真实的业务停顿。应用停写后、最终数据恢复且新机通过验收前,旧入口可能无法完成请求。若没有专门设计请求排空和在线双写协议,就不应宣传为“零停机”或“所有在途请求必定成功”。把停顿写进维护窗口,比把它藏在一串成功的命令后面更诚实。
回滚不是把 DNS 改回去
切换前后的回滚有一道分界线:新机是否已经接受过写入。
在新机尚未接受写入时,旧机仍有完整的最终状态,可以恢复旧应用与旧入口。新机开始接收注册、消息或文件后,旧机立即变成一份过时副本。此时如果只把 DNS 改回去,用户刚写到新机的数据就会“消失”在旧机视角里。正确回退需要先冻结新机写入,再把新增的数据库、缓存和文件状态反向同步并验证,之后才切回入口。
所以“保留旧机”提供的是回滚材料和一段缓冲时间,不是免费的撤销按钮。越早明确这条分界线,越不容易在故障时做出看似最快、实际丢数据的决定。
上线当天之外,还要回答两个问题
第一个问题是:如果明天新机彻底坏了,最多丢多少数据、多久恢复? 前者常叫 RPO,后者叫 RTO。每天一次的本机备份,可以帮助处理误删或部分故障,却无法覆盖整台机器或账号失效。至少要有另一台设备或另一处存储保存可恢复的副本;自己的电脑也可以成为起点,但它可能睡眠、断网、磁盘满,需要补拉、失败提醒和定期试恢复。数据库之外的文件与私密配置,也应进入完整恢复清单。
第二个问题是:今天测试通过的链路,未来谁来持续验证? 证书目前有效,不等于下次自动续期和服务重载成功;CI 凭据更新,不等于一次真实部署成功;监控页面有数据,不等于故障会主动告警。把这些项目留在观察清单,等它们第一次真实运行并留下证据后,再考虑删除旧机。
回头看,这次迁移最有价值的做法,是为每个阶段指定能证明它的证据:镜像看 digest,传输看校验和,数据看恢复和关键记录,TLS 从目标 IP 验,业务走真实链路,流量同时测旧 IP 与新 IP。复杂迁移不是靠一条神奇命令完成的;它是把许多小而明确的判断,按依赖关系排对顺序。