Kubernetes网络模型深度剖析:从CNI到Calico与Flannel的底层对决

对于许多开发者和运维工程师而言,Kubernetes 网络是一个既关键又神秘的“黑箱”。我们知道 Pod 可以跨节点通信,Service 提供了稳定的访问入口,但其底层的运作原理却常常被一层抽象所掩盖。本文旨在撕开这层抽象,从 Linux 内核的网络命名空间和 CNI 规范出发,深入剖析两种业界最主流的网络插件——Flannel 和 Calico 的架构设计与实现差异。我们将不止于功能对比,而是下探到数据包的完整生命周期,分析它们在性能、灵活性和网络环境依赖上的核心权衡,为技术选型和故障排查提供第一性原理层面的支撑。

现象与问题背景

在一个典型的 Kubernetes 集群中,我们面临着几个最基础的网络问题:

  • Pod IP 地址从何而来? 每个 Pod 启动时都会被分配一个集群内唯一的 IP 地址,这个地址由谁管理和分配?
  • 同节点 Pod 如何通信? 在同一个 Node 上的两个 Pod,它们之间的数据包是如何流转的?
  • 跨节点 Pod 如何通信? 当 Node A 上的 Pod 要访问 Node B 上的 Pod 时,网络是如何打通的?这通常是网络方案选型的核心。
  • Service 是如何工作的? `ClusterIP` 作为一个虚拟 IP,是如何被解析并负载均衡到后端真实 Pod IP 的?

Kubernetes 本身并没有直接实现这些功能,而是提出了一套网络模型(Network Model)的基本原则,并在此之上定义了 CNI(Container Network Interface)规范。这个模型的核心要求是:

  • 所有 Pod 之间可以直接通信,无需 NAT。 每个 Pod 都有一个独立的 IP,这个 IP 在整个集群中是可路由的。
  • 所有 Node 之间可以直接与所有 Pod 通信,无需 NAT。
  • Pod 看到的自身 IP 地址与其它 Pod 或 Node 看到的地址是完全一样的。

这个模型构建了一个扁平化的、IP-per-Pod 的网络空间。而 CNI 规范则将实现这一模型的具体任务,交给了各种第三方网络插件。当我们执行 `kubectl apply` 创建一个 Pod 时,`kubelet` 会调用容器运行时(如 containerd),运行时则根据 CNI 配置,调用相应的网络插件(如 Calico 或 Flannel 的二进制文件),来完成 Pod 的网络初始化。因此,理解这些插件的底层机制,是掌握 Kubernetes 网络的关键。

关键原理拆解:网络命名空间、veth 与 CNI

在深入插件细节之前,我们必须回到计算机科学的基础——Linux 内核提供的能力。Kubernetes 的 Pod 网络隔离与通信,完全构建在内核的几个核心功能之上。

第一性原理:网络命名空间 (Network Namespace)

这可以被看作是操作系统内核层面提供的网络虚拟化技术。每个网络命名空间都拥有一套独立的网络协议栈,包括自己的网络接口(interfaces)、路由表(routing tables)、防火墙规则(iptables/nftables)和网络套接字(sockets)。当我们创建一个 Pod,Kubernetes 实质上是为这个 Pod 创建了一个独立的网络命名空间。这就像在一栋大楼里为每个租户分配一个独立的信箱和门禁系统,实现了网络层面的“多租户隔离”。Pod 内的进程看到的 `eth0`、`lo` 等接口,以及它的路由规则,都是这个命名空间私有的,与宿主机(Host)以及其它 Pod 完全隔离。

连接的桥梁:veth pair (Virtual Ethernet Pair)

网络命名空间解决了隔离问题,但我们还需要一种方式让 Pod 与外界通信。`veth pair` 就是为此而生的。它是一种虚拟的网络设备,永远成对出现,就像一根虚拟的网线。一端连接在一个网络设备上,另一端也必须连接在另一个设备上。当数据包从一端进入,就会原封不动地从另一端出来。

在 Kubernetes 的实践中,当一个 Pod 被创建时,网络插件会:

  1. 创建一个 `veth` 对。
  2. 一端(例如,命名为 `eth0`)放入 Pod 的网络命名空间。
  3. 另一端(例如,一个随机生成的名字如 `veth-xxxx`)保留在宿主机的根网络命名空间中。
  4. 通常,宿主机上的所有 `veth` 端口会被连接到一个虚拟网桥(如 `cni0` 或 `docker0`),形成一个虚拟的局域网。

