咱们今天不聊那些干巴巴的理论定义,直接钻进 Kubernetes 网络那层神秘的“黑盒”里看看。很多刚上手 K8s 的同学,或者甚至是有几年经验的运维老手,在面对 Pod 之间连不上、Service 流量转发不通、或者跨节点延迟忽高忽低的时候,往往是一头雾水。其实,K8s 的网络模型设计得非常优雅,但它的抽象层也掩盖了很多底层细节。如果你能真正理解 Pod、CNI、Service 和 kube-proxy 是怎么协作的,排错就像看透明玻璃一样简单。
一、 核心基石:为什么 Pod 可以像单机一样通信?
在传统的虚拟机时代,每台 VM 都有自己的 IP,不同的宿主机之间通信需要 NAT、路由表甚至复杂的 Overlay 网络。但在 K8s 里,有一个铁律:每个 Pod 都拥有独立的 IP 地址,且所有 Pod 可以在不经过 NAT 的情况下直接相互通信。
这听起来很神奇,对吧?毕竟你的 Pod 可能跑在 Node A 上,而你要访问的 Pod 在 Node B 上。它们是怎么做到“直连”的?这就得提到 CNI (Container Network Interface) 插件了。
1. Linux 网络命名空间 (Network Namespace)
K8s 的核心魔法在于 Linux 内核的一个特性:Network Namespace。你可以把它想象成一个隔离的“网络小房间”。每个 Pod 启动时,K8s 都会为它创建一个独立的 Network Namespace。这意味着:
- Pod 拥有独立的网卡 (
eth0)。 - Pod 拥有独立的 IP 地址。
- Pod 拥有独立的路由表和 iptables 规则。
当你在 Pod 内部执行 ip addr,你看到的只是这个小房间里的东西,完全看不到宿主机的其他网络接口。这种隔离性保证了安全性,但也带来了通信的挑战——不同 Namespace 之间默认是隔离的。
2. veth Pair:连接两个世界的桥梁
为了让 Pod 这个“小房间”连入集群的大网络,CNI 插件(比如 Calico, Flannel, Cilium 等)通常会使用一种叫 veth pair 的技术。
想象一下,veth pair 就像一根虚拟的双向管道。这根管道有两端:
- 一端:放在 Pod 的 Network Namespace 里,通常命名为
eth0。 - 另一端:放在宿主机的 Network Namespace 里,通常命名为
vethxxxxx。
当数据包从 Pod 的 eth0 发出,它会瞬间穿过这根虚拟管道,出现在宿主机的 vethxxxxx 接口上。此时,数据包已经离开了 Pod 的隔离环境,进入了宿主机的物理网络视野。
[ Pod A (Namespace A) ]
| eth0
|
|<--- veth Pair --->|
| veth1234 |
[ Host Node A (Default Namespace) ]
|
| br0 / docker0 / cni0 (Bridge Interface)
|
| [物理网卡 eth1] ---> 交换机 ---> [Host Node B]
3. CNI 插件的角色:路由与覆盖
光有 veth pair 还不够,因为 Node A 不知道 Node B 上的 Pod IP 在哪里。这时候就需要 CNI 插件发挥作用了。目前主流的 CNI 插件主要有两种工作模式:
模式一:扁平网络模式 (如 Flannel UDP/VXLAN)
Flannel 会在每个节点上创建一个隧道接口(如 flannel.1)。当 Node A 上的 Pod 要访问 Node B 上的 Pod 时:
- 数据包从 Pod 出来,到达宿主机的网桥。
- 宿主机发现目标 IP 不在本地子网,于是封装 VXLAN 头。
- 数据包通过物理网卡发送到 Node B。
- Node B 解包,还原原始 IP 包,再传给对应的 Pod。
这种方式实现简单,但性能稍差,因为涉及双层封装和解封装。
模式二:路由模式 (如 Calico BGP)
Calico 则更硬核一点。它利用 BGP 协议,让每个节点上的 calico-node 组件互相交换路由信息。
- 每个 Pod 的 IP 被宣告到全局路由表中。
- 节点之间通过 OSPF/BGP 建立邻居关系。
- 当 Node A 收到发给 Node B 上 Pod 的数据包时,直接查本地路由表,通过物理网卡发送,无需隧道封装(除非跨云或复杂网络环境)。
关键点: 无论哪种模式,对于应用层来说,感知不到底层的差异。你只需要知道:Pod IP 是全局唯一的,且可达的。
二、 Service 负载均衡:从 ClusterIP 到 Endpoints
既然 Pod IP 是动态变化的(重启、扩缩容),我们怎么能稳定地访问服务呢?这就是 Service 登场的时候了。Service 提供了一个稳定的 IP(ClusterIP)和 DNS 名称,作为后端 Pod 的抽象代理。
1. Service 的工作原理
当你创建一个 Service 时,K8s 控制器会做两件事:
- 分配一个 ClusterIP。
- 监听该 IP 上的端口。
- 更新
Endpoints或EndpointSlices对象,记录当前匹配该 Service 标签的所有 Pod IP 列表。
2. kube-proxy:流量的搬运工
谁负责把请求从 ClusterIP 转发到具体的 Pod IP?答案是 kube-proxy。它是运行在每个节点上的守护进程。kube-proxy 主要有三种模式,理解它们的区别对排查问题至关重要。
模式一:userspace 模式 (已废弃)
早期的模式,流量经过用户空间的 iptables 转发,性能极差,现在基本不用了。
模式二:iptables 模式 (默认之一)
这是大多数集群的默认配置。kube-proxy 会定期监控 Service 和 Endpoints 的变化,并动态更新宿主机的 iptables 规则。
当请求到达节点的 ClusterIP 时,iptables 规则会随机(或轮询)选择一个后端 Pod IP,并进行 DNAT(目的地址转换)。
优点:配置简单,兼容性好。 缺点:
- 性能瓶颈:随着 Service 数量增加,iptables 规则数量呈线性增长。当规则超过几万条时,内核查找规则的速度会变慢,导致 CPU 飙升,延迟抖动。
- 同步延迟:kube-proxy 通过 API Server 获取变更,有一定延迟(通常几秒),期间可能出现流量丢失或转发到旧 Pod。
模式三:IPVS 模式 (推荐高性能场景)
IPVS (IP Virtual Server) 是 Linux 内核中实现 L4 负载均衡的技术,类似于 Nginx 的负载均衡功能,但在内核态实现。
kube-proxy 使用 IPVS 时,会将 Service 映射为一个 IPVS 虚拟服务器,将后端 Pod 映射为真实服务器 (Real Server)。
优点:
- 高性能:使用哈希表查找,即使有成千上万个 Service,性能也不会下降。
- 丰富算法:支持 RR (轮询), LC (最少连接), DH (目标哈希), SH (源哈希) 等多种调度算法。
- 低延迟:内核态处理,转发效率极高。
如何开启 IPVS?
在 kube-proxy 的配置 ConfigMap 中设置 mode: "ipvs" 即可。
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs # 关键配置
ipvs:
scheduler: rr # 轮询算法
strictARP: true # 严格 ARP,防止 IPVS 模式下的 ARP 缓存问题
三、 实战:解决跨节点网络延迟
很多同学在多节点集群中发现,同一节点内的 Pod 通信很快,但跨节点的 Pod 通信延迟高,甚至偶尔超时。这通常不是 K8s 本身的 bug,而是底层网络或配置问题。
1. 排查步骤
第一步:确认 CNI 插件类型
首先检查你的集群用的是哪种 CNI。如果是 Flannel 的 UDP 模式,跨节点必然经过 VXLAN 封装,性能不如原生路由。建议切换到 Flannel 的 VXLAN 模式或改用 Calico/Cilium。
# 查看 kube-system 下的 pod,确认 CNI 组件
kubectl get pods -n kube-system | grep -E 'calico|flannel|cilium'
第二步:检查 MTU (最大传输单元)
这是最常见的坑!如果物理网络的 MTU 是 1500,而你的 CNI 插件(如 Calico VXLAN 或 Flannel VXLAN)使用的隧道接口 MTU 也是 1500,那么当数据包加上外层封装头(VXLAN 头 50 字节)后,总大小会超过 1500,导致分片或丢弃。
现象:小包通,大包丢;或者 Ping 通,但传输大文件卡死。
解决方案:
确保所有节点的 CNI 隧道接口 MTU 设置为 物理网卡 MTU - 封装头部大小。
- 对于 VXLAN (50 bytes):
MTU = 1500 - 50 = 1450 - 对于 Geneve (8 bytes):
MTU = 1500 - 8 = 1492
以 Calico 为例,你可以在 DaemonSet 中设置环境变量:
env:
- name: CALICO_IPV4POOL_IPIP
value: "Never" # 如果使用 BGP,禁用 IPIP
- name: CALICO_IPV4POOL_VXLAN
value: "CrossSubnet"
- name: FELIX_MTUIFACE
value: "eth0" # 指定物理网卡
- name: FELIX_MTU
value: "1440" # 根据实际封装调整
第三步:检查 kube-proxy 模式
如果使用的是 iptables 模式,且 Service 数量巨大,检查节点的 iptables -L -n --line-numbers | wc -l。如果规则数超过 5 万,强烈建议切换到 IPVS 模式。
# 检查当前 kube-proxy 模式
kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode
第四步:网络带宽与拥塞
使用 iperf3 测试节点间的物理带宽。
# 在 Pod A 中安装 iperf3
apt-get install iperf3
iperf3 -s
# 在 Pod B 中测试
iperf3 -c <Pod-A-IP>
如果带宽打满,考虑升级网卡或优化应用并发策略。
四、 DNS 解析故障:CoreDNS 的那些事儿
K8s 集群内部的服务发现依赖 DNS。默认情况下,K8s 部署 CoreDNS 作为 DNS 服务器。很多“连不上”的问题,其实是 DNS 解析失败造成的。
1. 常见的 DNS 错误现象
nslookup: can't resolve 'my-service.default.svc.cluster.local'Error: lookup my-service on 10.96.0.10:53: server misbehaving- 应用启动成功,但调用下游服务时报
Connection refused或Timeout。
2. 排查指南
检查 CoreDNS Pod 状态
首先确保 CoreDNS 本身是健康的。
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system <coredns-pod-name>
如果 CoreDNS 崩溃,查看日志是否有 etcd 连接错误或 upstream 转发失败。
检查 resolv.conf
在每个 Pod 内部,查看 /etc/resolv.conf。
cat /etc/resolv.conf
正常的输出应该包含 nameserver 10.96.0.10 (这是 kube-dns 的 ClusterIP),并且 search 域包含 default.svc.cluster.local 等。
如果 nameserver 指向了错误的 IP,或者 options ndots: 5 导致解析路径过长,可能会引发超时。
注意 ndots 参数:
默认 ndots: 5 意味着如果域名中点数少于 5 个(如 my-service),客户端会尝试多个后缀组合:
my-service.default.svc.cluster.localmy-service.svc.cluster.localmy-service.cluster.localmy-service.localmy-service
如果前几个查询超时,才会返回结果。这会导致首次解析变慢。如果业务对延迟敏感,可以将 ndots 改为 1 或 0,但需确保应用中使用 FQDN (Fully Qualified Domain Name) 进行访问。
检查 CoreDNS 配置文件 (Corefile)
进入 CoreDNS Pod 查看配置:
kubectl exec -it -n kube-system <coredns-pod-name> -- cat /etc/coredns/Corefile
常见的配置片段:
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
- forward: 确保正确转发外部 DNS 请求。如果
/etc/resolv.conf中的上游 DNS 不可达,会导致所有非集群内域名解析失败。 - cache: 缓存 TTL 设置是否合理?过短的 TTL 会增加 CoreDNS 负载,过长的 TTL 可能导致服务变更无法及时生效。
模拟解析测试
在任意 Pod 中安装 dnsutils 镜像进行排查:
kubectl run -it --rm dns-test --image=dnsutils --restart=Never -- dig my-service.default.svc.cluster.local
观察返回的 IP 是否正确,以及响应时间。
五、 常见误区与最佳实践
误区 1: “Service 的 ClusterIP 是真实的 IP”
真相:ClusterIP 是一个虚拟 IP,它只在节点的 iptables/IPVS 规则中存在意义。数据包永远不会真正到达这个 IP 地址。它只是一个入口点,用于触发 kube-proxy 的转发逻辑。
误区 2: “Pod 重启后 IP 不变”
真相:Pod 重启后,通常会获得一个新的 IP 地址。这也是为什么必须使用 Service 或 Headless Service + DNS 来访问 Pod,而不是直接硬编码 Pod IP。
误区 3: “NodePort 可以直接暴露给公网”
真相:NodePort 确实会在每个节点上开放一个端口,但它默认只监听在节点的私有 IP 上(取决于 --nodeport-addresses 配置)。如果要暴露给公网,通常需要配合 Ingress Controller 或 LoadBalancer Service,并配置防火墙规则。直接暴露 NodePort 存在安全风险和管理复杂性。
最佳实践 1: 使用 Headless Service 用于有状态应用
对于数据库等有状态应用,不需要负载均衡,但需要稳定的 DNS 记录。可以使用 Headless Service (clusterIP: None)。
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
这样,mysql-headless.default.svc.cluster.local 会解析出所有后端 Pod 的 IP 列表,应用可以自己决定连接哪一个。
最佳实践 2: 启用 IPVS 并优化参数
对于生产环境,强烈建议启用 IPVS 模式,并设置 strictARP: true,以防止 IPVS 模式下的 ARP 缓存不一致导致的流量黑洞。
最佳实践 3: 监控网络指标
使用 Prometheus + Grafana 监控以下指标:
node_ipvs_rules_count: IPVS 规则数量。node_netstat_IpExt_InOctets/OutOctets: 网络吞吐量。container_network_receive_bytes_total: 容器网络接收字节。coredns_dns_request_duration_seconds: CoreDNS 查询延迟。
结语
K8s 的网络模型看似复杂,实则逻辑严密。从 Network Namespace 的隔离,到 veth pair 的连接,再到 CNI 插件的路由,最后通过 kube-proxy 实现 Service 的负载均衡,每一步都有其存在的意义。
当你遇到网络问题时,记住这个排查思路:
- Ping 通吗? 不通 -> 检查 CNI、路由、防火墙。
- DNS 解析吗? 不解析 -> 检查 CoreDNS、resolv.conf、ndots。
- Service 转发吗? 不转发 -> 检查 Endpoints、kube-proxy 模式、iptables/IPVS 规则。
- 延迟高吗? 高 -> 检查 MTU、带宽、kube-proxy 性能。
希望这篇指南能帮你拨开 K8s 网络的迷雾,让你的集群跑得更快、更稳。如果有具体的报错信息,欢迎随时贴出来,我们一起分析!
