☰
K8s网络实战:Flannel VXLAN、Calico替换与NetworkPolicy策略
2026/10/21 12:46:43 网站建设 项目流程

在企业里做Kubernetes维护,网络这块迟早要正面硬刚。我自己的学习路径就是边踩坑边补课,这篇笔记对应的是我整理的第4部分:k8s网络通信、Flannel vxlan模式、Calico替换,以及NetworkPolicy策略的实战总结。如果你正在被“Pod间通不通”“跨节点访问卡顿”“网络策略不生效”这些问题困扰,这篇文章应该能帮你把脉络理顺。

先说清楚这篇文章覆盖什么、适合谁看。它不是一个从零开始的K8s教程,而是围绕“网络”这个主题的实操笔记。默认你已经有一个能跑的集群,知道kubectl、kubeadm这些基本操作,但对网络原理和插件选型还比较模糊。我会用实际运维中遇到的问题作为切入点,把Flannel vxlan的封装原理、Calico替换Flannel的具体步骤、以及NetworkPolicy的配置范例逐一拆开讲,并附上我在真实环境里踩过的坑和排查方法。

1. k8s网络通信到底在解决什么问题

1.1 先理解Pod网络模型的设计初衷

Kubernetes网络模型最核心的一条规则是:所有Pod拥有独立的IP地址,且任意两个Pod之间可以直接通信,不分是否跨节点。这个设计把Pod当作一台台“微型服务器”来对待,应用层不需要关心底层物理拓扑,写代码时不用感知目标Pod到底在哪个节点上。

但这只是理想状态,底层实现却非常复杂。你想想,一台物理机上可能跑着几十个Pod,它们各自持有独立的IP;当Pod A在节点1、Pod B在节点2时,节点1发出的数据包怎么到达节点2上的Pod?在传统网络环境下,两个节点间的物理路由根本不知道这些虚拟IP的存在。所以,所有网络插件的本质工作,都是在物理网络之上“搭建”一层能让这些虚拟IP互通的路由或隧道。

我在学习初期最大的困惑是:既然Docker已经给容器分配了IP,为什么K8s还要再定义一套网络模型?实际上,Docker的NAT模式不满足K8s的通信模型要求,因为默认情况下容器IP被隐藏在后面,无法实现跨宿主机直接寻址。K8s要求的是“IP直达”,而不是靠端口映射。正是这个需求差异,催生了CNI(Container Network Interface)这一整套插件体系。

1.2 三大类通信场景拆解

K8s集群里的网络通信,可以归纳成三个维度,理解了它们就理解了大部分排障思路。

第一类是Pod内容器之间的通信。一个Pod内部的多个容器共享同一个网络命名空间,它们共用Pod的IP和端口。这类通信最简单,走localhost就可以。我们遇到的操作其实很少,因为实际部署大多是一Pod一容器,但这个设计是理解Pod网络沙箱的前提。

第二类是同一个节点上Pod与Pod之间的通信,这类场景的核心是veth pair加网桥。每个Pod里有一个veth设备,一头连Pod的网络命名空间,另一头挂在节点的cni0网桥上。同节点Pod互访时,数据包从源Pod通过veth到达cni0,再由cni0根据目标IP找到对应的另一个veth,从而进入目标Pod。整个过程都在宿主机内核里完成,不涉及外部网络,所以速度非常快。

第三类就是跨节点通信,也是最容易出问题的一环。数据包需要从节点1的cni0网桥出去,经过一条“通道”到达节点2,再进入节点2的cni0网桥。这条路怎么修,就是Flannel、Calico这些CNI插件各自发挥能力的地方。Flannel的vxlan模式负责在节点间架设VXLAN隧道,Calico则走的是纯三层路由,各有优劣。我们后续重点讨论的就是这一层。

1.3 CNI插件的接入方式

K8s本身并不实现网络功能,而是定义了一套接口规范,让外部插件来干活。这个接口就是CNI。每台节点的kubelet启动时会读取CNI配置目录(通常是/etc/cni/net.d/),并根据配置调用对应的插件二进制(通常在/opt/cni/bin/)来给Pod设置网络。