至此,Pod 内发出的数据包可以通过 `veth` 对“穿越”到宿主机上,实现了与宿主机的连接。

标准化的粘合剂:CNI (Container Network Interface)

CNI 是一套极简的规范,它定义了容器运行时与网络插件之间的通信协议。它本身只关心两件事:容器的网络设置(ADD)和网络清理(DEL)。当 `kubelet` 创建 Pod 时,它会调用 CNI 插件(一个可执行文件),并通过 `STDIN` 传入 JSON 格式的配置,告知插件需要执行的操作、Pod 的网络命名空间路径、容器 ID 等信息。插件完成工作后,将结果(如分配的 IP 地址)以 JSON 格式输出到 `STDOUT`。这种松耦合的设计,使得任何满足 CNI 规范的程序都可以成为 Kubernetes 的网络解决方案。

Flannel 架构解析:简单之上的 Overlay 网络

Flannel 是 CoreOS 开发的早期且广泛使用的 CNI 插件,其核心设计哲学是简单、易用。它致力于解决最核心的问题:跨节点 Pod 通信。它通过创建一个覆盖网络(Overlay Network)来实现这一目标,对底层物理网络的要求极低。

架构与核心组件

Flannel 的架构非常简洁。每个 Node 上都运行一个名为 `flanneld` 的 agent 进程。`flanneld` 启动后,会从 Kubernetes API(或独立的 etcd)中获取整个集群的网络配置。它为自己所在的 Node 申请一个独占的子网(Pod CIDR),例如 Node 1 分到 `10.244.1.0/24`,Node 2 分到 `10.244.2.0/24`。这些子网信息会被写入到所有节点的本地文件中,供 CNI 插件使用。

VXLAN 模式下的数据包之旅

VXLAN (Virtual eXtensible LAN) 是 Flannel 最常用也是性能最好的后端模式。它是一种网络虚拟化技术,通过将二层以太网帧封装在四层 UDP 包中,实现了跨越三层网络的二层隧道。

让我们以一个极客工程师的视角,追踪一个从 Node 1 上的 Pod A 发往 Node 2 上的 Pod B 的数据包:

  1. Pod A 发包: Pod A (IP: `10.244.1.2`) 向 Pod B (IP: `10.244.2.3`) 发送数据包。在 Pod A 的网络命名空间内,路由规则指示将所有非本地流量发送到默认网关,即 `veth` 对的另一端。
  2. 到达宿主机网桥: 数据包通过 `veth` 对到达宿主机 Node 1 上的 `cni0` 网桥。
  3. 路由决策: Node 1 的主路由表会有一条由 `flanneld` 创建的规则,内容大致是“所有发往 `10.244.0.0/16` 网段(除了本地子网 `10.244.1.0/24`)的流量,都交给 `flannel.1` 这个虚拟设备处理”。`flannel.1` 是一个 VTEP (VXLAN Tunnel End Point) 设备。
  4. 内核封装: Linux 内核的 VXLAN 模块接管了这个数据包。它查询自己的转发表(FDB),发现目标 Pod B 所在的 `10.244.2.0/24` 网段位于 Node 2 (IP: `192.168.1.102`)。于是,内核将原始的以太网帧(源 MAC/IP,目标 MAC/IP)作为 payload,外面套上一层 VXLAN 头,再套上一层 UDP 头(目的端口通常是 8472),最后套上外部 IP 头(源 IP: `192.168.1.101`,目标 IP: `192.168.1.102`)。
  5. 物理网络传输: 这个被层层封装的 UDP 包通过 Node 1 的物理网卡 `eth0` 发送出去。对于中间的物理交换机和路由器来说,这只是一个普通的 UDP 包。
  6. 内核解封装: Node 2 的内核在 8472 端口收到这个 UDP 包。识别出是 VXLAN 流量后,它会剥去外部的 IP、UDP 和 VXLAN 头部,还原出最原始的以太网帧。
  7. 送达目标 Pod: 还原后的数据包被送到 Node 2 的 `flannel.1` 设备,然后通过 `cni0` 网桥,最终经由与 Pod B 连接的 `veth` 对,送达 Pod B 的网络命名空间。

实现细节与坑点

Flannel 的美妙之处在于它不关心底层网络拓扑,只要 Node 之间三层可达即可。但这种便利是有代价的。封装/解封装会消耗 CPU 资源,并且由于增加了额外的头部(IP+UDP+VXLAN 大约 50 字节),有效载荷的 MTU (Maximum Transmission Unit) 会减小,可能导致网络性能下降和分片问题。在万兆网络环境下,VXLAN 的 CPU 开销会成为性能瓶颈。


