想象一下,你正站在一个巨大的集装箱码头。成千上万个集装箱(Pod)堆叠在一起,有些在A区,有些在B区,甚至有的被吊到了完全不同的港口(Node)。更棘手的是,这些集装箱内部并没有固定的地址,它们随时可能更换位置,或者因为故障瞬间消失又重生。如果你需要给某个特定的集装箱寄信,你不能只写“3号集装箱”,因为明天它可能就搬到100号位置去了。
这就是 Kubernetes (K8s) 网络模型每天要面对的残酷现实。很多刚接触 K8s 的开发者,尤其是那些习惯了传统虚拟机或单体应用架构的人,常常会在“为什么 Pod A 连不上 Pod B”、“为什么 Service 突然不可用”这些问题上撞得头破血流。
别担心,今天我们要做的,不是背诵枯燥的 RFC 文档,而是像拆解一台精密的钟表一样,把 K8s 的网络机制一层层剥开。我们会从最底层的 CNI 插件如何搭建桥梁,讲到跨节点的 IP 路由,最后深入 Service 发现的核心——kube-proxy 和 eBPF 的现代演进。我会用大白话配合真实的配置案例,让你不仅知其然,更知其所以然。
一、 基石:CNI 插件与“扁平化”网络的幻象
首先,我们需要打破一个误区:K8s 默认并不提供网络功能。 它是一个调度器,而网络是一个独立的子系统。这个子系统由 CNI (Container Network Interface) 标准来定义。
1.1 什么是 CNI?
CNI 就像是一个标准化的“插头”。无论你的容器运行时是 Docker 还是 containerd,也无论你的网络方案是 Calico、Flannel 还是 Cilium,只要它们实现了 CNI 接口,K8s 就能指挥它们去创建网络环境。
当你创建一个 Pod 时,Kubelet 会调用 CNI 插件。插件的主要任务是做两件事:
- 分配 IP:给这个 Pod 分配一个集群内唯一的 IP 地址。
- 连接网络:在宿主机上创建一个虚拟网卡(veth pair),一端连进 Pod 的网络命名空间,另一端连到宿主机的网桥或隧道接口。
1.2 核心原则:Pod 网络平面
K8s 有一个铁律:每个 Pod 都拥有自己的独立 IP,且所有 Pod 可以通过该 IP 直接互相通信,无需 NAT。
这意味着,在理想的 K8s 网络视图中,整个集群就像一个巨大的局域网。Pod A (10.244.1.5) 可以直接 ping 通 Pod B (10.244.2.8),哪怕它们在物理上相隔千里。
1.3 实战:Flannel vs Calico 的底层逻辑差异
为了理解跨节点通信,我们必须看看两种最常见的 CNI 实现者是如何处理这个“扁平网络”的。
场景 A:Flannel (Overlay 网络)
Flannel 是最简单的方案之一,它使用 VXLAN 技术。
- 原理:Flannel 在每台 Node 上创建一个
flannel.1或vxlan.calico接口。当 Pod A 发送数据包给 Pod B 时,数据包的源 IP 是 Pod A 的 IP,目的 IP 是 Pod B 的 IP。但是,操作系统内核发现目的 IP 不在本地子网,于是查找路由表。Flannel 的路由规则会将发往其他 Node 上 Pod 的流量,封装进 VXLAN 包,通过宿主机的物理网卡发送出去。 - 比喻:这就像是在两个城市之间铺设了一条隐形的魔法管道。你在城市 A 的家里写信(Pod 间通信),信封上写着城市 B 的地址。但这封信必须经过邮局(Host OS),邮局把它装进一个特制的集装箱(VXLAN 封装),通过高速公路(物理网络)运到城市 B,再拆开集装箱,送到收件人手里。
- 优点:配置简单,对底层网络依赖少,适合小型集群或测试环境。
- 缺点:双层封装带来性能损耗,且难以进行细粒度的网络策略控制。
场景 B:Calico (基于 BGP 的原生路由)
Calico 则完全不同,它追求高性能和安全性。
- 原理:Calico 不使用 Overlay 隧道,而是利用 Linux 内核的路由表。它在每台 Node 上配置静态路由,指向其他 Node 上的 Pod CIDR。同时,Calico 节点之间运行 BGP (Border Gateway Protocol) 协议,自动交换路由信息。
- 比喻:这就像是一个高效的快递网络。每个仓库(Node)都知道怎么去其他仓库。货物(数据包)直接在高速公路上奔跑,不需要额外的包装箱。而且,每个仓库门口都有安检员(Network Policy),可以精确检查谁可以进入,谁可以离开。
- 优点:性能极高,支持复杂的网络策略(Network Policies),原生路由无封装开销。
- 缺点:对底层网络有一定要求(如 BGP 邻居建立),配置稍复杂。
代码视角:查看 Flannel 的 VXLAN 接口
如果你在一个运行 Flannel 的节点上执行:
ip addr show flannel.1
你会看到类似这样的输出:
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP
link/ether 0a:58:0a:f4:00:01 brd ff:ff:ff:ff:ff:ff
inet 10.244.0.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::858:aff:fe00:1/64 scope link
valid_lft forever preferred_lft forever
这里 mtu 1450 是关键。因为 VXLAN 封装增加了头部开销,所以 MTU 必须比物理网卡小,否则大包会被丢弃,导致 TCP 连接超时或丢包。这是新手常踩的坑!
二、 跨越鸿沟:跨节点 Pod 通信的路由秘密
现在我们知道单个 Pod 有了 IP,也知道不同 Node 之间如何传输数据了。但还有一个问题:IP 路由。
当 Pod A (Node 1) 想访问 Pod B (Node 2) 时,Node 1 的内核必须知道:“哦,去往 10.244.2.x 的流量,应该发给 Node 2 的物理 IP。”
2.1 路由表的魔法
在 Calico 模式下,你在 Node 1 上执行 ip route,可能会看到:
# 假设 Pod CIDR 是 10.244.0.0/16
# 10.244.1.0/24 是本节点的 Pod 段,直接通过 cni0 网桥可达
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1
# 10.244.2.0/24 是远程节点的 Pod 段,下一跳是 Node 2 的物理 IP
10.244.2.0/24 via 192.168.1.102 dev eth0 proto bird
这里的 via 192.168.1.102 就是关键。它告诉内核:“要把去往 10.244.2.x 的数据包,扔给物理网卡 eth0,并发送给网关 192.168.1.102(即 Node 2 的宿主机 IP)。”
而在 Flannel 模式下,你可能看不到这么细致的 BGP 路由,取而代之的是一个指向 flannel.1 接口的默认路由或者特定子网路由,因为所有的跨节点流量最终都会汇聚到 VXLAN 隧道接口。
2.2 常见问题排查:为什么跨节点 Ping 不通?
如果你发现 Pod A 能 Ping 通同节点的 Pod B,但 Ping 不通跨节点的 Pod C,请按以下步骤检查:
检查安全组/防火墙:
- 如果是 AWS/Aliyun 等云环境,确保 Node 之间的安全组允许所有流量(或者至少允许 VXLAN 端口 8472/UDP 对于 Flannel,以及 BGP 端口 179/TCP 对于 Calico)。
- 注意:很多云厂商默认阻止非标准端口。
检查 MTU:
- 如前所述,如果物理网络 MTU 是 1500,而你的 Pod 发送大包,经过 VXLAN 封装后变成 1500+50=1550,会被路由器丢弃。
- 解决方案:调整 CNI 配置的 MTU。例如在 Flannel 中,修改
/etc/cni/net.d/10-flannel.conflist中的mtu字段为 1450 或更低。
检查路由表:
- 在 Node 上
ip route get <remote-pod-ip>。如果返回unreachable,说明路由缺失。
- 在 Node 上
三、 稳定器:Service 与 kube-proxy 的发现机制
解决了 Pod 之间的通信,我们迎来了 K8s 网络最复杂的部分:Service。
Pod 是短暂的,IP 是变化的。应用程序不能硬编码 Pod IP。我们需要一个稳定的入口点,这就是 Service。Service 提供了一个虚拟 IP (ClusterIP) 和 DNS 名称,将流量转发给后端的 Pods。
3.1 kube-proxy 的三种模式
kube-proxy 是运行在每个 Node 上的守护进程,它的职责是将 Service 的 VIP 映射到后端 Pod 的 IP 列表。它是如何做到的呢?主要有三种模式,代表了 K8s 网络演进的三个历史阶段。
模式一:userspace (已废弃)
这是最早的模式。kube-proxy 会在每个 Node 上监听 Service 的 ClusterIP 和端口。当流量到达时,kube-proxy 进程本身接收数据包,然后随机选择一个后端 Pod,建立代理连接。
- 缺点:性能极差,上下文切换多,CPU 占用高。
- 现状:仅在极其古老的集群中存在,现代 K8s 不再推荐。
模式二:iptables (默认模式,长期主流)
这是目前大多数生产环境使用的模式。kube-proxy 不直接处理流量,而是动态更新内核的 iptables 规则。
工作原理:
- kube-proxy 监控 API Server,获取 Service 和 Endpoint 的变化。
- 它生成 iptables 规则,大致如下:
# 1. 匹配发往 Service ClusterIP 的流量
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "default/kubernetes:https" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
# 2. 负载均衡:随机选择一个后端 Pod
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-1
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-2
-A KUBE-SVC-NPX46M4PTMTKRN6Y -j KUBE-SEP-3
# 3. DNAT:将流量转发到具体的 Pod IP
-A KUBE-SEP-1 -p tcp -m comment --comment "default/kubernetes:https" -j DNAT --to-destination 10.244.1.5:443
- 优点:利用内核 netfilter,性能比 userspace 好得多。
- 缺点:
- 扩展性问题:随着 Service 数量增加,iptables 链会变得非常长。如果集群有成千上万个 Service,iptables 规则的同步延迟会导致流量抖动。
- 缺乏灵活性:只能做简单的轮询或随机负载均衡,无法实现基于 Header 的高级路由。
模式三:IPVS (高性能推荐)
IPVS (IP Virtual Server) 是 Linux 内核中的一个负载均衡模块,类似于 LVS (Linux Virtual Server)。
工作原理:
kube-proxy 使用 libipset 和 libipvs 库,在内核中创建 IPVS 虚拟服务。
# 查看 IPVS 规则
ipvsadm -Ln
输出示例:
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.10:443 rr persistent 10800
-> 10.244.1.5:443 Route 1 0 0
-> 10.244.2.8:443 Route 1 0 0
-> 10.244.3.12:443 Route 1 0 0
- 优点:
- 性能极高:在内核态处理,几乎零拷贝。
- 扩展性好:支持数万条规则,查找效率为 O(1) 或 O(log N),远优于 iptables 的线性查找。
- 算法丰富:支持 RR (轮询), LC (最少连接), DH (目标哈希), SH (源哈希) 等多种算法。
- 配置方法: 在 kube-proxy 的配置 ConfigMap 中设置 mode: ipvs。
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
scheduler: rr # 或者 lc, dh, sh
3.2 CoreDNS:服务的名字解析
Service 有了 ClusterIP,但怎么通过名字访问呢?答案是通过 CoreDNS。
CoreDNS 是 K8s 默认的 DNS 服务器。它运行在一个名为 kube-dns 的 Service 背后。
工作流程:
- 你在 Pod 中发起 HTTP 请求
http://my-service.default.svc.cluster.local。 - 操作系统查询
/etc/resolv.conf,发现 nameserver 是10.96.0.10(kube-dns 的 ClusterIP)。 - 请求发送到 CoreDNS。
- CoreDNS 查询其内部存储的 Endpoints 数据,找到
my-service对应的 Pod IP 列表(或者如果启用了 Headless Service,则返回多个 A 记录)。 - CoreDNS 返回 IP 地址。
- Pod 拿到 IP,通过之前讲的 CNI 网络直接与后端 Pod 通信。
注意:CoreDNS 的缓存机制非常重要。如果后端 Pod 重启,IP 变化,CoreDNS 需要一定时间刷新缓存。在极端情况下,你可能会遇到短暂的服务不可用。可以通过调整 cache 指令来优化。
四、 现代变革:eBPF 与 Cilium 的革命
如果说 iptables/IPVS 是传统网络管理的巅峰,那么 eBPF (Extended Berkeley Packet Filter) 则是颠覆者。
4.1 什么是 eBPF?
eBPF 允许你在内核空间中运行沙箱程序,而无需修改内核源代码或加载内核模块。这些程序可以挂钩到网络栈、系统调用、跟踪点等各个位置。
4.2 Cilium 如何利用 eBPF?
Cilium 是一个基于 eBPF 的 CNI 和网络安全解决方案。它彻底重构了 K8s 的网络数据路径。
对比传统方案:
| 特性 | iptables/IPVS (kube-proxy) | eBPF (Cilium) |
|---|---|---|
| 负载均衡 | 用户态生成规则 -> 内核态执行 | 内核态直接执行 eBPF 程序 |
| 策略执行 | 需要在 iptables 链中插入大量规则,性能随规模下降 | 在数据包进入/离开网络栈时直接检查,O(1) 复杂度 |
| 可观测性 | 黑盒,难以追踪具体包的去向 | 原生集成 Hubble,可视化每一个包的流转 |
| L7 支持 | 不支持 | 支持基于 HTTP/gRPC 的 L7 网络策略 |
eBPF 如何工作?
当数据包到达 Node 的网卡时,Cilium 的 eBPF 程序会拦截它:
- 识别流量:判断这个包是否是 Pod 发出的,还是外部进入的。
- 执行策略:检查是否符合 Network Policy。如果不符,直接丢弃。
- 负载均衡:如果是 Service 流量,直接从 eBPF 映射表中选择后端 Pod IP,并进行 DNAT。
- 转发:将包交给正常的网络栈或直接通过 veth 发给目标 Pod。
整个过程都在内核态完成,速度极快,且没有 kube-proxy 那种用户态与内核态频繁切换的开销。
4.3 实战:部署 Cilium 并验证 eBPF 模式
假设你有一个正在运行的 K8s 集群,你想切换到 Cilium。
安装 Cilium CLI (简化操作):
curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum} sha256sum --check cilium-linux-amd64.tar.gz.sha256sum sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin rm cilium-linux-amd64.tar.gz{,.sha256sum}部署 Cilium:
cilium install启用 eBPF 模式: 默认情况下,Cilium 可能使用 iptables 兼容模式。要完全使用 eBPF,需要在 Helm values 中配置:
kubeProxyReplacement: true验证: 你可以使用
cilium bpf lb list查看 eBPF 负载均衡映射表,或者使用cilium monitor实时观察网络流量和策略命中情况。# 监控流量 cilium monitor --type drop,trace,l7你会看到类似这样的输出,清晰地展示了哪个 Pod 试图访问哪个 Service,以及是否被允许:
Defaulting monitor buffer to 8MB. Listening for events on 2 CPUs with 64x4096 of shared memory Press Ctrl-C to quit 16:52:01.453829: REPLY (4) default/nginx-6d4f9c8b7-x9k2l -> kubernetes (via eth0 <-> eth0 direction=ingress) flow 0x00000000 identity 0->0 state new ifindex 0 orig-nat 0 16:52:01.453850: DROP (Policy denied) default/nginx-6d4f9c8b7-x9k2l -> 10.0.0.5:80 (via eth0 <-> eth0 direction=egress) flow 0x12345678 identity 1234->0 state new ifindex 0 orig-nat 0 policy=deny
五、 高级话题:Network Policies 与微服务隔离
光有连通性是不够的,在微服务架构中,隔离同样重要。这就是 Network Policy 的用武之地。
5.1 默认拒绝一切
传统的网络模型中,除非你配置防火墙,否则内网通常是“允许所有”的。在 K8s 中,如果你不定义 Network Policy,Pod 之间是可以互相访问的(取决于 CNI 插件的默认行为,大多数插件默认允许)。
最佳实践:实施“默认拒绝”策略。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
这个 YAML 文件意味着:在 production 命名空间中,所有 Pod 默认既不能接收外部流量,也不能发起外部流量。
5.2 精确授权
然后,你需要显式地允许必要的通信。例如,允许 Frontend Pod 访问 Backend Pod 的 8080 端口。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
policyTypes:
- Ingress
5.3 调试 Network Policy
如果策略生效了,但服务还是不通,怎么办?
- 检查 CNI 支持:确保你的 CNI 插件(如 Calico, Cilium, Weave)支持 Network Policy。Flannel 默认不支持。
- 使用
calicoctl或cilium policy trace:- Cilium 提供了强大的策略跟踪工具:
cilium policy trace --from pod/frontend-pod --to pod/backend-pod:8080 - 它会告诉你,数据包在经过哪些规则时被允许或被拒绝,以及原因是什么。这对于调试复杂的嵌套策略至关重要。
- Cilium 提供了强大的策略跟踪工具:
六、 总结与未来展望
回顾一下,我们从 CNI 插件的基础设施构建讲起,理解了 Overlay 和 Native Routing 的区别;接着深入内核,分析了 iptables 和 IPVS 作为流量分发器的优劣;最后展望了 eBPF 带来的革命性变化。
K8s 的网络模型之所以复杂,是因为它要在动态性(Pod 随时变)、安全性(微隔离)、性能(低延迟)和易用性(Service 抽象)之间取得平衡。
给开发者的建议:
- 从小处着手:如果是学习或非关键业务,Flannel 足够简单。如果是生产环境,强烈建议使用 Calico 或 Cilium。
- 重视 MTU:这是最容易忽视却最常导致疑难杂症的问题。永远检查物理网络和虚拟网络的 MTU 兼容性。
- 拥抱 eBPF:如果你关注性能和安全性的极致,Cilium + eBPF 是目前 K8s 网络的未来方向。它不仅更快,还提供了前所未有的可观测性。
- 理解 Layer 7:随着 Istio 等 Service Mesh 的普及,网络不再仅仅是 IP 和端口。L7 的策略和遥测将成为常态。
K8s 的网络不是黑魔法,而是一套精心设计的工程体系。当你理解了数据包是如何从 Pod 的 veth 接口出发,穿过内核路由表,经过 iptables 或 eBPF 程序的过滤与转换,最终抵达目标 Pod 的旅程时,你就真正掌握了 K8s 网络的灵魂。希望这篇指南能帮你拨开迷雾,建立起清晰、稳固的网络认知框架。
