eBPF 排查 DNS 与 UDP 异常¶
DNS 返回了错误、DNS 没有观测到响应和 UDP socket 发送失败,是不同层面的事实。本篇用实际样本展示如何分别处理,并说明什么时候不能归属到具体进程或对端。
实测环境与查询入口见 网络排障实践总览。UDP socket 采集需要 enable_udp=true;DNS 还要检查独立的 dns_capture_available 与抓包损失。当前诊断只解析可观测的明文 DNS,不能展示 DoH/DoT 中的查询内容。
场景一:解析器明确返回 SERVFAIL¶
下面是实测日志 message 解析后的关键字段节选:
{
"schema": "ebpf_network_event/v1",
"entity": {
"scope": "dns_transaction",
"netns": "4026531840",
"protocol": "udp",
"role": "client",
"role_source": "dns_query",
"local_ip": "10.23.0.1",
"remote_ip": "10.23.0.1",
"service_port": 53
},
"problem_type": "dns_error_response",
"occurrences": 1,
"evidence_partial": false,
"samples": [
{
"local": "10.23.0.1:55171",
"remote": "10.23.0.1:53",
"stage": "dns",
"detail": "query=failure.example.test type=A rcode=2; capture observes plaintext DNS only"
}
]
}
采集确认 failure.example.test 的 A 查询收到 rcode=2,解析器地址为 10.23.0.1:53。根据 RFC 1035 的 DNS 返回码,2 表示服务器失败,3 表示域名不存在。故障注入同时测试了正常 NXDOMAIN 查询 missing.example.test,不会仅因为返回 NXDOMAIN 就发故障线索。
SERVFAIL 提示从解析器和它的上游链路检查;它不是“UDP 报文丢了”的证据。样本范围为 dns_transaction,没有 PID,因此只能先归属到网络命名空间和 DNS 端点,不能宣称已经定位到具体应用进程。根据实际域名和业务错误,进一步区分合法的负查询、上游不可达、区域配置或权限问题。
Kubernetes 中先做只读检查:
kubectl -n kube-system get service kube-dns -o wide
kubectl -n kube-system get endpointslice -l kubernetes.io/service-name=kube-dns -o wide
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system logs -l k8s-app=kube-dns --since=10m --tail=200
在受影响业务环境中核对 /etc/resolv.conf 和同一个完整域名的解析结果;检查 CoreDNS 日志、配置和上游,而不是先全局开启逐查询日志。对应的 Service、EndpointSlice 和 CoreDNS 排障步骤见 Kubernetes DNS 排障。
修复后按同一查询名/类型、解析器和命名空间复测,确认错误响应增量下降、解析得到预期结果;业务依赖不存在域名时,则应修正业务配置,不能把“清除所有 NXDOMAIN”作为目标。
场景二:6 秒没有匹配到 DNS 响应¶
{
"schema": "ebpf_network_event/v1",
"entity": {
"scope": "dns_transaction",
"netns": "4026531840",
"protocol": "udp",
"role": "client",
"role_source": "dns_query",
"local_ip": "10.23.0.1",
"remote_ip": "10.23.0.1",
"service_port": 53
},
"problem_type": "dns_no_response",
"occurrences": 1,
"evidence_partial": false,
"samples": [
{
"local": "10.23.0.1:51917",
"remote": "10.23.0.1:53",
"stage": "dns",
"detail": "query=silent.example.test type=A; no matching response observed within 6s, not proof of a dropped datagram"
}
]
}
这条记录确认采集器看到了 silent.example.test 的 A 请求,但在 6 秒内没有匹配到响应。它与明确的错误响应不同;evidence_partial=false 说明该记录没有已知的对应证据缺口,仍不代表端到端抓包绝对完整,也不证明数据报在网络上丢失。
按以下顺序缩小范围:
- 检查同一时段的
dns_capture_available、capture_loss_total/capture_unavailable_total和采集摘要;抓包损失会清除待匹配项,避免制造 DNS 无响应样本,但没有损失计数也不能证明每一跳都被观测。 - 按
netns与 DNS 端点确认是否集中在某个业务命名空间、节点或解析器;不能只查节点默认命名空间。 - 对齐 CoreDNS 的接收、处理延迟与上游查询,检查是否是解析器排队、上游慢或传输路径问题。
- 用少量受影响请求复测;必要时短时、定向抓 DNS 数据,避免在全节点持续抓包。
dns_slow 的至少 1 秒阈值也是信息级线索。慢响应、无响应和错误响应应该分开统计,再按业务允许的解析时延设置平台告警。单独的静默窗口不能证明 DNS 已恢复。
场景三:UDP 发送返回消息过大¶
{
"schema": "ebpf_network_event/v1",
"entity": {
"scope": "socket",
"netns": "4026531840",
"pid": 124,
"process_name": "fixture",
"service": "fixture",
"protocol": "udp",
"role": "unknown",
"role_source": "unknown"
},
"problem_type": "io_message_size",
"occurrences": 1,
"evidence_partial": true,
"samples": [
{
"stage": "send",
"errno": -90,
"detail": "UDP I/O error; original datagram destination unavailable, peer attribution omitted"
}
]
}
errno=-90 对应 EMSGSIZE。这次实测来自故意发送超大的 UDP 消息,能确认本地 fixture 进程的发送错误,不能用这个实验宣称已验证某条生产路径存在 MTU 黑洞。
Linux UDP 会使用路径 MTU 信息,并可能向应用返回之前数据报的异步错误。因此该样本保留 socket/进程归属,省略无法确认的远端,且 evidence_partial=true;不能把错误强行归给最近一次发送的目的地址。协议行为见 udp(7)。
先检查实际消息长度、应用协议限制和错误处理。长度正常但故障集中在特定路径时,再对齐业务路由、隧道 MTU、ICMP “报文过大”线索及端到端探测。不要把单条 EMSGSIZE 直接认定为 MTU 问题,也不要默认关闭 PMTU 来掩盖原因。修复后用相同消息大小和路径确认发送成功、相关错误不再增加。
UDP、网络栈和网卡数据如何补充¶
下面是教学示例,用于说明归属层级,不是实测值:
| 60 秒内观测 | 教学增量 | 排查方向 |
|---|---|---|
某 netns 的 UDP 接收缓冲区错误 |
+120 | 核对该命名空间 socket 缓冲区、应用消费和突发负载 |
同一 netns 的无监听端口 |
+8 | 检查目标端口、服务重启和目标配置 |
同一 netns 的 ICMP 不可达 |
+8 | 核对可达性;不能与上一行相加当作 16 次独立丢包 |
| 某接口接收丢弃 | +200 | 检查具体接口、驱动和节点资源,再对齐时间与业务流 |
这些是命名空间或接口层的来源计数,不是某个 socket 的精确丢包率。多个计数可能描述同一现象,接口还可能同时承载很多业务。默认节点指标汇总异常;对应异常日志保留来源范围。需要持续看每个命名空间/网卡时,可在少量节点开启完整指标模式,见 采集可信度与资源控制。
这类数据能把“域名或 UDP 有问题”分成解析结果、等待响应、socket 错误和网络栈压力。无法确认的 PID、UDP 远端或具体丢包位置保持未知,避免给出错误归因。