ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Kubernetes网络深度解析:Flannel与Calico对比及NetworkPolicy实战

Kubernetes网络深度解析:Flannel与Calico对比及NetworkPolicy实战

接触 Kubernetes 的人,多半都经历过这种场景:Pod 全是 Running,Service 也创建成功了,但应用就是访问不通。查日志、看状态、翻事件,折腾半天,最后发现锅在网络层。Kubernetes 的网络和传统虚拟机那套完全不是一回事,它抽象出了一套网络模型,具体实现交给 CNI 插件,而 Flannel 和 Calico 是这里面最主流、也最能代表两种技术路线的方案。这篇文章把 Kubernetes 网络通信这件事从头拆一遍,讲明白 Flannel 和 Calico 各自怎么工作、核心区别在哪、NetworkPolicy 网络策略又是怎么落地的。适合正在学 K8s 网络的新手,也适合准备给生产集群做网络选型的同学,看完可以直接拿结论指导实际操作。

1. Kubernetes 网络模型的底层逻辑

1.1 四个通信场景和一个叫 CNI 的约定

Kubernetes 的网络模型其实只回答了四个问题:Pod 和 Pod 之间能不能直连?Node 和 Pod 之间能不能直连?Pod 看到的 IP 和外面理解的 IP 是不是同一个?以及 NAT 这东西在集群内部到底该不该存在。官方的答案很干脆:所有 Pod 之间必须能直接通信,不需要 NAT;Node 上的 agent(比如 kubelet)也能直接访问 Pod;Pod 里看到的自己的 IP,就是别人访问它的那个 IP,没有地址转换。

这个模型本身没有实现细节,它只是一套约定。真正干活的是 CNI(Container Network Interface)。可以这么理解:Kubernetes 是业主方,CNI 是施工队。当 kubelet 需要创建一个带网络的 Pod 时,会调用 CNI 插件,插件负责把虚拟网卡插进容器、分配 IP、配路由,干完活汇报给 kubelet,然后这个 Pod 就"通了"。

Flannel 和 Calico 都是施工队,但施工方案完全不同。Flannel 的思路是打通一条虚拟通道,让各节点上的 Pod 网段互相"看得见";Calico 的思路更直接,把每个节点当路由器,用 BGP 协议把路由信息广播出去,纯三层转发。理解了这个分野,后面所有细节都能串起来。

1.2 Pod 网段是怎么切分的

集群在初始化的时候会设定一个 Pod CIDR,比如常见的 10.244.0.0/16。这个网段会被切成很多个小段,按节点分配,默认每个节点拿一个 /24,也就是 254 个可用 IP。比如有三个节点,那它们可能分别拿到 10.244.1.0/24、10.244.2.0/24、10.244.3.0/24。每个节点上的 Pod 只能从自己这个 /24 里拿 IP,不能跨段分配。

这个机制保证了 IP 不冲突,但也引入了路由问题:在 Node A 上的 Pod(10.244.1.5)访问 Node B 上的 Pod(10.244.2.8)时,数据包出了 Node A,谁会把它送到 Node B?宿主机内核只知道本机路由,它必须有一条规则说"目标 10.244.2.0/24 要走某某网关"。这条规则谁来加?就是 CNI 插件。Flannel 和 Calico 的核心工作之一,就是在每个节点上维护这样一套路由规则,保证任何一个节点的 Pod 网段,都能被集群里其他节点找到。

这里有一个新手容易踩的坑:很多人以为 Pod 跨节点通信,数据包是直接通过目标 Pod 所在节点转发的。实际不是,数据包是先发到宿主机,由宿主机根据路由表决定下一步,而不是由 Pod 里的容器自己发出去。容器里的默认网关永远是它所在节点上的那个虚拟网卡,容器的路由视野只有一跳,剩下的全靠节点内核。

2. Flannel:轻量网络铺路的经典方案

2.1 flanneld 守护进程和子网分配

Flannel 的架构非常朴素,核心就是一个叫 flanneld 的守护进程,以 DaemonSet 方式跑在每个节点上。它启动之后,先去集群的配置中心(早期是 etcd,现在直接走 Kubernetes API Server)注册自己,申请一个子网段,拿到比如 10.244.1.0/24 这个段。这个信息会同步到集群,其他节点上的 flanneld 通过 watch 机制拿到所有节点的子网分配表。

所以 Flannel 的控制平面非常简单:谁在哪个节点、用了哪个 /24、这个节点的 IP 是什么,这些信息在集群里互相可见。剩下的问题就只有一个——怎么把包从这个节点送到那个节点。Flannel 为此提供了好几种后端模式,其中最常用的是 VXLAN 和 Host-GW。

