「TCP握手成功一次,失败一次,再成功一次。」

DevOps工程师Le Phan Tan Loc在部署RabbitMQ到Amazon EKS时,遇到了这个诡异现象。没有错误日志,没有健康检查告警,端口测试却像抛硬币一样随机。问题最终指向一个默认关闭的AWS功能——跨区负载均衡(Cross-Zone Load Balancing)。

打开网易新闻 查看精彩图片

架构设计:省钱的巧妙与隐患

项目需求很明确:每个项目独立部署RabbitMQ集群,外部VPC通过VPC Peering访问AMQP 5672端口。

团队选择了AWS Load Balancer Controller的TargetGroupBinding方案。这个自定义资源(CRD)允许将Kubernetes Service绑定到现有NLB的Target Group,而非为每个集群创建独立NLB。多个项目共享同一台NLB,仅通过不同端口区分——rabbitmq-1用5672,rabbitmq-2用5673,以此类推。

成本省了,但网络路径变复杂了。EKS集群跨多个可用区部署,NLB在每个可用区都有节点(ENI),Target Group则指向Pod的IP地址。当外部流量从VPC Peering(10.21.0.0/16)进入时,路径涉及:NLB节点→跨可用区路由→Pod IP。

故障复现:没有规律的连接失败

测试命令很简单:

bash -c "timeout 2 bash -c 'cat < /dev/tcp//5672'"

结果令人困惑:连续执行10次,可能成功6次,失败4次,无固定模式。Telnet表现相同。NLB控制台显示所有Target健康,RabbitMQ应用日志无异常。

关键线索藏在网络层。用tcpdump抓包发现,失败的连接在TCP三次握手的第二步(SYN-ACK)无响应。但诡异的是,这个SYN-ACK并非来自客户端期望的源地址——NLB的跨区流量转发打破了TCP的状态对称性。

根因分析:跨区负载均衡的默认陷阱

AWS NLB的跨区负载均衡默认关闭。这意味着:NLB节点只会将流量转发到本可用区的Target,即使其他可用区的Target更空闲或更健康。

在TargetGroupBinding架构中,这个问题被放大。Pod IP可能位于可用区A,但客户端连接的NLB节点却在可用区B。由于跨区转发被禁用,流量到达可用区B的NLB节点后,无法路由到可用区A的Pod——连接直接失败。

为什么有时成功?因为NLB有多个IP地址(每个可用区一个),DNS轮询或客户端重试可能换到正确可用区的节点。这种随机性让故障难以定位。

更隐蔽的是健康检查机制。NLB的健康检查从每个可用区独立发起,只要本可用区能连通Target,就标记为健康——即使该Target实际位于其他可用区,正常流量根本无法到达。

解决方案与权衡

修复很简单:在NLB属性中启用跨区负载均衡(Cross-Zone Load Balancing)。AWS控制台、CLI或Terraform均可操作,变更即时生效,无需重建NLB。

但需注意成本影响。跨区流量会产生数据传输费用,在高吞吐量场景下不可忽视。团队评估了RabbitMQ的实际流量规模后,认为成本增量在可接受范围内。

替代方案包括:确保Pod与NLB节点同可用区部署(通过节点亲和性),或为每个可用区创建独立Target Group。但这些方案增加了运维复杂度,与最初"简化架构"的设计目标相悖。

为什么这件事值得记住

这个案例暴露了云原生架构中"默认配置"的隐性风险。AWS NLB的跨区负载均衡设计初衷是减少跨区流量成本,但在Kubernetes + VPC Peering + TargetGroupBinding的组合场景下,默认关闭变成了功能缺陷。

对于在EKS上部署有状态服务的团队,建议将跨区负载均衡检查纳入部署清单。特别是在使用共享NLB、多可用区Pod分布、外部VPC访问这三要素叠加时,这个设置从"可选优化"变为"必需配置"。

排查此类问题的关键,是理解NLB的可用区边界行为与Kubernetes网络模型的交互。当连接失败呈现随机性、无应用层日志、健康检查正常时,优先验证跨可用区的网络路径连通性。