当前位置: 首页 > news >正文

记一次 OKE 集群上的 TCP 流量黑洞排查与解决全过程

作为 Apecloud 团队,我们致力于通过开源的 KubeBlocks 项目,在 Kubernetes (K8s) 上为用户提供企业级的数据库高可用方案[1]。其中,SQL Server on K8s with Always On 是我们支持的关键能力之一,相比Microsoft 为在容器中运行 SQL Server 提供的基础StatefulSet方案, KubeBlocks 的 MSSQL Addon 提供了一整套生产级的生命周期管理能力,包括:多节点高可用配置动态扩缩容数据库/账户管理参数管理监控告警全量/增量/PITR备份恢复,以及TDE/TLS 数据加密等,是现有 SQL Server operator 中最成熟、最完善的方案之一[2][3]

近期,某客户计划在 Oracle Kubernetes Engine (OKE) 环境中部署我们的 SQL Server 高可用集群。为了确保方案的可靠性,我们在 OKE 环境中对 MSSQL Addon 进行了一轮全方位的回归测试。

在测试 Failover、资源动态扩缩容等运维操作(Ops)时,我们发现了一个在 OKE 上独有的异常现象,这与我们自建的 K8s 或其他云厂商环境中的表现截然不同:在 Pod 滚动重启或主备切换后,旧的主节点 Pod 在重建后需要耗时约 15 分钟才能重新加入集群。这个意料之外的延迟,促使我们深入排查,并最终揭示了一个潜藏在云网络深处的“TCP 流量黑洞”问题。本文将完整记录这一问题的排查与解决全过程。

一、 问题现象:切换后的长连接“假死”

在一次例行的主备切换测试中,我们搭建了如下的测试环境:

  • 原主节点:Pod IP 为10.0.10.129。在 09:33 左右被手动kill模拟故障,重建后的新 Pod IP 为10.0.10.98
  • 副本一:Pod IP 为10.0.10.227,主备切换后的新主节点,同时也是本次抓包分析的机器。
  • 副本二:Pod IP 为10.0.10.56

图1: 切换前pod状态

图2: 切换后pod状态

切换过程中的关键时间线日志如下:

  • 09:32:49:触发了新主的切换操作。
2026-03-18T15:48:05Z INFO SQLServer Setting replica to SECONDARY role... 2026-03-19T01:32:49Z INFO HA Cluster has no leader, attempt to take the leader 2026-03-19T01:32:49Z INFO SQLServer Replica is now PRIMARY 2026-03-19T01:32:49Z INFO HA Take the leader success! 2026-03-19T01:32:57Z INFO HA This member is Cluster's leader 2026-03-19T01:32:57Z DEBUG HA Refresh leader ttl 2026-03-19T01:33:57Z INFO HA This member is Cluster's leader 2026-03-19T01:33:57Z DEBUG HA Refresh leader ttl 2026-03-19T01:34:57Z INFO HA This member is Cluster's leader 2026-03-19T01:34:57Z DEBUG HA Refresh leader ttl 2026-03-19T01:35:57Z INFO HA This member is Cluster's leader 2026-03-19T01:35:57Z DEBUG HA Refresh leader ttl
  • 09:33:02:老节点(IP10.0.10.129)被正式关闭。

  • 09:48:05:原主节点重建后POD(IP10.0.10.98)以备节点身份重新加入集群。

异常现象:从 09:33:02 开始,原有连接的 TCP 流量仿佛掉入了黑洞,主备之间的同步彻底中断。SQL Server 的日志中只看得到同步中断,但没有任何连接异常的报错。系统长时间无法自动恢复,最终耗时约 15 分钟,旧主节点(Pod10.0.10.98)才以备库的身份重新加入集群。

