Kubernetes用了一年多,中间最绕不开的一个决定就是选网络插件。flannel和calico这两个名字,几乎每个搞K8s的人都纠结过一遍。我自己的经验是从flannel起步,后来部分集群迁到了calico,中间踩了不少坑,也对这两个插件背后的设计思路有了比较深的理解。这篇文章就把我对这两个插件的使用心得、原理拆解和选型思考一次性讲清楚。
1. 为什么网络插件是K8s绕不开的一环
1.1 K8s网络模型的底层要求
Kubernetes对网络有一个硬性要求:所有Pod之间必须能够直接通信,无论它们落在哪个节点上,都不需要NAT转换。这个模型听起来简单,但在底层实现上是个不小的工程。容器从宿主机视角看只是一个个隔离的进程,它们有自己的网络命名空间,有各自的虚拟网卡和IP地址。要让这些分布在不同物理机上的“虚拟网卡”互通,就必须有一种机制把二层或三层网络打通。
这就轮到CNI(Container Network Interface)出场了。CNI是一套标准接口,Kubelet创建Pod时会调用CNI插件,把Pod的网络命名空间交给插件来配置。flannel和calico都是CNI插件,但它们的实现哲学完全不同。打个比方,flannel像是给你一条“隧道”,把各节点的容器网络串起来;而calico像是一张“真实的互联网”,每个Pod都有一个真实可路由的IP,节点之间通过路由协议直接交换数据。
1.2 插件选型决定集群的流量路径
网络插件不只是“让Pod互通”这么简单。它决定了数据包从Pod出去之后,走的是哪条路径、经过了哪些封装和解封装、能不能被安全策略拦截。这些底层差异直接反映在时延、吞吐量和运维复杂度上。很多人的第一个集群会用flannel,因为它简单、够用;但一旦集群规模变大、安全要求变高、或者跑的是对延迟敏感的业务,flannel的一些短板就会暴露出来,这时候calico往往成为更合适的选择。
这篇文章不打算做那种“A好还是B好”的站队,而是把两个插件的工作机制、部署方式、性能特征和适用场景都摊开来说,最后大家可以根据自己的业务情况做判断。
2. flannel的设计哲学:用隧道简化一切
2.1 flannel的核心思路
flannel最早由CoreOS团队开发,设计目标是用尽量简单的方式解决Pod跨节点通信。它的思路很直接:每台节点上的Pod共享一个子网,flannel为每个节点分配一个独立的子网段(例如10.244.1.0/24),然后通过VXLAN或Host-GW等后端,把这些子网连通起来。
我最常用的是VXLAN模式。VXLAN是一种隧道协议,它把Pod发出的一层数据包塞进UDP包里面,经过宿主机网络转发到目标节点后,再解封装还原成原始的二层包。这个过程对Pod完全是透明的,Pod并不知道自己被“装进信封”寄到了另一台机器。
有个类比能帮助理解:把每个节点的容器网络看成一个小岛,VXLAN就是岛与岛之间的渡轮。岛上的车(Pod的数据包)不需要知道海怎么过,只要开上渡轮就行。渡轮在海上行驶时用的是海上的航路(宿主机网络),到了目的岛再把车开下来。
2.2 flannel的三种后端模式
flannel支持多个后端,我最常用的两个是VXLAN和Host-GW,还有一个已经很少用的UDP。
VXLAN模式下,flannel会创建一块flannel.1虚拟网卡,负责VXLAN封包和解包。数据包路径是:Pod eth0 -> cni0网桥 -> flannel.1 -> 宿主机eth0 -> 对端宿主机 -> 对端flannel.1 -> 对端cni0 -> 目标Pod。这条链路多了一次隧道封装和解封装,会带来少量性能损耗,但好处是它不要求宿主机在同一个二层网络,只要三层能通就能跑。VXLAN的端口默认是8472,UDP包里的VNI是1。
Host-GW模式则直接把每个节点当作一个路由器,flannel会在节点上写入路由规则,数据包从Pod出来后直接走宿主机网络转发到目标节点,不做任何隧道封装。这种模式的转发效率在三种方式里最高,因为数据包几乎走的是原生路径。但代价是:宿主机之间必须在同一个二层网络中,而且要关闭rp_filter(反向路径过滤),否则路由会出现不对称问题。
UDP后端现在基本被淘汰了,每次收发数据包都要在内核态和用户态之间切换,性能很差,早期版本才用它。
2.3 flannel部署与配置细节
部署flannel很简单,绝大多数情况下一条kubectl apply就能搞定。部署文件里核心的配置在ConfigMap中,包括Network(Pod网段)、Backend(后端类型)等。以下是一个VXLAN后端配置的片段:
net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan", "Port": 8472 } }这个配置里有两个值得注意的地方。一个是Network网段,需要保证和kube-controller-manager中--cluster-cidr参数一致,否则CNI分配的IP和Controller预期的范围对不上,引发IP冲突或路由混乱。另一个是Port参数,VXLAN模式默认使用内核VXLAN模块,不需要用户态进程监听UDP端口,所以这个端口更多是用于VXLAN模块识别VTEP(VXLAN Tunnel End Point)。
实际部署中还要注意一个细节:flannel要求每台节点上的docker0网桥和Pod网段不能冲突。如果之前装过其他CNI插件,或者docker的default-address-pools设置过私有网段,一定要先清理干净,否则Pod启动后IP就会错乱。我在一次测试环境里就是因为之前试过某插件,没有清理干净,结果新节点上的Pod全部跟已有Pod IP冲突,流量乱成了一锅粥。
3. calico的设计哲学:让网络回归路由
3.1 calico的三层路由模型
calico的核心思路和flannel恰好相反:不封装,直接路由。它把每个节点当路由器,Pod的IP是真实的可路由IP,节点之间通过BGP协议交换路由信息,数据包按原生路径直接送达。calico没有网桥、没有隧道,虚拟网络拓扑看起来更像一张“正经的”物理网络。
calico在每台节点上创建cali前缀的虚拟网卡(例如cali123abc),每张网卡对应一个Pod。然后通过路由规则,把目标Pod IP的下一跳指向对应的宿主机。BGP(Border Gateway Protocol)在这里扮演的角色就是把“哪个节点有哪些Pod子网”的信息广播给整个集群,让每台节点都知道如何到达任何一个Pod。
这种设计让calico的性能表现先天优于flannel的隧道模式,因为数据包没有额外封装,转发路径短,CPU开销也小。特别是在高频小包场景下,封装与否的差距会非常明显。
3.2 calico的IPIP和VXLAN封装模式
我前面说calico“不封装”,严格来说不准确。calico在跨网络场景下也会使用封装。IPIP模式是一种常见的跨子网传输方案,它将Pod发出的整个IP包作为负载,外面再套一个IP头。这个模式的好处在于,不管底层网络是否允许自由路由Pod网段,隧道都能把流量传到目标节点。代价和VXLAN类似,多了一轮封解包处理。
calico也支持VXLAN模式,它的VXLAN实现优势在于可以直接借用内核的VXLAN能力,做性能优化。但在选择时有一个关键的考量点:IPIP和VXLAN都需要宿主机间三层互通,而BGP模式下如果跨了子网,路由器的路由策略很可能会挡住Pod网段的路由通告。所以如果节点跨机房、跨VPC部署,calico的BGP模式很容易出现节点间路由不通的问题。这时要么配置IPIP或VXLAN,要么在路由器上增加静态路由策略。
3.3 calico的网络策略能力
calico另一个让flannel望尘莫及的功能是网络策略。K8s本身有NetworkPolicy API,但flannel并不实现NetworkPolicy,默认集群里网络策略是空摆设。而calico自带了一套完整的策略引擎,可以从Pod粒度、Namespace粒度、IP段粒度甚至服务账号粒度做流量控制。
我实际用过的一个例子是:一个多租户环境,不同团队共享同一个集群,其中几个业务有合规要求,只允许特定来源访问。用calico的GlobalNetworkPolicy配置之后,几行YAML就搞定了。配置片段大概长这样:
apiVersion: projectcalico.org/v3 kind: GlobalNetworkPolicy metadata: name: only-allow-frontend spec: selector: app == "payment" ingress: - action: Allow source: selector: app == "frontend" - action: Deny这个功能在生产环境下几乎是刚需。相比之下,flannel侧没有任何原生InCluster流量管控能力,只能在宿主机层面用iptables手工撸规则,费时费力还容易出错。
3.4 calico的eBPF模式
calico还有一个更进阶的数据面:eBPF模式。这个模式用一个运行在Linux内核的eBPF程序替代原本的iptables逻辑,不仅转发性能更好,还能支持DSR(Direct Server Return)等高级负载均衡能力。启用eBPF模式后,Kubernetes的Service流量可以直接在内核里完成转发,不再走kube-proxy的用户态逻辑。
当然,eBPF模式对内核版本有要求,一般需要Linux 5.x以上,而且节点上要开启相应权限。这里不建议一上来就启用eBPF,尤其是在内核版本老、容器运行时复杂的环境里,容易引入额外变数。当业务真正碰到性能瓶颈时再考虑升级数据面,是比较稳妥的路径。
4. flannel和calico的全维度对比
4.1 一张表看清性能与功能差异
| 对比维度 | flannel(VXLAN) | calico(BGP) | calico(IPIP) |
|---|---|---|---|
| 转发路径 | 宿主机网络+隧道封装 | 原生路由 | 原生路由+IP封装 |
| Pod IP路由 | 不依赖BGP | 依赖BGP | 依赖BGP |
| 跨网段支持 | 支持(三层互通即可) | 一般(需路由策略配合) | 支持 |
| 延迟开销 | 中(有封包解包) | 低 | 中低 |
| 吞吐量 | 中 | 高 | 中高 |
| NetworkPolicy | 不支持 | 完整支持 | 完整支持 |
| 部署复杂度 | 低 | 中 | 中 |
| 大规模集群 | 有性能瓶颈 | 良好 | 良好 |
注意表中“跨网段支持”这一项,BGP模式下calico不直接支持跨子网流量,但可以通过配置路由反射器(Route Reflector)或建立节点间的BGP peer解决,只是复杂度会上升。
4.2 CPU开销对比实测感受
我在一个8节点的测试集群里做过吞吐对比。同样的业务Pod,同样大小的数据包,calico BGP模式比flannel VXLAN模式大概快20%-30%,CPU占用也低一些。这个差距在HTTP短连接请求、DNS解析、批量日志上报这类高频小包场景里格外明显。
但如果只是内部跑点定时任务、压力不大的Web应用,理论上flannel VXLAN的性能也足够用了。所以性能不该是选型的唯一标准,还要看现有集群的规模趋势、团队对网络原理的掌握程度、以及有没有安全管控需求。
4.3 运维复杂度视角
从日常运维角度看,flannel的设计目标就是“零维护”,跑起来之后基本不用管。它没有BGP、没有路由反射器,所有状态都集中在etcd或者Kubernetes的Node对象里。出现问题时的排查链路也很短:查flannel.1网卡、查路由表、查VXLAN端口,三步基本能定位。
calico的运维曲线明显陡峭一些。BGP peering、node-to-node mesh、IPPool管理、Felix日志、eBPF数据面,这些概念都会在实际使用中遇到。尤其在节点数量超过50之后,calico默认的full-mesh BGP模式会产生大量BGP会话,这时需要引入BGP Route Reflector来控制会话数量。这个操作在官方文档里有专门描述,但没有一定网络基础的人第一次看会有点懵。
5. 生产环境选型:什么时候该用哪个
5.1 选flannel的典型场景
如果你是第一次搭建Kubernetes集群、团队没有专职网络工程师、集群规模不大(比如10台左右)、业务以内部低负载应用为主,那flannel其实很合适。它最大的优点是上手成本极低,部署完就能跑,出了问题也容易排查。而且它在公有云虚拟机、物理机甚至树莓派集群上都有良好的兼容性。
碰到节点需要跨VPC或跨机房部署的情况,flannel VXLAN也是相对省心的方案,只要底层网络三层能通,隧道就能建立,不需要动云上的路由表。我自己就有一个跨机房的测试集群,直接用flannel VXLAN跑了大半年,稳定性还不错。
5.2 选calico的典型场景
如果业务对时延敏感、流量规模大、或者集群需要多租户隔离与网络策略管控,那么calico是更适合的答案。尤其是有安全合规需求的场景,calico的NetworkPolicy是flannel替代不了的能力。
另外,如果你已经有物理网络背景,理解BGP的运作方式,用calico反而是“回归常识”的选择。因为它把容器的虚拟网络当成一张真实的路由网络来管理,问题排查时的思路可以完全复用传统网络的经验。
5.3 混合部署的可能性
有人可能会问:一个集群里能不能flannel和calico混用?答案是不建议。CNI标准下,同一时间的集群必须由同一个插件管理,混用会导致路由表互相覆盖、IP分配冲突。若有测试需要,也要在不同节点集合里通过调度标签完全隔离后再实践,否则很容易出现诡异问题。
5.4 切插件时的注意事项
如果是从flannel切换到calico,不能直接删flannel再装calico,要避免节点残留路由和iptables规则。比较稳妥的顺序是:先给集群打调度锁(kubectl cordon)→ 分批排空节点上的Pod → 删掉flannel DaemonSet → 清理每个节点上flannel创建的cni0、flannel.1网卡以及对应的路由和iptables链 → 再部署calico。整个过程如果集群资源有富余,建议先拿一个测试集群演练一遍再动手。
6. 实际部署与排查过程记录
6.1 一台新节点接入集群的完整步骤
以calico为例,假设有一个K8s集群已经在运行,现在要加一台新节点。如果是二进制方式部署的Kubernetes,新节点上需要保证以下几点:
- kubelet已启动并成功注册到集群
- kube-proxy已运行,确保Service的iptables规则正常注入
- 确认
/etc/cni/net.d/里没有残留的flannel或其他CNI配置
然后执行kubectl apply -f calico.yaml,calico的DaemonSet会在新节点上自动启动,创建对应的cali网卡和路由规则。正常情况下新节点的Pod就能拿到IP并跟其他节点互通。
6.2 flannel部署后的常见验证命令
部署完成后,我的习惯是先验证数据面再验证控制面。验证命令参考如下:
# 查看flannel pod状态 kubectl get pods -n kube-flannel # 查看本机flannel网卡是否存在 ip link show flannel.1 # 查看节点间路由是否正常 ip route | grep 10.244 # 跨节点ping测试 kubectl exec -it <pod-a> -- ping <pod-b-ip>如果Pod之间ping不通过,优先检查VXLAN的VTEP表。ip -d link show flannel.1可以看到详细的VXLAN信息;bridge fdb show | grep flannel.1可以看到对端节点的MAC地址记录。多数情况下,问题出在UDP端口被防火墙挡了,或者节点的物理网卡MTU值过大导致分片丢弃。
6.3 calico部署后的诊断命令
calico的排查工具比较全,我常用的命令包括:
# 查看BGP peer状态 calicoctl node status # 查看节点分配的IP池 calicoctl get ipPool # 查看路由表 ip route | grep 192.168 # 查看某Pod的endpoint状态 calicoctl get workloadEndpoint | grep <pod-name>遇到过的一个典型问题是:新节点加入后,节点上业务Pod可以互相通信,但集群里的DNS(CoreDNS)偶尔超时。排查后发现新节点的默认路由指向了一个旧的网关,导致数据包从错误网关出去了。因此在节点初始化时,需要先确认默认路由和防火墙都有正确配置,再部署Pod。
7. 用了一段时间后的真实体会
用flannel和calico都跑过生产集群之后,我现在的看法是:选网络插件没有绝对的标准答案,只有适合当下场景的答案。刚开始做K8s时,团队对网络理解不深,用flannel快速建集群是一个非常务实的决定,它降低了门槛,让团队把更多精力花在应用本身。当集群规模扩大、多租户需求出现、安全审计要求提上日程的时候,再切换到calico,有前面打下的基础,迁移过程也还算可控。
有一个技巧想分享给正在评估的人:不要光看官方文档上的特性对比,最好拿自己的业务Pod在同等网络环境下做一次A/B压测。不用太复杂,跑一个返回当前节点IP和时延的HTTP服务,分别部署在两个插件环境里,用相同的负载工具打两轮,数据出来就很清楚自己业务适合哪种模式了。这个实验比任何人告诉你“哪个好”都更有说服力。
还有一点是:网络插件升级不像普通Docker镜像那样无感,每次大版本变更前务必做好节点路由表和iptables快照备份。报错时不要只看Pod日志,dmesg和journalctl -u kubelet里往往藏着更关键的线索。
希望这些实践经验能帮你在flannel和calico之间做一个更清晰的判断。真到了具体的业务场景里,如果你有其他特殊情况或者有趣的性能和网络配置经验,也欢迎一起交流讨论。