我在整理一套复杂业务系统的测试环境时,遇到两个需要一起解决的问题。

一是资源。系统由四五十个 Java 服务组成,还依赖数据库、缓存、注册中心和消息队列。即使测试流量不大,把这些进程同时跑起来,也需要相当多的内存。已有的本地服务器能承担这部分负载,但没有可以直接提供给团队使用的公网入口。

二是访问。测试团队分散在全国各地,需要通过公网访问同一套环境。只在本地启动服务,还不足以让大家开展联调;让每个人各自搭一整套,资源和维护成本又会重复。

最后采用的方式是:本地服务器承载主要业务,阿里云服务器提供公网入口,服务器之间通过 Headscale 和 Tailscale 组网,公网请求由 OpenResty 转发到内网服务。

这篇文章整理自 2026 年 9 月的部署与网络调整记录。真实业务名称、域名、地址和内部路径均已省略,保留架构、取舍与实际遇到的问题。

内存需求决定了服务放在哪里

这套系统的压力首先来自多个服务同时常驻。除了每个 JVM 的内存,数据库连接池、缓存和消息组件也需要资源。单看某个服务能否启动,很难判断整套环境能否稳定运行。

当时的业务节点是数十 GB 内存的量级。已有本地资源可以利用,我希望先把这些机器组织起来,承载需要一起运行的服务,再解决远程访问。

这样安排以后,云服务器主要承担入口和网络相关工作,大量业务进程留在本地。具体分工如下:

部分职责
本地业务节点分批运行 Java 服务,承载主要内存需求
测试基础依赖提供数据库、缓存、注册中心和消息队列,供服务通过内网访问
阿里云公网节点提供域名入口、证书、OpenResty 代理及 Headscale 控制服务
测试人员的浏览器或客户端通过指定公网入口访问测试业务

测试基础依赖在环境演进中也有位置调整。对业务服务来说,需要维护的是正确的内网依赖地址,而不是假定所有基础组件永远放在同一台机器上。

这套安排利用了已有硬件,但我没有做过完整的成本对照。因此,它解决的是这次环境的资源分工问题,不能据此得出普遍的成本比例。

先把服务器连接起来

我使用自托管的 Headscale 管理组网,云服务器和本地节点运行 Tailscale 客户端,加入同一个虚拟内网。

Headscale 是 Tailscale 控制服务的开源自托管实现。在这个架构里,它负责组网控制;业务数据连接由各节点上的 Tailscale 客户端建立。

要理解这套网络,需要把控制服务和实际数据路径分开。Headscale 部署在阿里云,并不意味着所有节点之间的数据都经过这台云服务器。节点连接可能直连,也可能使用中继,取决于实际网络条件。排查时需要看连接状态,不能仅凭“已经加入同一个网络”判断路径和速度。Tailscale 连接方式说明

组网以后,云端代理可以使用虚拟内网地址连接本地服务;本地服务也可以通过内网地址访问测试依赖。对这次公网代理需求来说,接入运行服务的节点已经够用,没有必要顺带开放整个家庭或办公局域网。

给分散各地的团队一个公网入口

服务器互通解决以后,下一步是让测试人员访问业务。

对于浏览器里的测试页面,访问链路是这样的:

各地测试人员
      │ HTTPS
      ▼
测试域名
      │
      ▼
阿里云公网服务器
OpenResty 接收请求
      │ Tailscale 虚拟内网
      ▼
本地业务节点
      │ 内网调用
      ▼
测试数据库、缓存、注册中心等依赖

这条链路中,测试人员使用浏览器访问域名即可,无需为了打开业务页面而加入服务器组网。需要访问数据库或进行服务器运维的人员,则属于另外的访问权限范围。

公网域名解析到阿里云服务器,OpenResty 根据域名和路径选择对应的内网上游。新增一个测试网站时,重点是确认云服务器能连接目标服务、代理协议正确、路径和证书匹配。NGINX HTTP 代理文档

后端业务端口不需要因此逐个映射到公网。云端接收外部请求,再通过虚拟内网访问后端,响应沿这条连接返回。

不过,公网入口只解决可达性。测试账号、应用登录和操作权限仍需要单独管理,不能把一个不公开传播的网址当作访问控制。这次网络记录也没有覆盖完整的权限验收。

一次具体的坑:Socket 不能直接套网站反代

这套系统除了 HTTP 页面和接口,还有原生 TCP 长连接。

排查时发现,一条已有规则试图用 HTTP 反向代理连接原生 Socket 服务。客户端发送的是自己的协议报文,没有 HTTP 或 WebSocket 握手,因而与网站反代不匹配。

当时也考虑过复用公网 HTTP 端口,但这个端口已经承载多个网站。原生明文 TCP 报文里没有可供网站代理识别的 HTTP Host,不能仅靠增加一个域名就完成分流。若要共用入口,就要额外做协议识别,并调整已有网站监听,影响范围会扩大。