这里面有个运维上容易忽视的细节:CNI配置文件的命名顺序是有讲究的。kubelet会按文件名顺序读取配置,如果目录里同时存在多个配置文件,它会选择第一个可用的,并且这个文件的内容里一般会指明“type”字段对应的插件名。像我们自己维护的集群里,从Flannel切换Calico后,必须确认/etc/cni/net.d/下旧Flannel的配置文件被完整移除,否则即使Calico已经部署,节点仍然可能用旧插件去配置新Pod,造成Pod一直Pending或网络异常。

我个人的学习建议是:不要把CNI插件当成一个黑盒,遇到问题时先去节点上看CNI配置目录和插件日志。大部分网络插件异常,都能在/var/log/里找到线索。

2. Flannel vxlan模式的工作原理与部署要点

2.1 VXLAN隧道的基本概念

VXLAN是一种网络虚拟化技术,全称是Virtual Extensible LAN。它解决的问题是:让二层数据帧可以跨越三层网络传输,同时还支持更大的网络隔离空间。在K8s场景下,Flannel把每个节点当作一个VTEP(VXLAN Tunnel End Point),在节点间建立逻辑隧道。

用生活化的类比来解释:假设你在两个城市各有一间办公室,但希望它们像在同一个楼层里一样能直接通信。VXLAN就是在两个办公室之间修了一条虚拟的、看不见的走廊,数据包在走廊里传输时被套上了一层“信封”,信封上写的是两个城市之间真实可路由的物理IP。到了目的地,拆开信封,把里面的原始数据交给目标办公室。

具体到数据包形态:当节点1上的Pod A要给节点2上的Pod B发包时,原始数据包的目的IP是Pod B的虚拟IP。这个包会先被送到节点1的flannel.1网卡(也就是VTEP),然后Flannel依据内部维护的路由表,查找Pod B所在的节点2的物理IP,再通过Linux内核的VXLAN协议栈,在原始数据包外面包上一层UDP头(默认端口8472)、VXLAN头和外层IP头,然后从节点的物理网卡发送出去。节点2收到后,内核解掉UDP和VXLAN头,还原出原始的Pod IP数据包,交给目标Pod。

2.2 数据流转的完整步骤

下面我把一次跨节点Pod通信拆成详细步骤,你在排查网络问题时会非常有用:

  1. 源Pod内的进程通过veth pair把数据包送到Pod命名空间的eth0,此时目的IP是目标Pod的IP。
  2. 数据包到达节点根的cni0网桥,网桥查找转发表,发现目标IP不在本节点,于是把包转发给路由表指定的下一跳——通常是flannel.1。
  3. flannel.1收到数据包后,查内部FDB表或内核路由,判断目标Pod IP对应的宿主机物理IP。
  4. 内核执行VXLAN封装:原始以太网帧 + UDP头 + VXLAN头 + 外部IP头(源为节点1物理IP,目的为节点2物理IP)。
  5. 封装后的数据包从节点1物理网卡发出,经过交换机等物理设备到达节点2。
  6. 节点2内核识别到目的UDP端口是8472,剥掉外层IP、UDP、VXLAN头,还原出原始数据帧。
  7. 还原后的帧进入节点2的cni0网桥,最终通过veth到达目标Pod。

如果你要验证这个流程,可以在源节点上运行tcpdump命令,抓物理网卡上发往目标节点物理IP的UDP 8472端口的数据包。如果看不到这类包,说明数据在封装前就被丢弃或路由有问题;如果看到了但Pod还是不通,问题多半在目标节点的解封装或网桥转发环节。

2.3 配置文件里的关键参数

Flannel部署所用的yaml文件中,核心配置段通常长这样:

net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan", "Port": 8472 } }

