☰
Kubernetes容器网络实战:Flannel与Calico原理、对比与排错指南
2026/10/21 3:35:39 网站建设 项目流程

1. 先从最头疼的问题说起:Pod 网络地址漂移到底怎么解

在K8s里干活,你迟早会撞上一堵墙——网络。很多新手上手跑通了一个Nginx,正准备高兴,下一秒就把Pod删了重建,发现IP变了,连不上了。这不是玄学,这是K8s网络的本质:Pod是“临时公民”,IP是“临时身份证”,每次重启都可能换号。

用过Docker的人会有个错觉:容器嘛,不就是背后有个网桥,容器通过veth插到网桥上,然后拿一个网桥网段的IP就能通信了。这一套在单机场景下没问题,但到了K8s集群,问题直接翻倍。因为K8s的核心诉求是“跨节点调度”,同一个Deployment的Pod可能分布在不同的物理节点上,Pod A在节点1,Pod B在节点2,两边根本没有共同的二层网络,怎么通信?

如果只是“跨节点通信”这一个问题,方案其实也不少。比如把节点直接串成一个大二层,用路由协议打通;或者做Overlay隧道,让节点不管底层基础设施是什么,都能在逻辑上成为一个大二层网络。但这还不够,K8s对网络有几个硬性要求:Pod和Pod之间必须能直接通信,不需要NAT;Pod和Service之间要能通信;节点和Pod之间要能通信。这三个要求合起来,就形成了容器网络模型,也叫CNM。

在K8s里,真正干活的是CNI插件。CNI全称是Container Network Interface,容器网络接口,它是一套标准,任何网络方案只要实现了这套标准,就能被Kubelet调用,完成Pod网络的创建和销毁。目前社区里应用最广的两大插件,就是题目里写的flannel和calico。一个主打“简单省事”,一个主打“功能全面高性能”。

这篇文章不打算从零讲CNI标准,也不会把每个插件源码翻一遍,而是以我实际在集群里部署、排查、调优的经历为主线,把flannel和calico的核心原理、工作模式、参数选择、常见坑一次说清楚。看完你至少能回答这几个问题:为什么我的集群Pod之间偶尔通偶尔不通?为什么flannel装完一切正常,但跨节点延迟有点高?为什么calico这么难配,但大型集群都在用?

2. 容器网络模型与CNI通用架构

2.1 容器网络模型里四条铁律

在深入插件之前,有必要把K8s对网络的那几条硬性要求拆开看。这不是学术概念,每条都直接影响你选型和工作方式。

第一,Pod内的多个容器共享同一个网络命名空间。一个Pod里可以有多个容器,它们共用localhost通信,共享同一个IP和端口空间。这就意味着Pod内的容器不能用同一个端口,比如nginx占用了80,同Pod里的其他容器就不能再监听80。这个设计是为了让Pod成为最小的调度单位,减少了通信的复杂度。

第二,Pod与Pod之间可以不经过NAT直接通信,无论它们在哪台节点上。这句话是整个CNI模型的核心。想想看,如果每次跨Pod通信都要经过NAT转换,那IP地址的“身份”就名存实亡了,日志审计、服务发现、网络策略全都乱了套。因此,所有CNI方案的首要目标是:保证集群内任意两个Pod的流量,从发出到接收,源IP和目标IP始终不变,通信路径上不做地址转换。

第三,Pod与Service之间也能直接通信。Service是一层虚拟抽象,背后是iptables或IPVS规则。这条规则通常由kube-proxy实现,要求CNI插件只需要保证Pod的流量能出网、能到达节点和集群内部即可,Service本身是kube-proxy的活儿。

第四,节点的网络栈必须能访问Pod。比如你在节点上执行curl 10.244.1.5,也应该能通。这条要求看起来简单,但其实直接排除了很多“只做NAT”的方案,也推动了host-gw和BGP这类纯路由方案的诞生。