我在实际使用中的体会是,Flannel 之所以流行,很大程度上是因为它"不挑环境"。只需要一个能跑 Kubernetes 的集群,不需要网络设备配合,也不要求节点之间有特殊连通性。对刚接触 K8s 的人来说,先装一套 Flannel 把集群跑通,是最省心的路径。

2.2 VXLAN 模式:隧道里跑二层帧

VXLAN 是 Flannel 的默认后端,也是理解成本最高的部分。它的思路是:既然节点间原来不互通,那就干脆在节点之间架一条"隧道",把 Pod 发出的数据帧整个装进一个新的 UDP 包里,通过宿主机网络发送。

具体拆包看更直观。当 Node A 上的 Pod 要访问 Node B 上的 Pod 时,原始数据包是一个普通的二层帧:目标 MAC 是 Pod A 所在节点的虚拟网卡地址,目标 IP 是 Pod B 的地址。Flannel 会在每个节点上创建一个虚拟网卡 flannel.1(这就是一个 VTEP,VXLAN Tunnel Endpoint)。发送前,原始帧被当作 payload,外面包上 VXLAN Header(8 字节)、UDP Header(8 字节)、外层 IP Header(20 字节),总开销约 36 字节。这个新包的目标 IP 是 Node B 的物理 IP,走的完全是宿主机网络。

这里就牵出一个关键参数:MTU。物理网卡的 MTU 一般是 1500,VXLAN 封装多出 36 字节,如果 Pod 网络的 MTU 也设成 1500,就会导致分片。Flannel 默认把 Pod 侧 MTU 设成 1450,就是把封装开销提前扣除。你如果用默认配置在 1500 MTU 的物理环境下跑,没问题;但你要是上了 jumbo frame(物理 MTU 9000),就得手动调大,这个不调的话,性能会非常难看。

VXLAN 模式还有一个特点:它不要求节点间二层互通,只要三层路由能通就行。这对跨机房、跨云环境很友好。代价就是每个包都要多一轮封装和解封装,CPU 开销明显,性能比 Host-GW 低一些。

2.3 Host-GW 模式:最快但也挑环境

Host-GW 是 Flannel 的另一个高频后端。它不做任何封装,思路极其简单:既然 Pod 网段在集群里已经互相知道了,那就直接在节点的路由表里加一条规则——目标 10.244.2.0/24,网关是 192.168.1.20(Node B 的 IP)。这样数据包到了 Node A 内核,看一眼路由表,直接通过物理网络发给 Node B,中间没有任何隧道头。

性能上的差异是实打实的。Host-GW 的转发路径和原生路由没有本质区别,几乎是线速转发。我在实际压测里看到,Host-GW 比 VXLAN 的吞吐可以高出百分之二三十,延迟也低。但它的硬性要求是:所有节点必须在同一个二层网络里。因为 Host-GW 是在路由表里写"下一跳是某个具体 IP",如果节点跨了子网、中间隔着路由器,那这个下一跳可能根本不可达,包就丢了。跨 VLAN 或多机房环境,Host-GW 基本没法用。

2.4 Flannel 的软肋:网络策略等于零

Flannel 从头到尾只解决连通性,不解决隔离和管控。它没有实现任何 NetworkPolicy 语义,因为 NetworkPolicy 要求 CNI 插件能在数据路径上做 ACL 拦截,Flannel 的数据路径根本不设计这个能力。换句话说,你用 Flannel 搭起来的集群,所有 Pod 之间默认都是全通的,想限制某些 Pod 不能被另一些访问,Flannel 什么也做不了。

这不是 Flannel 的 bug,而是它的定位决定的。它目标就是"把路修好",至于路修好之后谁能在哪条路上走、流量该怎么管,不在它的职责范围内。所以后面很多人把 Flannel 和 Calico 拼起来用,把连通性交给 Flannel,把策略交给 Calico,这个方案有个专有名字叫 Canal,这个后面在选型部分再展开。

3. Calico:把路由和策略做进骨子里的选手

3.1 每个节点都是一台路由器

Calico 的架构和 Flannel 完全不同。它的核心思想是纯三层:每个节点都扮演一台路由器,节点上的 Pod 子网就是这台路由器直连的网段。为了让每台"路由器"都知道整个集群的路由信息,Calico 引入了 BGP 协议。

BGP 这个名字听着吓人,但在这里它的作用很简单:让所有节点互相交换"我这里有哪个网段"的信息。比如 Node A 通过 BGP 告诉大家"10.244.1.0/24 在我这",Node B 收到后,在自己的路由表里加一条"去 10.244.1.0/24,下一跳是 Node A"。所有节点都这么做,最终每个节点都知道了全量路由。这个过程完全不需要隧道,数据包就是实打实的三层路由转发。

