土法炼钢兴趣小组的算法知识备份

Kubernetes 网络疑难杂症排查手册

目录

集群网络故障是 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 错误    |
+----------+-----------------------------+---------------------------+

排查流程遵循以下步骤:

  1. 确定故障范围:单 Pod、单节点、跨节点、全集群。范围越小,越容易定位。
  2. 确定故障层级:从 L2 开始逐层检查,先排除底层问题再看上层。
  3. 构造最小复现:用 curlpingnslookup 等工具在故障 Pod 内复现,避免在复杂链路上排查。
  4. 对比分析:同节点的其他 Pod 是否正常?同 Service 的其他 endpoint 是否正常?
  5. 抓包定位:当日志和命令行工具无法定位时,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

进入命名空间后,所有网络命令(ipssiptablestcpdump)都在 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 retrans

tcpdump 实战技巧

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                      # 特定 Pod

bpftrace 和 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

预防措施


案例二:Service 访问间歇失败 – kube-proxy iptables 规则未同步

症状

新创建的 Service 在部分节点上访问失败,返回 No route to hostConnection 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 规则。以下情况会导致规则不同步:

  1. kube-proxy 与 apiserver 之间的 watch 连接断开后未及时恢复。
  2. 节点上 iptables 规则数量巨大(数千 Service),单次 iptables-restore 操作耗时过长,期间新变化被积压。
  3. 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 规则

预防措施


案例三:跨节点 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

预防措施


四、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.localapi.example.com.svc.cluster.localapi.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"}
]'

预防措施


案例五: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

预防措施


五、控制面与策略故障

控制面故障通常不影响已有连接,但会导致新连接建立失败、服务发现异常或管理操作超时。

案例六:kubectl exec 超时 – apiserver 到 kubelet 网络不通

症状

kubectl execkubectl logs 命令超时,返回 error: unable to upgrade connectioncontext deadline exceededkubectl 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)。

常见原因包括:

  1. 节点防火墙或安全组未放行 apiserver 到 kubelet 10250 端口的流量。
  2. 在使用私有 API endpoint 的托管集群中,apiserver 所在网络与 worker 节点网络之间的路由不通。
  3. 中间代理(如 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

预防措施


案例七: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。常见瓶颈:

  1. CoreDNS 的 cache 插件默认 TTL 为 30s,在此期间不会刷新。
  2. EndpointSlice controller 在大规模集群中可能存在更新延迟。
  3. 客户端侧的 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

预防措施


案例八:网络 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 不生效的最常见原因有两个:

  1. CNI 不支持 NetworkPolicy:Flannel 只实现了 CNI 网络插件规范,不实现 NetworkPolicy。使用 Flannel 时创建 NetworkPolicy 对象不会报错(apiserver 正常接受),但没有任何组件去执行这些策略。
  2. label 拼写错误或格式不匹配:podSelector 中的 label key 或 value 与 Pod 实际的 label 不匹配。例如 app: nginx vs app: Nginx(大小写敏感),或者 app.kubernetes.io/name: nginx vs app: 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

预防措施


六、启动时序与多集群故障

最后两个案例涉及较为特殊的场景: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 启动的网络就绪流程存在时序依赖:

  1. kubelet 调用 CNI ADD 为 Pod 分配 IP 和配置网络。
  2. CNI 插件执行 IP 分配、veth 创建、路由下发、eBPF 程序加载等操作。
  3. kubelet 启动容器,容器进程开始监听端口。
  4. 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 程序完全加载后才标记网络就绪

预防措施


案例十:多集群 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 的正确配置。常见错误包括:

  1. CoreDNS 没有配置 forward 规则将跨集群域名转发到远端 CoreDNS。
  2. 两个集群使用了相同的 cluster.local 域名,导致域名冲突。
  3. ClusterMesh 的 clustermesh-apiserver 未正确同步远端集群的 Service 和 Endpoint 信息。
  4. 远端 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

预防措施


七、排查决策树

将上述排查方法论和案例经验总结为决策树。完整的图形化决策树参见本文开头的 SVG 图,其核心路径为:

  1. 所有 Pod 不通 → 节点/CNI 全局故障
  2. 同节点不通 → L2:veth pair、bridge、ARP
  3. 跨节点 ping 不通 → L3:路由表、overlay 隧道
  4. 大包不通 → MTU/PMTUD 黑洞(案例三)
  5. Service ClusterIP 不可达 → L4:conntrack(案例一)、iptables(案例二)
  6. DNS 解析异常 → CoreDNS/ndots(案例四)、forward 配置(案例十)
  7. NetworkPolicy 阻断 → CNI 支持/label 匹配(案例八)
  8. 以上均正常 → 应用层问题:端口、协议、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 个故障场景,核心经验可以归纳为:

  1. 分层定位:永远从 L2 开始向上排查,不要跳层。同节点不通看 L2/L3,跨节点不通看 L3/MTU,Service 不通看 L4/kube-proxy,域名不通看 DNS,该通不通看 Policy。

  2. 工具组合nsenter + tcpdump 是万能组合。配合 conntrackiptablesss 覆盖 90% 以上的排查场景。Cilium 用户善用 hubble observe

  3. 预防为主:监控 conntrack 使用率、CoreDNS QPS、kube-proxy 同步延迟这三个核心指标,可以提前发现 80% 的网络故障。

  4. 记录沉淀:每次故障排查后记录完整的排查路径和根因,建立团队的故障知识库。下次类似故障出现时,排查时间可以从数小时缩短到分钟级别。

网络故障不可怕,可怕的是没有方法论。掌握分层排查思路,熟练使用排查工具,建立监控预防体系,就能在面对任何网络问题时从容应对。


系列导航:上一篇 多集群与 ClusterMesh | 下一篇 eBPF 网络编程实战


By .