这里有两个关键参数。Network字段定义了整个集群Pod网段,所有节点的Pod子网都会从这个大网段里划出。Port是VXLAN使用的UDP端口。运维人员要注意的是:如果节点间的防火墙或者云平台安全组没有放行UDP 8472,Flannel的节点间通信就会中断,表现出来就是跨节点Pod不通,但同一节点内正常。检查防火墙规则时,往往只放了TCP端口,UDP端口被遗漏是常见盲区,我就踩过两次。

另外注意,Flannel还有一个host-gw后端模式。这个模式不走VXLAN封装,而是直接在节点上写路由规则,把对端Pod网段的下一跳指向对端节点IP,性能比vxlan更好。但host-gw要求所有节点在同一个二层网络里,否则需要额外的路由协议支持,比较脆弱。在企业网络规划中,vxlan模式因为能跨三层网络,通用性更强,所以很多集群初始部署时都默认用它。这也符合题目里提到的“vxlan模式简介”这个定位——上手容易,对网络底层要求低。

2.4 性能特征与适用场景

VXLAN模式在网上被抨击最多的就是性能损失。由于每个数据包都要做UDP封装和解封装,CPU消耗明显增加,网络吞吐和PPS(每秒数据包数)都会下降。对于内部服务调用频繁、网络包小而多的业务,这个性能损耗不可忽略。

我在一次压测中就体会到差距:用iperf3测试跨节点TCP吞吐,裸物理网络能跑到接近网卡上限,而Flannel vxlan模式大约只有物理带宽的70%-80%,延迟也有所增加。如果业务对延迟和吞吐敏感,就必须把网络插件替换提上日程,这也是后面我们讨论Calico替换的现实诱因。

3. Calico替换Flannel的实战记录

3.1 为什么决定换掉Flannel

触发我们替换Flannel的直接原因是:业务方需要在命名空间级别做网络隔离,而Flannel vxlan模式本身不提供NetworkPolicy支持,哪怕装一个额外的组件也不稳定。Calico在原生层面就支持NetworkPolicy,还提供API可观测性,加上它的BGP路由模式性能比VXLAN封装好得多,所以替换势在必行。

支撑这个选择的数据也有:官方测试和社区各类评测都显示Calico在吞吐、延迟、每秒新建连接数上优于Flannel vxlan,在小包场景下尤其明显。另外,Calico支持的路由模式可以避免UDP封装CPU开销。还有一点容易被忽视:Calico不允许Pod网段和主机网段重叠,替换前要做好IP规划,如果是老集群,这步必须仔细评估。

3.2 替换前需要摸清的底细

替换一个网络插件,远比删除DaemonSet、重新部署另一个插件要复杂。最怕的是清理不干净,旧路由规则残留在节点上,和新插件的新规则产生冲突,导致网络时通时不通。

我建议在动手前做四件事:记录当前所有节点的路由表(ip route),记录CNI配置文件内容,记录Flannel相关网卡和网桥信息(flannel.1、cni0),把核心业务的Pod数量、节点数量列清楚。这样如果回滚,也能恢复到原始状态。

另一个关键决策是:替换后Pod的IP网段要不要变?Calico部署时默认会分配一个IP池,例如192.168.0.0/16,而旧Flannel用的是10.244.0.0/16。如果业务侧有配置白名单或安全策略基于网段,就需要在新IP池中配置成和原来相同的网段,避免替换后IP段变化引发访问控制失效。

3.3 整体替换步骤记录

第一步,在集群中删除Flannel DaemonSet和相关资源:

kubectl delete -f kube-flannel.yml kubectl delete daemonset kube-flannel-ds -n kube-flannel

第二步,清理各节点上的Flannel残留。每台节点都要执行:

rm -f /etc/cni/net.d/10-flannel.conflist ip link delete cni0 ip link delete flannel.1

注意,删除flannel.1网卡时可能提示设备忙,可以先停掉kubelet再清理,清理完再启动。

第三步,部署Calico。这里我建议使用版本对应的operator方式或手动yaml,取决于集群版本。以手动yaml为例,安装前先修改Calico的ConfigMap里的IP池配置:

ipPool: cidr: 10.244.0.0/16 ipipMode: Always natOutgoing: true