最终采用了独立 TCP 入口,通过 OpenResty 的 stream 转发到内网服务,随后又增加了 TLS 入口:

测试客户端
      │ TLS
      ▼
阿里云 OpenResty stream
      │ TLS 终止后,经 Tailscale 隧道转发
      ▼
本地 TCP 服务

stream 用于 TCP、UDP 等数据流代理,和 HTTP 网站代理是不同的处理方式。NGINX stream 文档

这次调整保留了本地服务的协议,由云端入口处理公网 TLS。外部电脑完成了证书校验、握手和无账号心跳往返;当时还没有完成移动端 TLS 兼容性、真实登录和完整业务流程验证。

这件事让我在整理入口时,把“服务使用什么协议”放在“给它绑定什么域名”之前。

后来补上的统一出网

公网访问解决的是测试人员如何进入环境。之后,我又给本地节点配置了经阿里云统一访问互联网的能力:

本地服务 → Tailscale Exit Node → 阿里云公网出口 → 外部服务

入站代理和统一出网是两项独立功能:

需求处理方式
外部测试人员访问本地业务公网域名、OpenResty、虚拟内网上游
本地节点用统一来源访问互联网在节点上选择阿里云 Exit Node

仅仅为了让团队访问测试页面,不需要开启 Exit Node。启用以后,选择它的节点会将公网流量交给该出口,下载、更新和外部接口调用都会受到影响;需要保留本地局域网访问时,也要明确配置。Tailscale Exit Node 文档

我按节点逐台切换,每切换一台,就检查公网来源、局域网管理连接和实际依赖端口,再处理下一台。

这里有两个值得留下的细节。

云端没有可用的 IPv6 公网出口

本地节点有原生 IPv6 地址和默认路由,但当时的阿里云节点没有 IPv6 公网出口。配置了转发,并不等于云端就具备 IPv6 互联网能力。

这次接受的范围是只保证 IPv4 出网,没有把它描述成完整的双栈出口。后续如果出现 IPv6-only 依赖,需要重新处理云端网络能力。

路由批准成功,转发仍可能失败

实施时,出口路由已经发布并批准,但 NAT 规则没有按预期生成。检查发现,Tailscale 在设置 IPv6 防火墙标记时遇到了兼容性错误。

我先保存原配置,再将 Tailscale 切换到原生 nftables 后端,重启云端 Tailscale 服务。随后确认健康状态恢复、NAT 规则生成,并用实际请求观察转发结果。Tailscale 提供了相应的防火墙后端配置说明。

这个修改针对当时的具体故障,不是所有环境的必做步骤。定位到问题以后,也就没有必要清空整套防火墙或全局放开转发。

我怎样判断这一轮做到哪里了

这次我把网络连通和业务验收分开记录。

节点能 ping 通,只能说明一部分网络路径可用;TCP 能连接,说明目标端口能接收连接;HTTP 有响应、TLS 握手成功,则分别说明更上层的一部分链路在工作。它们都不能单独证明完整业务已经正常。

当时完成的检查包括:

  • 云端与多台本地节点之间的连接,以及若干实际依赖端口的 TCP 建连。
  • 切换出口后,节点仍能通过本地局域网管理。
  • 本地节点的公网 IPv4 来源变为阿里云出口。
  • 原有网站代理配置保持一致,选取入口的响应与变更前一致。
  • 独立 TCP 入口的心跳往返,以及外部电脑对 TLS 入口的证书校验和心跳验证。

网站根路径返回原有的 404,只能用于确认入口响应没有发生变化,不能作为页面业务成功的依据。无账号心跳通过,也不能代替登录、会话和完整业务测试。

这轮没有做全国多地区的延迟对比、持续压力测试、全部 WebSocket 路径验证,也没有重启整台服务器来验收恢复。因此,分散各地的团队能够使用的公网访问路径已经建立,但不同地区的实际体验和整套业务的稳定性,仍要在后续测试中继续观察。

这套环境留下的取舍

我保留了切换前的配置、路由和服务状态,并按节点推进。出口出现问题时,可以先撤销客户端的出口选择,再处理云端;代理配置变更则有对应的备份和恢复记录。这样做让网络调整有了明确的检查点。

这套环境利用了本地内存资源,也把公网入口集中到了云端。代价同样明确:远程访问依赖本地供电和上行网络,云端带宽需要承载代理流量,统一出网后还会增加额外负载;公网入口本身也没有做高可用。

对这次测试环境来说,我得到的是一套可以继续验证和维护的资源分工:业务服务在本地运行,服务器通过虚拟内网互通,各地测试人员使用公网业务入口。接下来值得继续记录的,是多人并行测试时的内存余量、访问延迟、长连接表现,以及断网或重启后的恢复情况。