在容器化和云原生的世界里,网络是贯穿始终的“隐形”基础设施,却也是最容易引发疑难杂症的领域。Kubernetes 通过 CNI (Container Network Interface) 规范,将网络实现与核心控制平面解耦,催生了百花齐放的网络插件。然而,这种灵活性也给技术选型带来了挑战。本文将面向有经验的工程师,从 Linux 内核的网络虚拟化技术出发,深入剖析 Kubernetes 的“IP-per-Pod”模型,并聚焦于两大主流 CNI 插件——Flannel 和 Calico,从底层原理、实现机制、性能权衡到架构演进,揭示它们截然不同的设计哲学及其在真实工程场景中的适用性。
现象与问题背景
在没有容器编排的时代,我们习惯于基于物理机或虚拟机的 IP 地址和端口进行服务发现与访问控制。然而,Kubernetes 引入了 Pod 这一核心抽象,彻底改变了游戏规则。一个 Pod 可能包含多个紧密协作的容器,它们共享同一个网络栈。Kubernetes 对网络模型提出了几个基本且不容妥协的原则:
- 每个 Pod 拥有一个唯一的 IP 地址:集群内所有 Pod 都在一个扁平的网络空间中,可以直接通过 IP 地址相互通信,无需网络地址转换(NAT)。
- 节点上的 Pod 可以与该节点上的其他 Pod 通信:这是最基本的要求。
- 节点上的 Pod 可以与其他节点上的 Pod 通信:这是跨主机容器通信的核心难题。
- Pod 与 Service/Node 之间的通信:同样需要无缝衔接。
这套“IP-per-Pod”模型极大地简化了应用开发者和服务发现机制的心智负担,应用可以像在传统虚拟机环境中一样,认为自己拥有一个“真实”的 IP。但这个简化的上层模型,将复杂性完全推给了底层的网络实现。核心问题随之而来:当一个位于 Node A 上的 Pod(例如 IP 为 10.244.1.5)试图访问一个位于 Node B 上的 Pod(例如 IP 为 10.244.2.10)时,数据包是如何跨越物理主机的边界,并被正确路由和转发的?这正是 CNI 插件需要回答的核心问题,也是 Flannel 和 Calico 给出截然不同答案的地方。
关键原理拆解
要理解 CNI 插件的工作方式,我们必须回到 Linux 内核提供的底层能力。这些是构建容器网络虚拟化的基石,无论上层如何封装,都无法绕开它们。作为架构师,理解这些第一性原理至关重要。
-
网络命名空间 (Network Namespace)
这是 Linux 内核实现网络协议栈隔离的核心机制。每个网络命名空间都拥有一套独立的网络资源,包括网络接口(`lo`, `eth0` 等)、路由表、iptables 规则、ARP 表等。Kubernetes 中的每个 Pod 都被分配了一个独立的网络命名空间。当我们 `kubectl exec` 进入一个 Pod 时,我们所看到的网络环境,实际上就是这个独立的命名空间。这种隔离性是 Pod 拥有独立 IP 地址的前提。
-
虚拟以太网设备对 (veth pair)
Veth pair 是一项内核技术,可以理解为一根“虚拟网线”,它总是成对出现,一端连接一个网络命名空间,另一端连接另一个。当数据包从一端进入,它会原封不动地从另一端出来。在 Kubernetes 中,典型的做法是在 Pod 的网络命名空间内创建一端(通常命名为 `eth0`),另一端则留在主机的根网络命名空间中(通常有一个随机的名称,如 `vethxxxx`)。这根“虚拟网线”成功地将 Pod 的孤立世界与主机连接了起来。
-
网桥 (Linux Bridge)
Linux Bridge 是一个工作在数据链路层(L2)的虚拟交换机。它可以连接多个网络接口。在主机上,所有来自 Pod 的 `veth` 对的“主机端”都会被连接到同一个网桥上(例如 `cbr0`)。如此一来,同一节点上的所有 Pod 就被连接到了同一个虚拟交换机上。它们之间的通信就如同连接在同一物理交换机上的几台机器一样,通过 L2 寻址即可完成,无需经过主机更复杂的路由。这解决了“同一节点 Pod 间通信”的问题。
-
路由 (Routing) 与 IP 转发
当数据包的目的 IP 地址不在当前节点的网桥所连接的任何 Pod 中时,就需要路由。这就是跨节点通信的起点。主机的内核必须启用 IP 转发(`net.ipv4.ip_forward = 1`),使其能像路由器一样转发数据包。此时,核心挑战变成了:主机 A 的内核如何知道目标 Pod IP(例如 10.244.2.10)位于主机 B 上? 它需要一条路由规则来指导数据包的下一跳。Flannel 和 Calico 的根本区别,就在于它们如何创建和维护这些关键的路由规则。
系统架构总览
基于上述内核原理,CNI 插件的工作流程大体一致。当 kubelet 在一个节点上创建 Pod 时,它会调用 CNI 插件的二进制文件,并传入 `ADD` 命令以及 Pod 的网络命名空间等信息。CNI 插件随即执行以下操作:
- 为 Pod 创建网络命名空间(如果尚未创建)。
- 创建一对 veth pair,一端放入 Pod 的命名空间并命名为 `eth0`,另一端留在主机根命名空间。
- 为 Pod 的 `eth0` 分配一个从该节点被授予的 Pod CIDR 子网中获取的 IP 地址。
- 将 veth pair 在主机侧的一端连接到网桥 `cbr0`。
- (核心差异点)通过某种机制,确保发往其他节点 Pod IP 的流量能够被正确地转发出去。
数据包的旅程(从 Pod A on Node A 到 Pod B on Node B)如下:
Pod A (eth0) -> veth pair -> cbr0 bridge (on Node A) -> Node A's routing table -> [CNI 魔法发生区] -> Node A's physical NIC (eth0) -> Physical Network -> Node B's physical NIC (eth0) -> Node B's routing table -> cbr0 bridge (on Node B) -> veth pair -> Pod B (eth0)
Flannel 和 Calico 的所有设计和实现,都聚焦于上述流程中的“CNI 魔法发生区”,即如何解决跨节点的路由发现和数据包转发问题。
核心模块设计与实现
现在,让我们像一个极客工程师一样,深入剖析 Flannel 和 Calico 的内部机制,看看它们是如何实现各自的“魔法”的。
Flannel: Overlay Network 的简单主义者
Flannel 的设计哲学是简单、普适。它不关心底层物理网络是怎样的,只要主机之间能够通过 IP 互通即可。它通过创建一个“覆盖网络”(Overlay Network)来实现这一点,将 Pod 的数据包封装在主机网络的数据包中进行传输。最常用的后端是 VXLAN。
VXLAN (Virtual Extensible LAN) 模式工作原理:
1. 子网分配:每个节点上的 `flanneld` 守护进程会从 etcd 或 Kubernetes API 中获取整个集群的 Pod CIDR,并为自己所在的节点申请一个唯一的子网(例如,Node A 获得 `10.244.1.0/24`,Node B 获得 `10.244.2.0/24`)。
2. 虚拟网络设备:`flanneld` 会在每个主机上创建一个名为 `flannel.1` 的 VTEP (VXLAN Tunnel End Point) 设备。这是一个虚拟设备,负责 VXLAN 报文的封装和解封装。
3. 路由建立:`flanneld` 会在主机上添加路由规则。例如,在 Node A 上,会有一条规则,指示所有发往其他节点 Pod 子网(如 `10.244.2.0/24`)的流量,都应该通过 `flannel.1` 设备发送。
4. 数据包封装与转发:当 Pod A (`10.244.1.5`) 向 Pod B (`10.244.2.10`) 发送数据包时,该数据包到达 Node A 的 `cbr0` 网桥后,根据路由表被转发到 `flannel.1` 设备。VXLAN 驱动会接管这个数据包,将其作为 payload,并封装在一个新的 UDP 包里。这个新的 UDP 包的源 IP是 Node A 的物理 IP,目的 IP 是 Node B 的物理 IP。然后,这个被封装的包通过 Node A 的物理网卡发送出去。
5. 数据包解封装:Node B 的物理网卡收到这个 UDP 包后,内核识别出其目标端口是 VXLAN 端口,于是交给 `flannel.1` 设备处理。`flannel.1` 将外层的 UDP 和 IP 头部剥离,还原出原始的 Pod A 到 Pod B 的数据包,然后将其转发到 Node B 的 `cbr0` 网桥,最终送达 Pod B。
极客视角下的验证:
在 Flannel 集群的节点上,你可以用 `ip route` 命令看到这样的路由:
$ ip route show
...
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
...
这清楚地表明,去往其他节点的 Pod 子网的流量,下一跳是 `flannel.1` 设备。这种封装机制的代价是额外的网络头(VXLAN+UDP+IP 大约 50 字节),这会略微降低网络吞吐量,并可能引发 MTU (Maximum Transmission Unit) 问题,这是 Flannel 在生产环境中一个经典的坑点,必须仔细配置网络接口的 MTU 以避免数据包被分片或丢弃。
Calico: Underlay Network 的性能猛兽
Calico 的设计哲学是性能、策略、可扩展。它完全抛弃了 Overlay 封装,而是将每个节点都变成一个虚拟路由器(vRouter),通过标准的 BGP (Border Gateway Protocol) 协议来宣告和学习 Pod IP 的路由信息,构建一个纯三层的网络。
BGP 模式工作原理:
1. 节点间 BGP Peering:每个节点上运行一个名为 `bird` 的 BGP 客户端。默认情况下,集群中所有节点会建立一个 full-mesh(全网状)的 BGP peer 连接。这意味着每个节点都会和其他所有节点直接交换路由信息。
2. 路由宣告:当一个 Pod 在 Node A 上被创建并分配了 IP(例如 `10.244.1.5`)后,Node A 上的 Calico Agent (`felix`) 会在内核中创建一条指向这个 Pod 的精确路由。同时,BGP 客户端 `bird` 会通过 BGP 协议向所有其他节点宣告:“要访问 `10.244.1.5/32`,下一跳是我(Node A 的 IP 地址)”。
3. 路由学习与写入:Node B 的 `bird` 客户端收到这个宣告后,就会在 Node B 的内核路由表中添加一条规则:`10.244.1.5/32 via
4. 直接转发:现在,当 Node B 上的 Pod 要访问 `10.244.1.5` 时,数据包到达 Node B 的内核。内核查询路由表,发现匹配了这条新规则,于是直接将数据包(源 IP 是 Pod B 的 IP,目的 IP 是 Pod A 的 IP,没有任何封装!)通过物理网卡 `eth0` 发送给 Node A。
5. 网络策略:Calico 的另一大杀器是其强大的网络策略实现。`felix` 代理会监听 Kubernetes 的 NetworkPolicy 对象,并将其高效地转换为 `iptables` 或 `eBPF` 规则,在数据包路径的关键节点(如 veth 接口)上实施精细的访问控制。由于是纯三层网络,这些策略的实施非常直接和高效。
极客视角下的验证:
在 Calico 集群的节点上,`ip route` 的输出会截然不同:
$ ip route show
...
10.244.1.5 dev cali1a2b3c4d scope link
10.244.2.10 via 192.168.1.102 dev eth0 proto bird
...
注意 `proto bird`,这明确表示这条路由是由 BGP 客户端 `bird` 学习并注入内核的。`via 192.168.1.102` 指明了下一跳就是目标 Pod 所在节点的物理 IP。这种方式没有封装开销,性能接近物理网络,但在网络环境受限(如公有云的 VPC 不支持 BGP)或需要跨 L3 网络时,会需要 IPIP 或 VXLAN 封装模式作为备选。
性能优化与高可用设计 (Trade-off 分析)
选择 Flannel 还是 Calico,本质上是在简单性、普适性与性能、功能性之间做权衡。
-
吞吐量与延迟
Calico (BGP) 胜出。由于没有数据包封装和解封装的 CPU 开销,其吞吐量更高,网络延迟也更低。这对于网络密集型应用,如数据库、消息队列、实时音视频流、金融交易系统等至关重要。Flannel (VXLAN) 的封装会消耗 CPU 周期,并因为额外的包头占用带宽。
-
网络策略
Calico 绝对胜出。Calico 的网络策略是其核心卖点,功能丰富、实现高效。它支持更复杂的规则,如基于服务账户、端口范围、协议等。而 Flannel 本身不提供网络策略执行引擎,需要与其他组件(如 Calico for Policy)结合使用,即 Canal 项目。
-
部署复杂性与环境兼容性
Flannel 胜出。Flannel 的 VXLAN 模式几乎可以在任何二层网络环境中“开箱即用”,对底层网络没有特殊要求。Calico 的 BGP 模式在某些公有云环境中可能无法直接使用(因为云厂商限制了 BGP 协议),此时需要降级到 IPIP 或 VXLAN 模式,这会使其性能优势减弱。此外,BGP 协议本身的配置和排错对网络工程师的技能要求更高。
-
规模扩展性
Calico (BGP) 在大规模集群中更具优势,但有其复杂性。Flannel 的 VXLAN 模式依赖于一个集中式的存储(etcd/k8s API)来同步节点信息,当节点规模巨大时,可能会有性能瓶颈。Calico 的 BGP full-mesh 模式在节点数 N 增大时,会产生 N*(N-1)/2 个连接,同样会带来巨大的开销。但在大规模部署中,可以通过配置 BGP Route Reflector (路由反射器)来解决这个问题,将网络拓扑从 full-mesh 变为星型,大大减少 BGP 连接数,这是标准的电信级网络扩展方案。
架构演进与落地路径
一个务实的技术团队应该如何选择和演进其 Kubernetes 网络方案?
-
起步阶段 (1-50节点,开发/测试环境)
推荐 Flannel。在这个阶段,业务快速迭代是主要矛盾,基础设施的稳定性、简单性和快速部署能力是首要考虑的。Flannel 提供的网络功能已经足够,其性能开销对于大多数应用来说可以忽略不计。选择 Flannel 可以让团队将精力聚焦于业务逻辑,而不是复杂的网络问题排查。
-
成长阶段 (50-200节点,生产环境)
评估转向 Calico 或 Canal。随着业务上生产,对安全隔离和网络性能的要求开始提高。如果你的环境支持 BGP(例如自建数据中心),并且团队有能力运维,那么直接迁移到 Calico (BGP) 模式是最佳选择,可以一步到位获得高性能和强策略。如果环境不支持 BGP,或者希望平滑过渡,可以考虑 Canal (Flannel+Calico policy) 或 Calico 的 IPIP/VXLAN 模式。这样可以在不改变底层数据平面的情况下,获得 Calico 强大的网络策略能力。
-
成熟阶段 (200+节点,大规模或混合云环境)
深度定制或拥抱 Calico BGP + ToR。在超大规模集群中,Calico 的 BGP 方案配合 Route Reflector 几乎是唯一的选择。更进一步,可以配置 Calico 与物理网络的 Top-of-Rack (ToR) 交换机建立 BGP 对等关系,这样 Pod 的 IP 路由就可以直接扩散到物理网络中,实现 Pod 与物理机/虚拟机的无缝、高性能通信。这对于从传统架构向云原生迁移的混合云场景非常有价值。
总而言之,Flannel 和 Calico 代表了两种截然不同的 CNI 实现哲学。Flannel 是网络虚拟化的“封装派”,追求简单和兼容;而 Calico 则是“路由派”,追求极致的性能和原生体验。没有绝对的优劣,只有是否适合你的场景、团队和业务发展阶段。理解它们在 Linux 内核层面的根源,才能在面对复杂的网络问题时游刃有余,做出最明智的架构决策。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。