CIDR必须和原Flannel网段一致,避免业务侧IP变化。ipipMode这里我暂时用Always保证跨节点三层网络兼容;如果你的节点在同一个二层网络里,可以改成Never并开启BGP,走纯路由模式,性能更佳。

第四步,部署Calico并确认:

kubectl apply -f calico.yaml kubectl get pods -n calico-system -w

等所有calico-node Pod处于Running后,验证节点网络:

kubectl get nodes kubectl get pods -A -o wide

再到节点上看路由,Calico会在每台节点写入大量的192.168或10.244开头的路由,下一跳指向对端节点IP。

第五步,压力测试和连通性检查。启动一个测试Pod,分别与同节点Pod、跨节点Pod通信,再用sysbench或iperf3对跨节点网络做一轮性能和连通性验证。

这整个过程最坏的情况是替换后部分节点网络异常,所以强烈建议在测试集群先完整演练一遍。理论上这个过程可以压缩在窗口期内完成,但前提是每个步骤都提前准备好了清理脚本和验证脚本。

3.4 替换过程中遇到的两个典型问题

第一类问题是残留路由引发路由冲突。替换Calico后,节点上有时会残留Flannel旧的下发路由,导致某些目标IP的数据包仍走旧的flannel.1或cni0设备。排查方法很简单,在出问题的节点上执行ip route,看是否有指向异常设备的10.244网段路由。如果有,通过ip route del批量删掉即可。反复出现这类问题时,说明Flannel的清理并没有彻底,需要回去重新检查CNI目录和kubelet日志。

第二类问题是MTU不匹配。Flannel vxlan模式的MTU是1450(扣除了VXLAN头部的开销),而Calico在IPIP模式下的MTU也是1480(类型不同),BGP纯路由模式则用物理网卡的1500。如果新旧配置期间Pod的MTU和节点实际允许的MTU不一致,会出现“能ping通但大包传输失败”的诡异现象,比如SSH登录卡住、文件传输中断。遇到这类现象,优先检查容器网卡MTU。排查命令是:

ip link show eth0

在Pod内查看,如果MTU不是预期值,通常是Calico的felix配置没有正确下发,需要重启calico-node Pod或检查MTU相关配置项。

3.5 Calico和Flannel在运维视角的对比

从运维角度列一张对比表,方便后面选型参考:

对比项Flannel vxlanCalico(BGP路由模式)
网络模型Overlay隧道封装纯三层路由
跨三层网络支持,依赖UDP封装需要IPIP模式或跨子网BGP
性能损耗高,UDP封装解封装低,直接查路由表转发
NetworkPolicy不支持或支持弱原生支持且功能丰富
部署复杂度低,一条命令中等,需配置IP池和BGP
可观测性弱强,calicoctl工具链完善

这两个插件没有绝对的好坏,只看业务场景。小集群、网络环境复杂、对性能不敏感,Flannel够了;生产集群、多租户隔离、需要丰富的网络策略和审计能力,Calico更合适。

4. 网络策略范例与排查实录

4.1 NetworkPolicy到底在管控什么

Kubernetes默认的Pod网络是扁平的,所有Pod都能互相通信,这在大规模生产环境里是灾难。NetworkPolicy通过标签选择器来定义访问控制规则,实现“谁可以访问谁”。它本质上是一种防火墙规则的下发机制,标准实现里依赖iptables或eBPF来执行。

NetworkPolicy资源里有几个核心字段:podSelector用于选择目标Pod组,policyTypes表示规则类型(Ingress或Egress),ingress下包含from规则,egress下包含to规则。每次规则的增加或修改,最终都会转换为节点的iptables规则或路由决策,所以理论上规则的多少会影响节点上的网络性能,规则数量建议控制在合理范围内,避免iptables条目爆炸。

值得注意的关键点是:如果不部署支持NetworkPolicy的CNI插件(比如Flannel vxlan),定义YAML文件也不会生效,资源会创建成功,但完全没有流量拦截行为。这算是行业里新手容易踩的坑之一,排错时一定要先确认CNI插件是否支持。

