在Kubernetes构建的云原生世界中,计算和存储的抽象已近乎完美,但网络始终是那个最复杂、最关键也最容易被误解的领域。所有应用层的服务治理、RPC通信、服务网格,最终都建立在Pod与Pod之间能够稳定、高效、安全地通信这一基础之上。本文的目标读者是期望超越“配置YAML”层面,深入理解容器网络底层机制的资深工程师。我们将从Linux内核的网络虚拟化原语出发,系统性地剖析两大主流CNI插件——Flannel和Calico的设计哲学、数据通路、性能权衡以及在复杂生产环境中的架构选型考量。
现象与问题背景
Kubernetes对网络模型提出了三条基本且不容妥协的原则:
- 所有Pod都能直接与其他Pod通信,无需网络地址转换(NAT)。
- 所有Node都能直接与所有Pod通信(反之亦然),无需NAT。
- Pod看到的自身IP地址,就是其他Pod或Node看到的IP地址。
这三条原则本质上要求在集群范围内构建一个扁平化的、统一的IP网络空间,每个Pod都拥有一个唯一的、可直接路由的IP地址。然而,这一理想模型与物理世界的现实存在巨大鸿沟。传统的网络是为主机(物理机或虚拟机)设计的,IP地址是分配给主机的网卡。但在Kubernetes中,成百上千的Pod可能在同一台主机上高密度、高频率地创建和销毁,每个都需要IP。这就引出了几个核心工程问题:
- IP地址管理(IPAM): 如何在整个集群范围内,高效且无冲突地为海量短暂的Pod分配和回收IP地址?
- 跨节点通信: 当一个位于Node A上的Pod(例如 10.244.1.5)要访问Node B上的Pod(例如 10.244.2.10),数据包如何从Node A的物理网卡正确地路由到Node B,并最终送达目标Pod?
- 网络策略与隔离: 如何在扁平化的网络中实现精细化的访问控制,例如,只允许特定标签的Pod访问某个数据库Pod的特定端口?
为了解耦Kubernetes平台(由Kubelet代表)与具体的网络实现,CNI(Container Network Interface)规范应运而生。它定义了一个极简的插件式接口,Kubelet在创建或销毁Pod时,只需调用相应的CNI插件(一个可执行文件),传入Pod的网络命名空间、容器ID等参数,由插件负责完成IP分配、网络设备创建、路由配置等所有“脏活累活”。我们今天要讨论的Flannel和Calico,就是CNI生态中最具代表性的两种实现。
关键原理拆解
在深入对比Flannel和Calico之前,我们必须回归第一性原理,理解它们所依赖的、由Linux内核提供的底层网络虚拟化技术。这部分内容是理解一切容器网络方案的基石,我将以大学教授的视角来阐述。
- 网络命名空间 (Network Namespace)
这是Linux内核实现网络栈隔离的核心机制。每个Pod都拥有自己独立的网络命名空间,这意味着它有自己独立的网络设备(如`lo`, `eth0`)、路由表、iptables规则、ARP表和socket列表。从Pod内部看,它就像一台拥有完整网络功能的独立主机,与宿主机以及其他Pod的协议栈完全隔离。这是实现“IP-per-Pod”模型的基础。 - Veth Pair (Virtual Ethernet Pair)
Veth Pair是一种成对出现的虚拟网络设备,可以看作一根虚拟的“网线”。一端插入Pod的网络命名空间(通常被重命名为`eth0`),另一端留在宿主机的根网络命名空间。任何从一端进入的数据包都会原封不动地从另一端出来。它是在隔离的Pod网络命名空间和宿主机之间建立通信的桥梁。 - Linux Bridge
它是一个虚拟的二层交换机(Layer 2 Switch)。在宿主机上,可以将所有来自Pod的Veth Pair的“宿主机端”都连接到同一个Linux Bridge(如`cbr0`)上。这样,同一宿主机上所有Pod之间的通信就变成了简单的二层交换,数据包通过Bridge转发,无需经过宿主机的三层路由。 - 路由与IP转发
当数据包的目的地不在同一宿主机上时,就需要三层路由(Layer 3 Routing)。宿主机内核需要开启IP转发功能(`net.ipv4.ip_forward = 1`),使其能像路由器一样,根据路由表将收到的数据包转发到其他网络接口。所有跨节点通信的方案,本质上都是在解决“如何让每一台宿主机内核的路由表,能够正确地知道其他节点上的Pod IP应该发往哪里”的问题。 - Overlay vs. Underlay 网络模型
这是容器网络方案最核心的分野。- Overlay(覆盖网络):它在现有的物理网络(Underlay)之上,构建一个独立的虚拟网络层。节点之间的通信通过隧道技术(如VXLAN、IPIP)进行封装。原始的Pod数据包被封装在一个新的IP包头内,外部IP包头的源和目的地址是宿主机IP。这种方式对物理网络要求极低,只要节点间IP可达即可。Flannel的默认模式就是典型的Overlay网络。
- Underlay(底层网络):它不创建虚拟网络,而是让Pod IP直接成为物理网络中的一员,像物理机IP一样可被路由。这通常需要动态路由协议(如BGP)的帮助,让网络设备(或宿主机本身)学习到Pod IP的路由信息。这种方式性能极高,没有封装开销,但对物理网络环境有一定要求。Calico是Underlay网络的杰出代表。
系统架构总览
理解了上述原理,我们再来审视Flannel和Calico的架构,就会豁然开朗。它们本质上是对“如何管理和分发路由信息”这一核心问题的不同回答。
Flannel (VXLAN模式) 架构
Flannel的设计哲学是简单、普适。它的目标是在任何网络环境中都能“开箱即用”。
- 控制平面:每个节点上运行一个`flanneld`守护进程。启动时,`flanneld`会通过Kubernetes API(或直连etcd)为自己所在的节点申请一个独占的Pod IP子网段(例如,Node A获得`10.244.1.0/24`,Node B获得`10.244.2.0/24`)。它会将这个“节点IP -> Pod子网”的映射关系写入一个所有节点都能访问的存储中(通常是Kubernetes API Server的Annotation)。然后,`flanneld`会持续监听这个存储,构建一个完整的集群路由信息表。
- 数据平面:
- 当Node A的Pod(10.244.1.5)要访问Node B的Pod(10.244.2.10)时,数据包先通过veth pair到达宿主机的Linux Bridge `cbr0`。
- `cbr0`发现目的IP不在本二层网络,将其交给宿主机三层协议栈。
- 宿主机路由表有一条规则,将所有非本机的Pod CIDR(如10.244.0.0/16)的流量都指向一个特殊的虚拟设备`flannel.1`。这个设备是VXLAN的VTEP(VXLAN Tunnel End Point)。
- 数据包进入`flannel.1`后,内核的VXLAN驱动(或`flanneld`)会介入。它根据内存中的路由信息表,查到`10.244.2.10`属于Node B(物理IP为192.168.0.102)。
- 驱动将原始的Pod以太网帧封装成一个UDP数据包,外部IP头源为Node A的IP,目的为Node B的IP,目的端口为VXLAN的标准端口(如4789或8472)。
- 这个封装后的UDP包通过物理网络发送到Node B。
- Node B的内核收到UDP包,解封后得到原始的Pod以太网帧,再通过本地的`cbr0`和veth pair送达目标Pod。
本质上,Flannel用VXLAN隧道在所有节点间构建了一个巨大的虚拟二层网络,将跨节点通信伪装成本地通信,从而绕开了对物理网络的依赖。
Calico (BGP模式) 架构
Calico的设计哲学是高性能、原生、策略驱动。它将数据中心网络的设计理念引入Kubernetes。
- 控制平面:每个节点上运行一个`calico-node` Agent,它包含两个关键组件:
- BIRD:一个功能完善的BGP(边界网关协议)客户端。它负责与集群中其他节点的BIRD实例建立BGP Peering(对等连接)。一旦建立连接,它就会将本节点负责的Pod IP路由信息(例如,“前缀10.244.1.0/24的可达下一跳是我”)通告给所有对等体。同时,它也接收来自其他节点的路由通告,并将其写入本机的内核路由表。
- Felix:Calico的策略执行引擎。它监听Kubernetes API,获取最新的NetworkPolicy、Pod、Label等信息,然后高效地将这些策略规则转换成底层数据平面(如iptables或eBPF)的访问控制列表。
- 数据平面:
- 当Node A的Pod(10.244.1.5)要访问Node B的Pod(10.244.2.10)时,数据包通过veth pair(Calico通常不使用Linux Bridge,而是直接在宿主机为每个Pod配置路由)到达宿主机协议栈。
- 宿主机内核查询路由表。由于BGP协议已经工作,路由表中会有一条由BIRD写入的、非常明确的路由规则,形如:`10.244.2.0/24 via 192.168.0.102 dev eth0`。
- 内核直接将原始的、未经任何封装的IP包,通过物理网卡`eth0`发送给下一跳地址——Node B的物理IP。
- Node B收到这个IP包,其目的地址就是Pod IP `10.244.2.10`。Node B的内核同样根据本地路由表,将其转发给对应的veth pair,送入目标Pod。
Calico彻底摒弃了Overlay,让Pod间的通信回归到最纯粹的IP路由。这带来了极致的性能,但也对网络环境提出了要求——节点之间必须支持BGP协议通信。
核心模块设计与实现
作为一名极客工程师,我们不能只停留在架构图层面。让我们看看代码和配置,感受一下这两种方案在工程实现上的“手感”。
Flannel的实现精要
Flannel的核心是`flanneld`如何与Kubernetes协同工作来分配子网。Kube-controller-manager会为每个Node对象分配一个`podCIDR`字段。`flanneld`通过`–kube-subnet-mgr`参数启动后,会watch Node对象的变化。
// 伪代码: flanneld中kube_subnet_manager的核心逻辑
func (m *KubeSubnetManager) Run(ctx context.Context) {
// 1. 创建Kubernetes客户端
clientset, err := kubernetes.NewForConfig(m.kubeConfig)
// 2. 获取自己的Node对象,并为Node设置一个Annotation,表示flanneld已准备就绪
// `flannel.alpha.coreos.com/backend-type: "vxlan"`
// `flannel.alpha.coreos.com/backend-data: '{"VNI": 1, "Port": 8472}'`
// 3. 启动一个Informer,持续Watch所有Node对象的变化
nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
node := obj.(*v1.Node)
// 当新节点加入时,从node.Spec.PodCIDR读取子网信息
// 并从Annotation中读取该节点的VTEP MAC地址和公网IP
// 更新本地的VXLAN转发表(FDB)和ARP表
m.updateNode(node)
},
UpdateFunc: func(old, new interface{}) {
// 节点信息(如IP)变更时,同样更新转发表
m.updateNode(new.(*v1.Node))
},
DeleteFunc: func(obj interface{}) {
// 节点被删除时,清理相关的路由和转发表项
m.deleteNode(obj.(*v1.Node))
},
})
// ... 启动Informer并永久运行 ...
}
在数据平面,当你在一台安装了Flannel的节点上执行`ip route`命令,你会看到这样的路由条目:
# 假设本机Pod子网是10.244.0.0/24
# 集群总Pod子网是10.244.0.0/16
# flannel.1是VXLAN设备
default via 192.168.0.1 dev eth0
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.0.0/16 dev flannel.1
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.101
关键是`10.244.0.0/16 dev flannel.1`这条规则。它是一个覆盖范围更广的路由,捕获了所有发往其他节点的Pod的流量,并将其导向`flannel.1`设备进行VXLAN封装。本地Pod的流量(`10.244.0.0/24`)则被更精确的路由规则匹配,直接通过`cni0`(通常就是Linux Bridge)进行本地转发。
Calico的实现精要
Calico的核心是BIRD如何动态维护内核路由表,以及Felix如何将NetworkPolicy翻译成iptables规则。
BIRD的配置通常是自动生成的。在一个节点上查看BGP邻居状态,你会看到它与集群中其他所有节点都建立了对等连接(在默认的Node-to-Node Mesh模式下)。
# birdc show protocols
BIRD 1.6.8 ready.
name proto table state since info
kernel1 Kernel master up 10:00:00
device1 Device master up 10:00:00
direct1 Direct master up 10:00:00
Kube-Router-1 Kernel master up 10:00:01 BGP peer to node-1
Kube-Router-2 Kernel master up 10:00:02 BGP peer to node-2
...
此时查看内核路由表,你会看到截然不同的景象:
# ip route
default via 192.168.0.1 dev eth0
10.244.1.192/26 via 192.168.0.102 dev eth0 proto bird
10.244.2.0/26 via 192.168.0.103 dev eth0 proto bird
...
# 每一条去往远程Pod子网的路由都清晰地指向了对应的节点IP
# 'proto bird' 表明这条路由是由BIRD进程注入的
至于Felix,它的工作可以看作是一个高性能的控制器。它Watch着NetworkPolicy对象,然后计算出需要更新的iptables规则集。为了避免频繁、低效地调用`iptables`命令,Felix会在内存中构建出完整的iptables-restore格式的数据,然后一次性、原子化地应用到内核,性能极高。
// 伪代码: Felix的核心工作流
func (fc *FelixCalculator) OnUpdate(policy *v3.NetworkPolicy) {
// 1. 解析NetworkPolicy对象,提取selector, ingress, egress规则
parsedRules := parsePolicy(policy)
// 2. 将高级的策略规则,转换成具体的IPSet或iptables规则链
// 例如,一个基于label的selector会被转换成一个包含所有匹配Pod IP的IPSet
ipset := fc.resolveSelectorToIPSet(policy.Spec.PodSelector)
// 3. 将这些规则集放入一个待处理队列中
fc.dirtyRules.Add(policy.UID, ipset, parsedRules)
}
// 在另一个goroutine中定期执行
func (dp *Dataplane) applyUpdates() {
// 1. 从队列中取出所有脏数据
updates := fc.drainDirtyRules()
// 2. 在内存中计算出最终需要生成的iptables-restore脚本
restoreScript := dp.iptablesGenerator.generateUpdateScript(updates)
// 3. 调用iptables-restore命令,一次性应用所有变更
cmd := exec.Command("iptables-restore", "--noflush")
cmd.Stdin = strings.NewReader(restoreScript)
cmd.Run()
}
这个“计算-差量-批量应用”的模型是所有高性能控制器的共同模式,Felix是其中的典范。
对抗层:性能与场景的权衡
选择Flannel还是Calico,从来不是一个技术优劣的问题,而是一个基于具体场景和约束的Trade-off分析。
- 网络性能(吞吐量与延迟)
- Calico (BGP) 胜出:数据包是原生的IP包,没有封装和解封装的CPU开销。网络路径最短,延迟最低。对于网络I/O密集型或延迟敏感型应用(如高频交易、实时音视频、大规模数据处理),Calico的性能优势是决定性的。
- Flannel (VXLAN):封装会带来额外的CPU负载和约50字节的包头开销,这会略微降低最大吞吐量并增加延迟。同时,更大的包头可能会导致需要分片,或要求调大物理网络的MTU值,否则会引发性能问题,这是个常见的“坑”。不过,对于大多数Web应用和服务,这点性能损耗通常可以忽略不计。现代网卡的VXLAN Offloading功能可以在硬件层面完成封装,能极大缓解CPU压力,但这依赖于底层硬件。
- 网络策略与安全性
- Calico 胜出:这是Calico的王牌功能。它提供了功能强大且丰富的NetworkPolicy实现,支持基于Label、Namespace、IP段、端口范围的复杂规则,甚至支持全局网络策略和策略排序。对于需要严格多租户隔离、遵循安全合规(如PCI-DSS)的金融、政企场景,Calico几乎是唯一选择。
- Flannel:本身不提供NetworkPolicy实现。需要与其他组件(如Calico的策略引擎,组合成Canal项目)或使用原生的Kube-router等才能获得策略能力,配置和维护会更复杂。
- 环境适应性与运维简易度
- Flannel 胜出:VXLAN的Overlay模型对底层网络“一无所知”,也“一无所求”,只要节点间三层可达即可。这使得它在公有云(VPC网络)、私有云以及各种复杂的、你无法控制的传统企业网络中,都能轻松部署。它的配置简单,心智负担小,是“快速启动和运行”的最佳选择。
- Calico (BGP):要求节点之间能够建立BGP连接。在某些公有云上这不成问题,但在很多有严格网络分段和防火墙策略的本地数据中心,你可能需要网络团队的配合来放行BGP协议(TCP端口179)。如果网络不支持,Calico虽然也提供了IPIP或VXLAN的Overlay模式作为备选,但这会丧失其主要的性能优势,变得和Flannel类似。
- 可扩展性
- 两者均可扩展,但方式不同:Flannel的控制平面非常简单,扩展性瓶颈通常不在自身。Calico的Node-to-Node BGP Mesh模式在集群规模非常大(例如超过100-200个节点)时,`N*(N-1)/2`的连接数会带来不小的控制平面压力。此时,需要引入BGP Route Reflector(路由反射器)来优化拓扑,将网状变为星型,这是数据中心网络的标准扩展实践,但对运维人员的BGP知识有一定要求。
演进层:架构演进与落地路径
一个成熟的架构决策,不仅要考虑当前,更要规划未来。对于Kubernetes网络方案的选择,可以遵循一条清晰的演进路径。
- 阶段一:默认与起步 (Flannel)
对于初创团队、开发测试环境、或者业务对网络性能和安全隔离要求不高的场景,Flannel-VXLAN 是最稳妥、最省心的选择。它的普适性和易用性可以让你快速搭建起可用的集群,专注于上层业务的开发。 - 阶段二:性能与策略觉醒 (评估Calico)
当集群规模扩大,开始承载核心生产业务,以下信号出现时,就应该考虑向Calico演进:- 应用团队抱怨网络延迟或吞吐量成为瓶颈。
- 安全或合规团队要求实现严格的租户隔离和应用访问控制。
- 需要与现有的物理网络(如ToR交换机)进行路由对接,实现Pod IP的全局可达。
这个阶段的核心工作是评估你的基础设施是否支持BGP。与网络团队沟通,或在云环境中验证BGP的可行性。
- 阶段三:迁移与混合 (Canal或分步替换)
从Flannel迁移到Calico是一个有风险的操作。直接在生产集群上替换CNI插件通常会导致全网中断。推荐的路径是:- 增量演进:先使用Canal(Flannel + Calico Policy Engine)。这可以在不改变现有数据平面的情况下,为集群引入强大的网络策略能力,是一个低风险的“加固”步骤。
- 蓝绿发布:建立一个全新的、使用Calico作为CNI的集群,然后通过流量迁移的方式,逐步将应用从旧的Flannel集群迁移到新集群。这是最安全、最可控的方案。
- 阶段四:终极形态 (Calico + eBPF)
当你已经全面拥抱Calico并对其运维有了充分信心后,可以探索更前沿的路径。Calico和Cilium等项目都提供了基于eBPF的数据平面。eBPF允许在内核中执行自定义的、安全的字节码,从而绕过传统的iptables和netfilter框架,直接在网卡驱动层面处理网络包。这可以带来数量级的性能提升、更低的延迟、以及更强的可观测性(如直接在内核层面监控API调用)。从`Flannel(Overlay)` -> `Calico(BGP/iptables)` -> `Calico(BGP/eBPF)`,这条路径代表了云原生网络从兼容并包到追求极致性能与安全的演进方向。
结论:Flannel以其无与伦比的简易性和兼容性,成为了云原生网络世界的“入门守卫”;而Calico则凭借其原生L3性能和强大的安全策略,定义了生产级容器网络的“黄金标准”。理解它们各自的底层原理与设计权衡,是每一位架构师在构建稳定、高效、安全的Kubernetes平台时,必须掌握的核心技能。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。