eBPF 排查连接失败与监听压力¶
本篇展示三个实际场景:连接被拒绝、发起连接时网络不可达、服务监听队列出现压力。它们的共同现象可能都是“请求失败”,但对应的排查入口不同。
样本环境与查询入口见 网络排障实践总览。以下 JSON 均为实测异常日志 message 解析后的关键字段节选;数值来自测试负载。
场景一:客户端连接被拒绝¶
客户端访问 10.23.0.1:18082 失败,采集得到:
{
"schema": "ebpf_network_event/v1",
"entity": {
"scope": "connection",
"netns": "4026531840",
"pid": 124,
"process_name": "fixture",
"service": "fixture",
"protocol": "tcp",
"role": "client",
"role_source": "connect",
"remote_ip": "10.23.0.1",
"service_port": 18082
},
"problem_type": "connect_refused",
"occurrences": 1,
"evidence_partial": false,
"samples": [
{
"local": "10.23.0.1:55256",
"remote": "10.23.0.1:18082",
"stage": "close",
"state": "CLOSE",
"errno": -111,
"queue_values_available": false,
"received_resets": 1
}
]
}
这条记录确认 fixture 的 PID 124 在网络命名空间 4026531840 主动发起连接,最终得到 ECONNREFUSED,Linux 内核返回码是 -111。优先检查目标是否监听、目标端口是否正确,以及服务或代理是否在发布期间暂时不可用。返回码含义见 connect(2)。
这里 stage=close 是取最终样本时的生命周期阶段,不能把它解读成“已经正常建立后才断开”;连接结果应看 problem_type 和 netflow 的结果字段。同一测试连接还记录了收到 reset,因此 tcp_received_reset 也不能独立证明业务通信中途断开。
按下面顺序验证:
- 在平台中筛选同一主机、进程、目标端点和时间,确认失败是否集中在单个对端,成功连接是否仍存在。
- 在目标进程所在网络命名空间检查监听:
ss -lntp 'sport = :18082'。若从节点执行,使用容器运行时查到的当前宿主机 PID,再执行nsenter -t <宿主机 PID> -n -- ss -lntp 'sport = :18082';日志 PID 只作为线索,进程可能已退出或 PID 已复用。 - Kubernetes 中核对 Service 的
port/targetPort、后端 EndpointSlice 和 Pod 就绪状态;可以执行下面的只读查询。
kubectl -n <业务命名空间> get service <服务名> -o yaml
kubectl -n <业务命名空间> get endpointslice -l kubernetes.io/service-name=<服务名> -o wide
kubectl -n <业务命名空间> get pods -o wide
Service 与后端分层核对的方法见 Kubernetes 服务排障。如果确认目标未监听,修复监听/端口配置;如果只在滚动发布发生,检查就绪和流量切换。修复后比较同一端点的连接成功与失败增量,而不是只看日志是否安静。
这组数据的价值是把“调用失败”缩小到具体发起进程、目标端口和建立阶段;它本身还不能判定是应用、代理还是中间设备产生拒绝。
场景二:连接尚未分配完整本地端点就不可达¶
{
"schema": "ebpf_network_event/v1",
"entity": {
"scope": "connection",
"netns": "4026531840",
"pid": 124,
"process_name": "fixture",
"service": "fixture",
"protocol": "tcp",
"role": "client",
"role_source": "connect",
"remote_ip": "10.44.0.1",
"service_port": 18087
},
"problem_type": "connect_unreachable",
"occurrences": 1,
"evidence_partial": false,
"samples": [
{
"local": "0.0.0.0:0",
"remote": "10.44.0.1:18087",
"stage": "connect",
"errno": -101,
"queue_values_available": false,
"detail": "connect failed before a complete local flow tuple was available"
}
]
}
errno=-101 对应 ENETUNREACH。采集确认尝试目标为 10.44.0.1:18087,失败发生在 connect 阶段。此时 local=0.0.0.0:0 是尚无完整本地四元组的表现,不应该猜一个客户端地址,也不能只凭该字段认定漏采。
在该进程实际所在命名空间检查:
节点默认命名空间的路由可能与业务 Pod 不同,命令要在相同 netns 执行。先确认目标地址、地址族、接口与路由,再核对 CNI 配置和访问策略;明确的本地不可达错误比盲目排查服务端线程池更有针对性。确认路由或配置修复后,观察相同目标的成功连接是否出现。
超时与不可达应分开。connect_timeout 能确认最终超时,但单端无法区分 SYN 或返回报文在哪一段受阻;需要结合服务端是否收到连接、访问策略、路由和必要的短时定向抓包。握手等待至少 10 秒记录 connect_pending,成功建立耗时至少 3 秒记录 connect_slow,两者均为排查线索;connect_canceled 是应用提前取消,不能混入确认的网络超时。非阻塞连接的 EINPROGRESS/EALREADY/EAGAIN 不作为硬失败。
场景三:服务监听端点接受连接不及时¶
实测故意设置很小的监听 backlog 并放慢接受连接,得到以下样本:
{
"schema": "ebpf_network_event/v1",
"entity": {
"scope": "listener_endpoint",
"netns": "4026531840",
"protocol": "tcp",
"role": "server",
"role_source": "listener",
"local_ip": "10.23.0.1",
"service_port": 40022
},
"problem_type": "listener_queue_full",
"occurrences": 9,
"evidence_partial": false,
"samples": [
{
"local": "10.23.0.1:40022",
"stage": "accept",
"accept_queue": 2,
"accept_queue_limit": 1,
"queue_values_available": true,
"detail": "accept queue exceeded backlog at SYN hook; endpoint may contain SO_REUSEPORT listeners, PID unavailable, not proof this SYN was dropped"
}
]
}
队列为 2,上限为 1;同组观测次数为 9。该观测来自 SYN 钩子,表明这个监听端点存在接受队列压力。它没有 PID,不能直接指认某个工作进程;同一个端点可能有 SO_REUSEPORT 的多个监听 socket。
队列超过配置值也不矛盾:具体内核实现可能允许 backlog 为 n 的队列暂时容纳 n+1 个连接。occurrences=9 是压力观测次数,重试也会产生观测,不能解释成 9 个不同客户端连接丢失。结合网络栈的 ListenOverflows/ListenDrops 增量确认压力影响;这两个计数可能重叠,不应相加当作独立丢失量。参见 Linux ListenOverflows 与 ListenDrops。
下一步应检查服务端:
- 在相同命名空间用
ss -lntp 'sport = :40022'定位监听进程,检查压力是否持续。 - 对齐线程池、事件循环、CPU 节流和应用接受连接的速度;确认是处理阻塞还是突发连接超过容量。
- 必要时在少量受影响节点启用
network_diagnostics_full_metrics=true,查看带netns的网络栈计数及来源新鲜度;配置与基数成本见 采集可信度与资源控制。
确认接受线程被阻塞时修复应用处理路径;确认负载超过服务容量时优化连接复用或扩容,再评估 backlog。单纯增大 backlog 可能只延长等待。验收应同时满足连接成功改善、建立耗时回到业务基线、监听压力和溢出增量降低。
建立耗时如何读¶
在 netflow 中选择同一客户端观测侧,查看成功结果的 tcp_connect_success_duration_count/sum_us/max_us。总耗时除以样本数得到平均建立耗时,最大值帮助发现尾部线索;这些字段无法还原 P99。跨窗口的连接可能稍后才有结果,不宜直接用当期失败数除以当期尝试数作为严格失败率。
将结果分成成功、硬失败、取消和未知,才能避免把应用重试、取消或采集缺口混成网络故障。再结合 TCP 传输实践,区分建立阶段问题与建立后的通信受阻。