从新加坡迁到东京后,我踩过的三个真实坑

记录一次从新加坡服务器迁移到东京服务器后的完整复盘:迁移清单、DNS 双指向、以及 Tailscale 节点身份被镜像继承的问题。

1 / 1

这次把机器从新加坡迁到东京,起点其实很简单:我觉得链路速度不太行,想试试看东京线路会不会更稳一点。

本来以为这只是一次常规迁移,按镜像恢复、改域名、检查服务、验证 HTTPS 这样的流程走完就可以了。结果真正开始排障之后,问题一个接一个出现,而且三个问题还不是独立的:

  • 先是迁移执行要补很多“镜像本身不会帮你处理”的东西
  • 然后是 sub2api 域名打不开,排查后发现 DNS 同时指向了新旧节点
  • 最后又卡在 Tailscale 上,看起来东京和新加坡像是同一个节点,手机上始终只能切新加坡出口

这篇文章就按这个顺序,做一次完整复盘。

一、迁移不是“复制镜像然后开机”这么简单

迁东京之前,我先整理了一份执行清单,核心思路不是“把盘拷过去”,而是明确哪些东西会跟着镜像走,哪些东西不会。

真正值得提前确认的,至少有这几类:

  • 业务数据和密钥有没有完整保留
  • 云侧资源会不会跟着迁移
  • 域名和证书切换要怎么做
  • Tailscale、出口节点、路由这些网络状态会不会一起被复制
  • 博客和 sub2api 的发布链路迁过去后还能不能继续维护

我当时整理出来的重点包括:

  1. 迁移前要做一次停写后的最终同步,尤其是 PostgreSQL 和 Redis 这种有状态数据,不应该只依赖运行中快照。
  2. 要保全的关键目录不只是应用代码,还包括 /etc/nginx/、/etc/letsencrypt/、/home/ubuntu/.ssh/、/var/www/blog/ 这些“运行依赖”。
  3. 公网 IP、安全组、防火墙、VPC、路由、监控之类云侧资源不会随着镜像自动迁移。
  4. chen-api.cloud 和 blog.chen-api.cloud 的解析必须切到东京的新 IP,否则服务起得再好,外部流量也不会过来。
  5. 如果服务器上保留了 Tailscale,迁移后一定要额外确认 tailnet 身份和出口节点状态,而不是默认它会“自然正确”。

这一步最大的经验是:镜像能帮你复制文件系统,但它不会自动帮你重建一台“逻辑上全新的服务器”。

对于单机应用来说,迁移最容易漏掉的恰恰不是业务代码,而是外围状态:

  • DNS 指向
  • HTTPS 续期
  • Tailscale 设备身份
  • 云控制台侧网络规则

这些东西只要有一个没切干净,表面上就会表现成“服务好像起来了,但总有地方不对”。

二、sub2api 打不开,根因不是程序没启动,而是域名同时指向了新旧节点

迁移完成后的第一轮验证里,最直观的问题是:sub2api 对应的网址打不开。

这种时候最容易先怀疑应用本身,比如:

  • Docker 容器是不是没起来
  • Nginx 是不是没转发对
  • HTTPS 证书是不是有问题

但这次真正的根因并不在应用层,而在 DNS。

后来排查发现,域名解析当时并不是“已经完全切到东京”,而是新旧两台机器同时在解析结果里。也就是说,请求打到哪里并不稳定:

  • 有的请求会落到东京
  • 有的请求会落到新加坡

这样一来,现象就会非常迷惑:

  • 你明明在东京已经把服务拉起来了
  • 但访问域名时,外部流量并不一定真的落到东京
  • 如果旧节点上的配置、证书、容器状态和新节点不完全一致,就会出现“有时能打开、有时打不开”的情况

这个问题的关键不在于 sub2api 本身,而在于迁移切流没有一次性切干净。

回头看,这类问题的正确判断顺序应该是:

  1. 先确认域名当前到底解析到哪些 IP
  2. 再确认目标 IP 上的 Nginx 和应用是否正常
  3. 最后才去怀疑应用逻辑或 HTTPS

否则很容易在服务器里来回改配置,结果真正的问题其实还停留在 DNS 层。

所以如果你也在做跨区迁移,我现在会很明确地建议:

  • 提前把 TTL 降低
  • 切换窗口内确认 A 记录只保留新节点
  • 不要让新旧节点长时间同时对外接流量

