K8s 网络排障:从抓包开始,一步步定位诡异"502"
网络一出事,别先重启,从 Pod 打到内核的思路才是硬道理
你有没有遇到过这种情况:K8s 集群里的服务突然返回 502 Bad Gateway,翻日志啥也没有,问开发说"代码没改",看监控 CPU、内存都挺健康,最后只能默默重启 Pod 或节点,问题暂时消失,但过几天又来了?
说句掏心窝子的话:Kubernetes 里,十个疑难杂症,八个最后都能追溯到网络 。而 502 错误,恰恰是网络问题的集大成者------它可能发生在从 Ingress 到 Service、从 Service 到 Pod、甚至 Pod 内部的任何一个环节。
今天我想干一件事:用一个真实的 502 排查案例,串起 K8s 网络的各个层次,从抓包开始,一步步拆解,带你建立一套系统化的网络排障思路。
案例背景:诡异的滚动更新 502
某客户反馈,生产业务的应用在做滚动更新时大量报 502,虽然设置了 prestop 优雅下线(延迟关闭),问题依然存在 。
我们搭建测试环境复现,发现一个诡异现象:Pod 切换完成后,客户端访问服务会超时一段时间,QPS 直接掉到 0 。
这不是玄学,这是有规律可循的网络问题。按分层思路来:
第一层:Pod 内部,别急着甩锅
很多人一遇到网络不通,第一反应就是"CNI 出问题了"或"kube-proxy 又抽风了"。但其实应该先从 Pod 自己查起 。
kubectl exec -it pod-name -- sh
# 在 Pod 内执行
ip addr
ip route
ping 127.0.0.1
你要确认的只有三件小事 :
- Pod 有没有 IP?
- 默认路由是不是指向 eth0?
- 本地 loopback 通不通?
血的教训:有人遇到过 Pod 用的是 distroless 镜像,容器里连 ip 命令都没有,最后靠猜查了一晚上 。
我们的案例中,Pod 本身是正常的,IP 也有,路由也在。那就往下走。
第二层:Pod ↔ Pod,关键的分叉点
这是一个90%的人忽略但极其关键的分叉点 :先判断目标 Pod 在同节点 还是跨节点 。
kubectl get pod -o wide
看 NODE 列。
在滚动更新场景中,新老 Pod 可能分布在不同的节点上。我们抓包分析:
在 Node 上抓 eth0 对应 Service 的 NodePort:
tcpdump -i eth0 port 31199 -s 0 -w nodeport.pcap
发现有 SYN 重传,但不知道重传给哪个 Pod 。继续抓所有端口:
tcpdump -i any host <pod-ip> -w all.pcap
结果发现一个关键现象:客户端 SYN 重传无法建联,但 server 端为什么不返回 SYN ACK?
进一步抓包发现,新请求的五元组和旧连接复用了------上一次复用该端口的连接已经正常 FIN 掉了,间隔了一分钟左右才复用,但新 Pod 没有重传报文,为什么还是将报文转发给了老 Pod?
第三层:Service/kube-proxy,真相在此
怀疑是 ipvs 规则更新慢导致转发给了老 Pod。写脚本采集 endpoint、pod、ipvs 等组件的变化 :
bash
for i in {1..200}; do
echo $(date) >> pod.txt
kubectl get pods -n app -o wide |grep test >> pod.txt
kubectl describe ep -n app test-service >>pod.txt
sudo ipvsadm -Ln |grep "10.27.255.42:80" -A 5 >>pod.txt
sudo ipvsadm -Ln -c |grep "10.27.1." >>pod.txt
sudo egrep "10.27.1." /proc/net/nf_conntrack >>pod.txt
sleep 1s
done
从抓取的记录看到:ipvs 规则、pod 状态、endpoint 都是新 Pod IP 。但转发层面呢?
抓 ipvs 及 nf_conntrack 全量 session 对比发现:上一秒 TIME_WAIT 还在过期倒计时,下一秒复用端口变成了 SYN_RECV,并转发给了老 IP 。
这就是根因:conntrack 在五元组复用时,将新请求转发给了已下线的 Pod 。
conntrack 原理:当客户端用相同五元组(源 IP、源端口、目的 IP、目的端口、协议)快速建立新连接时,conntrack 认为这是旧连接的"残留包",直接转发给原后端,即使 Service 的 endpoints 已经更新 。
第四层:内核参数,终极形态
当你走到这一步,说明你已经比 80% 的 K8s 使用者走得更深了 。
检查 conntrack 表:
conntrack -L | wc -l
sysctl net.netfilter.nf_conntrack_max
另一个真实案例:虚拟机挂起恢复后,Node 间 Pod 不通,ping 100% 丢包 。查路由表正常,双端抓包能收到,但 Node2 不转发 。
真相是:net.ipv4.ip_forward = 0
Kubernetes 的 Node 节点本质是一台路由器。当虚拟机从挂起状态恢复时,CentOS 的网络服务重启网卡,出于安全策略将 ip_forward 重置为 0 。
临时修复:
sysctl -w net.ipv4.ip_forward=1
永久修复:
cat <<EOF > /etc/sysctl.d/k8s-forward.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 0
EOF
sysctl --system
第五层:应用层,别忽视 keepalive
还有一个经典案例:业务偶发 502,发生在 apisix 和后端 Pod 之间 。
抓包发现:TCP 连接被重置(RST)
后端 Pod 中抓包也出现 RST,说明是端到端问题 。分析发现:相邻请求间隔 4-5 秒,后端 response header 里有 keep-alive_timeout=5 。
原因:apisix upstream keepalive_timeout 默认 60s,后端 Node.js 应用 keepalive_timeout=5s 。空闲连接在后端已被关闭,但 apisix 还在用,导致 RST 。
解法:调整后端 keepalive_timeout > 60s 。
必备工具链
- netshoot:网络排查 Swiss Army Knife,包含 tcpdump、tshark、dig、curl 等
- tcpdump:抓包神器
- conntrack:查看连接跟踪表
- ipvsadm:查看 IPVS 规则
- calicoctl:Calico 诊断
- check-gke-ingress:GKE Ingress 诊断工具
排查思路总结
K8s 网络至少分 5 层 :
- Pod 内部:ip addr, ip route, ping 127.0.0.1
- Pod ↔ Pod:同节点/跨节点,traceroute
- Service/kube-proxy:endpoints、iptables/ipvs 规则
- CNI 网络:Pod IP 分配、跨节点封装
- Linux 内核:ip_forward、conntrack、rp_filter
不要跳步 ,不要一上来重启节点、升级 CNI、甩锅云厂商 。重启解决的问题,通常不是被你解决的,而是被你掩盖的 。
写在最后
Kubernetes 网络这玩意儿,说难是真难,但一旦你脑子里有了分层模型,很多"玄学问题"会突然变得特别理性。
从 Pod 开始,一层一层往下,用抓包终止争论,用内核参数找到真相。
下次遇到 502,别再重启了,按这条路线图走一遍,你会发现------网络排障,也可以如此清晰。
你在 K8s 网络排障中遇到过最坑的问题是什么?评论区分享出来,一起避坑!