# 在一个使用 Flannel VXLAN 的 Node 上查看路由表
# 你会看到发往其他 Node Pod CIDR 的流量都指向了 flannel.1 设备
$ ip route
...
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
...

Calico 架构解析:三层路由的性能野兽

如果说 Flannel 是为了“能用”,那么 Calico 就是为了“好用”和“高性能”。Calico 放弃了 Overlay 网络的思路,采用纯三层路由方案。它的核心理念是:将每个 Node 都变成一个路由器,直接通过 BGP (Border Gateway Protocol) 协议来宣告和学习 Pod IP 的路由信息,从而实现数据包的直接转发。

架构与核心组件

  • Felix: 在每个 Node 上运行的 agent。它的主要职责是监听 Calico 的数据存储,并将路由信息(FIB – Forwarding Information Base)和网络策略(ACLs)编程到 Linux 内核中。它会操作路由表、iptables 或 eBPF 来实现数据转发和安全隔离。
  • BGP Speaker (Bird): Calico 通常使用开源的 BGP 客户端 `BIRD`。它负责从 Felix 获取本节点应负责的路由(即本节点的 Pod CIDR),并通过 BGP 协议将这些路由通告给集群中的其他 BGP Speaker。
  • etcd/Kubernetes API: 作为 Calico 的中心数据存储,保存网络策略和网络配置信息。

BGP 模式下的数据包之旅

在 Calico 的世界里,网络是扁平且透明的。让我们再次追踪那个从 Pod A 到 Pod B 的数据包:

  1. 路由学习: 集群启动时,Node 2 上的 BGP Speaker 会向其他所有节点宣告:“任何想到达 `10.244.2.0/24` 网段的数据包,下一跳(next-hop)请指向我(IP: `192.168.1.102`)”。Node 1 的 BGP Speaker 收到这个宣告后,Felix 会在 Node 1 的内核路由表中添加一条规则:`10.244.2.0/24 via 192.168.1.102 dev eth0`。
  2. Pod A 发包: Pod A (`10.244.1.2`) 向 Pod B (`10.244.2.3`) 发包。数据包通过 `veth` 对到达宿主机 Node 1。
  3. 路由决策: Node 1 的内核查询路由表。这次,它匹配到了一条非常明确的、由 BGP 学习到的路由。内核知道,要想到达 `10.244.2.3`,需要将数据包从 `eth0` 网卡发出,并把下一跳地址设为 Node 2 的 IP `192.168.1.102`。
  4. 物理网络传输: 数据包没有任何封装,就是一个纯粹的 IP 包(源 IP: `10.244.1.2`,目标 IP: `10.244.2.3`),直接在物理网络上传输。物理交换机根据目标 IP `192.168.1.102` 的 MAC 地址进行二层转发。
  5. 送达目标 Pod: Node 2 收到这个包后,查询自己的路由表,发现 `10.244.2.3` 是一个本地连接的 Pod,于是将包转发给对应的 `veth` 接口,最终送达 Pod B。

实现细节与网络策略

Calico 的 BGP 方案避免了所有 Overlay 网络的开销,性能几乎等同于物理网络。更重要的是,由于它在三层工作,可以非常高效地实现 Kubernetes 的 `NetworkPolicy`。Felix 会将 `NetworkPolicy` 规则直接翻译成宿主机上的 `iptables` 或 `eBPF` 规则链,在数据包路径的早期就进行过滤,效率极高。


# 在一个使用 Calico BGP 模式的 Node 上查看路由表
# 你会看到去往其他节点 Pod CIDR 的具体、非封装的路由
$ ip route
...
10.244.2.0/24 via 192.168.1.102 dev eth0 proto bird onlink
10.244.3.0/24 via 192.168.1.103 dev eth0 proto bird onlink
...

这里的 `proto bird` 明确表示这条路由是由 BIRD 进程通过 BGP 协议学习到的。

对抗与权衡:Overlay vs. BGP 的终极对决

