跳转至

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 也不能独立证明业务通信中途断开。

按下面顺序验证:

  1. 在平台中筛选同一主机、进程、目标端点和时间,确认失败是否集中在单个对端,成功连接是否仍存在。
  2. 在目标进程所在网络命名空间检查监听:ss -lntp 'sport = :18082'。若从节点执行,使用容器运行时查到的当前宿主机 PID,再执行 nsenter -t <宿主机 PID> -n -- ss -lntp 'sport = :18082';日志 PID 只作为线索,进程可能已退出或 PID 已复用。
  3. 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 是尚无完整本地四元组的表现,不应该猜一个客户端地址,也不能只凭该字段认定漏采。

在该进程实际所在命名空间检查:

ip route get 10.44.0.1
ip address show
ip rule show

节点默认命名空间的路由可能与业务 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。

下一步应检查服务端:

  1. 在相同命名空间用 ss -lntp 'sport = :40022' 定位监听进程,检查压力是否持续。
  2. 对齐线程池、事件循环、CPU 节流和应用接受连接的速度;确认是处理阻塞还是突发连接超过容量。
  3. 必要时在少量受影响节点启用 network_diagnostics_full_metrics=true,查看带 netns 的网络栈计数及来源新鲜度;配置与基数成本见 采集可信度与资源控制。

确认接受线程被阻塞时修复应用处理路径;确认负载超过服务容量时优化连接复用或扩容,再评估 backlog。单纯增大 backlog 可能只延长等待。验收应同时满足连接成功改善、建立耗时回到业务基线、监听压力和溢出增量降低。

建立耗时如何读

在 netflow 中选择同一客户端观测侧,查看成功结果的 tcp_connect_success_duration_count/sum_us/max_us。总耗时除以样本数得到平均建立耗时,最大值帮助发现尾部线索;这些字段无法还原 P99。跨窗口的连接可能稍后才有结果,不宜直接用当期失败数除以当期尝试数作为严格失败率。

将结果分成成功、硬失败、取消和未知,才能避免把应用重试、取消或采集缺口混成网络故障。再结合 TCP 传输实践,区分建立阶段问题与建立后的通信受阻。

文档评价

文档内容是否对您有帮助?