客户端连接到多台 OpenVPN 服务器中的一台,随后需要该节点上运行的小服务:状态页、指标端点、备份代理。给每个节点同一个私有服务地址,客户端配置就与它落到哪台服务器无关。这份方便是真的,也很容易被过度解读:共享地址说明你到了哪个节点,并不说明有任何冗余。

为什么同一地址在每个节点上都行得通

每个节点在本地持有该地址并为它应答,因此经由当前隧道到达该地址的客户端,得到的是该隧道另一端上的服务。节点之间没有任何协调,也没有流量从一个节点穿到另一个节点。

该地址是一个本地标签,被重复而不是被复制。每个节点只为它自己应答,也没有哪个节点替别的节点转发该地址的流量。正是这一点让一份客户端配置保持有效,而各服务器彼此独立。这也是该设计不需要节点间协调的原因。

因此同一份配置在每台服务器上都可用,彼此之间不需要路由协议。这也是该设计对健康一言不发的原因:地址在故障节点上与在健康节点上一模一样地存在,因为它绑定的是主机,而不是服务是否有用。

把它分配给回环接口,而不是隧道

把地址放在回环接口上,使它脱离任何隧道设备的生命周期,所以重连或重载配置都不会移除它。只要主机还在,地址就在。

lo 上的地址不是过滤器。服务仍然必须监听它,主机防火墙仍然评估真实的入站路径,而那条路径是从隧道接口进来的。以为回环就等于只限本地的人,最后要么挡住正常客户端,要么暴露了他并不打算暴露的东西。

通过主机自身的网络配置让分配持久化,而不是从钩子里重新添加。在没有隧道起来的节点上,安装钩子不会运行,服务地址也不该依赖隧道活动。把地址写进你所使用发行版管理的接口配置,然后重启一次并检查。重启是唯一能挺过日后配置改动的测试。

ip address show dev lo | grep 10.250.0.1
ip address add 10.250.0.1/32 dev lo

只推送一条路由,并且只意味着一个目的地

客户端需要该地址的主机路由,由它所连接的服务器提供。因为每台服务器提供同一个目的地,客户端移动时配置档案不变。

在责怪服务之前,先确认客户端装了什么。

ip route get 10.250.0.1

对 HTTP 服务,给该地址一个名字,并为这个名字配一张证书。共享 IP 不是身份,它不免除 TLS 验证,也不构成把一份私钥复制到每个节点的理由。解析到共享地址的名字让证书这件事保持平常,也让你在需要时保留按节点区分的身份。

共享地址做不到的事

它不提供故障切换。所选节点一旦死亡,地址随之死亡,客户端必须到别处重连。

它不迁移活着的 TCP 会话,因为新连接终止在另一台主机上,而那台主机对旧连接一无所知。

它不挑选健康节点、不做负载均衡、也不在节点之间同步数据。这些各自是独立机制,有自己的失效方式;把寻址技巧说成覆盖了它们,正是一个简单设计捞到不该有的韧性名声的路径。如果两条隧道同时活动,目的地的含义就变得含糊,所以要把只有一条活动隧道这个前提写明。

把服务身份和名字放在一起

写下这个地址意味着什么:它到达的是客户端此刻所连节点上的服务。就这一句话,挡住了这种模式招来的大部分误解。

然后把各部分一起写下来:地址、它所依附的接口、服务器推送的路由,以及服务出示的名字与证书。四个小事实,未来读者可以逐项核对,顺序就按他会去查的顺序。

如果需求是节点本地服务的一个稳定目的地,这个模式合适,而且几乎不花什么成本。

如果需求是可用性,就该往上走一层:负载均衡器、带健康检查的服务名,或者一个已经知道怎样替换节点的编排系统。

早点把它定下来,共享地址就还是它本来的样子——一个方便的标签,而不是一个被悄悄指望去做从未被设计过的工作的部件。

相关文章