惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
美团技术团队
NISL@THU
NISL@THU
C
Cisco Blogs
SecWiki News
SecWiki News
N
Netflix TechBlog - Medium
Forbes - Security
Forbes - Security
Cloudbric
Cloudbric
雷峰网
雷峰网
T
Tailwind CSS Blog
博客园 - 司徒正美
The Register - Security
The Register - Security
L
LangChain Blog
S
Security Affairs
Hacker News - Newest:
Hacker News - Newest: "LLM"
B
Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Threat Research - Cisco Blogs
I
InfoQ
S
Schneier on Security
L
Lohrmann on Cybersecurity
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Martin Fowler
Martin Fowler
Schneier on Security
Schneier on Security
F
Fortinet All Blogs
TaoSecurity Blog
TaoSecurity Blog
K
Kaspersky official blog
Google DeepMind News
Google DeepMind News
Cisco Talos Blog
Cisco Talos Blog
PCI Perspectives
PCI Perspectives
Attack and Defense Labs
Attack and Defense Labs
WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
Project Zero
Project Zero
The GitHub Blog
The GitHub Blog
D
Docker
N
News | PayPal Newsroom
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Hacker News: Front Page
云风的 BLOG
云风的 BLOG
Microsoft Security Blog
Microsoft Security Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 聂微东
Webroot Blog
Webroot Blog
MongoDB | Blog
MongoDB | Blog

博客园 - 百衲本

Centos7 副本集模式部署 MongoDB centos7部署Nacos集群模式 centos7 yum快速安装clickhouse单机版 k8s高可用部署Seata kubernetes中pod磁盘占用排查 jumpserver V3 终端常用操作 Python 列表生成式、字典生成式与生成器表达式 Python 的可迭代对象、迭代器对象与生成器 python email模块自动化操作邮件 python yagmail 模块自动化操作邮件 python自动化操作PDF 国内企业邓白氏编码免费申请流程 Python psutil模块 免费公共API调用清单 Python pathlib 模块 systemd详解 APP性能指标 Linux 上禁用 USB 存储设备 线上故障的排查清单,运维小哥拿走不谢!
容器网络故障排查:从 ping 到 tcpdump 的全链路思路
百衲本 · 2025-08-20 · via 博客园 - 百衲本

有时候接口 5xx 突增,应用层排查一圈没看出明显异常,更多像是网络可达性间歇抖动。出现这个情况的时候我习惯用一条“线”把路径捋顺:容器网络命名空间 → veth → 宿主机 bridge(docker0/cni0)→ kube-proxy/NAT → underlay(物理/隧道)→ 目标节点/外网。只要在这条线上逐段探针,总有一个点暴露真相。下面是我当晚的完整操作留痕,命令与输出都保留,随取随用。

一、先定界:谁到谁,哪一段不通

快速摸底(Kubernetes 场景)
$ kubectl get pods -o wide -n prod | head -5
NAME                         READY   STATUS    RESTARTS   AGE   IP             NODE
api-67f6cc8974-2kmdg         1/1     Running   0          4d    10.244.1.23    node-a
api-67f6cc8974-lxw4n         1/1     Running   0          4d    10.244.2.57    node-b
redis-0                      1/1     Running   0          4d    10.244.1.42    node-a
gateway-7f9d64b8d7-2xk6d     1/1     Running   0          4d    10.244.3.18    node-c
$ kubectl
get svc,ep -n prod | egrep 'api|redis' service/api ClusterIP 10.96.12.34 <none> 80/TCP endpoints/api 10.244.1.23:8080,10.244.2.57:8080 service/redis ClusterIP 10.96.98.76 <none> 6379/TCP endpoints/redis 10.244.1.42:6379

我会挑三条链路做"样本":