4.2 经典防御性范例配置

第一个场景:默认拒绝所有流量。这相当于给某个命名空间加了一圈“防火墙”。配置如下:

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

这个策略会把该命名空间内所有Pod的入站流量全部拒绝,除非另有其他策略允许。如果你希望整个命名空间默认拒绝,再放行特定流量,这是最基础也是最重要的第一层防护。

第二个场景:放行来自特定标签Pod的流量。比如前端服务frontend组,只允许标签为app=api的Pod访问:

spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend

这段规则的意思是:目标Pod必须带app=backend标签,来源Pod必须带app=frontend标签,两者同时满足才能访问。标签选择器是K8s的强项,比维护IP列表要好用得多,因为Pod重建后IP会变,但标签一直稳定。

第三个场景:限制出口流量。比如某些应用仅允许访问某个网段的数据库服务,不允许访问外网:

spec: podSelector: matchLabels: app: worker policyTypes: - Egress egress: - to: - ipBlock: cidr: 10.50.0.0/16 ports: - protocol: TCP port: 3306

这个配置会拦截worker Pod除目标网段3306端口以外的所有出站流量。需要注意的是,一旦定义了Egress策略,默认对该Pod的所有出站流量都会拒绝,除非显式允许。DNS请求可能因此被拦截,因为Pod访问外部域名时先要发DNS请求到kube-dns,建议在Egress策略中把DNS的UDP 53端口也放行,否则应用会出现“DNS解析超时”的诡异故障。

这类镜像范例实际就是从零到一的洋葱模型:默认拒绝、按需放行。我个人的习惯是,每个新建命名空间先套一个默认拒绝策略,再根据业务梳理放行清单。这个习惯有效避免了大量未预期的网络暴露问题。

4.3 策略不生效的排查思路

最典型的问题是“我明明加了NetworkPolicy,流量还是通的”。既然提到“网络策略范例总结”,排错就必须摆上桌面。我的排查顺序是这样的:

先检查CNI插件类型。上面说过,Flannel vxlan不支持NetworkPolicy,当你在这种环境调试时,要先看插件,而不是怀疑策略语法。接下来检查策略是否选择到了目标Pod,用如下命令查看策略详情,以及对应Pod的标签:

kubectl describe networkpolicy <policy-name> kubectl get pods --show-labels

如果标签选择有误,策略自然不会生效。

再检查policyTypes字段。很多人只写ingress规则,却忘了声明policyTypes,或者声明了但只写了一个方向。例如,你想限制出站,但规则里policyTypes仅包含Ingress,那么Egress部分不会被执行。

如果策略都正常,但流量还是无法控制,此时要进节点查看iptables链。Calico的规则通常挂在名为CALICO_*的自定义链下,执行:

iptables -S | grep CALICO

判断CALICO_FORWARD链或相关的DNAT链中是否出现了预期的规则。如果没有,多半是calico-node组件异常或Felix没有同步策略,需要检查calico-node的日志。

4.4 日常维护网络策略的注意事项

维护NetworkPolicy时间一长,我发现有几个通病容易反复。第一条,不要把所有策略都写在一个文件里。文件的粒度应该是按命名空间分组,并给出清晰的命名规范,例如“default-deny-ingress”和“allow-frontend-to-backend”,否则几个月后回来排查,根本不知道某条策略是谁加的、为什么加。

第二条,网络策略变更前先在测试命名空间验证。因为一个配置错误可能导致整个服务不可用,尤其Egress策略配置错误连DNS都会被阻断。我遇到过一次某团队给Pod加了严格的Egress规则,结果所有新Pod启动后都无法拉镜像,因为kubelet拉镜像需要访问镜像仓库,也被策略兜底拒绝了。

第三条,注意命名空间级策略的组合效应。多个NetworkPolicy同时匹配一个Pod时,规则是“或”的关系,即只要有一个策略允许,流量就能通过。这意味着你“只加一条拒绝规则”实现不了“拒绝某流量”的效果,因为其他允许策略依然放行。明白这个语义,你就不会写出“拒绝某IP”的规则后抱怨不生效了。