对静态站点这可能只是偶发错页,但对 sub2api 这类实际业务服务来说,双指向会把排障复杂度直接放大。

三、最难定位的问题:东京和新加坡在 Tailscale 里竟然像是同一个实例

前两个问题处理完之后,本来以为迁移已经差不多了,结果还有一个更绕的现象:

  • 理论上,新加坡服务器和东京服务器应该是 Tailscale 里的两个节点
  • 但我在手机上切换出口节点时,始终只能看到新加坡,或者 none
  • 东京这台机器怎么都不像一个真正独立的新节点

一开始我怀疑过很多方向:

  • 是不是东京机的出口节点路由没有被批准
  • 是不是 tailscale up 参数没带全
  • 是不是手机端列表没有刷新
  • 是不是两台机器里有一台根本没连上控制面

结果最后发现,问题比这些都更底层:东京服务器是从新加坡机器的镜像迁出来的,而镜像里把 Tailscale 的状态文件也一起复制过来了。

这意味着两台机器携带的是同一套 Tailscale 设备身份,而不是两个独立身份。

于是就会出现非常符合直觉、但一开始又很难想到的现象:

  • 管理后台里它们可能看起来像同一个节点
  • 手机上的出口节点列表里,你只会看到其中一个
  • 谁最后连上控制面,谁就“代表”这个节点出现

我这次的现场现象就是这样:

  • 本机系统主机名已经是东京机器的新名字
  • 但 Tailscale 里仍然带着旧机器的节点信息
  • 新加坡节点还能继续显示为出口节点
  • 东京节点虽然本地在广播出口能力,但在 tailnet 里并没有真正作为新的出口候选出现

这个现象并不是我碰巧遇到的“玄学 bug”,而是 Tailscale 官方文档里明确提到过的情况。

Tailscale 在这两篇文档里都说明了类似问题:

官方文档的结论可以概括成一句话:如果你用一台机器的备份或克隆文件系统去创建另一台机器,而把 Tailscale 配置状态也一并带过去,就可能出现重复节点键、多个设备共享同一个 100.x 地址、或者后台把它们识别成同一设备的情况。

这也正好解释了我这次为什么会在手机上看不到东京出口节点。

最后是怎么修好的

真正的修复方法,不是继续反复执行 tailscale up,而是把东京机从旧的节点身份里彻底剥离出来。

我最后做的动作大致是:

  1. 备份当前 tailscaled.state
  2. 停掉 tailscaled
  3. 移走旧的状态文件
  4. 重新启动 tailscaled
  5. 用新的主机名重新执行 tailscale up
  6. 再到后台批准东京节点广播的出口路由

做完之后,东京和新加坡终于在 tailnet 里变成了两个真正独立的节点:

  • 东京有自己的新节点名
  • 东京有自己的新 100.x 地址
  • 旧的新加坡节点不再和东京“共用身份”
  • 手机端也终于可以把东京识别为一个独立的出口节点候选

这一步给我的最大提醒是:

做机器迁移时,不要只想到应用数据和证书,像 Tailscale 这种“设备级身份状态”也要当成迁移对象认真审查。

它不是简单的安装包,而是一个会把“这台机器是谁”记录下来的网络身份系统。

四、这次迁移给我的三个明确教训

这次从新加坡迁到东京,看起来是一次为了“速度更好”的基础设施调整,但真正花时间的,其实都是那些最开始觉得“不至于出问题”的部分。

如果把这次经历压缩成三个结论,我会写成下面这样:

  1. 迁移时最危险的不是显眼的大组件,而是那些附着在系统上的状态。
  2. 域名切换如果让新旧节点同时接流量,排障会立刻变得不可靠。
  3. 从镜像恢复出来的新机器,并不天然等于一个“全新的网络身份”。

现在回头看,最初觉得“东京速度可能更好”这个判断没错;但真正让迁移变复杂的,从来不是跨区本身,而是那些被镜像顺手带过去、却不该继续复用的状态。

如果以后我再做类似迁移,我会把下面这条检查项直接写进迁移 SOP:

  • 切流前确认 DNS 只会指向新节点
  • 切流后确认 Tailscale 身份是重新生成的
  • 只有当网络身份、证书、反代和业务流量都验证完成后,旧机器才真正可以退场

这次东京迁移,最后修好的不只是机器,而是我对“服务器迁移”这件事的理解。

使用 Hugo 构建
主题 Stack 由 Jimmy 设计
参考 iOS 26 液态玻璃风格改造