Kubernetes 基础体系 · 第 35/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 网络排障:DNS、Service、CNI、MTU、Conntrack 和抓包
Kubernetes 网络故障经常表现为同一句话:“Pod 访问不了某个地址”。但“某个地址”可能是 DNS 名称、Service ClusterIP、Pod IP、NodePort、外部 IP 或入口负载均衡器;对应的数据路径、负责组件和故障证据完全不同。
一次有效的排障,首先要回答四个问题:
- 名称是否解析成功?
- 目标 IP 是否可达?
- 中间是否发生了 Service 转发、SNAT 或 DNAT?
- 连接失败发生在应用、内核、CNI、节点网络还是外部网络?
可以把常见路径抽象为:
flowchart LR
A[应用进程] --> B[Pod 网络命名空间]
B --> C[Pod veth]
C --> D[CNI 数据路径]
D --> E[节点内核路由与防火墙]
E --> F[kube-proxy Service 转发]
F --> G[目标 Pod 或外部地址]
A --> H[CoreDNS Service]
H --> I[CoreDNS Pod]
I --> J[集群 DNS / 上游 DNS]
DNS 负责把名称转换成地址,但不负责建立连接;Service 负责把虚拟地址转换到后端端点,但不一定负责跨节点传输;CNI 负责 Pod 网络接入和 Pod 间路径;MTU 决定单个报文能否无分片通过;conntrack 保存连接状态并参与 NAT;抓包则用于验证每一跳实际发生了什么。
一、先建立网络对象与数据路径模型
1. Pod IP、Service IP 和 Node IP 不是同一类地址
Kubernetes 中常见的三类地址如下:
| 地址 | 所属对象 | 典型用途 |
|---|---|---|
| Pod IP | Pod | Pod 之间直接通信 |
| ClusterIP | Service | 集群内部访问 Service |
| Node IP | Node | 节点管理、NodePort、节点间网络 |
| LoadBalancer IP | 云厂商或外部负载均衡器 | 集群外部访问 |
| ExternalName 对应名称 | Service | DNS 别名,不提供四层代理 |
Pod IP 通常来自集群 Pod CIDR 或插件管理的地址池。Service 的 ClusterIP 来自 Service CIDR,通常不是某个网卡上真实配置的地址,而是由 kube-proxy 等组件实现虚拟转发。
因此:
访问 Pod IP:
客户端 -> 路由/CNI -> 目标 Pod
访问 ClusterIP:
客户端 -> Service 规则 -> 某个 EndpointSlice 中的 Pod
访问 NodePort:
客户端 -> 节点地址和端口 -> kube-proxy 规则 -> Service 后端
不能因为 ping ClusterIP 失败,就直接推断 Service 不可用。Service 的主要语义是传输层转发,ICMP 是否被实现或转发并不是诊断 HTTP/TCP 服务的可靠方法。应使用与真实业务相同的 TCP 或 UDP 协议测试。
2. Service 的后端来自 EndpointSlice,而不是 Service 本身
Service 通过选择器关联 Pod。控制器根据匹配结果生成 EndpointSlice,kube-proxy 再观察 Service 和 EndpointSlice,编程本节点的数据平面。
查看关系:
kubectl get svc web -n demo -o wide
kubectl get endpointslice \
-n demo \
-l kubernetes.io/service-name=web \
-o wide
典型输出会包含:
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
web-abc123 IPv4 8080 10.244.2.17 3m
这里至少要检查:
- Service 的
port:客户端访问的 Service 端口; - Service 的
targetPort:后端 Pod 监听的端口; - EndpointSlice 中的端点地址;
- 端点是否为 ready;
- Service 是否为
ClusterIP: None的 Headless Service; - Service 的协议是 TCP、UDP 还是 SCTP。
例如:
kubectl get svc web -n demo \
-o jsonpath='{.spec.clusterIP}{" "}{.spec.ports[*].port}{" -> "}{.spec.ports[*].targetPort}{"\n"}'
kubectl get endpointslice -n demo \
-l kubernetes.io/service-name=web \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses}{" ready="}{.conditions.ready}{"\n"}{end}'
如果 EndpointSlice 没有端点,问题通常发生在 Service 选择器、Pod 标签、端口声明或 readiness 状态,而不是 iptables、IPVS 或 CNI。此时继续查看节点 NAT 规则通常是错误方向。
3. Service 转发不等于应用监听
假设:
apiVersion: v1
kind: Service
metadata:
name: web
namespace: demo
spec:
selector:
app: web
ports:
- port: 80
targetPort: 8080
客户端访问:
web.demo.svc.cluster.local:80
经过 DNS 后得到 ClusterIP,例如 10.96.12.34。kube-proxy 选择端点 10.244.2.17:8080。这里有两个不同的正确性条件:
Service 正确性:
ClusterIP:80 -> 10.244.2.17:8080
应用正确性:
10.244.2.17:8080 上确实有进程监听,并且返回协议正确
只要 targetPort 配置错误,Service 规则即使完全正常,连接仍会得到 Connection refused 或超时。
在后端 Pod 内检查监听:
kubectl exec -n demo deploy/web -- ss -lntp
如果容器镜像没有 ss,可以使用调试容器或在节点上检查网络命名空间;不要把“镜像缺少诊断工具”误判为“服务没有监听”。
二、DNS:先区分解析失败与连接失败
1. Pod 中的 DNS 配置从哪里来
普通 Pod 通常会获得类似配置:
nameserver 10.96.0.10
search demo.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
这些字段的含义是:
nameserver:Pod 应向哪个 DNS 地址发送查询;search:短名称失败或满足搜索规则时,客户端尝试追加的域名后缀;ndots:名称中点号少于该值时,解析器通常优先尝试 search 列表。
检查实际配置:
kubectl exec -n demo deploy/web -- cat /etc/resolv.conf
10.96.0.10 只是示例,实际地址应以集群中的 DNS Service 为准:
kubectl get svc -n kube-system
kubectl get svc -n kube-system kube-dns -o wide
Kubernetes 使用名为 kube-dns 的 Service 作为集群 DNS 的常见约定,即使实际后端组件是 CoreDNS。不要仅根据 Service 名称推断后端实现。
2. Service DNS 名称的构成
对于命名空间 demo 中名为 web 的普通 Service,常见名称为:
web
web.demo
web.demo.svc
web.demo.svc.cluster.local
完整形式通常是:
<service>.<namespace>.svc.<cluster-domain>
其中 cluster-domain 通常是 cluster.local,但可以在集群配置中修改。
在另一个命名空间中解析 web,短名称可能会先尝试当前命名空间的 web.demo.svc.cluster.local,而不是目标命名空间。排障时应优先使用完整名称:
kubectl run dns-test \
-n demo \
--rm -it \
--restart=Never \
--image=busybox:1.36 \
-- nslookup web.demo.svc.cluster.local
预期结果应包含 ClusterIP。这个命令要求节点能拉取镜像,并且集群允许创建临时 Pod;生产环境不应把任意公共镜像直接用于敏感网络区域。
3. ndots 可能制造额外查询和延迟
如果 ndots:5,应用查询:
api.example.com
由于其中只有两个点,某些 libc 或语言运行时会先尝试:
api.example.com.demo.svc.cluster.local
api.example.com.svc.cluster.local
api.example.com.cluster.local
api.example.com
这会导致:
- DNS 查询数量增加;
- 外部名称解析延迟增加;
- 上游 DNS 或 CoreDNS 日志出现许多看似“错误”的搜索域查询;
- 某些严格限流的 DNS 服务更容易被触发。
这不是 Kubernetes DNS 必然故障,而是解析器、search 和 ndots 共同产生的行为。使用绝对域名时可写成:
api.example.com.
末尾的点表示 DNS 绝对名称,避免搜索域追加;但应用是否保留并正确处理末尾点,仍取决于客户端库。
4. DNS 排障要逐层验证
建议按以下顺序验证:
# 1. DNS Service 是否存在
kubectl get svc -n kube-system kube-dns -o wide
# 2. CoreDNS Pod 是否运行
kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide
# 3. CoreDNS 后端是否有端点
kubectl get endpointslice -n kube-system \
-l kubernetes.io/service-name=kube-dns -o wide
# 4. Pod 是否使用预期 nameserver
kubectl exec -n demo deploy/web -- cat /etc/resolv.conf
# 5. 从业务网络命名空间实际查询
kubectl run dns-test -n demo --rm -it --restart=Never \
--image=busybox:1.36 \
-- nslookup kubernetes.default.svc.cluster.local
若第 5 步超时,再从 CoreDNS 日志判断请求是否到达:
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100
日志中没有请求,可能是 Pod 到 DNS Service 的路径、Service 转发、NetworkPolicy 或 DNS 客户端配置问题;有请求但没有响应,则继续检查 CoreDNS 到上游 DNS 的路径、CoreDNS 配置和上游服务。
CoreDNS 的常见配置来源是 ConfigMap:
kubectl get configmap coredns -n kube-system -o yaml
但具体 Corefile、插件和日志格式可能因发行版或云厂商而不同。不能假设所有集群都启用了相同的缓存、转发、DNSSEC 或可观测性配置。
5. 普通 Service、Headless Service 和 ExternalName 的差异
普通 Service 通常返回 ClusterIP:
web.demo.svc.cluster.local -> 10.96.12.34
Headless Service 设置:
spec:
clusterIP: None
此时 DNS 通常直接返回后端 Pod 地址,而不是虚拟 IP。客户端可能获得多个 A 或 AAAA 记录,并自行选择端点。于是:
- 客户端是否轮询由 DNS 库和应用决定;
- 连接是否经过 kube-proxy 取决于客户端访问的地址;
- 后端 Pod 地址变化会直接影响客户端缓存和连接复用。
ExternalName 则主要返回 CNAME,不创建通常意义上的 ClusterIP 和后端转发规则。对它执行“检查 EndpointSlice、检查 kube-proxy NAT”通常没有意义。
三、CNI:Pod 网络是怎样接入节点的
1. CNI 的职责边界
CNI 是容器网络接口规范及其插件生态。Kubernetes 通过运行时调用 CNI 插件,为 Pod 网络命名空间配置:
- Pod 网卡;
- Pod IP;
- 默认路由;
- 与节点网络命名空间连接的 veth 或其他设备;
- 跨节点所需的路由、隧道、桥接、封装或主机网络配置。
Kubernetes API 并不规定某一种 CNI 实现。Calico、Cilium、Flannel、云厂商 VPC CNI 等插件在地址分配、路由、封装、NetworkPolicy 和可观测性方面差异很大。
因此,下面这个抽象路径是常见模型,不是所有插件的硬性实现:
Pod eth0
-> veth pair
-> 节点 bridge / eBPF hook / host device
-> 节点路由或隧道设备
-> 目标节点
-> 目标 Pod
2. Pod 创建时的状态变化
Pod 网络初始化通常可理解为以下过程:
- kubelet 创建 Pod 沙箱;
- 容器运行时创建网络命名空间;
- 运行时调用 CNI 主插件;
- CNI 调用 IPAM 分配地址;
- CNI 将网卡、地址和路由写入 Pod 网络命名空间;
- CNI 或其控制器配置节点间路径;
- 容器启动并使用该网络命名空间。
如果 CNI ADD 阶段失败,Pod 可能长期处于 ContainerCreating,事件中出现 FailedCreatePodSandBox。此时故障尚未进入 DNS 或 Service 层:
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
节点侧还要检查 CNI DaemonSet、插件日志和 /etc/cni/net.d/、/opt/cni/bin/ 等节点文件。但这些路径属于常见部署约定,不是 Kubernetes API 保证的固定路径。
3. 先测试 Pod IP,再测试 Service
假设:
客户端 Pod: 10.244.1.10
目标 Pod: 10.244.2.17
Service: 10.96.12.34:80
应至少做两次测试:
# 访问后端 Pod 的真实端口
kubectl exec -n demo deploy/client -- \
wget -S -O- -T 3 http://10.244.2.17:8080
# 访问 Service 端口
kubectl exec -n demo deploy/client -- \
wget -S -O- -T 3 http://10.96.12.34:80
结果解释:
| Pod IP | Service | 常见推断 |
|---|---|---|
| 成功 | 成功 | 基础路径和 Service 大致正常 |
| 失败 | 失败 | CNI、路由、NetworkPolicy、目标进程或跨节点路径优先 |
| 成功 | 失败 | Service 选择器、EndpointSlice、kube-proxy、端口映射或 Service 策略 |
| 失败 | 成功 | 不常见,可能是 Service 后端选择了其他可用端点,或测试地址不等价 |
测试必须考虑目标 Pod 是否与客户端同节点。跨节点失败而同节点成功,通常指向节点间路由、Overlay、Underlay、MTU 或跨节点策略;同节点也失败,则先检查 Pod 接口、默认路由、目标监听和本地策略。
4. Overlay 与 Underlay
Underlay 是底层承载网络,例如节点网卡之间的 VPC、物理网络或云网络。
Overlay 是在 Underlay 之上封装的逻辑网络,例如 VXLAN、Geneve、IP-in-IP 或 WireGuard 隧道。
Overlay 报文大致是:
外层以太网/IP/UDP/隧道头
+ 内层原始 Pod 报文
节点间网络可能只看到外层源、目的地址;目标 Pod 看到的是解封装后的内层地址。抓包位置不同,看到的五元组也不同。
跨节点 Pod 通信可能有三类实现:
- 纯路由:Underlay 直接知道 Pod CIDR 路由;
- Overlay:节点之间发送封装报文;
- VPC 原生地址:Pod 地址直接由底层网络承载,可能没有传统隧道。
不能看到 vxlan.calico、flannel.1 或类似设备,就断言所有集群都应有 Overlay 设备。设备名和路径由 CNI 决定。
四、MTU:为什么小请求成功,大响应却失败
1. MTU 的定义和条件
MTU(Maximum Transmission Unit)是某个链路一次能够承载的最大三层 IP 载荷大小,通常指 IP 包总长度上限,不包括外层以太网帧头。
若底层链路 MTU 为 ,封装额外开销为 ,则内层路径可用 MTU 近似为:
其中:
- :Pod 内层报文允许的最大 IP 包长度;
- :Underlay 路径上的最小 MTU;
- :Overlay 外层 IP、UDP、隧道等头部开销。
“最小”很重要:路径上任一设备 MTU 更小,整个路径的有效 MTU 就由它决定。
2. 完整算例:1500 字节底层网络和 VXLAN
假设:
Underlay MTU = 1500
外层 IPv4 头 = 20
UDP 头 = 8
VXLAN 头 = 8
外层以太网相关开销不计入 IP MTU
则:
但实际插件通常还会为其他封装细节、实现差异或保守余量选择 1450 左右,而不是机械地只减去一个固定数字。
如果 Pod 使用 TCP/IPv4,TCP 最大报文段长度可近似为:
当 Pod MTU 为 1450、无 TCP options 的基本头部长度为 20 字节时:
实际 TCP options 会影响有效空间,Linux 也可能通过 MSS clamping 调整 SYN 中的 MSS。
3. 典型 MTU 故障路径
当报文超过路径 MTU 时,可能发生:
- 发送端尝试发送大 IP 包;
- 中间设备无法转发;
- IPv4 设备可能分片或返回 ICMP Fragmentation Needed;
- IPv6 设备依赖路径 MTU 发现,由源端根据 ICMPv6 Packet Too Big 调整;
- 如果 ICMP 被防火墙丢弃,发送端可能持续使用过大的报文;
- 小报文和 TCP 三次握手成功,但大响应、TLS 握手或文件传输卡住。
因此,“TCP 端口能连通”不能证明 MTU 正常。
检查节点和 Pod 的 MTU:
# Pod 内
kubectl exec -n demo deploy/client -- ip link show eth0
# 节点上
ip link show
ip route
在 Linux 上进行不分片探测时,具体参数要区分 IPv4 和 IPv6:
# IPv4 示例:请求总 IP 包不超过 1400 字节
ping -M do -s 1372 -c 3 <目标IP>
# IPv6 示例:payload 1400 字节,不能使用 IPv4 的 -M 参数
ping6 -s 1400 -c 3 <目标IPv6>
IPv4 中 ping -s 通常指定 ICMP payload,因此总 IP 长度约为 payload + 20 + 8;上例 1372 加上 IPv4 和 ICMP 头正好约 1400。不同 ping 实现参数略有差异,必须先查看本机 ping --help。
4. 为什么 Ping 结果不能单独证明 MTU
ICMP Echo 可能:
- 被 NetworkPolicy 或节点防火墙阻止;
- 被云网络安全策略限制;
- 被目标 Pod 或节点忽略;
- 走与 TCP 不同的路径;
- 没有触发与业务报文相同的封装和策略。
更接近业务的验证方式是:
# 在客户端 Pod 中测试 TCP
kubectl exec -n demo deploy/client -- \
curl -v --connect-timeout 3 --max-time 10 http://<service-name>:<port>/large-response
同时在客户端 Pod、节点隧道设备和目标 Pod 处抓包,观察大报文是否发出、是否收到 ICMP、是否在某一跳消失。
五、Conntrack:NAT 和连接状态为什么会影响 Service
1. Conntrack 保存什么
Linux conntrack 是内核连接跟踪子系统。它根据报文的协议和五元组等信息,为连接建立状态,例如:
NEW -> ESTABLISHED -> FIN_WAIT/TIME_WAIT
对 TCP,状态会结合 SYN、ACK、FIN、RST 等标志变化;对 UDP,内核只能根据报文方向和超时近似跟踪“连接”,因为 UDP 本身无握手和关闭过程。
conntrack 通常还保存 NAT 映射。例如客户端访问:
10.244.1.10:43000 -> 10.96.12.34:80
Service 规则可能把它转换为:
10.244.1.10:43000 -> 10.244.2.17:8080
返回包需要沿相反方向恢复地址,使客户端仍认为自己在与 10.96.12.34:80 通信。后续同一连接的报文通常依赖已建立的 conntrack/NAT 状态,而不是每次重新随机选择后端。
2. Service 规则和 conntrack 的关系
kube-proxy 可能使用 iptables、IPVS 或 nftables 等数据平面模式,具体可用模式与版本、发行版和配置有关。无论实现细节如何,排障时都应区分:
- 规则是否存在:Service 和 EndpointSlice 是否被同步;
- 报文是否命中规则:计数器或内核观测是否增加;
- 连接状态是否正确:conntrack 是否建立、是否冲突或耗尽;
- 后端是否返回:目标 Pod 是否收到并响应。
在节点上可查看 conntrack:
sudo conntrack -L
sudo conntrack -S
常见统计字段可能包括:
- 当前表项数量;
- insert failed;
- drop;
- early drop;
- error。
查看当前限制:
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
如果 count 接近 max,新连接可能被丢弃,表现为间歇性超时。扩大表容量并不是自动修复:它会增加内核内存消耗,且不能解决应用未关闭连接、UDP 超时不合理或异常流量持续增长的问题。
3. “清空 conntrack”是有破坏性的操作
下面命令会影响节点上的大量连接:
sudo conntrack -F
它可能导致:
- 现有 TCP 连接中断;
- NAT 映射丢失;
- 长连接和数据库连接重建;
- 大规模重试形成新的连接洪峰;
- 节点上其他业务同时受影响。
生产环境不应把它作为第一反应。更安全的做法是先记录故障时间、五元组和节点范围,再针对特定流删除;但不同 conntrack 工具版本的过滤参数存在差异,执行前必须查看:
conntrack --help
同时需要注意,删除 conntrack 表项只能改变内核状态,不能修复 EndpointSlice 错误、后端不监听、CNI 丢包或外部防火墙问题。
4. Conntrack 常见故障表现
表耗尽
表现可能是:
- 新连接间歇性超时;
- 节点上的多个无关 Service 同时异常;
- 内核日志出现 conntrack 相关丢包;
- 已建立连接比新连接稳定。
验证应结合 nf_conntrack_count、nf_conntrack_max、内核日志和抓包,而不是只观察应用错误率。
非对称路由
假设请求从节点 A 进入,返回却经节点 B 离开,两个节点上的 conntrack 可能分别只看到半条流。对于依赖状态的防火墙和 NAT,这可能造成:
ESTABLISHED判断失败;- 回包被丢弃;
- Service 访问超时;
- 只在跨节点或特定方向发生故障。
抓包应同时记录进入、离开节点和 Pod 的路径,比较源目的地址、接口和方向。
长连接与后端变化
Service 选择后端通常发生在连接建立或首次处理阶段;已建立 TCP 连接不会因为 EndpointSlice 后来移除某个端点而自动迁移到另一个 Pod。Pod 滚动发布期间,旧连接可能由应用、连接池或代理负责关闭和重建。不能期待 kube-proxy 把一个已建立的 TCP 流“搬运”到新 Pod。
六、抓包:把“推断”变成路径证据
1. 抓包位置决定你能看到什么
常见抓包点和用途如下:
| 位置 | 能观察到的内容 |
|---|---|
Pod eth0 |
Pod 看到的内层源/目的地址 |
| 节点 veth | Pod 与节点之间的报文 |
| 节点物理网卡 | Underlay 上的报文,可能是封装后的外层地址 |
| Overlay 设备 | 封装或解封装相关报文 |
目标 Pod eth0 |
目标 Pod 实际收到的报文 |
| CoreDNS Pod | DNS 请求是否到达 DNS 后端 |
只在客户端 Pod 中抓包,无法证明报文到达了目标节点;只在物理网卡中抓包,也可能看不到解封装后的 Pod 内层流量。
2. 在 Pod 网络命名空间中抓包
如果镜像包含 tcpdump:
kubectl exec -n demo deploy/client -- \
tcpdump -ni eth0 -vv 'host 10.96.12.34 and tcp port 80'
但业务镜像通常没有抓包工具,也不应为了排障长期修改生产镜像。可使用临时调试容器:
kubectl debug -n demo pod/<pod-name> \
-it \
--image=nicolaka/netshoot \
--target=<container-name> \
-- bash
这依赖集群支持 ephemeral containers,且运行时、权限、镜像来源和安全策略可能限制其使用。--target 使调试容器尝试进入目标容器的进程命名空间;网络命名空间共享方式和运行时实现应在目标集群中验证。
如果需要进入节点网络命名空间,常见方式是:
kubectl debug node/<node-name> -it \
--image=nicolaka/netshoot \
--privileged \
-- bash
进入后,节点根文件系统通常挂载在 /host,但具体调试 Pod 模板和权限策略可能不同。也可以在节点上直接执行:
sudo tcpdump -ni any -vv \
'host 10.244.2.17 or host 10.96.12.34'
-i any 方便初步定位,但会合并多个接口,可能重复看到同一报文,也不适合精确判断某个链路上的单次转发。确认范围后,应改用具体接口:
sudo tcpdump -ni eth0 -s 0 -w /tmp/service.pcap \
'tcp port 80'
-s 0 尽量保留完整报文,-w 写入 pcap 文件,便于 Wireshark 或 tshark 后续分析。抓包本身有 CPU、磁盘和隐私风险,过滤条件应尽可能具体。
3. 使用五元组和 TCP 标志判断故障位置
TCP 五元组为:
协议、源 IP、源端口、目的 IP、目的端口
在 Service NAT 前后,IP 和端口可能改变。因此要根据观察位置判断预期:
客户端 Pod 看到:
10.244.1.10:43000 -> 10.96.12.34:80
目标 Pod 看到的可能是:
10.244.1.10:43000 -> 10.244.2.17:8080
如果客户端发送 SYN,但目标 Pod 完全没有收到:
- 可能是 Service 规则、节点路由、CNI、NetworkPolicy 或中间防火墙;
- 也可能是抓包点选错,目标端点并不是你检查的那个 Pod。
如果目标 Pod收到 SYN并回复 SYN-ACK,但客户端收不到:
- 优先检查返回路径;
- 检查 SNAT、非对称路由、节点防火墙和 conntrack;
- 不要只检查入口方向。
如果目标 Pod收到 SYN,却立即返回 RST:
- 通常说明该地址端口没有监听,或应用主动拒绝;
- 这与“网络完全不通”的超时不同。
如果三次握手成功,随后大报文重传:
- 检查 MTU、MSS、PMTUD 和中间设备;
- 检查应用是否在等待 DNS、TLS 或上游响应;
- 不要把所有重传都归因于 conntrack。
4. DNS 抓包示例
在 DNS 测试 Pod 或 CoreDNS Pod 中:
tcpdump -ni any -vv \
'udp port 53 or tcp port 53'
DNS 通常优先使用 UDP;响应过大、截断或客户端策略变化时可能转 TCP。只抓 UDP 53 会漏掉 TCP DNS 流量。
观察顺序:
客户端是否发送查询
-> 查询目的地址是否为预期 DNS Service
-> DNS Service 是否转发到 CoreDNS Pod
-> CoreDNS 是否向上游发送查询
-> CoreDNS 是否返回客户端
如果客户端发往 ClusterIP,而 CoreDNS Pod看不到请求,不代表 Service 一定没有转发:抓包接口、kube-proxy 模式、节点本地转发路径都可能影响可见性。需要结合 EndpointSlice、节点规则和客户端/节点两侧抓包。
七、kube-proxy:iptables、IPVS 与 nftables 只解决数据平面的一部分
1. iptables 模式
在 iptables 模式中,kube-proxy 为 Service 和端点创建一组规则。规则可能涉及:
- ClusterIP;
- NodePort;
- ExternalIP;
- DNAT;
- SNAT;
- 会话亲和性;
- 端点选择;
- 健康检查相关路径。
具体链名和规则细节属于实现行为,不应编写依赖某一个版本的固定链名排障脚本。查看规则时:
sudo iptables-save -t nat
sudo iptables -t nat -L -n -v
如果系统使用 nftables 后端兼容 iptables 命令,还要确认查看的命令是否对应实际规则集:
sudo nft list ruleset
iptables-legacy、iptables-nft 和底层 nftables 的组合受发行版配置影响;看到某个命令没有规则,不足以证明 kube-proxy 没有编程规则。
2. IPVS 模式
IPVS 在内核中提供虚拟服务器和后端调度。常见检查命令是:
sudo ipvsadm -Ln
可以观察虚拟服务、后端 Real Server 以及连接计数。IPVS 仍可能依赖 iptables 处理部分流量、SNAT 或其他辅助逻辑,因此不能仅凭 ipvsadm 输出就认为整个路径不受 iptables 影响。
IPVS 的调度器、连接状态和会话保持也会影响“同一个客户端是否总是命中同一个后端”。故障时应将实际连接五元组、后端 Pod 和会话亲和性配置对应起来。
3. nftables 模式
较新的 Kubernetes 版本和 kube-proxy 可提供 nftables 数据平面模式,但可用性取决于 Kubernetes 版本、kube-proxy 配置、内核和发行版。检查时:
sudo nft list ruleset
kubectl -n kube-system get configmap kube-proxy -o yaml
不能因为节点安装了 nft 命令,就断言 kube-proxy 正在使用 nftables 模式;应查看 kube-proxy 实际配置和日志。
三种模式的共同排障原则是:
Service/EndpointSlice 正确
-> kube-proxy 已同步
-> 规则或虚拟服务存在
-> 报文命中
-> 后端 Pod 收到
-> 返回路径正确
只验证“规则存在”不等于验证“报文走过规则”。iptables 的计数器、IPVS 连接统计、nftables 计数器或 eBPF 观测工具可以提供命中证据,但计数器读取也可能受规则刷新和实现差异影响。
八、一个完整的排障流程
假设业务报告:
client Pod 访问 http://web.demo.svc.cluster.local/large
偶发超时;直接访问某个 Pod IP 有时正常
第一步:确认名称解析
kubectl exec -n demo deploy/client -- \
getent hosts web.demo.svc.cluster.local
若镜像没有 getent:
kubectl run dns-test -n demo --rm -it --restart=Never \
--image=busybox:1.36 \
-- nslookup web.demo.svc.cluster.local
需要记录:
- 返回的是 ClusterIP 还是 Pod IP;
- 是否同时返回 IPv4 和 IPv6;
- 是否存在超时、SERVFAIL 或 NXDOMAIN;
- DNS 查询是否受 search/ndots 影响。
第二步:确认 Service 和端点
kubectl get svc web -n demo -o yaml
kubectl get endpointslice -n demo \
-l kubernetes.io/service-name=web -o yaml
核对:
客户端使用的端口 = Service.spec.ports[].port
Service 转发端口 = targetPort 解析后的端口
实际后端地址 = EndpointSlice 中的 addresses
后端状态 = conditions.ready
如果端点为空,先修复标签或 readiness,不要继续修改节点 NAT。
第三步:分别测试 Pod IP 和 Service IP
kubectl exec -n demo deploy/client -- \
curl -v --connect-timeout 3 --max-time 10 \
http://10.244.2.17:8080/large
kubectl exec -n demo deploy/client -- \
curl -v --connect-timeout 3 --max-time 10 \
http://10.96.12.34:80/large
真实环境中应替换为实际地址,并确保后端端口和 HTTP 协议正确。若业务是 TLS、gRPC 或 UDP,应使用对应协议测试,而不是用 HTTP 工具替代。
第四步:按节点位置缩小 CNI 范围
kubectl get pod -n demo -o wide
kubectl get pod -n demo -l app=client -o wide
比较:
- 同节点访问;
- 跨节点访问;
- 单个目标 Pod;
- 多个后端 Pod。
如果只有某一节点上的 Pod 失败,应检查该节点的 CNI agent、路由、隧道设备、内核日志和 MTU。若所有节点都失败,应优先看 Service、策略、端点或统一配置。
第五步:检查策略和节点状态
NetworkPolicy 可能允许 DNS,但禁止业务端口;也可能允许到 ClusterIP,却因实现方式对 Pod IP 的规则不同而表现不同。检查:
kubectl get networkpolicy -A
kubectl describe networkpolicy -n demo
NetworkPolicy 的实际执行由 CNI 插件负责,Kubernetes API 只定义对象语义;插件支持范围、对 HostNetwork、Named Port、DNS 名称和集群外流量的处理可能不同。
节点状态与事件:
kubectl get nodes -o wide
kubectl describe node <node-name>
kubectl get pods -n kube-system -o wide
特别关注:
- CNI DaemonSet 是否在异常节点运行;
- kube-proxy 是否正常;
- CoreDNS 是否集中在受影响节点;
- 节点是否出现 NotReady、网络设备重建或内核错误。
第六步:抓取双向证据
在客户端 Pod、客户端节点和目标 Pod/目标节点分别抓包,至少比较:
客户端是否发送 SYN
节点是否看到 SYN
目标 Pod 是否收到 SYN
目标 Pod 是否返回 SYN-ACK
客户端是否收到 SYN-ACK
大报文是否重传
是否出现 ICMP Fragmentation Needed 或 ICMPv6 Packet Too Big
一次抓包只能说明一个观察点发生了什么;只有把多个观察点按时间和五元组关联,才能定位“报文消失”的边界。
九、按现象反推故障层
1. NXDOMAIN
通常表示名称不存在或查询的名称不对,例如:
- Service 名称拼写错误;
- 命名空间错误;
- 使用短名称访问了其他命名空间;
- 查询的是尚未创建的 Service;
- Headless Service 的预期记录与实际状态不符。
先执行:
kubectl get svc -A | grep -w web
不要先重启 CoreDNS。NXDOMAIN 与“DNS 超时”是不同证据:前者通常有明确的否定响应,后者可能是路径或服务不可达。
2. DNS SERVFAIL 或超时
可能来自:
- Pod 到 DNS Service 不通;
- CoreDNS Pod 无端点或崩溃;
- CoreDNS 无法访问上游;
- UDP 被丢弃而 TCP fallback 也失败;
- DNS 响应被策略或防火墙阻断;
- 大 DNS 响应遇到 MTU 或分片问题。
需要同时检查 Pod 内 resolv.conf、CoreDNS 日志、EndpointSlice 和 53 端口抓包。
3. Connection refused
通常表示 TCP RST 到达客户端,常见原因是目标端口没有监听或应用主动拒绝。应核对:
kubectl exec -n demo <pod> -- ss -lnt
以及 Service 的 targetPort。这与静默超时的处理路径不同。
4. 连接超时
超时可能是:
- SYN 被丢弃;
- 返回包被丢弃;
- CNI 或路由故障;
- NetworkPolicy 或节点防火墙;
- conntrack 表耗尽;
- MTU/PMTUD 问题;
- 外部负载均衡器或云安全组。
“超时”本身定位信息很少,必须通过抓包判断请求包和响应包分别到达了哪里。
5. 小响应成功,大响应卡住
优先检查:
- Pod MTU 与节点/隧道 MTU;
- TCP MSS;
- IPv4 分片;
- IPv6 Packet Too Big;
- ICMP 是否被错误阻断;
- Overlay 外层路径是否存在更小 MTU;
- TLS 握手或应用响应是否跨越了触发阈值。
不要只用 ping 得出结论,也不要一看到重传就修改 conntrack。
6. 只有长连接或特定客户端失败
应检查:
- Service
sessionAffinity; - IPVS 或其他实现的连接状态;
- conntrack 超时;
- NAT 映射;
- 客户端连接池;
- 后端滚动发布期间的连接排空;
- 非对称路由。
同一个 Service 的新连接和已有连接可能走不同的状态路径,因此“重试后成功”不能证明网络已经恢复。
十、常见误区与生产边界
1. 把 DNS 当成网络连通性测试
DNS 解析成功只说明某个 DNS 服务器返回了记录,不说明:
- ClusterIP 可达;
- Endpoint Pod 可达;
- 目标端口监听;
- HTTP/TLS 协议正常。
反过来,业务使用 IP 访问成功,也不能证明业务使用的 DNS 名称正确,因为这绕过了名称解析和搜索域行为。
2. 直接修改 Service ClusterIP 或 Pod IP
ClusterIP 和 Pod IP 通常由控制面和网络插件管理。手动在节点上加路由、加 NAT 规则或修改 Pod 地址,可能只修复一个节点上的临时现象,并在 kube-proxy、CNI 或 Pod 重建后消失。应先修复声明式对象、插件状态或底层网络配置。
3. 随意重启 CoreDNS、kube-proxy 或 CNI
重启可能暂时清除缓存、重新编程规则或重建隧道,但同时会:
- 造成 DNS 短暂不可用;
- 触发连接重建;
- 改变节点上的 NAT/conntrack 状态;
- 掩盖可复现证据。
生产环境应先保留:
kubectl get svc,endpointslice,pod -A -o yaml
kubectl get events -A --sort-by=.lastTimestamp
kubectl logs -n kube-system <相关Pod>
再根据变更窗口和影响范围执行恢复动作。
4. 把实现行为当成 Kubernetes API 保证
以下内容经常被误认为固定规范:
- iptables 的链名和规则排列;
- IPVS 一定存在;
- 节点一定有 VXLAN 设备;
- DNS Service 一定使用某个固定 ClusterIP;
- 所有 CNI 都支持相同的 NetworkPolicy 语义;
- 所有 Service 流量都经过相同的 SNAT 路径。
这些都可能随 Kubernetes 版本、kube-proxy 模式、CNI、Linux 内核、发行版和云厂商网络实现变化。排障命令应先确认实际组件和模式,再解释输出。
十一、把一次故障记录成可验证的证据链
一份有价值的网络故障记录,不应只有“访问失败”,而应至少包含:
时间窗口:
客户端 Pod、节点:
目标名称、解析结果:
目标 Service、ClusterIP、端口:
EndpointSlice 中的实际端点:
Pod IP 直连结果:
Service IP 访问结果:
同节点/跨节点差异:
Pod 与节点 MTU:
kube-proxy 模式:
conntrack count/max 和异常统计:
客户端、节点、目标端抓包:
DNS、CNI、kube-proxy 和内核日志:
采取的恢复动作及其副作用:
例如,下面这条结论才具备可验证性:
客户端 Pod 10.244.1.10 能解析 web.demo.svc.cluster.local,
得到 ClusterIP 10.96.12.34;直接访问后端
10.244.2.17:8080 成功,但访问 ClusterIP:80 超时。
EndpointSlice 有两个 ready 端点,kube-proxy 规则存在。
客户端抓包有 SYN,节点 eth0 能看到请求,目标 Pod eth0
没有看到 SYN。故障边界位于目标节点到 Pod 的转发路径,
优先检查该节点 CNI 路由、NetworkPolicy 和 Service 规则命中,
而不是检查应用监听。
这类描述把 DNS、Service、CNI、MTU、conntrack 和抓包放回同一条数据流中:先确定名称对应的地址,再验证 Service 后端,再比较真实路径,最后用不同网络命名空间和节点接口的报文证据确定丢失位置。只有完成这条链路,才有足够依据选择修改 Service、修复 CNI、调整 MTU、处理 conntrack,或转向外部负载均衡和底层网络。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 证书与 TLS:集群 PKI、cert-manager、轮换和故障
- 下一篇:Kubernetes PV 与 PVC:绑定、访问模式、回收策略和生命周期
- 延伸:kube-proxy 与服务转发:iptables、IPVS、nftables、Session 和诊断
- 延伸:Kubernetes CNI 网络:Pod IP、路由、Overlay、Underlay 和插件选型
- 延伸:Kubernetes DNS:CoreDNS、Service Discovery、Search、缓存和故障
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论