理解了这四条,你再看所有CNI方案,本质上都是在回答两个问题:一是如何给每个Pod分配一个唯一的IP,且网段规划不重复;二是如何让这些IP在跨节点的场景下还能被路由到目标节点。前者靠IPAM,后者靠路由或隧道。

2.2 CNI插件的调用流程

CNI这套标准其实很简洁。Kubelet在创建Pod时,会去读取CNI配置目录下的配置文件,一般是/etc/cni/net.d/目录,里面有一个以数字开头的配置文件,比如10-flannel.conflist。这个数字就是插件的执行优先级,Kubelet会按顺序执行。

CNI定义了三类操作:ADD、DEL、CHECK。ADD就是给容器添加网络,DEL是删除网络,CHECK是检查网络配置是否正确。每个插件都需要实现这三个接口。Kubelet调用插件的方式也不是直接执行二进制文件,而是把容器信息通过环境变量和标准输入传给插件,插件处理完,再把结果通过标准输出返回。

这里有个容易忽略的点:CNI插件常常需要多个二进制配合工作。比如flannel的CNI插件是flannel,calico的CNI插件是calico,但它们在底层可能还会依赖IPAM插件,比如host-local或calico-ipam。配置里会指定"type": "flannel"和"ipam": { "type": "host-local" },Kubelet拿到这个配置后,会先执行主插件,再执行IPAM插件,两个插件都是独立的二进制文件,放在/opt/cni/bin/目录下。如果你发现Pod一直ContainerCreating,卡在“Failed to create pod sandbox”,先别急着怀疑网络,打开kubelet的日志看看,很大概率是CNI插件二进制文件缺失,或者配置文件里的路径写错了。

2.3 为什么每个节点都要规划独立子网

这是很多新手在手动安装K8s时会忽略的一步。你需要在每个节点上给Pod分配一个不冲突的子网段。比如节点1用10.244.0.0/24,节点2用10.244.1.0/24,以此类推。这样从全局看,所有Pod的IP地址是一个整体网段,但具体落在哪个节点上,由节点IP段的规划决定。

flannel的官方方案里,默认使用10.244.0.0/16作为整个集群的Pod CIDR,然后切成10.244.x.0/24,每个节点领取一个/24。这个分配工作是flanneld进程通过Kubernetes API的lease机制完成的。节点上的flanneld会通过kube-controller-manager来申请一个子网,并把分配结果写入节点的podCIDR字段。

为什么要这么设计?因为IPAM是分布式的,每个节点自己申请自己的网段,不用中心化的DHCP服务器。好处是扩展性好,新节点加入时自动就能拿到不冲突的网段;坏处是如果网段规划错了,比如两个节点拿到了重叠的网段,那整个集群的网络就会出各种玄学问题。这个问题在calico里同样存在,配置calico时务必确认IP池和节点的子网规划没有冲突。

3. Flannel深入拆解:Overlay方案与三种工作模式

3.1 VXLAN模式下数据包如何穿越节点

flannel最经典的模式是VXLAN。用一句话概括,它就是在现有的三层网络上,再叠加一层虚拟的二层网络。这就是Overlay网络的核心思路。

每个节点上的flanneld会创建一个虚拟网卡flannel.1,这个网卡就是VXLAN的VTEP设备。VTEP的全称是VXLAN Tunnel End Point,隧道端点。每个节点的flannel.1会有一个Mac地址,这个Mac地址对应的IP就是VTEP的IP。Pod的流量到达节点的flannel.1后,会被封装成VXLAN报文,然后通过UDP发到目标节点的8472端口,目标节点的flannel.1接收后解封装,再转给目标Pod。

