引言
"Service 配好了,为什么调用还是失败?""Pod 明明在 Running,却收不到任何流量!""DNS 解析不了服务名,但 kubectl get svc 显示一切正常!"
如果你在 Kubernetes 中反复遭遇这类"玄学"问题,很可能是因为你只记住了命令,却没看清 K8s 网络与服务发现的底层协作机制。
Kubernetes 的网络模型看似简单------每个 Pod 有独立 IP,Service 提供稳定入口------但在这层抽象之下,CNI、kube-proxy、CoreDNS 三大组件环环相扣,任何一个环节出错,都会导致"不通"。今天这篇文章,就带你彻底搞懂这三者分别负责什么,以及它们是如何配合完成一次完整的访问请求的。
一、CNI(Calico / Flannel)
核心职责:分配 IP 地址,保证基本连通。
Kubernetes 本身并不实现网络,而是依赖 CNI(Container Network Interface)插件来为 Pod 分配 IP 并打通跨节点通信。
你可以这样理解:CNI 就像是给你的电脑插上网线,分配一个固定的内网 IP(比如 172.17.0.3),并且保证你能 ping 通隔壁工位同事的电脑(不同 Node 上的 Pod)。
只要 CNI 正常工作,任意 Pod 都可以直接通过 IP 访问其他 Pod,无需 NAT 或端口映射。跨节点通信是 CNI 的核心能力之一:当 Pod 分布在不同节点时,数据需要跨越物理网络。Flannel 等插件通过 VXLAN 隧道封装数据包,使跨节点流量像在同一局域网内传输;而 Calico 则基于 BGP 路由协议,通过节点路由表直接转发,性能更高。
| CNI 插件 | 工作模式 | 性能 | 网络策略 | 适用场景 |
|---|---|---|---|---|
| Flannel | VXLAN / Host-gw | 中等 | ❌ 不支持 | 快速上手、小规模集群 |
| Calico | BGP / IPIP / eBPF | 高 | ✅ 支持 | 生产环境、需安全隔离 |
CNI 的局限在于:Pod 是"短命"的 ------今天这个 Pod 的 IP 是 172.17.0.3,明天它挂了重启后可能就变成了 172.17.0.5。你不能让前端写死这个 IP,否则一变就挂。
二、kube-proxy
核心职责:提供虚拟 IP(ClusterIP)和负载均衡。
kube-proxy 是 Kubernetes 集群的关键组件,负责 Service 和其后端 Pod 之间的负载均衡转发。每个 Kubernetes 节点上都运行着一个 kube-proxy。
你可以这样理解:kube-proxy 就像公司前台的总机号码(ClusterIP)。你不用记住每个员工的手机号(Pod IP),只需要打总机号码,前台(kube-proxy)会自动帮你转接到当前空闲的对应员工(Pod)上。
具体来说,kube-proxy 做了两件事:
-
给 Service 分配一个永不过期的虚拟 IP(ClusterIP)。
-
当流量访问 ClusterIP 时,劫持该流量,并随机/轮询地转发给后端真正健康的 Pod IP。
kube-proxy 本身不做实际的包转发,而是根据从 API Server 监听到的 Service 与 EndpointSlice 的变化,维护节点上的包处理规则(iptables 或 IPVS 规则),最终由 Linux 内核来完成真实流量的转发。
两种主流模式:
-
iptables 模式(默认) :利用 Linux 内核的 iptables 规则链实现 DNAT(目标地址转换)。规则数量随 Service 数量线性增加,大规模集群下性能下降明显。
-
IPVS 模式(生产推荐) :基于 IP Virtual Server 内核模块,使用哈希表管理后端,负载均衡算法丰富(rr、lc、dh 等),性能 O(1),更适合高并发、大规模场景。
关键点 :kube-proxy 不负责连通性 (连通性是 CNI 干的),它只负责修改 Linux 内核的网络转发规则。
三、CoreDNS
核心职责:域名解析(服务名 → ClusterIP)。
CoreDNS 是 Kubernetes 集群中默认内置的 DNS 服务器,用于提供动态服务发现和域名解析的能力。
你可以这样理解:CoreDNS 就像公司的通讯录/电话黄页 。你只需要喊一句"转接财务部"(DNS 查询),它告诉你财务部总机号码是 10.96.1.2(ClusterIP),然后你再去找 kube-proxy。
CoreDNS 监听 API Server,每当创建 Service,它就将 服务名 → ClusterIP 的映射记录存入 DNS。这样一来,Pod 就可以通过服务名称 (如 mysql.db-namespace.svc.cluster.local)来查找目标,而不是写死 IP 地址。
四、一次完整的访问流程(串联三者)