Calico 在每个节点上跑两个关键组件:Felix 和 BIRD。Felix 负责管理本机 iptables 规则和路由写入,BIRD 负责 BGP 协议的路由广播。Felix 监听 Kubernetes API Server,一旦发现新的 Pod 创建,就会把它对应的路由和 IP 信息写好;一旦发现 NetworkPolicy 变化,就会更新 iptables 规则。控制面和数据面各司其职,配合非常严密。

3.2 IPIP 隧道:跨子网时的 B 计划

Calico 默认的 Route 模式有一个前提:节点之间三层可达,但 BGP 邻居通常要求直连或者网络设备能配合。如果节点跨了子网,中间的路由设备不认识 BGP,那路由就广播不过去了,怎么办?Calico 给了一个折中方案:IPIP 模式。

IPIP 也是一种隧道,但比 VXLAN 轻得多。它把原始的 IP 包直接装进一个新的 IP 包里,外层 IP 头 20 字节,没有 UDP 和 VXLAN 头。新增的封装开销比 VXLAN 少 16 字节。你在节点上会看到一个叫 tunl0 的虚拟网卡,数据包通过它做隧道转发。

实际使用中,我一般建议:节点在同一二层网络里,优先用 Calico 的 Route 模式(不开隧道,也叫 Direct 模式);跨子网又没法改网络设备的,再用 IPIP。IPIP 的性能虽然比 VXLAN 好一些,但比纯路由模式还是有差距,能不开隧道就不开。

3.3 策略引擎:Calico 的立身之本

Calico 的 NetworkPolicy 支持是最完整的。它的做法是:Felix 把策略翻译成 iptables 规则,配合 ipset 做高效匹配。每个工作负载(Pod)对应一组规则链,流量进来先匹配链上的规则,符合就放行,不符合直接 drop。

这里有一个很多人会忽略的细节:Calico 实现了两种策略接口,一个是 Kubernetes 官方的 NetworkPolicy,另一个是 Calico 自己扩展的 NetworkPolicy(CRD 方式)。后者支持更细粒度的规则,比如按 ICMP 协议过滤、按源端口过滤、支持 deny 规则(官方 NetworkPolicy 只有 allow,没有显式 deny)。如果你需要"默认全部拒绝,只放行特定来源"这种强管控,Calico 的 CRD 策略比官方原生策略好用得多。

我在生产环境里见过不少团队,因为不了解这个差异,硬是用原生 NetworkPolicy 写不出想要的规则,最后绕了一大圈改用 Calico CRD 才解决。

4. Flannel 与 Calico 的选型对照

4.1 一张表看清核心差异

特性Flannel(VXLAN/ Host-GW)Calico(Route/ IPIP)
数据路径隧道(VXLAN)或宿主路由(Host-GW)纯三层路由(或 IPIP 隧道)
是否需要节点二层互通VXLAN 不需要,Host-GW 需要Route 模式需要,IPIP 不需要
NetworkPolicy 支持不支持完整支持,另有 CRD 扩展
跨子网能力VXLAN 可以直接跨IPIP 模式可以跨
部署复杂度低,manifest 一把梭中高,涉及 BGP 配置
性能中(VXLAN)到高(Host-GW)高(Route)到中高(IPIP)
故障排查难度相对容易需要懂路由和策略表

这张表列完之后,你会发现选型其实就是在回答三个问题:节点之间能不能二层互通?需不需要网络策略?运维同学对网络原理熟不熟?

4.2 不同场景下的推荐方向

先说最省心的场景:本地开发环境或者测试集群,节点就几台,跑在同一个二层网络,不需要管控谁访问谁。这种情况 Flannel 直接上,Host-GW 模式性能也好,出问题也好排查。你不需要学 BGP,不需要理解 iptables 链,遇到 Pod 不通,看一眼路由表和 flannel.1 网卡就能定位。

如果是生产环境,节点上了几十台、上百台,还对安全有要求,那就得认真考虑 Calico。它的 BGP 路由反射器模式可以支撑大规模集群,策略引擎也是生产环境做网络隔离的刚需。但注意,Calico 的部署和运维复杂度确实高一个台阶,你得留出学习成本。

