一次请求在集群里走的路
K8s 网络排障难,是因为一条请求要穿过好几层:客户端 → Ingress(七层入口)→ Service(四层负载)→ Endpoints(真实 Pod IP)→ 容器网络(CNI)。每一层都可能出问题,而且症状都是「访问不通」。排障的第一步,就是先确定请求卡在哪一层,而不是在应用层瞎查。
可以把流量分成两个方向:南北向(集群外到集群内,走 Ingress/NodePort/LoadBalancer)和东西向(Pod 到 Pod、Pod 到 Service,走 ClusterIP)。故障范围也能据此粗分:只影响集群外访问,问题多半在 Ingress 或入口层;集群内互相访问也不通,问题多半在 CNI 或 kube-proxy。
# 网络排障第一问:哪里通、哪里不通
kubectl get svc,ep -A # 服务与后端端点是否就绪
kubectl get pods -A -o wide # 看 Pod IP 与所在节点
kubectl get ingress -A # 入口规则是否正常
# 分层探测顺序
curl http://<pod-ip> # Pod 层(最底层)
curl http://<clusterip> # Service 层
curl https://<ingress-host> # 入口层(最上层)
- 分层按 Pod → Service → Ingress 逐层收敛故障。
- CNIPod 网络由 veth + 网桥 + 路由实现。
- 转发Service 靠 kube-proxy 生成规则并依赖 Endpoints。
- 解析集群内 DNS 由 CoreDNS 承担,带 search domain。
CNI 与 Pod 网络:第一层连通性
每个 Pod 都有自己的 IP,这靠的是 CNI(容器网络接口)插件。最简模型:Pod 里一块 veth 虚拟网卡连到节点上的网桥(cni0),节点间再通过路由或隧道打通。主流实现里,Calico 用 BGP 直连路由(性能好、可配网络策略),Flannel 用 VXLAN 隧道(简单、跨子网友好),Cilium 用 eBPF 实现(性能与可观测性最强)。
Pod 间不通,先分清是「同一节点不通」还是「跨节点不通」。同节点不通多半是网桥或 veth 问题,跨节点不通多半是路由、隧道或防火墙问题。快速定位用 ping Pod IP,再逐个检查节点的路由表和 CNI 状态。
# Pod 网络层排查
kubectl get pods -n kube-system | grep -E 'calico|flannel|cilium'
kubectl -n kube-system logs -l k8s-app=calico-node # CNI 组件日志
# 进节点看路由(Pod 网段怎么走)
ip route show
# 常见输出:10.244.1.0/24 via 192.168.1.2 dev eth0(Calico BGP)
# 10.244.0.0/16 dev flannel.1(Flannel VXLAN)
# 检查 veth 是否成对
ethtool -S eth0 2>/dev/null | head -5 # Pod 侧
ip link show | grep veth # 节点侧
还有一个高频坑:节点安全组/防火墙挡了 CNI 所需的端口(Calico 的 BGP 179、VXLAN 的 4789、健康检查端口等),导致新节点上的 Pod 永远跨不过去。加完节点发现跨节点不通,优先怀疑安全组。
Service 与 Endpoints:负载与转发
Service 是 Pod 的稳定入口:ClusterIP 集群内访问,NodePort 在每台节点上开端口,LoadBalancer 交给云厂商负载均衡器。Service 本身不转发流量,真正干活的是 kube-proxy(iptables 或 IPVS 模式)加上后端 Pod 的 Endpoints。
「Service 访问不通」先看 Endpoints:kubectl get ep 里如果没有后端地址,说明 Pod 没打上 Service 的 selector 标签,或者 Pod 不在 ready 状态。这是最高频的 Service 故障原因,比怀疑 kube-proxy 要常见得多。
# Service 排查三步
kubectl get svc <svc> -n <ns> -o yaml # 看 selector 与 type
kubectl get ep <svc> -n <ns> # Endpoints 是否就绪(关键!)
kubectl get pods -n <ns> --show-labels # Pod 标签是否匹配 selector
# 确认 kube-proxy 模式(iptables 还是 ipvs)
kubectl -n kube-system get cm kube-proxy -o yaml | grep mode
Endpoints 正常仍不通,再查 kube-proxy 与节点 iptables/ipvs 规则。IPVS 模式下 ipvsadm -ln 能看到 Service 的虚拟 IP 和后端列表;iptables 模式下规则链很长,别直接读,用 curl ClusterIP 实测更快。会话保持(source IP 透传)需求会影响 externalTrafficPolicy 的选择,配错了会出现「节点间跳转后客户端 IP 全变成节点 IP」的怪现象。
Ingress 与南北向流量
集群外访问通常走 Ingress,最常见实现是 ingress-nginx(Nginx 控制器,七层)。Ingress 资源声明「哪个域名/路径转发到哪个 Service」,控制器把规则落成 Nginx 配置。南北向问题集中在三块:域名解析与证书、Ingress 规则本身、控制器到后端 Service 的转发。
证书问题最常见也最隐蔽:证书过期、证书与域名不匹配(SAN 缺失)、secret 不存在。出现「浏览器报证书错误但服务其实正常」,先看 Ingress 的 TLS 段引用的 secret 是否真实存在且未过期。
# Ingress 排查
kubectl get ingress -n <ns> -o yaml # 看规则与 TLS
kubectl -n ingress-nginx get pod # 控制器是否正常
kubectl -n ingress-nginx logs -l app.kubernetes.io/name=ingress-nginx # 控制器日志
# 证书检查
kubectl get secret <tls-secret> -n <ns> -o jsonpath='{.data.tls\.crt}' |
base64 -d | openssl x509 -noout -dates -subject
如果 Ingress 控制器日志显示转发 502/504,多半是后端 Service 或 Pod 的问题,回到第 3 节继续查;如果日志干净但外部访问超时,则是入口负载(云 LB / NodePort)到控制器的链路问题。记住:Ingress 只是把外部流量导到 Service,它自己不产生后端故障。
DNS 与集群内解析
集群内服务间通信靠域名而不是 IP,解析由 CoreDNS 承担。Pod 里访问 svc-name.namespace.svc.cluster.local 能直接命中,访问短名 svc-name 则靠 Pod 的 resolv.conf 里配的 search domain 自动补全。DNS 故障的症状很典型:用 IP 访问正常、用域名访问时好时坏。
排查顺序:先确认 Pod 的 resolv.conf 正常(search 列表对不对、nameserver 指向 CoreDNS 的 ClusterIP),再确认 CoreDNS 本身健康,最后用 nslookup 在 Pod 内实测解析。
# Pod 内 DNS 排查
kubectl exec <pod> -n <ns> -- cat /etc/resolv.conf
# 期望输出:nameserver 10.96.0.10(CoreDNS ClusterIP)
# search ns.svc.cluster.local svc.cluster.local cluster.local
kubectl exec <pod> -n <ns> -- nslookup svc-name.ns.svc.cluster.local
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
CoreDNS 自身故障常见原因:CPU 打满(大量 Pod 解析流量)、副本太少或没吃满、以及集群大了之后 resolv.conf 的 ndots:5 导致每个域名查询都先试多次后缀。看到「查询很慢但最终能通」,先数数 CoreDNS 副本和 CPU。
常见网络故障速查与抓包
把最常见的故障症状和对应检查点列成速查表,排查时按图索骥:Pod 通、Node 不通,多半是云平台路由/安全组没放行 Pod 网段;跨节点 Pod 不通,查 CNI 路由与隧道端口;集群内通、集群外不通,查 NodePort/Ingress/LoadBalancer;时通时不通,优先怀疑 DNS 与会话保持。
抓包是网络排障的最终手段。进 Pod 网络命名空间抓包用 nsenter:找到 Pod 的容器 PID,进入它的 netns,再用 tcpdump 看真实流量——比在节点上抓 eth0 干净得多。配合 conntrack 看连接跟踪状态,能判断是连接被丢还是被重置。
# 进 Pod 网络命名空间抓包
POD_PID=$(kubectl get pod <pod> -n <ns> -o jsonpath='{.status.containerStatuses[0].containerID}' |
sed 's|docker://||')
# 实际用 crictl inspect 拿 PID,再进命名空间
nsenter -t <PID> -n tcpdump -i eth0 -n port 8080
# 看连接跟踪状态
conntrack -L | grep 10.244.1.5 | head -10
最后强调一个判断原则:抓包要抓「发起侧」和「接收侧」两端。只在发起侧看到 SYN 发出、接收侧毫无踪迹,是中间路由/防火墙丢包;接收侧能看到包但应用没响应,才是应用层问题。两端一对比,故障归属立刻清晰。
网络排障清单
把上面的方法固化成清单,按层收敛,避免在错误层浪费一小时:
网络排障清单(按顺序执行)
1. 定方向 南北向还是东西向?哪里通哪里不通
2. Pod 层 curl Pod IP;看 CNI 组件与节点路由
3. Service 层 get ep 看后端;selector 标签是否匹配
4. 入口层 get ingress;证书与控制器日志
5. DNS 层 resolv.conf;CoreDNS 状态;nslookup 实测
6. 抓包定案 nsenter + tcpdump,对比发起侧与接收侧
7. 记录与沉淀 把根因写进 runbook,同类故障下次 10 分钟
网络排障真正的护城河,是脑子里有那张「请求路径图」。故障来时先画一遍路径,再逐层验证,多数问题卡在两层交界处(标签不匹配、证书过期、安全组漏放行),而不是深层技术。这套思路和 Pod 故障排查、异地多活的跨集群流量治理是同一套方法论。