详细点说,当节点A上的Pod A(10.244.0.2)要访问节点B上的Pod B(10.244.1.5)时,整个数据链路是:

  1. Pod A的流量通过veth进入节点A的cni0网桥(flannel创建的二层网桥)。
  2. cni0通过ARP查找到目标IP 10.244.1.5对应的Mac地址。但这个Mac地址不是Pod B的,而是节点B上flannel.1的Mac地址。为什么?因为flanneld会在启动时通过Kubernetes API拿到所有节点的子网信息,知道10.244.1.0/24在节点B上,然后把这个子网的路由信息写入主路由表,下一跳指向节点B的flannel.1的IP,并且它会主动在节点A上添加一条“静态ARP”记录,把10.244.1.5的Mac地址对应到flannel.1的Mac地址。这样一来,Pod的流量在二层上就会把目标Mac设为flannel.1的Mac,从而被送到VTEP设备。
  3. flannel.1拿到原始报文后,查看文目的IP(10.244.1.5),根据UDP端口8472,把原始报文作为VXLAN payload封装起来,外层加上目的节点的IP和UDP头,发到节点B。
  4. 节点B的flannel.1收到后,解封装,把原始报文扔给本机网络栈,网络栈根据路由表再转发到Pod B。

这就是为什么flannel用起来“无脑”:你不需要在每个节点上配置路由,它通过网络层自己搞定了跨节点的转发。代价是每次通信都多了一层UDP封装,性能会有所下降。

3.2 host-gw模式:牺牲灵活性换性能

如果你的底层网络本身就支持VLAN、或者各节点的二层可达,你可以考虑用host-gw模式。这个模式下,flannel完全不做封装,直接把Pod子网的路由写进节点的路由表。

比如节点A和节点B在同一个二层网络里,节点A要访问Pod B的网段10.244.1.0/24,flanneld会在节点A上添加一条路由:

10.244.1.0/24 via 192.168.1.101 dev eth0

这里的192.168.1.101就是节点B的物理IP。整条链路就是:Pod A -> cni0 -> 节点A路由表 -> eth0 -> 节点B -> cni0 -> Pod B。没有额外封装,性能接近裸机网络。

但host-gw模式有一个非常硬的限制:所有节点必须在同一二层网络内。如果节点跨了VLAN、跨了三层网络,这条路由就走不通了,因为你不能假设一个二层的下一跳能直接跑到另一个网段去。所以host-gw只适合物理机在同一个网段、没有防火墙拦截的场景。云环境里如果你能保证节点在同一个VPC子网,也可以考虑用,但如果用云上VPC的路由表来打通的话,flannel文档不建议这么干,云厂商的路由数量有上限,Pod多了会超出配额。

3.3 UDP模式:一个只剩下教学价值的旧方案

flannel早期版本还有一个UDP模式。这个模式其实是在用户态做一次隧道封装,性能非常差,因为每个包都要经过两次内核态与用户态的切换。flannel官方在很早就默认不使用UDP模式了,因为性能比VXLAN差了一个数量级。

我看到很多老教程还在讲UDP模式,建议直接忽略。你不需要为了“全模式能力”或者“兼容性”去开启它。现在的flannel版本里,如果你不指定,默认就是VXLAN。UDP模式主要用于“没有VXLAN内核支持”这种极端历史场景,今天几乎不存在这种限制。

3.4 部署Flannel的配置参数详解

flannel的部署极简单,一条命令就能跑起来:

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

但不建议你直接盲用默认配置。我习惯先把YAML文件拉下来改一改再部署。有两个关键参数值得关注。

一个是net-conf.json里的Network字段,它决定了Pod的IP段。默认值是10.244.0.0/16。如果你集群里的Service CIDR用了其他网段,或者和其它网络的IP段冲突,就需要改这里。有两个注意点:这个值必须和kube-controller-manager的--cluster-cidr参数保持一致;改完之后整条网段就固定了,后期不能随意变更,否则IPAM会错乱。

另一个是Backend字段。默认是"Type": "vxlan"。你可以在这里指定"Type": "host-gw"切换到host-gw模式。如果在VXLAN模式下还想对VNI或者端口做修改,可以加"VNI": 1和"Port": 8472来指定。