[HADR TRANSPORT] AR[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] Setting Reconnect Delay to 0 s [HADR TRANSPORT] LOCAL AR:[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in CHadrTransportReplica::Reset called from function [CHadrTransportReplica::ReconnectTask], primary = 0, primaryConnector = 1[HADR TRANSPORT] LOCAL AR:[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in CHadrConfigState::ChangeState with session ID A23BA718-9B73-4588-893B-0F18C0275526 change from HadrSessionConfig_ConfigRequest to HadrSessionConfig_ConfigRequest - function [CHadrSession::Reset][HADR TRANSPORT] AR[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] Session:[1DF067D5-EB9C-4A15-9216-A025B534D66F] CHadrTransportReplica State change from HadrSession_Timeout to HadrSession_Configuring - function [CHadrTransportReplica::Reset_Deregistered][HADR TRANSPORT] AR[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33], Seesion:[1DF067D5-EB9C-4A15-9216-A025B534D66F] Queue Timeout (10) from [CHadrTransportReplica::Reset_Deregistered][HADR TRANSPORT] LOCAL AR:[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in CHadrConfigState::ChangeState with session ID 1DF067D5-EB9C-4A15-9216-A025B534D66F change from HadrSessionConfig_ConfigRequest to HadrSessionConfig_WaitingSynAck - function [CHadrSession::GenerateConfigMessage][HADR TRANSPORT] LOCAL AR: [16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in CHadrSession::GenerateConfigMessage with session ID 1DF067D5-EB9C-4A15-9216-A025B534D66F Generate configure message(1) with viersion(1)[HADR TRANSPORT] AR [16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] Transport is not in a connected state, unable to send packet2026-03-18 10:22:48.60 spid27s Using 'dbghelp.dll' version '4.0.5'

二、 抓包排查:经典的 TCP 指数退避重传

为了弄清流量去向,我们对通信链路(10.0.10.227:38101->10.0.10.129:5022,5022 为 SQL Server Always On 的端点端口)进行了抓包分析。

关键报文序列如下:

09:32:54.234 129:5022 → 227:38101 ACK=40464 [正常确认] 09:33:02.246 227:38101 → 129:5022 seq=40464:52704 len=12240 [发送数据] 09:33:02.247 129:5022 → 227:38101 ACK=52704 [确认收到,这是129最后的遗言] # 之后 227 继续发送新数据,但再也等不到 129 的 ACK 09:33:13.258 227:38101 → 129:5022 seq=52704:64944 len=12240 [发送新数据] 09:33:13.468 227:38101 → 129:5022 seq=52704:61652 len=8948 [重传1,间隔0.4s] 09:33:13.876 227:38101 → 129:5022 seq=52704:61652 len=8948 [重传2,间隔0.9s] 09:33:14.740 227:38101 → 129:5022 seq=52704:61652 len=8948 [重传3,间隔1.7s] ... 09:36:46.964 227:38101 → 129:5022 seq=52704:61652 len=8948 [持续重传,间隔已达106.5s]

分析结论:这是一个非常典型的TCP 重传机制现象。当发送端未收到 ACK 时,会触发 RTO(超时重传),并且重传间隔呈指数级退避(0.4s → 0.9s → 1.7s → 3.3s … 106.5s)。在 Linux 默认配置下,会重传 15 次,总耗时约 15 分钟才会彻底放弃并断开连接。

三、 剖析:为何应用层(SQL Server)未触发超时?

很多同学可能会问:SQL Server HADR(高可用灾备)机制默认有 10 秒的SESSION_TIMEOUT,为什么没有生效?

陷阱在于:SESSION_TIMEOUT主要用于检测心跳 ping 的丢失
在当前场景下:

  1. 操作系统传输层的 TCP 连接仍然处于ESTABLISHED状态,内核正在努力重传,并没有向应用层抛出连接断开的 Error。
  2. SQL Server 认为“底层 TCP 连接还活着,只是数据传输慢”,因此一直在挂起等待。
  3. 最终结果:应用层无限期等待,直到 15 分钟后 TCP 栈最终放弃。这就造成了所谓的“幽灵连接”。
TCP 层:持续重传,连接未断开(ESTABLISHED) ↓ SQL Server 层:认为连接还活着(TCP 未报告错误) ↓ HADR 层:等待数据同步完成,未触发 SESSION_TIMEOUT ↓ 结果:无限等待,直到 TCP 最终放弃(15 次重传 ≈ 15-20 分钟)

四、 根因定位与云网络背景

结合业内经验与本次故障特征,我们梳理出了完整的根因链条:

  1. 云网络环境的特殊性:在部分云厂商的基础网络架构中,为了抵抗网络抖动,底层网络设备会尽量避免向对端发送RST报文。但在容器化 IP 频繁变更(Pod 重建)的场景下,这会导致对端无法感知连接断裂,从而引发 15 分钟黑洞问题。

图1: OKE环境宿主机tcp_retries2参数默认值

  1. 应用关闭动作粗暴:最根本的原因是,在执行 Delete Pod 时,MSSQL 进程没有进行优雅关闭(Graceful Shutdown)。进程被暴力 Kill,操作系统和应用都来不及向对端发送FINRST报文主动断开连接,对端(案例中的227 节点)毫不知情,傻傻地等待并不断重传。

五、 解决之道:双管齐下

针对上述根因,我们采取了“应用层主动阻断 + 系统层被动兜底”的综合解决方案。

1. 应用层修复:实现优雅关闭 (主动解法)

最彻底的解法是让连接主动断开。我们为 MSSQL Addon 引入了 Graceful Shutdown 的 Patch。在接收到停止信号时,保证应用能主动执行close()释放 TCP 连接,向对端发送FIN/RST

2. 系统层兜底:优化内核参数tcp_retries2(被动防御)

[!IMPORTANT]
在 Kubernetes 这样的容器化环境中,Pod 的生命周期是动态的,其 IP 地址随时可能因为调度、升级或故障而改变。虽然实现优雅关闭(Graceful Shutdown)是应用开发的最佳实践,但在很多突发场景下,例如 OOMKilled、节点故障(Node Failure)或进程直接崩溃(Crash),应用根本没有机会执行优雅关闭。这就导致了连接的另一端无法感知对端已经消失,从而陷入长时间的等待,形成网络黑洞。因此,仅仅依赖应用层的优雅关闭是不够的,必须在系统层面建立一道“被动防御”的兜底机制。

对于使用长连接访问的应用来说(默认使用都是 tcp 长连接,无论 HTTP/2 还是 HTTP/1),在没有设置合适请求 timeout 参数的情况下可能会出现 15mins 的超时问题,为了应对网络脑裂或机器突然断电等无法执行优雅关闭的极端物理故障,我们需要缩短操作系统的 TCP 放弃时间。[4]

Linux 内核的net.ipv4.tcp_retries2参数[5]控制着处于ESTABLISHED(已建立连接)状态下的数据传输失败重传行为。需要澄清的一个常见误区是:tcp_retries2并不是简单的绝对重传“次数”,它其实决定了内核计算总超时时间的边界。

TCP 超时计算逻辑原理:
内核采用指数退避算法计算重传超时时间(RTO,初始为 1s,受限于TCP_RTO_MIN200ms 和TCP_RTO_MAX120s),计算公式大致如下:

  • retries2 <= 9时,总超时时间呈指数增长:timeout = ((2 << retries2) - 1) * 200ms
  • retries2 > 9时,总超时时间转为线性增长:timeout = (2^9 - 1) * 200ms + (retries2 - 9) * 120s

基于上述算法:

  • 默认值 15:计算得出的总超时时间约为924.6 秒(15.4 分钟)。在这 15 分钟内,应用层将毫无察觉地死等,这也是引发 15 分钟黑洞的直接原因。
  • 优化值 8:计算得出的总超时时间大幅降至约 25.5 秒

我们将tcp_retries2=8的配置项纳入了基础设施的交付流程中(可通过初始化节点sysctl或 PodinitContainer注入)。这样即使遇到连接黑洞,TCP 层最多等待约 25 秒就会强制断开连接,并向上层应用抛出ETIMEDOUT异常,从而让应用层的 Failover 机制迅速介入。

六、 验证与后续改进

在 OCI 集群应用上述修复后,我们进行了场景验证和测试:

  • 测试场景:集群下发变配Ops,测试滚动重启过程中主备切换是否正常。
  • 测试结果:我们通过对比tcp_retries2参数在默认值(15)和优化值(8)下的表现,来验证修复效果。
对比项场景一:默认值 (tcp_retries2=15)场景二:优化后 (tcp_retries2=8)
恢复时长约 16 分钟2 分钟内
现象主备切换后,连接长时间中断,出现明显的“流量黑洞”,集群长时间无法恢复同步。主备切换后,连接迅速恢复,集群快速完成同步,有效解决了流量黑洞问题。
GIF 演示

后续改进与展望
此次故障排查不仅解决了眼前的问题,也为我们提升系统整体的健壮性提供了宝贵的经验。后续,KubeBlocks团队将从以下几个方面深化改进:

  1. 全面推广优雅关闭(Graceful Shutdown)实践
    将优雅关闭作为所有有状态应用(包括但不限于 Redis、PostgreSQL 等数据库)开发和上线的强制性标准。确保应用在退出时能主动清理和释放资源,从根源上杜绝“幽灵连接”。

  2. 优化基础设施的交付流程
    tcp_retries2=8等关键内核参数的优化,作为节点初始化的标准配置项,纳入了基础设施的交付流程中,确保集群环境的一致性和可靠性。

  3. 常态化混沌工程演练
    将此类网络分区、Pod 强制删除等故障场景纳入常态化的混沌工程演练平台。通过主动注入故障,持续性地检验和提升系统的弹性和自愈能力,变被动响应为主动防御。

Ref:

  1. KubeBlocks 官方网站
  2. KubeBlocks 官方博客 - SQLServer Addon
  3. KubeBlocks 官方博客 - SQLServer Always On AG 深度解析
  4. Kubernetes Controller高可用诡异的15mins超时
  5. net.ipv4.tcp_retries2深度解析与优化指南
http://www.cnnetsun.cn/news/1879417.html

相关文章:

  • Redis 菜鸟学习
  • 别再死记硬背ESP32 BLE API了!用这个“事件驱动”思维导图,5分钟理清GAP/GATT回调逻辑
  • 44、链表和数组有什么区别?
  • NaViT实战:如何用Patch n‘ Pack技术处理任意分辨率图像(附代码示例)
  • M2LOrder模型实战:赋能AIGC内容创作的情感一致性校验
  • 告别枯燥文本!用像素语言·维度裂变器一键生成10种创意文案
  • Pixel Couplet Gen 从零部署教程:Ubuntu系统环境与依赖项全配置
  • K-Means聚类在图像分割中的优化实践:从理论到代码实现
  • M7iBASE-AC-1GE直流电源路由器
  • Keil5实战:手把手教你制作自定义FLM插件(附完整驱动配置流程)
  • AI超清画质增强问题解决:大图片处理、内存优化等实战技巧
  • Pi0机器人控制实战:多视角图像输入与动作生成案例
  • AIAgent机器人控制如何突破“感知-决策-执行”延迟瓶颈?2026奇点大会实测数据显示端到端时延压降至87ms以下
  • Qwen2.5-VL视频分析案例:长视频关键事件定位与摘要生成
  • 卡内基梅隆大学团队破解“手机语音助手为什么听不懂外国腔“之谜
  • 量子力学的太极效应
  • RVC语音克隆新手教程:3分钟极速训练,AI翻唱轻松上手
  • 快速上手nli-distilroberta-base:开箱即用的自然语言推理工具
  • 别再为接线发愁!手把手教你搞定西门子S7-1200 PTO脉冲轴与台达A2伺服驱动器的24V/5V信号匹配
  • Plan-and-Execute:Agent规划与执行分离模式
  • 海上搜救(SAR)小目标检测打造 海上搜救小目标检测数据集 深度学习YOLOv8 的完整训练代码 无人机航拍+水上漂浮物检测(人、船、冲浪板等)海上搜救检测数据集
  • 交警机器人上岗常州护航苏超揭幕战;管理者敬业度已不再高于普通员工 | 美通社一周热点简体中文稿
  • Qwen3-0.6B-FP8部署教程:vLLM服务健康检查(llm.log)、Chainlit端口映射与CORS配置
  • OpenClaw安装教程:nanobot镜像内建日志系统(llm.log)解读与异常定位方法
  • Alpamayo-R1-10B惊艳效果:多目标(车辆+行人+自行车)交互轨迹联合预测展示
  • 快速上手PP-DocLayoutV3:无需代码,网页点选完成文档版面智能分析
  • Qwen3-14B私有部署镜像Java面试题智能解析与模拟面试
  • RAG系统智能升级:精准识别用户意图,告别无效检索与答非所问!
  • MogFace人脸检测模型数据库集成案例:构建人脸信息管理系统
  • 大模型应用实战:智能问答系统开发