还有一个中间态方案:Canal,也就是 Flannel 加 Calico 的组合。数据平面用 Flannel 的 VXLAN,策略平面用 Calico 的 felix,两者互补。这个方案的好处是接入成本比纯 Calico 低,不需要关心 BGP 部署细节,但又拿到了 NetworkPolicy 的能力。我见过不少中小规模生产集群用 Canal 跑得很稳。缺点是它在策略层面用的是 Calico 的 felix,但网络本身又是 Overlay,性能会稍弱,调试时也要同时看两套组件。

实际选型的时候还有个参考:节点规模 50 台以下、不需要 NetworkPolicy,选 Flannel;规模上去了或者安全合规要求高,选 Calico。不用纠结,这个选择题的现实答案从来没有"唯一正确",只有"当前阶段最合适"。

5. 网络策略(NetworkPolicy)的落地与实战

5.1 策略模型:谁能访问谁,用标签说话

NetworkPolicy 本身不难理解:你选中一组 Pod,然后声明谁能访问它们的哪些端口。关键在于,Kubernetes 用 Label Selector 而不是 IP 来圈定范围。这个设计我一开始觉得很不直观,但想明白之后发现这才是容器时代正确的抽象方式——Pod 是动态的,IP 会变,标签是稳定的,策略应该跟着标签走。

一个最基础的策略是这样:允许所有带 app=api 标签的 Pod 访问带 app=web 标签的 Pod 的 8080 端口。

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-api-to-web namespace: prod spec: podSelector: matchLabels: app: web policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: api ports: - protocol: TCP port: 8080

这个 yaml 有三层关键信息:podSelector 是"策略作用在谁身上";policyTypes 声明了只处理入站流量;ingress.from 里的 podSelector 是"允许谁进来"。注意 ingress.from 下面还可以写 namespaceSelector 和 ipBlock,用来实现跨命名空间和按网段放行。

5.2 一个完整的默认拒绝模型

生产环境最常见的做法是"命名空间级的默认拒绝"。也就是说,这个命名空间里所有 Pod 默认不接收任何入站流量,然后针对具体服务一条条放行。实现方式非常巧妙:只要创建一个 podSelector 为空的 NetworkPolicy,即选中所有 Pod,并且 rules 为空,就相当于下了全局禁入令。

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress namespace: prod spec: podSelector: {} policyTypes: - Ingress

注意这里 podSelector 写空对象 {},含义是"选中所有 Pod"。策略一旦生效,prod 命名空间里所有 Pod 的入站流量全部被拒。这个模式我在实际项目里非常推荐,因为如果先默认开放、再按需收紧,很容易漏掉一两个服务导致安全隐患。反过来,先默认拒绝,每加一个策略就精确放行一组流量,整个安全边界是可控的。

但注意,创建了默认拒绝策略之后,你可能发现集群里一些系统组件访问 Pod 也被挡了。比如监控采集器要拉取 metrics 接口,或者 Service Mesh 的 sidecar 要转发流量,这些都需要单独写策略放行。我就踩过这个坑,一条默认拒绝下去,监控数据全断了,排查半天才发现是策略把监控 Pod 的访问拦了。所以做默认拒绝前,先把你要放行的流量清单列出来,再动手。

5.3 排查策略不生效的几个高频原因

NetworkPolicy 不生效,百分之八十是下面几个原因。

第一个:CNI 根本不支持 NetworkPolicy。用 Flannel 搭集群然后写了 NetworkPolicy 发现没用,是最容易掉进去的坑。Flannel 本身没有策略引擎,规则编写得再对,也没有组件去执行它。要么换 Calico,要么把 Canal 方案落地,否则这个坑绕不开。

第二个:策略选的命名空间不对。NetworkPolicy 是命名空间级的资源,你写策略的时候如果没注意 namespace,podSelector 匹配的 Pod 范围就跟你想的完全不同。跨命名空间访问更没有"全局"这种说法,都得靠 namespaceSelector 显式声明。

第三个:policyTypes 没写。如果不显式声明 policyTypes,默认只有 Ingress 生效,你想限制 Egress(出站)的话就会被静默忽略。我见过有人写策略时忘了加 Egress,结果 Pod 照样能往外访问数据库,排查好久才发现是这个问题。

第四个:多个策略叠加时的"并集"逻辑。Kubernetes 对同一组 Pod 的多个 Ingress 规则是取并集的,只要匹配上任意一个就放行。这意味着你不能用两条分离的 deny 策略去模拟"排除某个来源",因为并集逻辑下,只要有一条策略没匹配到那个来源,数据包就被拒;但如果你有一条宽松的 allow 策略存在,所有来源就都被放行了。想要精细的 deny 逻辑,只能靠 Calico CRD 的显式 deny。

5.4 我在生产环境的排查路径