这里有个我想强调的小坑:flannel的VXLAN模式里,默认使用的UDP端口是8472,但有些网络设备对高UDP端口有限制或者做了负载均衡的干扰。如果发现跨节点通信时有不稳定的丢包,可以换个不常用的端口,比如4789(VXLAN的标准端口),但改了之后所有节点的flanneld和防火墙规则也要同步改。

另外还有一个配置细节:flannel的PublicIP。在云环境里,节点往往有内网IP和外网IP,flanneld会自动探测节点的IP,但如果探测结果不对,比如探测到了外网IP,会导致VXLAN封装后的外层包无法被对方解析。处理办法是在flanneld的启动参数里指定--public-ip,或者在Deployment的环境变量里加FLANNELD_PUBLIC_IP,强制指定内网IP。

4. Calico深入拆解:BGP与完全路由

4.1 Calico的架构组件与核心设计

calico和flannel走的是截然不同的思路。flannel倾向于“隧道”,calico倾向于“路由”。

calico的核心组件有四个:

  • Felix:运行在每个节点上的DaemonSet,负责管理防火墙规则(iptables/ipset)、路由表,以及虚拟网卡。
  • BIRD(BIRD Internet Routing Daemon):负责BGP路由协议的实现。calico把每个节点都当成了一个BGP Speaker,节点之间通过BGP协议自动学习Pod网段的路由。
  • calicoctl:命令行工具,用于管理calico配置,比如查看节点状态、设置IP池等。
  • CNI插件(calico、calico-ipam):负责与Kubelet交互,创建网络和分配IP。

这套架构的本质是:每个节点都是一个自治的路由器,通过BGP路由协议,把Pod子网的信息在集群内广播。当节点A要访问节点B的Pod时,它只需要查询BGP学到的路由,把包直接发给节点B的物理IP即可。没有隧道封装,性能优秀。

4.2 IPIP模式与VXLAN模式:冠以“隧道”之名,其实各有所长

虽然calico主打无隧道,但它也提供了IPIP和VXLAN两种封装模式来应对“底层三层网络不可达”的场景。

IPIP模式是在原有的IP包外面再套一个IP包,协议号是4,外层IP头就是两个节点的物理IP。这个模式最大的好处是能穿越不同网段,只要外层IP能路由到即可。缺点是会让MTU变复杂,如果外层网络的MTU是1500,IPIP封装后整个包超出1500,会导致分片或丢包。因此在使用IPIP时,要么把底层网络MTU调大,要么把Pod的MTU减小。

calico的VXLAN模式跟flannel的VXLAN大同小异,都是UDP封装,端口默认4789。区别在于,calico在很多版本里对VXLAN的配置更灵活,并且可以和BGP模式混合使用。

我自己的经验是:如果你在同机房、同VPC的节点上跑,优先用BGP模式,性能最好;如果你的节点跨了多个不可直接路由的网段,那用VXLAN模式更稳,因为不需要对底层网络做过多假设。IPIP模式能不用尽量不用,它虽然能透明路由,但MTU问题在云环境里经常搞死人。

4.3 Felix如何实现NetworkPolicy

calico最大的卖点之一是NetworkPolicy。K8s原生NetworkPolicy在很多CNI方案里是“摆设”,但calico是真正把它们落实到iptables里的。

Felix在节点上维护一组资源:每个Pod的IP会被标记成的一个workload endpoint,Felix会为每个endpoint生成对应的iptables链。当你在集群里定义NetworkPolicy,允许A访问B但禁止C访问B,Felix会把这些规则转换成iptables规则,插入到节点的PREROUTING或FORWARD链中。

具体执行时,数据包从Pod A出来,到达节点的网络栈时,会经过cali-FORWARD等自定义链。Felix在这些链上放置了大量规则,用于匹配源IP、目标IP、协议、端口。一条Policy里的多个规则是OR关系,而不同Policy之间是AND关系,这一点我在排错的时候吃过亏。曾经我自定义了两个NetworkPolicy,一个只放行80端口,另一个放行了所有端口,结果发现流量还是不通,因为两个Policy之间取的是交集,规则严格的那个反而成了瓶颈。