Pod→同 Node Pod(api@node-a → redis@node-a)
Pod→跨 Node Pod(api@node-b → api@node-a)
Pod→Service VIP(api@* → 10.96.12.34:80

二、容器内视角:icmp、tcp、dns 各打一枪

容器镜像常常"瘦身"得连 ping 都没有;没有工具就先走 busybox 或在宿主机用 nsenter 进入网络命名空间。

1.进入容器(或进入容器网络命名空间)

$ kubectl -n prod exec -it api-67f6cc8974-2kmdg -- sh
/ # uname -a
Linux api-67f6cc8974-2kmdg 5.15.0-1051-azure #59~20.04 SMP x86_64 GNU/Linux

若容器无 shell,用宿主机 nsenter:

$ PID=$(docker inspect -f '{{.State.Pid}}' api-67f6cc8974-2kmdg 2>/dev/null || crictl inspect --output go-template --template '{{.info.pid}}' <containerID>)
$ sudo nsenter -t "$PID" -n bash -lc 'ip -br a; ip route'
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             10.244.1.23/24 fe80::2c1f/64
default via 10.244.1.1 dev eth0

2.同 Node Pod:先 icmp 再 tcp

/ # ping -c2 10.244.1.42
PING 10.244.1.42 (10.244.1.42): 56 data bytes
64 bytes from 10.244.1.42: seq=0 ttl=64 time=0.374 ms
64 bytes from 10.244.1.42: seq=1 ttl=64 time=0.341 ms

--- 10.244.1.42 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1ms
rtt min/avg/max/mdev = 0.341/0.357/0.374/0.016 ms
/ # nc -zv 10.244.1.42 6379
10.244.1.42 (10.244.1.42:6379) open

3.跨 Node Pod:丢包就记住 TTL/延迟特征

/ # ping -c4 10.244.2.57
PING 10.244.2.57 (10.244.2.57): 56 data bytes
64 bytes from 10.244.2.57: seq=0 ttl=63 time=0.892 ms
64 bytes from 10.244.2.57: seq=1 ttl=63 time=1.104 ms
64 bytes from 10.244.2.57: seq=2 ttl=63 time=25.331 ms
64 bytes from 10.244.2.57: seq=3 ttl=63 time=120.442 ms

--- 10.244.2.57 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3ms
rtt min/avg/max/mdev = 0.892/36.942/120.442/51.012 ms

延迟波动大,像是隧道或队列拥塞。再用 TCP 验证:

/ # curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' http://10.244.2.57:8080/health
200 0.123

4.Service VIP:走 kube-proxy/ipvs/iptables 的路径

/ # curl -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' http://10.96.12.34/health
200 10.96.12.34

如偶发 000/超时,通常是 kube-proxy、端点漂移或 SNAT 问题。

5.DNS 只认证据

/ # getent hosts api.prod.svc.cluster.local
10.96.12.34    api.prod.svc.cluster.local
/ # dig +short api.prod.svc.cluster.local @10.96.0.10
10.96.12.34

DNS 没抖。继续往“线”下一个节点走。

三、宿主机视角:veth、bridge、路由、NAT、conntrack

1.宿主机端找到 pod 对应的 veth

$ # Pod IP 10.244.1.23 所在宿主机 node-a
$ ip -br addr | egrep 'cni0|flannel|vxlan|docker0'
cni0             UP   10.244.1.1/24
flannel.1        UP   10.244.1.0/32
$ ip route | grep 10.244.1.23
10.244.1.23 dev vethb42c3 scope link
$ ethtool -S vethb42c3 | egrep 'rx_|tx_' | head
rx_packets: 129284
tx_packets: 130112
rx_dropped: 0
tx_dropped: 12
tx_dropped 不为 0,记一笔。

2.bridge FDB 与 ARP 邻居表

$ bridge fdb show br cni0 | head -3
02:42:ed:7a:1b:2c dev vethb42c3 master cni0
12:2e:9a:15:00:aa dev veth9a7d1 master cni0
33:33:00:00:00:16 dev cni0 self permanent
$ ip neigh show dev cni0 | head -2
10.244.1.23 lladdr 02:42:ed:7a:1b:2c REACHABLE
10.244.1.42 lladdr 02:42:6a:aa:bb:cc STALE

3.kube-proxy 模式与规则(iptables / IPVS)

$ kubectl -n kube-system get cm kube-proxy -o yaml | grep -i mode
  mode: "ipvs"
IPVS 模式下看虚拟服务与后端:

$ sudo ipvsadm -Ln | egrep '10.96.12.34|8080' -A2
TCP  10.96.12.34:80 rr
  -> 10.244.1.23:8080           Masq    1      0          0
  -> 10.244.2.57:8080           Masq    1      0          0
iptables 模式下看 NAT 表命中计数:

$ sudo iptables -t nat -vnL KUBE-SERVICES | head -3
pkts bytes target     prot opt in  out  source  destination
 600 36000 KUBE-SVC-XXXX  tcp  --  *   *    0.0.0.0/0  10.96.12.34  /* api */ tcp dpt:80

4.连接跟踪与上限

$ sudo conntrack -S
cpu=0 found=524288 invalid=0 ignore=0 insert=0 insert_failed=0 drop=0 early_drop=0 error=0 id=0
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 524288
达到上限时会出现莫名其妙的新连接超时。如果在峰值时间有问题,先临时调高(评估内存):

$ sudo sysctl -w net.netfilter.nf_conntrack_max=1048576
net.netfilter.nf_conntrack_max = 1048576

四、跨节点那条“暗线”:隧道、BGP、MTU

不同 CNI 路径不一样,但命令思路一样。

1.Flannel(VXLAN 8472)是否"透气"

$ ss -lunp | grep 8472
udp   UNCONN 0 0 10.0.0.11:8472 0.0.0.0:* users:(("flanneld",pid=1320,fd=12))
$ sudo tcpdump -ni any udp port 8472 -c 4
14:01:21 VXLAN, flags [I] (0x08), vni 1
14:01:21 VXLAN, flags [I] (0x08), vni 1
14:01:22 VXLAN, flags [I] (0x08), vni 1
14:01:22 VXLAN, flags [I] (0x08), vni 1

有包,说明隧道在跑。

2.Calico(BGP 179)是否起邻居

$ ss -tnp | grep ':179 '
ESTAB 0 0 10.0.0.11:60514 10.0.0.12:179 users:(("bird",pid=980,fd=18))
$ sudo calicoctl node status | sed -n '1,15p'
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE         | STATE | SINCE    | INFO        |
+--------------+-------------------+-------+----------+-------------+
| 10.0.0.12    | node-to-node mesh | up    | 02:11:34 | Established |
+--------------+-------------------+-------+----------+-------------+

3.Cilium(eBPF)直接看丢包

$ cilium status | head -6
KVStore:                Ok   Disabled
Kubernetes:             Ok   1.28 (v1.28.3) [linux/amd64]
Cilium:                 Ok   1.14.5
$ sudo cilium monitor -t drop -n 5
xx drop (Policy denied) flow 0x0 to endpoint 234, identity 12345->56789

如果这里出现 policy deny,别再怀疑 TCP,本质是网络策略阻断。

4.MTU 不匹配:最隐蔽的刀

容器常见 mtu 1450(VXLAN 头开销),物理口 1500;若路径某段更小(云厂商负载均衡、VPN),碎片/DF 就会出问题。
$ ip -br link | egrep 'eth0|cni0|flannel|vxlan'
eth0             UP   mtu 1500 …
cni0             UP   mtu 1450 …
flannel.1        UP   mtu 1450
在 Pod 内测试 Path MTU(DF 不分片):
/ # ping -M do -s 1472 8.8.8.8 -c2 PING 8.8.8.8 (8.8.8.8): 1472 data bytes From 10.0.0.1 icmp_seq=1 Frag needed and DF set (mtu = 1450)
缩到
1452 仍不通,就用 1430 再试: / # ping -M do -s 1430 8.8.8.8 -c2 64 bytes from 8.8.8.8: seq=0 ttl=113 time=35.2 ms 64 bytes from 8.8.8.8: seq=1 ttl=113 time=35.1 ms

有结论:路径 MTU≈1470-40-?,生效 MTU 1450。生产里要么调 NIC/隧道 MTU,要么强制应用侧 --mtu,要么在边界做 MSS clamp。

五、Service/Hairpin 这些"坑",别忘了

1.hairpin NAT:Pod 访问同 Node 的 Service 自己

$ kubectl -n kube-system get cm kube-proxy -o yaml | grep -i hairpin
  hairpinMode: "hairpin-veth"

如果 hairpin 关闭,现象是Pod 访问 Service 偶发失败。开启后重启 kube-proxy;某些 CNI 需设置 hairpinMode: true

2.rp_filter 反向路径过滤

$ sysctl net.ipv4.conf.all.rp_filter
net.ipv4.conf.all.rp_filter = 1
容器多网卡/多路由场景把它调到宽松:
$ sudo sysctl -w net.ipv4.conf.all.rp_filter=2
net.ipv4.conf.all.rp_filter = 2

3.SNAT/外网出口混乱

$ ip route get 1.1.1.1
1.1.1.1 via 10.0.0.1 dev eth0 src 10.0.0.11
$ sudo iptables -t nat -vnL POSTROUTING | egrep 'MASQUERADE|KUBE-'
120K 7.2M MASQUERADE  all  --  *   eth0  10.244.0.0/16  0.0.0.0/0

SNAT 缺失会导致回包走错路,表现为 SYN 发送但无 SYN-ACK。

六、抓包这件事:抓哪儿,抓什么,怎么看

1.位置选择

Pod 内 eth0:确认包是否发出/收到
宿主机 veth 对端:确认离开命名空间后是否异常
cni0/网桥:确认是否转发
隧道口 flannel.1/vxlan:确认跨节点流量
物理口 eth0:确认上/下行是否丢

2.典型场景一:Pod → Service 80/TCP 超时

在 Pod 内抓:
/ # tcpdump -ni eth0 tcp port 80 -c 4
14:22:01 IP 10.244.1.23.46322 > 10.96.12.34.80: Flags [S], seq 12345, win 64240
14:22:02 IP 10.244.1.23.46322 > 10.96.12.34.80: Flags [S], seq 12345, win 64240
14:22:04 IP 10.244.1.23.46322 > 10.96.12.34.80: Flags [S], seq 12345, win 64240
14:22:08 IP 10.244.1.23.46322 > 10.96.12.34.80: Flags [S], seq 12345, win 64240
只有 SYN,没 SYN-ACK。到宿主机 cni0 再抓:
$ sudo tcpdump -ni cni0 host 10.244.1.23 and tcp port 80 -c 4
14:22:01 IP 10.244.1.23.46322 > 10.96.12.34.80: Flags [S]
14:22:01 IP 10.96.12.34.80 > 10.244.1.23.46322: Flags [S.], ack 12346
14:22:02 IP 10.244.1.23.46322 > 10.96.12.34.80: Flags [S]
14:22:02 IP 10.96.12.34.80 > 10.244.1.23.46322: Flags [S.], ack 12346

宿主机能看到 SYN-ACK,而 Pod 内看不到回包,说明问题在 命名空间边界(veth/hairpin/NF)。常见成因:

rp_filter 严格导致回包被丢;
veth 队列/网桥掉包(tx_dropped 升);
eBPF/iptables filter 规则误伤。

此时对照 ethtool -S vethX 与 iptables -vnL 的计数就能坐实。

3.典型场景二:跨节点 Pod 延迟抖

抓 VXLAN:
$ sudo tcpdump -ni any udp port 8472 -c 6
14:25:10 IP 10.0.0.11.4789 > 10.0.0.12.8472: VXLAN, vni 1, encapsulated Ethernet
14:25:10 IP 10.0.0.12.8472 > 10.0.0.11.4789: VXLAN, vni 1, encapsulated Ethernet
...
再抓物理口: $ sudo tcpdump
-ni eth0 host 10.0.0.12 and udp port 8472 -vv -c 4 14:25:12 IP (tos 0x0, ttl 64) 10.0.0.11.4789 > 10.0.0.12.8472: UDP, length 1542 14:25:13 IP (tos 0x0, ttl 64) 10.0.0.11.4789 > 10.0.0.12.8472: UDP, length 1542

length 1542 超过 1500,若路径不支持巨帧,就会碎片或丢包,对应上文 MTU 现象。对策:把隧道口 MTU、cni0、Pod 一致调低(1450 或更小),或在边界启用 MSS clamping:

$ sudo iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

典型场景三:DNS 间歇性解析失败

Pod 内抓 53/ # tcpdump -ni eth0 port 53 -c 6
14:30:11 IP 10.244.1.23.50524 > 10.96.0.10.53: 1234+ A? api.prod.svc.cluster.local.
14:30:11 IP 10.96.0.10.53 > 10.244.1.23.50524: 1234 1/0/0 A 10.96.12.34
...
CoreDNS 侧看 QPS 与延迟: $ kubectl
-n kube-system logs -l k8s-app=kube-dns --tail=20 | sed -n '1,5p' [INFO] 14:30:11.123 query: A api.prod.svc.cluster.local. (1 servers, time 0.5 ms)

如果解析慢,八成是上游递归 DNS 或 NetworkPolicy 限制了 UDP/53。

七、一把趁手的"随手脚本":定位 Pod→veth→抓包

随手放在运维工具箱里,节省来回敲命令的时间。

cat > knet.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
POD="$1"; NS="${2:-default}"
NODE=$(kubectl get pod "$POD" -n "$NS" -o jsonpath='{.spec.nodeName}')
IP=$(kubectl get pod "$POD" -n "$NS" -o jsonpath='{.status.podIP}')
CID=$(kubectl get pod "$POD" -n "$NS" -o jsonpath='{.status.containerStatuses[0].containerID}' | sed -E 's/.*:\/\///')
ssh "$NODE" "sudo bash -lc '
  PID=\$(crictl inspect \"$CID\" | jq -r .info.pid 2>/dev/null || docker inspect -f {{.State.Pid}} \"$CID\"); 
  VETH=\$(ls -l /proc/\$PID/ns/net >/dev/null 2>&1; 
         for v in /sys/class/net/veth*/ifindex; do 
           test -e \$v || continue;
           if ip -j addr show \${v%/*} | jq -re \".[].addr_info[]? | select(.local==\\\"$IP\\\")\" >/dev/null; then echo \${v%/*##*/}; fi; 
         done)
  echo NODE=$NODE POD=$POD IP=$IP VETH=\$VETH
  tcpdump -ni \$VETH -c 10 port 80 or port 53
'"
EOF
chmod +x knet.sh
示例执行:

$ ./knet.sh api-67f6cc8974-2kmdg prod
NODE=node-a POD=api-67f6cc8974-2kmdg IP=10.244.1.23 VETH=vethb42c3
14:41:10 IP 10.244.1.23.50122 > 10.96.12.34.80: Flags [S]
14:41:10 IP 10.96.12.34.80 > 10.244.1.23.50122: Flags [S.]
14:41:11 IP 10.244.1.23.50524 > 10.96.0.10.53: 1234+ A? api.prod.svc.cluster.local.
14:41:11 IP 10.96.0.10.53 > 10.244.1.23.50524: 1234 1/0/0 A 10.96.12.34
...

八、留痕与回放:抓包文件与系统状态

事故现场只来一次,证据得留全。

$ sudo tcpdump -ni cni0 host 10.244.1.23 -w /tmp/api-$$.pcap
tcpdump: listening on cni0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
42 packets captured
$ sudo iptables-save > /tmp/iptables-$(date +%F-%H%M).rules
$ sudo nft list ruleset > /tmp/nft-$(date +%F-%H%M).rules 2>/dev/null || true
$ sudo ss -s > /tmp/ss-$(date +%F-%H%M).txt
$ sudo conntrack -L | head -5 > /tmp/conntrack-$(date +%F-%H%M).txt

九、常见症状与"一步到位"的定位锚点

Service 偶发 000/超时:Pod 内只见 SYN,无 SYN-ACK;cni0 能见回包 → 看 rp_filter/hairpin/NF。
跨节点延迟/丢包:VXLAN 抓到 1500+ 载荷或 ICMP Frag needed → MTU/MSS。
外网访问失败:ip route get 出口正常,iptables MASQUERADE 计数为 0 → 缺 SNAT。
仅特定端口不通:cilium monitor -t drop 或 iptables -vnL 命中计数暴涨 → 网络策略/防火墙。
突发大量新建连接超时:conntrack 逼近上限 → 提升 nf_conntrack_max 并削峰。

十、收尾的两句:别迷信单个命令

ping 只告诉你"有呼吸",curl 才说明"能说话",tcpdump 让你"看见血液循环"。容器网络的每一跳都可验证,把黑盒拆成透明管道,问题就不再神秘。把上面的命令按你环境换个 IP、换个接口名,照着走,十分钟内你会知道故障在哪一段。其余的,就是修补而已。