现在,让我们用一个完整的例子,把三个组件串起来。
假设前端 Pod(A)要访问后端 Pod(B,如 MySQL),完整流程如下:
| 步骤 | 负责组件 | 动作描述 |
|---|---|---|
| ① 服务发现 | CoreDNS | 前端代码写死域名 mysql.db.svc.cluster.local,发起 DNS 查询。CoreDNS 返回 Service 的固定虚拟 IP(ClusterIP) |
| ② 流量拦截 | kube-proxy | 数据包发往 ClusterIP 后,宿主机内核检查到该 IP 是 Service 网段,立即触发 iptables/IPVS 规则。kube-proxy 负责维护这些规则,挑选一个健康的 Pod IP |
| ③ DNAT 转换 | 内核 Netfilter | 内核将数据包的目标 IP 和端口 (ClusterIP:3306)改写 为后端 Pod 的真实 IP(172.17.0.8:3306) |
| ④ 路由转发 | CNI(Calico/Flannel) | 内核根据目标 Pod IP 查询路由表。CNI 提供该路由表(BGP 路由或 VXLAN 隧道信息),决定数据包从哪张网卡发出 |
| ⑤ 响应返回 | 内核 SNAT | 后端 Pod 处理后,响应包返回给前端。内核自动执行 SNAT,将源 IP(Pod IP)还原为 Service 的 ClusterIP,前端 Pod 感受不到后端真实 IP 的变化 |
用一句话概括:
前端代码写域名 → CoreDNS 返回 ClusterIP → kube-proxy 的 iptables/IPVS 规则将 ClusterIP 转为 Pod IP → CNI 负责把包送到目标 Pod。
五、如果少了某一个,会怎样?
-
如果你只有 CNI,没有 kube-proxy:Pod 间能互相 ping 通,但一旦 Pod 重启换 IP,你的程序就挂了,而且无法做多副本的负载均衡。
-
如果你没有 CoreDNS :你得手动在程序里填写 ClusterIP(比如
10.96.100.1)。虽然这个 IP 通常不变,但如果 Service 被删掉重建,IP 变了,程序还得改配置重启,极不方便。 -
如果 CNI 出问题:连最基本的 Pod 间 IP 互通都做不到,上层的一切都无从谈起。
六、选型建议
在实际生产环境中,如何选择这些组件?
-
CNI 选型:
-
Flannel:配置简单,适合学习环境、小规模集群,但不支持 NetworkPolicy。
-
Calico:性能更优,支持细粒度网络策略,适合生产环境。
-
-
kube-proxy 模式:
-
iptables:兼容性好,适合中小规模集群。
-
IPVS:性能更高,适合大规模、高并发场景。
-
-
DNS 优化 :在大规模集群中,可以考虑启用 NodeLocal DNSCache,将 DNS 缓存代理以 DaemonSet 形式跑在每台节点上,缩短 DNS 查询路径,降低 CoreDNS 压力。
总结
用一个形象的比喻来收尾:
-
CNI(Calico/Flannel) 是 "路和车" ------解决"能不能到"(底层物理网络和路由)。
-
kube-proxy 是 "交警和调度员" ------解决"找哪个 IP 去"(虚拟 IP 转换和负载均衡)。
-
CoreDNS 是 "导航地图" ------解决"那个 IP 叫什么名字"(服务发现,方便代码不写死 IP)。
三者配合,Kubernetes 的网络才算真正可用。下次再遇到"网络不通"的问题,按这个思路逐层排查:先看 CNI(Pod IP 能不能通),再看 kube-proxy(Service 能不能通),最后看 CoreDNS(域名能不能解析)。