集群网络故障是 Kubernetes 运维中最令人头疼的问题。与应用层报错不同,网络问题往往表现为”间歇性失败”或”部分节点不通”,没有明确的错误日志可循。排查者需要同时具备 Linux 内核网络、容器运行时、Kubernetes 控制面三个维度的知识,才能在复杂的网络栈中快速定位根因。
本文基于生产环境中真实遇到的 10 个故障案例,建立一套从 L2 到 Policy 的分层排查方法论,并给出每个案例完整的”症状、排查命令、根因、修复、预防”五步拆解。
环境说明:本文案例基于 Kubernetes 1.30、Cilium 1.16 / Calico 3.28、CoreDNS 1.11、Linux 6.x 内核。不同版本的命令输出可能略有差异,但排查思路通用。
一、排查方法论:从 L2 到 Policy 的分层定位
网络故障排查的核心原则是分层隔离。TCP/IP 模型中每一层都可能成为故障点,盲目抓包或随机修改配置只会浪费时间。我们将 Kubernetes 网络问题划分为六个排查层级,从底层向上逐层排除:
+----------+-----------------------------+---------------------------+
| 层级 | 排查对象 | 典型故障 |
+----------+-----------------------------+---------------------------+
| L2 | veth pair、bridge、ARP | MAC 地址冲突、ARP 风暴 |
| L3 | 路由表、MTU、IPAM | 路由黑洞、MTU 不匹配 |
| L4 | conntrack、iptables/nft | conntrack 表满、SNAT 冲突 |
| L7 | kube-proxy、Service、Ingress | iptables 规则未同步 |
| DNS | CoreDNS、ndots、search | 解析慢、NXDOMAIN |
| Policy | NetworkPolicy、CNI 策略 | 策略不生效、label 错误 |
+----------+-----------------------------+---------------------------+
排查流程遵循以下步骤:
- 确定故障范围:单 Pod、单节点、跨节点、全集群。范围越小,越容易定位。
- 确定故障层级:从 L2 开始逐层检查,先排除底层问题再看上层。
- 构造最小复现:用
curl、ping、nslookup等工具在故障 Pod 内复现,避免在复杂链路上排查。 - 对比分析:同节点的其他 Pod 是否正常?同 Service 的其他 endpoint 是否正常?
- 抓包定位:当日志和命令行工具无法定位时,tcpdump 是最终手段。
二、网络排查工具箱:nsenter / tcpdump / bpftrace 组合拳
在正式进入案例之前,先建立排查工具箱。生产环境中 Pod
通常是 distroless
镜像,没有任何调试工具,因此我们需要在宿主机上使用
nsenter 进入 Pod 的网络命名空间进行排查。
获取 Pod 的网络命名空间
# 获取 Pod 所在节点和容器 ID
kubectl get pod <pod-name> -o jsonpath='{.spec.nodeName}'
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].containerID}'
# SSH 到对应节点后,获取容器进程 PID
crictl inspect <container-id> | grep -i pid
# 输出示例:
# "pid": 12345
# 使用 nsenter 进入网络命名空间
nsenter -t 12345 -n进入命名空间后,所有网络命令(ip、ss、iptables、tcpdump)都在
Pod 的网络视角下执行。
常用排查命令速查
# L2:veth pair 和 ARP
ip link show && ip neigh show && bridge fdb show
# L3:路由和 MTU
ip route show && ip -d link show | grep mtu && traceroute -n <target-ip>
# L4:conntrack 和 iptables
conntrack -S # 关注 drop/insert_failed
cat /proc/sys/net/netfilter/nf_conntrack_count # 当前条目数
cat /proc/sys/net/netfilter/nf_conntrack_max # 上限
iptables -t nat -L -n -v --line-numbers
# DNS
nslookup kubernetes.default.svc.cluster.local && cat /etc/resolv.conf
# 连接状态
ss -tnp && ss -s && netstat -s | grep -i retranstcpdump 实战技巧
tcpdump -i lxcXXXX -nn -w /root/capture.pcap # veth 宿主端抓包
tcpdump -i any -nn port 53 # 只抓 DNS
tcpdump -i any -nn 'tcp[tcpflags] & (tcp-syn|tcp-rst) != 0' # SYN/RST
tcpdump -i any -nn host 10.244.1.5 and port 80 # 特定 Podbpftrace 和 Hubble
当 tcpdump 仍无法定位问题时,可以使用内核级别的跟踪工具:
bpftrace -e 'kprobe:__nf_conntrack_confirm { @[comm] = count(); }' # conntrack 跟踪
hubble observe --namespace default --verdict DROPPED # Cilium 丢包观测
hubble observe --to-pod default/nginx --protocol TCP三、数据面故障:conntrack、iptables 与 MTU
本节覆盖三个与 Linux 内核数据面直接相关的案例。这类问题通常表现为”间歇性”失败,因为它们只在特定条件触发时才出现。
案例一:Pod 间歇性超时 – conntrack 表满
症状
生产集群中多个 Pod 同时出现间歇性连接超时。Service
访问偶尔返回
Connection timed out,重试后恢复正常。问题在业务高峰期尤为明显。
排查命令
# 1. 检查 conntrack 表使用情况
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 输出:
# 262144
# 262144 <-- count == max,表已满
# 2. 检查 conntrack 统计中的 drop
conntrack -S
# 输出:
# cpu=0 found=0 invalid=1234 insert=0 insert_failed=5678 drop=9012 ...
# ^^^^^^^^
# 3. 确认内核日志
dmesg | grep conntrack
# 输出:
# nf_conntrack: table full, dropping packet根因分析
Linux 内核使用 conntrack 表跟踪所有经过 Netfilter 的连接。在 Kubernetes 中,每个 Service 访问都会经过 DNAT(iptables 模式)或 conntrack(IPVS 模式),因此 conntrack 表的消耗量远超传统环境。当表满时,新连接的 conntrack 条目无法插入,内核直接丢包。
默认 nf_conntrack_max 通常为 262144(26
万),对于大规模集群(数百 Pod、每秒数万连接)远远不够。
修复方案
# 临时修复:调大 conntrack 表
sysctl -w net.netfilter.nf_conntrack_max=1048576
# 同时调整 hashsize(建议为 max/4)
echo 262144 > /sys/module/nf_conntrack/parameters/hashsize
# 永久生效:写入 sysctl 配置
cat >> /etc/sysctl.d/99-conntrack.conf <<EOF
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
net.netfilter.nf_conntrack_tcp_timeout_established = 86400
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
EOF
sysctl --system预防措施
- 在节点初始化脚本中预设 conntrack 参数。
- 配置 Prometheus 告警:当
nf_conntrack_count / nf_conntrack_max > 0.8时触发告警。 - 对于超大规模集群,考虑使用 Cilium 的 eBPF conntrack 替代内核 conntrack,或者启用 kube-proxy 的 nftables 模式以减少 conntrack 条目数量。
案例二:Service 访问间歇失败 – kube-proxy iptables 规则未同步
症状
新创建的 Service 在部分节点上访问失败,返回
No route to host 或
Connection refused。已有 Service 在滚动更新
Deployment 后,短暂出现 502 错误。
排查命令
# 1. 检查 kube-proxy 状态和 iptables 规则
kubectl get pods -n kube-system -l k8s-app=kube-proxy
iptables -t nat -L KUBE-SERVICES -n | grep <service-cluster-ip>
# 输出为空说明规则未同步
# 2. 检查同步 metrics
curl -s http://localhost:10249/metrics | grep kubeproxy_sync
# 3. 检查 Endpoints
kubectl get endpoints <service-name>
kubectl get endpointslices -l kubernetes.io/service-name=<service-name>根因分析
kube-proxy 通过 watch apiserver 的 Service 和 EndpointSlice 变化来更新本地 iptables 规则。以下情况会导致规则不同步:
- kube-proxy 与 apiserver 之间的 watch 连接断开后未及时恢复。
- 节点上 iptables 规则数量巨大(数千 Service),单次
iptables-restore操作耗时过长,期间新变化被积压。 - kube-proxy 的
--iptables-sync-period(默认 30s)和--iptables-min-sync-period(默认 1s)配置不合理。
修复方案
# 方案一:重启问题节点上的 kube-proxy 强制全量同步
kubectl delete pod -n kube-system <kube-proxy-pod-on-affected-node>
# 方案二:切换到 IPVS 模式减少 iptables 规则数量
# 编辑 kube-proxy ConfigMap
kubectl edit configmap kube-proxy -n kube-system
# 修改 mode: "ipvs"
# 方案三:对于大规模集群,考虑使用 Cilium 替代 kube-proxy
# Cilium 使用 eBPF map 实现 Service 负载均衡,无需 iptables 规则预防措施
- 监控
kubeproxy_sync_proxy_rules_duration_seconds指标,超过 5s 时告警。 - 集群规模超过 2000 Service 时,建议迁移到 IPVS 模式或 Cilium kube-proxy replacement。
- 定期检查 kube-proxy 日志中的
sync failed或error关键字。
案例三:跨节点 Pod 不通 – MTU 不匹配 + PMTUD 黑洞
症状
同节点 Pod 之间通信正常,跨节点 Pod
之间小包(ping)正常但大包(HTTP
请求、文件传输)失败或极慢。curl 请求卡在
Connected to ... 之后不再有输出。
排查命令
# 1. 使用禁止分片的 ping 二分查找实际 MTU
ping -M do -s 1400 <remote-pod-ip> # 通常成功
ping -M do -s 1450 <remote-pod-ip> # 开始失败 -> "Message too long"
# 2. 检查各接口 MTU
ip link show eth0 # Pod 内 veth
ip link show flannel.1 # overlay 接口
# 3. 抓包观察 PMTUD 是否被丢弃
tcpdump -i any -nn 'icmp[icmptype] == 3 and icmp[icmpcode] == 4'
# 无输出说明 ICMP Frag Needed 被中间设备丢弃(黑洞)根因分析
Overlay 网络(VXLAN、Geneve)会在原始数据包外层封装额外的头部,VXLAN 封装增加 50 字节。如果 Pod 的 veth MTU 仍为 1500,而底层网络也是 1500,则封装后的包大小为 1550,超过物理链路 MTU。
正常情况下,PMTUD(Path MTU Discovery)机制会通过 ICMP
Fragmentation Needed
消息通知发送端减小包大小。但在云环境中,安全组或中间防火墙经常丢弃
ICMP 包,导致 PMTUD 失效,形成”MTU 黑洞”。
修复方案
# 方案一:正确设置 Pod MTU = 物理 MTU - 封装开销
# VXLAN: 1500-50=1450 | Geneve: 1500-50=1450 | WireGuard: 1500-60=1440
kubectl edit configmap cilium-config -n kube-system # 设置 mtu: "1450"
# Calico: FELIX_IPINIPMTU=1440, CALICO_VXLAN_MTU=1450
# 方案二:启用 Jumbo Frame(MTU 9000),封装后 1550 仍在 MTU 之内
# 方案三:TCP MSS Clamping 兜底
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu预防措施
- CNI 部署时统一配置 MTU,写入 CNI 的 ConfigMap 或 Helm values。
- 云环境中确保安全组放行 ICMP
type 3 code 4(Fragmentation Needed)。 - 使用 DaemonSet 定期检查各节点接口 MTU 一致性。
- 参考 MTU 与 PMTUD 了解详细原理。
四、DNS 与服务发现故障
DNS 是 Kubernetes 中最常被低估的故障源。集群内几乎所有服务间调用都依赖 DNS 解析,CoreDNS 的任何异常都会引发大面积故障。
案例四:DNS 解析慢 – ndots:5 查询放大 + CoreDNS OOM
症状
应用日志显示大量 DNS 超时,nslookup 在 Pod
内执行需要 5-10 秒才返回结果。CoreDNS Pod 频繁 OOMKilled
重启。
排查命令
# 1. 检查 Pod 的 resolv.conf
kubectl exec <pod-name> -- cat /etc/resolv.conf
# 输出:
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
# 2. 抓取 DNS 查询,观察查询放大
nsenter -t <pid> -n tcpdump -i any -nn port 53
# 一次 nslookup api.example.com 会产生:
# api.example.com.default.svc.cluster.local -> NXDOMAIN
# api.example.com.svc.cluster.local -> NXDOMAIN
# api.example.com.cluster.local -> NXDOMAIN
# api.example.com. -> A 记录
# 每个域名还会同时查询 A 和 AAAA,共 8 次查询
# 3. 检查 CoreDNS 负载
kubectl top pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system <coredns-pod> --tail=100
# 4. 检查 CoreDNS 的 metrics
kubectl port-forward -n kube-system <coredns-pod> 9153:9153
curl -s http://localhost:9153/metrics | grep coredns_dns_request_duration根因分析
Kubernetes 默认设置
ndots:5,意味着当域名中的点号少于 5
个时,会依次追加 search 列表中的后缀进行查询。对于外部域名
api.example.com(3 个点),DNS 客户端会先尝试
api.example.com.default.svc.cluster.local、api.example.com.svc.cluster.local、api.example.com.cluster.local,全部返回
NXDOMAIN 后才查询 api.example.com. 本身。
再加上 glibc 默认会同时查询 A 和 AAAA 记录,一次外部域名解析实际产生 8 次 DNS 查询。在高并发场景下,这种查询放大会导致 CoreDNS 内存和 CPU 激增,触发 OOM。
修复方案
# 方案一:在频繁访问外部域名的 Pod 中降低 ndots
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
dnsConfig:
options: [{ name: ndots, value: "2" }]
containers:
- { name: app, image: myapp:latest }# 方案二:使用 FQDN(api.example.com.),直接查询不走 search 列表
# 方案三:为 CoreDNS 启用 autopath 插件减少查询次数
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
autopath @kubernetes
kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure; fallthrough in-addr.arpa ip6.arpa }
forward . /etc/resolv.conf
cache 30
reload
loadbalance
}# 方案四:增加 CoreDNS 副本数和资源限制
kubectl -n kube-system scale deployment coredns --replicas=5
kubectl -n kube-system patch deployment coredns --type=json -p='[
{"op":"replace","path":"/spec/template/spec/containers/0/resources/limits/memory","value":"512Mi"},
{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests/memory","value":"256Mi"}
]'预防措施
- 集群规模超过 200 Pod 时,部署 NodeLocal DNSCache 作为节点级 DNS 缓存。
- 监控 CoreDNS 的
coredns_dns_requests_total和coredns_dns_request_duration_seconds指标。 - 应用代码中访问外部域名时使用 FQDN 格式。
- 参考 CoreDNS 与集群 DNS 了解更多调优方法。
案例五:NodePort 不通 – cloud-provider 安全组未放行
症状
Service 配置了 NodePort(如 30080),在集群内通过
<NodeIP>:30080
可以正常访问,但从集群外部访问超时。所有节点均无法从外部访问该端口。
排查命令
# 1. 确认 Service 和 Endpoints 正常
kubectl get svc <service-name> -o wide && kubectl get endpoints <service-name>
# 2. 本地 vs 外部对比
curl -v http://localhost:30080 # 节点本地:成功
curl -v http://<node-external-ip>:30080 # 集群外部:超时
# 3. 检查节点防火墙和云平台安全组
iptables -t filter -L INPUT -n -v | grep 30080
aws ec2 describe-security-groups --group-ids <sg-id> \
--query 'SecurityGroups[].IpPermissions[?FromPort<=`30080` && ToPort>=`30080`]'根因分析
Kubernetes NodePort 默认使用 30000-32767 端口范围。云平台(AWS、GCP、阿里云等)的安全组默认只放行 SSH(22)和少数端口,NodePort 范围通常不在放行列表内。
此外,部分云平台的 Load Balancer 型 Service 会自动配置安全组规则,但 NodePort 型 Service 不会自动配置,需要手动添加。
修复方案
# AWS:添加安全组入站规则(仅放行所需 NodePort 和来源 IP)
aws ec2 authorize-security-group-ingress \
--group-id <sg-id> --protocol tcp --port 30080 --cidr <office-cidr>/32预防措施
- 生产环境优先使用 LoadBalancer 类型 Service 或 Ingress,避免直接暴露 NodePort。
- 使用 IaC(Terraform、Pulumi)管理安全组规则,将 NodePort 范围纳入版本控制。
- 在集群部署清单中记录所有对外暴露的端口和对应安全组配置。
五、控制面与策略故障
控制面故障通常不影响已有连接,但会导致新连接建立失败、服务发现异常或管理操作超时。
案例六:kubectl exec 超时 – apiserver 到 kubelet 网络不通
症状
kubectl exec 和 kubectl logs
命令超时,返回
error: unable to upgrade connection 或
context deadline exceeded。kubectl get pods
等只读操作正常。
排查命令
# 1. kubectl exec 流量路径:client -> apiserver -> kubelet:10250
curl -k https://<node-ip>:10250/healthz # 超时说明不通
ss -tlnp | grep 10250 # 确认 kubelet 在监听
traceroute -n -p 10250 <node-ip> # 排查中间网络
# 2. 检查 apiserver 日志
kubectl logs -n kube-system <apiserver-pod> | grep -i "exec\|stream\|upgrade"根因分析
kubectl exec 使用 SPDY 或 WebSocket
协议,需要从 apiserver 到 kubelet 的 10250
端口建立长连接。与普通 API 请求不同,这是反向连接(apiserver
主动连接 kubelet)。
常见原因包括:
- 节点防火墙或安全组未放行 apiserver 到 kubelet 10250 端口的流量。
- 在使用私有 API endpoint 的托管集群中,apiserver 所在网络与 worker 节点网络之间的路由不通。
- 中间代理(如 nginx reverse proxy)不支持 WebSocket/SPDY 协议升级。
修复方案
# 方案一:调整安全组,确保 kubelet 10250 端口从 apiserver 可达
aws ec2 authorize-security-group-ingress \
--group-id <worker-sg-id> --protocol tcp --port 10250 --source-group <master-sg-id>
# 方案二:使用 Konnectivity 代理(检查 agent 是否正常)
kubectl get pods -n kube-system -l k8s-app=konnectivity-agent预防措施
- 在集群网络架构设计时,确保 control plane 到 worker 节点的 10250 端口双向可达。
- 使用 Konnectivity 代替直连模式,通过隧道解决网络隔离问题。
- 监控 apiserver 的
apiserver_request_duration_seconds{verb="CONNECT"}指标。
案例七:Headless Service 返回旧 IP – endpoint 未及时更新
症状
使用 Headless Service(clusterIP: None)的
StatefulSet 在 Pod 重建后,DNS 解析仍然返回旧 Pod 的 IP
地址,导致连接失败。
排查命令
# 1. 检查当前 Endpoints
kubectl get endpoints <headless-svc> -o yaml
# 确认 IP 列表是否包含旧 IP
# 2. 检查 EndpointSlice
kubectl get endpointslice -l kubernetes.io/service-name=<headless-svc> -o yaml
# 关注 conditions.ready 和 addresses 字段
# 3. DNS 解析测试
kubectl exec <debug-pod> -- nslookup <headless-svc>
kubectl exec <debug-pod> -- nslookup <pod-name>.<headless-svc>
# 4. 检查 CoreDNS 缓存
# 如果 CoreDNS 配置了 cache 插件,缓存 TTL 可能导致旧记录未过期
kubectl get configmap coredns -n kube-system -o yaml | grep cache
# 5. 检查 kubelet 上报 Pod 状态的延迟
kubectl get pod <pod-name> -o yaml | grep -A5 conditions根因分析
Headless Service 的 DNS 记录直接映射到 Pod IP,不经过 kube-proxy。当 Pod 被删除重建时,Endpoint 更新链路为:kubelet 上报 Pod 状态变化 -> apiserver 更新 Pod 对象 -> EndpointSlice controller 更新 EndpointSlice -> CoreDNS watch 到变化并更新 DNS 记录。
任何一个环节的延迟都可能导致 DNS 缓存中保留旧 IP。常见瓶颈:
- CoreDNS 的
cache插件默认 TTL 为 30s,在此期间不会刷新。 - EndpointSlice controller 在大规模集群中可能存在更新延迟。
- 客户端侧的 DNS 缓存(Java 默认缓存 DNS 30s,Go 不缓存)。
修复方案
# 方案一:降低 CoreDNS cache TTL(将 cache 30 改为 cache 5)
data:
Corefile: |
.:53 { cache 5 ... }
# 方案二:使用 publishNotReadyAddresses
apiVersion: v1
kind: Service
metadata:
name: my-headless-svc
spec:
clusterIP: None
publishNotReadyAddresses: true
selector: { app: my-statefulset }
# 方案三:客户端侧 DNS 缓存控制
# Java: -Dsun.net.inetaddr.ttl=5
# Go: 默认不缓存,但连接池会复用旧连接,需配合 health check预防措施
- 客户端实现重试和健康检查机制,不依赖 DNS 一致性保证。
- 使用 Readiness Probe 确保 Pod 就绪后才加入 Endpoints。
- StatefulSet 的
terminationGracePeriodSeconds应配合 DNS TTL 使用。
案例八:网络 Policy 不生效 – CNI 不支持或 label 拼写错误
症状
创建了 NetworkPolicy 限制某 namespace 的入站流量,但策略完全不生效,所有 Pod 仍然可以自由通信。或者策略过度生效,所有流量都被阻断。
排查命令
# 1. 确认 CNI 是否支持 NetworkPolicy
# Flannel 不支持!需要搭配 Calico 作为策略引擎
kubectl get pods -n kube-system | grep -E "calico|cilium|weave"
# 2. 检查 NetworkPolicy 定义
kubectl get networkpolicy -n <namespace> -o yaml
# 3. 验证 label selector 是否匹配目标 Pod
kubectl get pods -n <namespace> --show-labels
kubectl get networkpolicy <policy-name> -n <namespace> \
-o jsonpath='{.spec.podSelector.matchLabels}'
# 对比两者的 label 是否一致
# 4. 使用 Cilium 的策略诊断(如果使用 Cilium)
cilium policy get -n <namespace>
cilium endpoint list
hubble observe --namespace <namespace> --verdict DROPPED
# 5. 使用 Calico 的策略诊断(如果使用 Calico)
calicoctl get networkpolicy -n <namespace> -o yaml
calicoctl node status根因分析
NetworkPolicy 不生效的最常见原因有两个:
- CNI 不支持 NetworkPolicy:Flannel 只实现了 CNI 网络插件规范,不实现 NetworkPolicy。使用 Flannel 时创建 NetworkPolicy 对象不会报错(apiserver 正常接受),但没有任何组件去执行这些策略。
- label 拼写错误或格式不匹配:podSelector
中的 label key 或 value 与 Pod 实际的 label 不匹配。例如
app: nginxvsapp: Nginx(大小写敏感),或者app.kubernetes.io/name: nginxvsapp: nginx(key 不同)。
策略过度生效的原因通常是:创建了一个空的 ingress 规则
ingress: [],这会阻断所有入站流量。
修复方案
# 确保 NetworkPolicy 的 label 与 Pod 完全匹配
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: backend # 必须与 Pod label 完全一致
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: frontend # 必须与来源 Pod label 完全一致
ports:
- { protocol: TCP, port: 8080 }# 如果使用 Flannel,需要额外部署 Calico 策略引擎
# 安装 Calico for policy only(不使用 Calico 网络)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/calico-policy-only.yaml预防措施
- 在 CI/CD 中加入 NetworkPolicy 的 label 校验步骤,确保 selector 能匹配到目标 Pod。
- 部署新策略前先使用
--dry-run=server验证,并用kubectl diff对比变化。 - 选择 CNI 时确认其 NetworkPolicy 支持级别(完整支持、部分支持、不支持)。
- 参考 NetworkPolicy 实战 了解策略编写规范。
六、启动时序与多集群故障
最后两个案例涉及较为特殊的场景:Pod 生命周期的时序问题和多集群网络。
案例九:Pod 启动后 30s 内不通 – CNI ADD 尚未完成 + readiness probe 过早
症状
Pod 状态显示 Running 且 Ready,但在启动后约 30 秒内无法接收流量。30 秒后自动恢复正常。应用日志显示在此期间收到的请求全部超时。
排查命令
# 1. 检查 Pod 事件时间线(Scheduled -> Created -> Started vs NetworkReady)
kubectl describe pod <pod-name> | grep -A20 Events
# 2. 检查 CNI ADD 耗时
kubectl logs -n kube-system <cilium-agent-pod> | grep <pod-name>
# 3. 检查 readiness probe 配置(initialDelaySeconds 是否过小)
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[0].readinessProbe}'
# 4. 观察 endpoint 注册时间
kubectl get endpoints <service-name> -w根因分析
Pod 启动的网络就绪流程存在时序依赖:
- kubelet 调用 CNI ADD 为 Pod 分配 IP 和配置网络。
- CNI 插件执行 IP 分配、veth 创建、路由下发、eBPF 程序加载等操作。
- kubelet 启动容器,容器进程开始监听端口。
- readiness probe 探测成功后,Pod 被加入 Service 的 Endpoints。
在某些 CNI(特别是 Cilium)中,步骤 2
可能需要数秒甚至更长时间,因为涉及 eBPF
程序编译和加载、身份分配、策略同步等操作。如果 readiness
probe 的 initialDelaySeconds 设置过短,Pod
可能在 CNI 网络配置完成前就被标记为 Ready
并开始接收流量。
修复方案
# 方案一:配置合理的 readiness probe,给 CNI 足够的初始化时间
spec:
containers:
- name: app
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
# 方案二:使用 startup probe 处理慢启动
startupProbe:
httpGet: { path: /healthz, port: 8080 }
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 12 # 最多等待 65 秒# 方案三:Cilium 开启 endpoint 就绪等待
# 在 Cilium ConfigMap 中设置 wait-bpf-mount: "true"
# 确保 eBPF 程序完全加载后才标记网络就绪预防措施
- 所有生产 Pod 必须配置 readiness probe,不能依赖 Running 状态判断服务可用性。
- 了解所用 CNI 的初始化耗时特征,据此设定
initialDelaySeconds。 - 使用
postStart生命周期钩子执行网络连通性检查。
案例十:多集群 ClusterMesh DNS 解析失败 – CoreDNS forward 配置错误
症状
在 Cilium ClusterMesh 多集群环境中,从 Cluster-A 的 Pod
访问 Cluster-B 的 Service 时,DNS 解析返回
NXDOMAIN。直接使用 Cluster-B 的 Service
ClusterIP 可以正常访问(说明数据面连通)。
排查命令
# 1. 确认 ClusterMesh 连接状态
cilium clustermesh status
# 2. 检查 CoreDNS 是否有跨集群 forward 规则
kubectl get configmap coredns -n kube-system -o yaml
# 3. DNS 解析对比测试
nslookup <service-name>.<namespace>.svc.cluster.local # NXDOMAIN
nslookup <service-name>.<namespace>.svc.cluster-b.local # 测试跨集群域名
# 4. 检查 Cilium 全局 Service 和跨集群 EndpointSlice
cilium service list | grep <service-name>
kubectl get endpointslice -A | grep <service-name>根因分析
Cilium ClusterMesh 的 DNS 解析依赖 CoreDNS 的正确配置。常见错误包括:
- CoreDNS 没有配置
forward规则将跨集群域名转发到远端 CoreDNS。 - 两个集群使用了相同的
cluster.local域名,导致域名冲突。 - ClusterMesh 的
clustermesh-apiserver未正确同步远端集群的 Service 和 Endpoint 信息。 - 远端 CoreDNS 的 Service IP 不在本集群的路由表中。
修复方案
# 方案一:使用 Cilium ClusterMesh 的共享 Service 功能
# 在 Cluster-B 的 Service 上添加注解
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: production
annotations:
service.cilium.io/global: "true"
service.cilium.io/shared: "true"
spec:
selector: { app: backend }
ports: [{ port: 80 }]# 方案二:配置 CoreDNS 跨集群转发(如果两个集群域名不同)
# Cluster-A 的 CoreDNS ConfigMap,新增 cluster-b.local 的 server block
data:
Corefile: |
cluster-b.local:53 {
errors
cache 30
forward . <cluster-b-coredns-ip>
}
.:53 {
kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure; fallthrough in-addr.arpa ip6.arpa }
forward . /etc/resolv.conf
cache 30
reload
loadbalance
}# 方案三:确保两个集群使用不同的 ClusterDNS 域名
# 在 kubeadm 配置或集群创建时指定:
# Cluster-A: --cluster-domain=cluster-a.local
# Cluster-B: --cluster-domain=cluster-b.local预防措施
- 多集群架构在规划阶段就确定各集群的域名和 Pod/Service CIDR,避免冲突。
- 使用 Cilium ClusterMesh 时优先使用
service.cilium.io/global注解实现跨集群服务发现。 - 部署跨集群连通性探针,持续监控跨集群 DNS 解析和数据面可达性。
- 参考 多集群与 ClusterMesh 了解完整的多集群网络架构。
七、排查决策树
将上述排查方法论和案例经验总结为决策树。完整的图形化决策树参见本文开头的 SVG 图,其核心路径为:
- 所有 Pod 不通 → 节点/CNI 全局故障
- 同节点不通 → L2:veth pair、bridge、ARP
- 跨节点 ping 不通 → L3:路由表、overlay 隧道
- 大包不通 → MTU/PMTUD 黑洞(案例三)
- Service ClusterIP 不可达 → L4:conntrack(案例一)、iptables(案例二)
- DNS 解析异常 → CoreDNS/ndots(案例四)、forward 配置(案例十)
- NetworkPolicy 阻断 → CNI 支持/label 匹配(案例八)
- 以上均正常 → 应用层问题:端口、协议、TLS 配置
八、预防体系建设
故障排查的最高境界是在故障发生之前将其消灭。以下是建设网络可观测性和预防体系的核心要素。 ### 监控指标清单
# 节点级别
- { name: conntrack_usage_ratio, expr: "node_nf_conntrack_entries / node_nf_conntrack_entries_limit", threshold: "> 0.8" }
- { name: interface_mtu_mismatch, expr: "比较同集群各节点 overlay MTU", threshold: "不一致" }
# kube-proxy 级别
- { name: kube_proxy_sync_duration, expr: "kubeproxy_sync_proxy_rules_duration_seconds", threshold: "> 5s" }
# CoreDNS 级别
- { name: coredns_request_rate, expr: "rate(coredns_dns_requests_total[5m])", threshold: "> 10000 qps" }
- { name: coredns_error_rate, expr: "rate(coredns_dns_responses_total{rcode='SERVFAIL'}[5m])", threshold: "> 100" }定期巡检脚本
#!/bin/bash
# k8s-network-healthcheck.sh -- 定期巡检集群网络健康状态
echo "=== Conntrack 使用率 ==="
for node in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
count=$(ssh "$node" cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null)
max=$(ssh "$node" cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null)
[ -n "$count" ] && [ -n "$max" ] && \
echo " $node: $count / $max ($(echo "scale=1; $count*100/$max" | bc)%)"
done
echo "=== MTU 一致性 ==="
for node in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
mtu=$(ssh "$node" ip link show cilium_vxlan 2>/dev/null | grep -oP 'mtu \K[0-9]+')
echo " $node: overlay MTU = ${mtu:-N/A}"
done
echo "=== CoreDNS 状态 ==="
kubectl top pods -n kube-system -l k8s-app=kube-dns --no-headers 2>/dev/null
echo "=== NetworkPolicy 统计 ==="
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
count=$(kubectl get networkpolicy -n "$ns" --no-headers 2>/dev/null | wc -l)
[ "$count" -gt 0 ] && echo " $ns: $count policies"
done应急工具 Pod
在每个集群中预部署包含完整网络调试工具的 DaemonSet,确保故障时无需临时安装:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: network-debug
namespace: kube-system
spec:
selector:
matchLabels: { app: network-debug }
template:
metadata:
labels: { app: network-debug }
spec:
hostNetwork: true
hostPID: true
tolerations: [{ operator: Exists }]
containers:
- name: debug
image: nicolaka/netshoot:latest
command: ["sleep", "infinity"]
securityContext: { privileged: true }
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { cpu: 100m, memory: 128Mi }# 使用方式:exec 进入对应节点的 debug Pod,再 nsenter 进入目标 Pod
kubectl exec -it -n kube-system \
$(kubectl get pod -n kube-system -l app=network-debug \
--field-selector spec.nodeName=<target-node> -o name) -- bash
nsenter -t <pid> -n tcpdump -i any -nn九、总结
本文覆盖了 Kubernetes 网络排查中最常见的 10 个故障场景,核心经验可以归纳为:
分层定位:永远从 L2 开始向上排查,不要跳层。同节点不通看 L2/L3,跨节点不通看 L3/MTU,Service 不通看 L4/kube-proxy,域名不通看 DNS,该通不通看 Policy。
工具组合:
nsenter+tcpdump是万能组合。配合conntrack、iptables、ss覆盖 90% 以上的排查场景。Cilium 用户善用hubble observe。预防为主:监控 conntrack 使用率、CoreDNS QPS、kube-proxy 同步延迟这三个核心指标,可以提前发现 80% 的网络故障。
记录沉淀:每次故障排查后记录完整的排查路径和根因,建立团队的故障知识库。下次类似故障出现时,排查时间可以从数小时缩短到分钟级别。
网络故障不可怕,可怕的是没有方法论。掌握分层排查思路,熟练使用排查工具,建立监控预防体系,就能在面对任何网络问题时从容应对。
系列导航:上一篇 多集群与 ClusterMesh | 下一篇 eBPF 网络编程实战