4.4 BGP模式下数据链路拆解

假设集群有两个节点:节点A的Pod网段是10.48.0.0/24,节点B的Pod网段是10.48.1.0/24。它们在同一个二层网络里,calico采用BGP模式。

节点A上的BIRD会通过BGP协议向节点B宣告“10.48.0.0/24 在节点A上”,节点B同理。这样两个节点都维护了一张完整的路由表。节点A的Pod要访问10.48.1.5时,流量从Pod出去,到节点的tunl0或eth0(取决于是否封装),节点A查看路由表发现10.48.1.0/24的下一跳是节点B的物理IP,然后直接把包发过去。节点B收到后,根据路由表将包转到目标Pod。

相比flannel host-gw,calico BGP模式的最大优势就是“自动学习路由”。host-gw需要flanneld通过API Watch所有节点变化,然后手动增删路由表项;而calico通过BGP协议自动维护路由,节点多了以后,你可以在配置里启用Route Reflector来减少BGP连接数,但默认的全互联模式在节点数量超过100后会导致BGP会话数爆炸。

5. 网络异常排查与常用诊断工具

网络排查是基本功。我总结了一套“三层定位法”,遇到网络问题先按层次来排查:第一层是Pod内网络,第二层是节点到Pod,第三层是节点间的跨节点通信。

5.1 从Pod里看网络栈

怀疑Pod网络有问题,第一步是进入Pod看看自己的网络到底长什么样。

kubectl exec -it my-pod -- ip addr kubectl exec -it my-pod -- ip route

如果Pod的IP没有正常配置,你会看到eth0的IP为空,或者根本没有任何路由。这时就要去看Kubelet日志或者CNI日志:

journalctl -u kubelet | grep -i cni journalctl -u kubelet | grep -i "error"

有段时间我遇到Pod一直ContainerCreating,排查下来发现是cni0网桥被误删了,flannel插件的bridge插件没有成功重建网桥。Kubelet的日志里报的是“failed to setup network for pod”,这其实说明问题根本不在于K8s本身,而在于底层的CNI网络设备状态异常。这种情况有个笨办法但很有效:把所有Pod驱逐到一个节点,把这个节点的flannel pod重启,然后看网桥和路由是否恢复。

5.2 不同模式下抓包分析

跨节点通信慢或者不通时,抓包能直接告诉你问题出在传输层还是网络层。

在flannel VXLAN模式下,你在源节点抓包会看到UDP封装:

tcpdump -i eth0 udp port 8472 -nn

如果你看到外层目的IP是目标节点的IP,但目标节点没有回应,那说明目标节点的flannel.1设备有问题,或者UDP端口被防火墙挡了。如果你根本看不到8472端口的包,那问题更早,可能在源节点容器侧就断了。

在calico BGP模式下,抓包要看是否走了tunl0或者eth0直发。用ip route get 10.48.1.5可以确认下一跳。如果返回的是tunl0,说明当前是IPIP封装模式;如果返回的是eth0直发,说明是纯BGP路由模式。

如果跨节点流量通了但延迟高得离谱,先检查两个节点的MTU。Pod侧MTU如果大于底层MTU,就会导致分片,表现就是“时而通时而不通”,很典型。查看MTU可以用:

ip link show flannel.1 ip link show cni0

建议Pod侧MTU设置成底层网络MTU减去外层封装的头部大小。比如底层MTU 1500,VXLAN封装多了50字节,那Pod侧的MTU就是1450,这样才不会分片。

6. 常见问题速查表

网络排错大多数时候是重复劳动,我把高频问题和处理思路整理成一个参考表。