选择 Flannel还是 Calico,并非一个简单的“好”与“坏”的问题,而是一个基于特定场景的复杂 Trade-off 分析。

  • 性能与延迟: Calico 胜出

    在 BGP 模式下,Calico 提供了接近物理网络的性能,没有封装开销,延迟更低。这对于需要高吞吐、低延迟的应用(如金融交易、实时数据处理、在线游戏)至关重要。Flannel 的 VXLAN 封装会引入不可忽视的 CPU 开销和延迟,尤其是在网络速率超过 10Gbps 时。

  • 网络环境依赖: Flannel 胜出

    这是 Calico 最大的“坑”。纯 BGP 模式通常要求所有 Node 处于同一个二层网络中,这样它们才能直接建立 BGP 会话。如果 Node 跨越了三层网络(例如,在不同子网或不同数据中心),就需要配置物理路由器(如 ToR 交换机)与 Calico 节点建立 BGP peer,这极大地增加了运维复杂性,并且需要网络团队的深度介入。而 Flannel 的 Overlay 模型可以无视底层网络拓扑,只要 Node 间 IP 可达即可,在公有云等多变网络环境中适应性极强。

  • 功能与安全性: Calico 胜出

    Calico 将网络策略作为其核心功能之一,实现得非常高效和完善。对于有严格安全合规、需要微服务间精细化访问控制的场景,Calico 是不二之选。Flannel 自身不提供网络策略执行,需要与其他组件(如 kube-router)配合,或者其策略实现效率不如 Calico。

  • 可扩展性: 平手,但各有侧重

    Calico 的默认 BGP 模式是全网状(full-mesh),每个节点都与其他所有节点建立连接,连接数是 O(N²)。当集群规模超过 100-200 个节点时,BGP 会话数会爆炸式增长。此时需要引入 BGP 路由反射器(Route Reflector)来优化拓扑,但这又增加了架构的复杂性。Flannel 的控制平面压力主要在 etcd/K8s API,数据平面的扩展性则受限于封装带来的性能瓶颈。

架构演进与落地路径

在实际工程中,网络方案的选择和演进通常遵循一个务实的路径。

阶段一:起步与验证阶段 (Dev/Test 环境,小型集群)

选择:Flannel。 在项目初期,快速搭建环境、验证业务逻辑是首要任务。Flannel 的简单性和对网络环境的零要求,使其成为最佳选择。它能让你专注于应用开发,而不必陷入复杂的网络配置。对于大多数中小型应用,Flannel 的性能损失完全可以接受。

阶段二:性能与安全驱动的演进 (生产环境,规模化)

当应用上生产后,可能会遇到性能瓶颈,或者安全团队提出微隔离的需求。此时,迁移到 Calico 成为一个重要的议题。但直接在生产集群上替换 CNI 插件是高危操作。推荐的路径是:

  1. 使用 Calico 的 IPIP 或 VXLAN 模式: Calico 也支持 Overlay 模式。可以先在新的或现有的集群中部署 Calico 的 Overlay 模式。这种模式下,它对底层网络的要求和 Flannel 一样低,但你可以立即获得其强大的网络策略能力。
  2. 熟悉并应用网络策略: 在 Overlay 模式下,团队可以开始实践和落地 `NetworkPolicy`,建立安全访问模型。
  3. 规划 BGP 模式迁移: 当团队对 Calico 运维有了充分信心,并且网络条件允许时,再规划一次变更,将 Calico 从 Overlay 模式切换到 BGP 模式,以解锁极致性能。这通常需要与网络团队协作,确保二层连通性或配置 BGP 对等。

阶段三:面向未来的高级架构 (超大规模,混合云)

对于更大规模或更复杂的场景,可以考虑更前沿的方案。例如,基于 eBPF 的网络方案(如 Cilium,或 Calico 的 eBPF 数据平面),它通过在内核中注入字节码,绕过了部分 iptables 的复杂链路,提供了更高的性能和更强的可观测性。同时,Calico 与物理网络设备的集成能力,也为打通容器网络和传统数据中心网络、实现裸金属与 Pod 混合部署提供了可能。

总而言之,Kubernetes 网络的世界没有银弹。Flannel 以其简单性赢得了广泛的入门市场,而 Calico 则以其高性能和强大的安全策略,成为了生产环境和大规模部署的事实标准。理解它们各自的底层原理和设计权衡,是每一位资深工程师构建稳定、高效、安全的云原生系统的必备技能。

延伸阅读与相关资源

  • 想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
    交易系统整体解决方案
  • 如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
    产品与服务
    中关于交易系统搭建与定制开发的介绍。
  • 需要针对现有架构做评估、重构或从零规划,可以通过
    联系我们
    和架构顾问沟通细节,获取定制化的技术方案建议。
滚动至顶部