遇到"Pod 能通但 Service 不通""策略写了但没生效"这类问题,我一般按固定路子排查。先确认 CNI 类型,是 Flannel、Calico 还是其他;再确认策略本身有没有被 API Server 接受,kubectl get netpol 看列表;然后确认 Pod 的标签和策略里 podSelector 的标签是不是匹配上了。这一步最容易走眼,标签多了个空格、少了前缀就全白写。

再往下就是看数据路径了。如果是 Calico,我会用 calicoctl get hep 查工作负载端点,确认 Felix 为这个 Pod 创建了对应的策略链;再 iptables -L 看具体规则是否按预期生成。如果是 Flannel,先 ip route 看节点路由表对不对,再看 flannel.1 的状态。大部分问题到这一步都能定位。

这里分享一个小技巧:如果你想快速验证 NetworkPolicy 是否生效,不要用生产服务试,起两个临时的测试 Pod,一个带客户端标签,一个带服务端标签,一个带完全不匹配的标签,分别请求一次。几秒钟就能验证通和不通两种情况,比盯着日志猜高效太多。

6. 常见问题与排查实操

6.1 跨节点 Pod 不通,怎么排查

这个问题是 K8s 网络里出现频率最高的。节点的 Pod 都能起来,同节点的 Pod 互通也没问题,但跨节点就是不通。先看节点路由表有没有对应的 podCIDR 路由。以 Flannel 为例,ip route 应该能看到类似 10.244.1.0/24 via 192.168.1.10 这样的条目。如果缺了这条路由,说明 flanneld 没正常工作,去看 flannel Pod 日志。

如果路由在,那就抓包看数据包有没有到对端节点。在目标节点上 tcpdump -i flannel.1 或者 -i eth0 抓 VXLAN 的 UDP 4789 端口流量。包到了但 Pod 还是不通,大概率是 MTU 不一致导致的。检查 Pod 侧 MTU、veth 的 MTU、物理网卡的 MTU 是否匹配。我在云环境下就遇到过底层网络自动加了一层封装,物理 MTU 实际只有 1400,Flannel 默认的 1450 反而偏大,所有跨节点大包全部丢。

6.2 Calico 集群遇到的路由反射问题

Calico 大规模集群如果启用了 BGP Route Reflector,节点的路由条目是靠反射器分发的。Reflector 挂了或者配置错,整个集群的路由就会部分失效,表现为某些节点之间通,某些不通。排查方法是看 calico-node Pod 的日志,确认 BGP session 是 Established 状态;再用 calicoctl node status 看每个节点的对等状态。状态不是 Established 的,去查 BGP peer 配置里的 IP 和 AS 号,多半是配错了。

生产环境里我把 Reflector 放在专门的节点上,用 nodeSelector 固定住,避免它漂移。Reflector 挂掉的影响是全局性的,建议至少部署两个形成冗余。

6.3 实测总结:MTU、安全组和防火墙

把常见的网络不通问题汇总成一个速查思维模型:先看路由,再看防火墙/安全组,最后看 MTU。这个顺序可以覆盖绝大多数场景。路由是"包往哪走",防火墙是"包能不能走",MTU 是"包能不能走完整"。这三个层面挨个查,很多看似玄学的网络问题都能落地。

安全组这个点尤其重要。云环境里节点安全组没放行 VXLAN 的 UDP 4789 端口、或者 Calico 的 BGP 179 端口,表现就是集群内部间歇性不通或完全不通。有一次我排查了很久,发现是同集群节点间 ICMP 放行但 UDP 4789 没放行,tcpdump 看得到请求发出去但没有响应——因为响应被安全组拦了。这个坑写出来,希望后来人少走几步弯路。

6.4 给新人的一条实操建议

如果你刚接触 K8s 网络,我特别建议你自己动手搭一个最小集群,先装一遍 Flannel,看它怎么分配子网、路由表长什么样、flannel.1 网卡的数据流向;然后把集群拆了,再装一遍 Calico,对比看它有没有 tunl0 网卡、路由表多了哪些条目、NetworkPolicy 生成后 iptables 里多了什么链。对比着看一遍,比看十篇文章都有用。我在带新人的时候,几乎都是用这个流程让他们建立 K8s 网络直觉的,效果比直接讲原理好得多。

我个人在实际操作中的体会是:Kubernetes 网络本身不复杂,复杂的是把网络模型、CNI 插件和实际数据路径对应起来。Flannel 和 Calico 没有绝对的好坏,关键是先搞清楚你的集群环境、安全需求和运维能力,再选合适的那一个。选完之后也别忘定期检查路由表和策略链,网络问题最好的解法,永远是尽早发现问题、尽早定位问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进