5. 日常运维中额外的排障经验

这里单独整理几个我在多次网络故障中总结的操作心得。

第一个心得:排查跨节点Pod不通,不要一上来就抓包,先按“路由—封装—网桥”顺序定位。先把源节点ip route、目标节点ip route、两边的CNI配置文件快速过一遍,多数问题集中在路由表或配置残留。路由没有问题后,再抓包看封装是否到达。这个顺序帮助我大幅缩短了平均故障定位时间。

第二个心得:熟悉ip netns命令。在节点上使用容器网络命名空间直接验证Pod网络,比反复进Pod执行命令更高效。例如拿到Pod的容器ID后执行:

pid=$(docker inspect -f '{{.State.Pid}}' <container-id>) ip netns exec /proc/$pid/ns/net ip addr

可以快速检查Pod内的网卡状态和IP配置,比kubectl exec更底层,很多Pod本身没有ip命令的情况下它依然好用。

第三个心得:善用“最小复现”思路。遇到疑难网络问题,在故障命名空间里单独创建一个测试Pod,从一个纯静态的视角再验证一遍连通性。这样做能排除业务自身的劫持、代理等干扰,直接暴露底层网络状态。

第四个心得:窗口期操作顺序宁可慢不要乱。当你同时维护多个插件或要切换CNI时,严格按照“停止业务写入→备份网络配置→清理旧插件→部署新插件→验证连通→恢复业务”的顺序执行。我见过不少因贪快省略备份步骤,故障发生后镜像无法回滚,只能手工恢复路由表的案例。

第五个心得:在Calico环境下,规则同步偶尔会延迟,变更NetworkPolicy后需要几秒才能生效。如果刚改完策略立刻验证,可能得到“错误的结果”。合理的做法是等10到20秒再测试,或者观察calico-node Pod日志确认策略同步更新。

6. 一条实用的替换演练清单

把一个可复用的检查清单分享出来,格式上可以作为团队评审依据。整个切换过程中要关注的点就集中在这一份表格里:

阶段检查项参考命令或方法
切换前节点路由备份ip route > 路由备份.txt
切换前CNI配置备份cp -r /etc/cni/net.d /opt/cni-bak
切换中旧插件清理kubectl delete -f flannel.yml;清理conflict和网卡
切换中新插件配置确认calico的IP池和MTU字段检查
切换中节点Pod运行状态kubectl get pods -A -o wide; kubectl get nodes
切换后同节点Pod连通性kubectl exec测试同节点Pod互ping
切换后跨节点Pod连通性跨节点Pod互ping,检查路由和封装
切换后DNS解析验证创建一个临时Pod解析内部Service名
切换后性能验证iperf3或压测工具验证吞吐和延迟
切换后NetworkPolicy验证部署测试策略,验证放行和拒绝都生效

这张表看起来很长,但真正操作时每一步都不会超过几分钟。关键是别跳步,尤其不要跳过备份。网络插件的回滚,在没有干净备份的情况下,很难通过“重装一遍”来恢复。

在我自己维护的某测试集群上做替换时,按照这张清单严格执行,整个过程大概用了40分钟,其中包含了一次因残留路由导致的小故障处理。相比之前某次没备份就直接替换的教训——那次花了近一个下午手工抢救路由,这个清单的性价比非常高。

对于刚接触K8s网络的朋友,我特别想说:Flannel和Calico之争、vxlan和BGP性能对比,这些内容看一百篇文档不如自己动手搭一次集群、改一次网络插件、写一个拒绝策略来的深刻。这份笔记只是把路线图铺好,具体的踩坑手感还得靠自己动手去体会。

最后再分享一个小技巧:维护网络策略时,养成给每个Policy写注释和owner标签的习惯,格式例如:

metadata: labels: owner: network-team purpose: default-deny

这个习惯在三个月后的审视中会帮你省下大量时间。当同事跑来问“这条策略是谁加的”时,你不需要翻git记录,一条标签就说明了一切。

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

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

立即咨询