Kubernetes网络故障分层诊断:从DNS到CNI的实战排查指南 1. 为什么 Kubernetes 网络组件“看不见”却最常出问题在 K8s 集群里你可能花三天时间调通一个 Pod 的 CPU 限制结果它一跑起来就卡在 Pending 状态也可能刚部署完一个 Service前端死活连不上后端curl -v http://svc-name:8080返回Connection refused而kubectl get pods显示所有 Pod 都是 Running。这时候翻日志、查事件、看资源配额全都没报错——问题就藏在那层“看不见”的网络里。这不是玄学而是 Kubernetes 网络模型的天然设计决定的它把网络抽象成“每个 Pod 拥有独立 IP、可被集群内任意节点直接访问”的逻辑平面但这个平面背后是由 CNI 插件、kube-proxy、iptables/ipvs、CoreDNS、Service CIDR、Pod CIDR、节点路由表、主机防火墙、甚至云厂商 VPC 路由规则等至少 7 层组件协同撑起来的。它们不暴露在kubectl get列表里不写进 Deployment YAML也不出现在 Prometheus 的默认指标中。你删掉一个 CoreDNS Pod集群会自动拉起新实例但如果你误删了calico-node的 DaemonSet整个集群的 Pod 间通信会在 90 秒内集体失联——而kubectl get nodes依然显示 Ready。我第一次在生产环境遇到这个问题是在一次 Ubuntu 22.04 升级后。系统内核从 5.15 升到 6.2iptables-nft默认替代了iptables-legacy而当时用的 Calico v3.22 还没完全适配 nftables 后端。现象是新调度的 Pod 可以互相 ping 通但老 Pod 就连不上新 Podtcpdump抓包发现 SYN 包发出去了SYN-ACK 却根本没回来。排查链路花了整整 6 小时先确认 Service 和 Endpoints 正常 → 再验证 kube-proxy 日志无异常 → 接着检查节点路由表发现10.233.64.0/18Calico 的 Pod CIDR路由指向了tunl0设备 → 最后在ip rule show里发现一条被内核升级自动注入的from all lookup local规则把本该走 Calico FIB 的流量劫持到了本地协议栈。这个细节任何“Kubernetes 菜鸟教程”都不会提因为它的触发条件太具体Ubuntu 22.04 内核 6.2 Calico v3.22 iptables-nft 切换。但它真实存在且每天都在不同团队重演。所以“Инспектирование сетевых компонентов Kubernetes”Kubernetes 网络组件检查不是一项可选技能而是 K8s 工程师的生存底线。它不等于“看一眼kubectl get svc”而是要像网络工程师一样分层穿透从 DNS 解析层CoreDNS、服务发现层kube-proxy iptables/ipvs、容器网络层CNI 插件的 dataplane、节点网络层路由表 防火墙、再到底层传输层MTU、TCP MSS、网卡 offload。每层都有其专属的检查工具、关键指标和典型故障模式。接下来我会带你一层一层拆解用实操命令、输出解读和真实踩坑案例把这套检查体系变成你肌肉记忆的一部分。2. DNS 层诊断当nslookup nginx-svc.default.svc.cluster.local返回server cant find...时你该查什么DNS 是 Kubernetes 网络的“门面”90% 的连接失败第一步就卡在这里。但很多人只记得kubectl get pods -n kube-system | grep coredns看到两个 Pod Running 就以为万事大吉。实际上CoreDNS 的健康状态、配置正确性、上游解析能力、以及客户端 Pod 的/etc/resolv.conf配置四者缺一不可。2.1 确认 CoreDNS Pod 状态与资源占用首先别只看Running。执行kubectl get pods -n kube-system -l k8s-appkube-dns -o wide重点看三列READY列必须是1/1如果显示0/1说明容器启动失败立刻kubectl logs -n kube-system coredns-pod-nameRESTARTS列非零值意味着容器反复崩溃常见于内存不足OOMKilled或配置语法错误NODE列确保两个 Pod 分布在不同节点上避免单点故障。接着查资源使用kubectl top pods -n kube-system -l k8s-appkube-dns如果 CPU 持续 800m 或内存 300Mi说明解析压力过大。此时要检查是否启用了autopath插件它会为每个查询生成多个上游请求或是否存在恶意域名爆破如*.random123456.com的泛解析请求。我们曾在一个测试集群发现一个开发误将curl http://api.example.com写成curl http://api.example.com.末尾多了一个点导致 CoreDNS 对api.example.com.进行递归查询而该域名不存在触发了长达 5 秒的超时等待拖垮了整个 DNS QPS。2.2 验证 CoreDNS 配置与插件链CoreDNS 的 ConfigMap 名为coredns位于kube-system命名空间。执行kubectl get configmap coredns -n kube-system -o yaml核心检查点有三个forward插件的上游 DNSforward . 8.8.8.8是常见配置但企业内网应指向内部 DNS 服务器如10.10.10.10。如果上游 DNS 不可用CoreDNS 会缓存 NXDOMAIN 响应 30 秒导致新服务上线后无法解析。kubernetes插件的pods insecure参数默认为insecure允许通过pod-name.namespace.pod.cluster.local解析 Pod IP。如果改为verified则要求 Pod 必须有对应 Endpoint否则返回 NXDOMAIN。这个参数常被误改导致调试时nslookup失败。cache插件的 TTL 设置cache 30表示缓存 30 秒。如果服务频繁扩缩容TTL 过长会导致客户端拿到过期的 Endpoint IP。提示修改 ConfigMap 后CoreDNS Pod 不会自动重启。必须手动滚动更新kubectl rollout restart deployment coredns -n kube-system。这是新手最容易忽略的步骤改完配置却看不到效果就是因为 Pod 还在用旧配置运行。2.3 从客户端 Pod 内部验证 DNS 解析链路这才是最关键的一步。不能只在控制节点上nslookup必须进入目标 Podkubectl exec -it your-pod-name -- sh # 进入后执行 cat /etc/resolv.conf标准输出应类似nameserver 10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local options ndots:5nameserver必须是 ClusterIP10.96.0.10CoreDNS 的 Service IP而不是127.0.0.1或宿主机 IPsearch域必须包含.svc.cluster.local否则curl nginx-svc会尝试解析nginx-svc.default.svc.cluster.local而非nginx-svc.default.svc.cluster.local.注意末尾点ndots:5表示如果域名中点号数量少于 5就依次追加search域进行查询。这就是为什么curl nginx-svc能成功而curl nginx-svc.default会失败它被追加为nginx-svc.default.default.svc.cluster.local。然后执行真实解析测试# 测试集群内服务解析 nslookup nginx-svc.default.svc.cluster.local # 测试外部域名解析验证 upstream nslookup google.com # 测试反向解析验证 PTR 记录 nslookup 10.233.64.10如果第一项失败但第二项成功问题一定在kubernetes插件配置或 Service/Endpoints 对象本身如果第二项也失败则上游 DNS 或 CoreDNS 网络连通性有问题。2.4 实战排错CoreDNS 日志里的“silent fail”有一次用户报告说curl nginx-svc超时但nslookup nginx-svc却能返回正确 IP。这说明 DNS 解析成功但网络层不通。我们进入 CoreDNS Pod 查日志kubectl logs -n kube-system coredns-5d4dd4b4db-abcde | tail -20发现大量类似日志[INFO] 10.233.64.5:42123 - 12345 A IN nginx-svc.default.svc.cluster.local. udp 54 false 512 NOERROR qr,aa,rd 106 0.000123456sNOERROR表示解析成功qr,aa,rd表示是响应、权威、递归。一切正常再看更早的日志[ERROR] plugin/errors: 2 nginx-svc.default.svc.cluster.local. A: read udp 10.233.64.10:53535-10.10.10.10:53: i/o timeout原来CoreDNS 在尝试向上游 DNS 查询nginx-svc.default.svc.cluster.local时超时了但因为它配置了fallthrough就转而查询kubernetes插件最终从本地 Service 对象拿到了 IP。所以nslookup成功了但这个过程耗时 2.3 秒日志里0.002345678s远超客户端curl的默认 30 秒超时。解决方案是在forward插件后添加policy random并增加max_fails 1让 CoreDNS 在上游失败时立即 fallback而不是等待超时。这个案例说明DNS 层的“成功”不等于“低延迟”而高延迟在网络调用链中会被指数级放大。检查 DNS永远要带着time nslookup和tcpdump -i any port 53一起上。3. Service 与 kube-proxy 层为什么kubectl get endpoints显示有 IP但curl还是 Connection refusedService 是 Kubernetes 的服务发现抽象但它的背后是 kube-proxy 这个“流量翻译官”在默默工作。它监听 API Server 的 Service 和 Endpoints 变化然后在每个节点上生成对应的 iptables 或 ipvs 规则把ClusterIP:Port的流量转发到真实的 Pod IP 上。这一层的问题特点是“表象正常实则失效”。3.1 理解 kube-proxy 的两种工作模式iptables vs ipvskube-proxy 有两种主流模式选择直接影响性能和可观测性特性iptables 模式ipvs 模式规则生成方式为每个 Service/Endpoint 生成独立的-A KUBE-SERVICES链创建虚拟服务10.96.0.10:443绑定真实服务器10.233.64.10:443连接跟踪开销高每个连接都需遍历规则链低内核级哈希查找会话保持SessionAffinity仅支持 ClientIP且依赖 conntrack支持多种算法rr, lc, dh, sh故障排查难度极高规则链嵌套深iptables-save输出可达万行中等ipvsadm -Ln输出清晰在 Ubuntu 22.04 安装 Kubernetes 集群时如果你用的是 KubeKey 或 kubeadm默认启用的是 ipvs 模式因为它对大规模集群更友好。但很多老旧文档仍以 iptables 为例导致新手在ipvsadm命令找不到时误以为 kube-proxy 没启动。3.2 验证 kube-proxy 的状态与模式先确认 Pod 状态kubectl get pods -n kube-system -l k8s-appkube-proxy -o wide确保所有节点上都有一个 kube-proxy Pod且READY为1/1。然后查其日志kubectl logs -n kube-system kube-proxy-xyzab | head -10关键线索在启动日志里如果看到Using ipvs Proxier.说明是 ipvs 模式如果看到Using iptables Proxier.则是 iptables 模式如果看到F0712 10:23:45.123456 1 server.go:471] failed to load kernel module ip_vs说明内核未加载 ip_vs 模块需手动执行modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh。3.3 检查 Service 与 Endpoints 的一致性这是最基础也最容易被忽视的检查。执行kubectl get service nginx-svc kubectl get endpoints nginx-svc理想输出NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-svc ClusterIP 10.96.0.100 none 80/TCP 2d NAME ENDPOINTS AGE nginx-svc 10.233.64.10:80,10.233.64.11:80 2d关键点ENDPOINTS列必须有 IP:Port且 IP 必须是 Pod 的真实 IP可通过kubectl get pods -o wide核对如果ENDPOINTS为空说明 Selector 不匹配Service 的selector标签与 Pod 的labels不一致或 Pod 处于 NotReady 状态kubectl get pods中STATUS列不是Running如果ENDPOINTS有 IP但curl仍失败问题一定在 kube-proxy 的规则生成或节点网络上。3.4 直接检查节点上的转发规则ipvs 模式登录到运行目标 Pod 的节点比如10.233.64.10所在节点执行# 查看所有虚拟服务 ipvsadm -Ln # 查看特定 ClusterIP 的规则 ipvsadm -Ln | grep 10.96.0.100标准输出应类似TCP 10.96.0.100:80 rr - 10.233.64.10:80 Masq 1 0 0 - 10.233.64.11:80 Masq 1 0 0rr表示轮询算法Masq表示使用 NAT 模式即修改源 IP 为节点 IP两行-表示两个真实后端且权重Weight列均为 1。如果这里没有-行说明 kube-proxy 没有为该 Service 生成规则原因通常是Service 的clusterIP字段为NoneHeadless Service或 kube-proxy 配置了--proxy-modeuserspace已废弃。3.5 实战排错iptables 模式下的“隐形丢包”在一次 Kubernetes 企业项目实战中客户集群使用 iptables 模式。现象是从节点 Acurl 10.96.0.100:80成功但从节点 B 失败。ipvsadm查不到规则因为是 iptables 模式于是我们导出规则iptables-save /tmp/iptables.rules在/tmp/iptables.rules中搜索10.96.0.100找到-A KUBE-SERVICES -d 10.96.0.100/32 -p tcp -m comment --comment default/nginx-svc: cluster IP -m tcp --dport 80 -j KUBE-SVC-XXXXXX再找KUBE-SVC-XXXXXX链-A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YYYYYY -A KUBE-SVC-XXXXXX -j KUBE-SEP-ZZZZZZ看起来没问题。但继续看KUBE-SEP-YYYYYY-A KUBE-SEP-YYYYYY -s 10.233.64.10/32 -j KUBE-MARK-MASQ -A KUBE-SEP-YYYYYY -p tcp -m tcp -j DNAT --to-destination 10.233.64.10:80问题来了-s 10.233.64.10/32这条规则要求源 IP 必须是10.233.64.10才能匹配。而从节点 B 发起的curl源 IP 是节点 B 的 IP如192.168.1.100根本不会进入这条链真正的规则应该是-m addrtype --src-type LOCAL表示匹配来自本机的流量。这个错误源于一个自定义的kube-proxy配置补丁它错误地将--masquerade-alltrue与--cluster-cidr10.233.0.0/16组合使用导致规则生成逻辑错乱。修复方法是删除错误的补丁重启 kube-proxy并用iptables -t nat -L KUBE-SERVICES -n --line-numbers逐行验证。这个案例揭示了一个核心原则Service 层的检查必须在发起请求的源节点上进行而不是在控制平面节点上。因为 kube-proxy 的规则是 per-node 的。4. CNI 插件层当ping 10.233.64.10通但curl http://10.233.64.10:80不通时你在跟谁打架CNIContainer Network Interface插件是 Kubernetes 网络的“地基”它负责给每个 Pod 分配 IP、设置网络命名空间、配置 veth pair、管理 ARP 表、以及实现跨节点通信。主流插件有 Calico、Cilium、Flannel、Weave。它们的调试方式差异巨大但底层原理相通所有流量都必须经过 CNI 插件创建的虚拟网络设备如caliXXXXX,cni0,lxcXXXXX。4.1 识别当前 CNI 插件与版本首先确定你用的是哪个插件。查看/etc/cni/net.d/目录ls -l /etc/cni/net.d/如果看到10-calico.conflist则是 Calico如果看到05-cilium.conf则是 Cilium如果看到10-flannel.conflist则是 Flannel如果看到10-weave.conf则是 Weave。然后查插件进程# Calico ps aux | grep calico-node # Cilium ps aux | grep cilium-agent # Flannel ps aux | grep flanneld版本信息至关重要。例如Calico v3.25 修复了内核 6.2 下的tunl0设备 MTU 错误而 v3.22 会因此导致大包分片丢失。执行# Calico calicoctl version # Cilium cilium version # Flannel flanneld --version4.2 检查 Pod 网络命名空间与 veth 设备进入一个有问题的 Podkubectl exec -it nginx-pod -- sh执行ip a你会看到1: lo: LOOPBACK,UP,LOWER_UP mtu 65536 qdisc noqueue state UNKNOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 3: eth0if4: BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN mtu 1440 qdisc noqueue state UP link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff inet 10.233.64.10/32 scope global eth0 valid_lft forever preferred_lft forever关键信息eth0if4表示这是一个 veth 设备if4指向宿主机命名空间中的对端索引 4mtu 1440是 Calico 的典型值比标准 1500 小为 VXLAN 头留空间10.233.64.10/32是 Pod IP掩码/32表示这是一个主机路由所有流量都需通过网关即 veth 对端。现在退出 Pod登录到该 Pod 所在的宿主机执行# 查看所有网络命名空间 ls /var/run/netns/ # 找到对应 Pod 的 netns通常以 pod-uuid 命名 ip netns exec pod-netns-name ip a # 或者直接查 veth 对端 ip link | grep -A2 if4:你会看到宿主机上有一个名为caliXXXXX的设备其 MAC 地址与 Pod 内eth0的link/ether一致。这就是 veth pair 的宿主机端。4.3 验证跨节点通信ARP 与路由表Pod 能 ping 通同节点其他 Pod但 ping 不通跨节点 Pod问题大概率出在 CNI 的跨节点隧道或路由上。首先在源 Pod 所在节点查路由表ip route get 10.233.65.20假设目标 Pod IP 是10.233.65.20属于另一个节点的 Pod CIDR正确输出应为10.233.65.20 via 192.168.1.200 dev eth0 src 192.168.1.100 uid 0via 192.168.1.200表示下一跳是目标节点的物理 IPdev eth0表示走eth0网卡src 192.168.1.100是本节点物理 IP。如果输出是10.233.65.20 dev caliXXXXX scope link说明路由认为目标 IP 在本地但实际不在这是典型的Pod CIDR重叠或 CNI 配置错误。然后查 ARP 表ip neigh show | grep 192.168.1.200应该看到类似192.168.1.200 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE如果状态是FAILED或INCOMPLETE说明无法通过 ARP 获取目标节点的 MAC 地址原因可能是目标节点防火墙ufw或iptables阻止了 ARP 请求云厂商安全组未放行 ICMP 和 ARPcalico-node或cilium-agent未运行导致 BGP 或 VXLAN 隧道未建立。4.4 实战排错Calico 的tunl0设备与 MTU 不匹配在 Ubuntu 22.04 安装 Kubernetes 集群时我们使用 Calico v3.22。现象是小包ping -s 100跨节点通大包ping -s 1400不通curl直接超时。tcpdump在源节点抓包发现SYN 包发出大小 1500 字节SYN-ACK 包返回大小 1500 字节但目标 Pod 的tcpdump只收到 SYN没收到 SYN-ACK。这说明中间某个环节进行了分片而接收方无法重组。查tunl0设备ip link show tunl0输出5: tunl0NONE: NOARP,UP,LOWER_UP mtu 1440 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0mtu 1440是正确的。但再查物理网卡eth0ip link show eth0输出2: eth0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ffmtu 1500。问题就在这里VXLAN 封装需要额外 50 字节头UDP VXLAN Ethernet所以tunl0的 MTU 必须比eth0小至少 50。1440 1500 - 60看似合理。但内核 6.2 的ipip模块计算有偏差实际需要1420。解决方案是手动调整tunl0MTUip link set tunl0 mtu 1420并永久生效在 Calico 的InstallationCRD 中添加spec: calicoNetwork: mtu: 1420这个细节任何“Kubernetes 面试”题库都不会考但它决定了你的集群能否承载视频流或大文件上传。5. 节点网络层当curl http://10.233.64.10:80在宿主机上成功但在 Pod 内失败时防火墙在搞鬼最后一层也是最容易被忽略的一层宿主机自身的网络栈。Linux 内核的netfilteriptables/nftables、conntrack、sysctl网络参数、以及云厂商的安全组共同构成了 Pod 流量的“守门人”。这一层的问题特点是“Pod 内部一切正常但流量就是出不去”。5.1 检查 conntrack 表溢出conntrack是内核维护的连接跟踪表记录所有 NAT 连接的状态。当表满时新连接会被丢弃表现为curl随机超时。检查# 查看当前连接数 conntrack -L | wc -l # 查看最大容量 sysctl net.netfilter.nf_conntrack_max # 查看当前使用率 conntrack -S在高并发场景下nf_conntrack_max默认值通常 65536很容易被耗尽。解决方案是# 临时增大 sysctl -w net.netfilter.nf_conntrack_max131072 # 永久生效写入 /etc/sysctl.conf echo net.netfilter.nf_conntrack_max131072 /etc/sysctl.conf sysctl -p注意增大nf_conntrack_max会消耗更多内存每个连接约 300 字节需按节点内存比例设置。5.2 验证 iptables/nftables 规则链Ubuntu 22.04 默认使用nftables后端但很多 Kubernetes 文档仍教iptables命令。执行# 查看当前使用的后端 update-alternatives --display iptables # 如果是 nftables用以下命令查规则 nft list ruleset | grep -A10 kubernetes重点检查FORWARD链它控制节点间流量nft list chain inet filter FORWARD你应该看到类似规则chain FORWARD { type filter hook forward priority filter; policy accept; meta l4proto tcp tcp dport 6443 ct state established,related accept meta l4proto tcp tcp dport 80 ct state established,related accept ... }如果policy是drop且没有明确的accept规则放行 Pod 流量那么所有跨节点通信都会被拦截。修复方法是在nftables配置中添加# 允许来自 Pod CIDR 的流量 ip saddr 10.233.0.0/16 counter accept # 允许转发到 Pod CIDR 的流量 ip daddr 10.233.0.0/16 counter accept5.3 检查 sysctl 网络参数几个关键参数必须开启否则 CNI 插件无法工作# 必须为 1允许 IP 转发 sysctl net.ipv4.ip_forward # 必须为 1允许非本地 IP 的数据包进入 sysctl net.bridge.bridge-nf-call-iptables # 必须为 1允许桥接流量被 iptables 处理 sysctl net.bridge.bridge-nf-call-ip6tables在 Ubuntu 22.04 上bridge-nf-call-iptables默认为0这会导致 Calico 的caliXXX设备流量绕过 iptables从而无法被 kube-proxy 规则捕获。永久修复echo net.bridge.bridge-nf-call-iptables1 /etc/sysctl.conf echo net.bridge.bridge-nf-call-ip6tables1 /etc/sysctl.conf sysctl -p5.4 云厂商安全组与 VPC 路由如果你的集群部署在 AWS、阿里云或腾讯云上安全组Security Group是第一道防线。必须确保所有节点的安全组入站规则放行TCP:6443API Server、TCP:10250kubelet、UDP:8472VXLAN、TCP/UDP:30000-32767NodePortVPC 路由表中Pod CIDR如10.233.0.0/16必须指向集群的主节点或 Transit Gateway。一个经典错误是在创建节点时忘记将Pod CIDR添加到安全组的入站规则中导致calico-node的 BGP 会话无法建立ip route看不到跨节点路由。6. 一套完整的检查清单与自动化脚本把以上所有检查点浓缩成一份可执行的清单并附上一个一键诊断脚本这是 Kubernetes 工程师的终极武器。6.1 手动检查清单按优先级排序步骤检查项命令预期结果失败含义1CoreDNS Pod 状态kubectl get pods -n kube-system -l k8s-appkube-dnsREADY1/1,RESTARTS0CoreDNS 宕机或配置错误2DNS 解析测试kubectl exec pod -- nslookup nginx-svc.default.svc.cluster.local返回10.96.0.100DNS 层故障3Service Endpointskubectl get svc nginx-svc kubectl get endpoints nginx-svcENDPOINTS列有 IPService 选择器不匹配或 Pod NotReady4kube-proxy 模式kubectl logs -n kube-system kube-proxy-xxxhead -5Using ipvs Proxier.或Using iptables Proxier.5节点转发规则ipvsadm -Ln | grep 10.96.0.100(ipvs) 或iptables -t nat -L KUBE-SERVICES -n | grep 10.96.0.100(iptables)有对应规则kube-proxy