问题现象可能原因处理办法
Pod一直ContainerCreating,kubelet日志报CNI失败flanneld或calico CNI二进制缺失、配置文件权限错误检查/opt/cni/bin/下是否有对应二进制,重新apply CNI YAML
Pod能访问本节点其他Pod,跨节点不通VXLAN端口被防火墙拦截,或VTEP设备异常telnet测试8472端口,ip link show flannel.1确认设备存在
跨节点通但延迟高MTU不匹配导致分片检查底层网络MTU,调低Pod侧MTU
使用host-gw模式但跨网段节点不通节点不在同一二层网络检查节点间VLAN隔离,改用VXLAN模式
calico创建Policy后流量被切断Policy间是AND关系,规则过严检查所有Policy的selector和port,用calicoctl get networkpolicy查看配置
节点重启后Pod网段丢失etcd没有保存IPAM数据确认etcd备份正常,重启后检查calico/fannel的status
Pod IP分配重复CIDR规划冲突检查所有节点的podCIDR,确认没有重叠
访问Service有时通有时不通kube-proxy模式问题,或后端Pod IP变化检查kube-proxy日志,确认后端endpoints更新正常

这些场景里,最容易被忽视的两个:一是“防火墙拦截UDP”,二是“MTU不匹配”。如果集群网络看起来一切正常但延迟或丢包不稳定,先看这两项,往往比深入内核要快得多。

7. 选型建议与个人实操体会

7.1 适用场景对照

每次有人问我集群到底该选flannel还是calico,我的建议都是先看你的诉求。下面这份对照表简单粗暴:

对比项flannelcalico
部署难度极低,一条命令搞定相对复杂,需要关注BGP和IP池
性能host-gw接近裸机,VXLAN有损耗纯路由模式性能好,封装模式有损耗
跨网段支持VXLAN模式可以跨三层IPIP/VXLAN可以跨三层
NetworkPolicy不原生支持强大的原生支持
大规模集群节点多了会依赖API Watch,有压力BGP自动路由更适合大规模,但需部署Route Reflector
云环境适用性VXLAN模式对云网络很友好纯路由模式强依赖云网络支持

我的经验是,如果你只想快速搭一套内部用的集群,节点不多、没有强网络隔离需求,flannel是最省心的;如果你要上生产,Pod数量上千,有严格的访问控制需求,那calico是更合适的选择,前提是你愿意花时间理解BGP和它的iptables模型。

7.2 一些我在实操中才明白的细节

有些坑,只有自己跑过一遍才知道。

第一,无论用哪个插件,都建议先在测试环境验证“节点重启”这个场景。一台节点重启后,flanneld或calico的Felix需要在新节点环境里重新申请IPAM子网、重建网桥、恢复路由表。如果你的etcd没有备份,节点重启后Pod的IP可能错乱,导致集群状态不健康。

第二,别轻易在生产环境直接改CNI插件。我之前有过一次经历:为了想要NetworkPolicy,从flannel切换到calico,结果一切换就发现节点之间的路由还没收敛好,整整折腾了一个小时。如果要切换,准备一个完全独立的备份集群,或者干脆在业务低峰期做,做好回滚预案。

第三,calico的NetworkPolicy功能再强大,也不要滥用。每条Policy都会转化成大量iptables规则,规则数量过多时,节点的网络转发性能会下降得很明显。我建议你把规则控制在合理的数量区间,定期用calicoctl get ippool和calicoctl get bgppeer检查配置,把不再使用的规则清理掉。

第四,不管用哪个组件,都要留意“天然的MTU差异”——集装箱网络的MTU是围绕底层基础设施设计的,不是你想调就能调的。在云环境里,如果底层启用了Jumbo Frame(9000字节MTU),那VXLAN链路上的MTU可以做相应放大,但如果是普通1500的物理网络,别硬撑,老老实实设1450。

最后还想说一点:网络不是“配完就不管”的东西。Pod的IP漂移、节点扩缩容、防火墙策略调整,每一个变化都会影响网络层。建议你从一开始就做好基础网络基线记录,包括每个节点的Pod子网分配、路由表的关键路由、CNI配置文件,这些对日后故障排查的帮助,比你想象的还要大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询