
做Kubernetes网络绕不开Calico而Calico绕不开三种流量模式纯BGP、IPIP、VXLAN。很多刚上手的朋友问既然已经有BGP为什么还要搞IPIP原因是纯BGP模式依赖一个很容易被忽略的前提——所有节点都在同一个二层网络里或者底层网络愿意帮你转发Pod网段的路由。现实里这个前提经常不成立。你跨VPC建集群、多机房容灾节点分布在不同的网段BGP广播出去的Pod路由到了路由器那层就被丢掉了因为路由器不认识192.168.0.0/16这种私有网段该往哪走。IPIP的思路其实很朴素在原始Pod数据包外面再套一个IP头外层IP头的源和目的写的是节点自身的地址。底层路由器只需要处理普通IP包完全不需要了解Pod网段。之所以有人选IPIP而不是VXLAN主要看开销和性能。IPIP外层头只有20字节VXLAN要36字节起步UDP头加VXLAN头封装越重性能越差。在“必须上overlay”的前提下IPIP通常是性能优先的选择。这篇文章我会把Calico IPIP的原理、配置、MTU调优和故障排查全部摊开讲都是我维护集群时真实验证过的东西。1. 先说结论Calico的IPIP模式到底解决什么问题1.1 Calico网络模式家族BGP、IPIP、VXLAN怎么选Calico的三种模式本质上是在“性能”和“网络兼容性”之间做取舍。纯BGP模式性能最好数据包从Pod出去不封不装按路由表直接跳延迟接近裸机网络。但它要求所有节点之间的路由对Calico负责的Pod网段完全可达。自建机房把交换机配好BGP可以直接和物理网络对话那纯BGP就是最优解。一旦拓扑变成多子网、多VPC或者底层网络管理员不愿意为容器网段动路由配置纯BGP就会碰壁。VXLAN是走UDP封装兼容性最强公有云和各种网络设备都认但33字节的额外开销外层IP 20字节加UDP 8字节加VXLAN 8字节实际常用计算还要加内层以太网头让它的吞吐和延迟都差一些。IPIP恰好卡在中间兼容性比纯BGP好性能比VXLAN好代价是外层IP头占用20字节而且内核需要支持IPIP隧道协议。很多老集群默认就是IPIP模式原因是它足够通用。节点在同一个二层网络时IPIP能跑跨子网时也能跑不需要网络管理员配合改交换机。官方安装Calico时给的默认配置也倾向IPIP你装完什么都不改默认的IP池就会带ipipMode: Always。这一点让IPIP成为Calico最常见的工作模式但也是很多人踩坑的起点。1.2 什么场景IPIP是首选什么场景别硬上根据我维护过的环境可以总结一个选型套路同二层网络用纯BGP跨二层但底层允许普通IP转发用IPIP公有云安全组限制多或者对网络兼容性要求高用VXLAN。IPIP最适合自建机房或私有云。比如集群节点分布在两个网段节点之间三层可达但交换机不接BGP你只需要打开IPIPPod之间的跨网段通信就能正常工作。底层路由器只转发节点IP完全感知不到Pod网段。另外一个典型场景是外部设备需要访问Pod IP纯BGP路由发布不出去IPIP能把Pod流量“藏”在节点IP后面外部访问节点IP再由节点解封装转给Pod。但有几个场景我强烈不建议硬上IPIP。AWS等公有云的安全组默认不认IP-in-IP这种自定义协议你要额外放行协议号4操作麻烦不说有些云环境甚至根本不支持。其次是底层网络MTU已经被压到1450甚至更低的环境你再减20字节Pod实际可用MTU只剩1430大包很容易触发分片。这时老老实实上VXLANUDP端口放行容易MTU余量也更好估算。简单讲IPIP解决的是“跨网段路由不可达”的问题不是万能解药选型前先搞清楚自己网络环境长什么样。2. IPIP拆解一次跨节点通信数据包到底怎么走2.1 IP-in-IP封装到底是个什么东西IP-in-IPIPIP说起来很简单把完整的一个IP数据包当作另一个新IP数据包的载荷。外层新IP头的协议号标记为4代表里面装的也是一个IP包内层IP头保持Pod到Pod的源目地址不变。你可以理解成寄快递时里面的包裹是一个完整的小盒子外面再套一个大纸箱大纸箱上写着节点A和节点B的地址快递员只看纸箱就能把货送到拆开后里面那个小盒子才是真正要投递的东西。这个封装在Calico里是由内核的ipip隧道模块完成的。Calico会创建一个叫tunl0的隧道接口路由表里凡是需要走IPIP的Pod网段都会把出口指定到tunl0。当数据包进入tunl0时内核自动在原有包外面加上新的IP头再做路由转发。整个过程对Pod完全透明Pod里的应用程序根本不知道外面还被套了一层头。要注意的是IPIP协议号是4这不是TCP的4号保留位也不是UDP是一个独立编号。所以用tcpdump排查时抓包过滤条件要写成ip proto 4而不是tcp或udp的常规端口。这个细节很多人第一次排查时压根没想到导致明明看到有流量却不知道那就是IPIP封装后的包。2.2 Calico BGP是怎么把路由传出去的Calico的控制平面靠BGP。节点上运行着一个叫BIRD的BGP进程它负责把本节点的Pod网段路由广播给其他节点。默认情况下节点之间是全mesh模式也就是每个节点都和其他节点建立BGP会话。节点规模超过50个时全mesh会话数量会膨胀到N乘以N的量级这时候就需要引入路由反射器Route Reflector来收敛连接数。BGP广播的内容很简单每个节点会告诉别人“我这里有某个Pod网段下一跳是我自己”。比如节点1的Pod CIDR是192.168.0.0/26节点2是192.168.0.64/26节点1通过BGP把192.168.0.0/26通告给节点2下一跳指向节点1的IP。节点2收到后在自己路由表里插入一条192.168.0.0/26 via 节点1 IP的路由。控制平面上的“下一跳”是节点IP数据平面怎么封装则取决于IP池的IPIP模式配置。如果启用了IPIP路由表里指向远端Pod网段的条目会带上dev tunl0下一跳是远端节点IP同时还有onlink标记。onlink的作用是告诉内核虽然远端节点IP不在本机的直连网段内但因为它要通过tunl0隧道访问所以允许把这个IP当作可直连的下一跳。这个标记是Calico自动加上去的你手动调路由时不要轻易删掉否则隧道流量会起不来。2.3 一次完整的数据包之旅我举个实际例子。集群两个节点node1 IP: 10.10.0.11Pod网段192.168.0.0/24上面跑了pod-aIP是192.168.0.5node2 IP: 10.10.0.12Pod网段192.168.1.0/24上面跑了pod-bIP是192.168.1.5pod-a访问pod-b时数据包的源地址是192.168.0.5目标地址是192.168.1.5。包先到达node1的根网络命名空间内核查路由表看到192.168.1.0/24这条路由的出口是tunl0于是触发IPIP封装。封装后的新包头部是外层源IP: 10.10.0.11 外层目标IP: 10.10.0.12 外层协议号: 4IPIP 内层源IP: 192.168.0.5 内层目标IP: 192.168.1.5然后node1通过eth0把封装后的包发出去。底层网络看到的是一个从10.10.0.11到10.10.0.12的普通IP包按正常路由转发到node2即可。node2收到后内核识别出协议号是4解封装还原出内层数据包再根据Pod路由转发给192.168.1.5。这个过程中最需要注意的是整条路径里底层路由器全程只关心节点IP不需要知道192.168.x.x怎么走。这就是IPIP的核心价值。你在节点上抓包时也会看到node1发出的是外层源目地址的包node2收到后再拆开抓包位置不同看到的内容完全不同。理解这条路径后面排查性能问题和连通性问题就有方向了。3. 配置实战从安装到切换IPIP模式的完整操作3.1 安装阶段指定IPIP模式安装Calico时如果用的是官方manifest环境变量里就有个关键参数CALICO_IPV4POOL_IPIP。这个变量控制默认IP池的IPIP策略合法的值有Always、Never、CrossSubnet三种。Always表示所有节点之间的Pod流量都走IPIP封装Never表示禁用IPIPCrossSubnet表示只有跨子网流量才封装同一个子网内的节点直连。如果安装前就已经确定网络环境建议直接在manifests里改好。比如两个网段之间的集群用Always最省心如果所有节点都在同一个大二层里想追求性能可以改成Never。示例如下env: - name: CALICO_IPV4POOL_IPIP value: CrossSubnet这里我踩过一个坑只在安装时改了这个变量但集群里已经存在旧IP池改环境变量并不会自动修改已经创建的IPPool对象。所以升级或改网络模式前一定要检查现有IPPool的ipipMode字段改完环境变量后还得手动编辑IPPool才能生效。另一种常见安装方式是用Calico Operator。在Installation资源里设置calicoNetwork.ipPools下的ipipMode比如spec: calicoNetwork: ipPools: - name: default-ipv4-ippool cidr: 192.168.0.0/16 ipipMode: Always natOutgoing: trueOperator的好处是升级时不会丢失配置。我一般推荐生产环境用Operator临时测试环境才用kubectl直接apply manifest。3.2 动态切换IPIP模式改IPPool就够了集群已经在运行临时想切模式不需要重装Calico直接改IPPool对象就能生效。先看一下当前IPPool配置kubectl get ippool default-ipv4-ippool -o yaml输出里能看到spec.ipipMode字段。想改成Always就执行kubectl patch ippool default-ipv4-ippool --typejson \ -p[{op: replace, path: /spec/ipipMode, value: Always}]改完之后Calico会重新计算所有节点上的路由表。观察一下节点上的变化ip route | grep tunl0如果从Never改成Always你会看到原本指向eth0的远端Pod网段路由都变成了通过tunl0。切换过程一般在几秒到几十秒内完成但数据面会有一个短暂的中断生产环境建议选维护窗口操作。我自己的经验是先改一小批节点做验证再全局切换。另外记得同步检查BGP状态因为模式变化会影响路由通告和下一跳状态最好在切换后用calicoctl node status扫一眼所有节点的BGP会话是否健康。3.3 CrossSubnet只对跨子网流量封装性能更优CrossSubnet是我在实际生产里最推荐的一种模式。它兼顾了性能和兼容性同一个子网内的节点之间走纯BGP直连完全没有封装开销跨子网的节点之间才走IPIP。这样设计能在多机房间保持低延迟又不用牺牲跨网段的连通性。启用方式同样简单apiVersion: projectcalico.org/v3 kind: IPPool metadata: name: default-ipv4-ippool spec: cidr: 192.168.0.0/16 ipipMode: CrossSubnet natOutgoing: true配置完成后你可以用一条命令验证效果。假设node1和node2在同一子网node3在另一个子网查看路由表时应该能发现192.168.1.0/24 via 10.10.0.12 dev eth0 # 同子网节点走eth0 192.168.2.0/24 via 10.20.0.13 dev tunl0 onlink # 跨子网节点走tunl0这个模式唯一的坑是节点IP必须能正确体现所属子网。Calico在识别子网时会根据节点IP和掩码判断如果节点上有多个网卡或网卡配了多个IP可能会误判。我遇到过一次多网卡节点把内网IP当成了跨子网地址导致同子网流量也走了IPIP后来通过给calico-node容器设置IP自动检测规则解决了。官方默认的检测方式是first-found生产环境建议根据实际网卡名或正则指定避免误判。4. MTU和性能调优这些参数值得抠一抠4.1 MTU计算20字节的开销到底怎么算IPIP封装带来20字节的额外IP头意味着如果底层网卡MTU是1500Pod内的网络接口MTU就不能超过1480否则封装后总长度超过1500会触发分片。计算方式很简单容器MTU 底层网络接口MTU - 20IPIP 容器MTU 底层网络接口MTU - 36VXLAN但实际环境里底层MTU不一定正好是1500。比如某些云环境虚拟网卡MTU是1450那IPIP模式下的容器MTU应该是1430如果有些网络设备还会额外增加VLAN tag等开销这个数字还要再往下调。我自己的做法是先确认所有节点实际物理网卡的MTU取最小值再扣掉封装开销。在Calico里可以通过IPPool的mtu字段显式设置spec: mtu: 1480如果不设置较新版本的Calico会自动探测节点接口MTU并做相应扣除。自动探测大多数时候没问题但多网卡或特殊VXLAN叠加的复杂环境可能会算错所以我更倾向于在IPPool里写死一个值统一管理。4.2 IPIP、VXLAN、纯BGP三种模式的性能对比从测试结果看纯BGP模式由于没有封装吞吐最高、延迟最低最适合对性能敏感的业务。IPIP模式多了一层20字节的外层IP头转发时CPU要多做一次封装和解封装性能大概会损失5%到10%。VXLAN由于走UDP封装、头部更大加上常见实现里要处理校验和性能最差损失可能在10%到20%左右。我整理了一个大概的对比表格方便你对照选型对比维度纯BGPIPIPVXLAN额外头部开销0字节20字节约36字节起步吞吐损耗最低中等较高延迟最低中等较高跨子网支持看底层路由配置支持支持公有云兼容性较差一般较好MTU余量最大中等最小从表格能看出纯BGP永远是性能天花板。但现实中跨子网场景太多IPIP是折中后的首选。CrossSubnet模式进一步把同子网流量降级成纯BGP实际生产环境里大部分访问留在同子网内所以整体性能损耗会明显小于Always模式。4.3 实际调优经验什么时候用Always、CrossSubnet、Never调优首先要回答一个问题你的流量是不是大量跨子网如果业务本身是跨机房的或者Pod分布在多个VPC跨子网流量占比高那CrossSubnet的优势会缩水直接用Always反而简单可控。如果业务基本集中在一个子网内只有少量管理面流量跨网段CrossSubnet几乎就是最优解。我还有一个建议是关注大包场景。很多计算任务会发送MTU接近上限的数据包比如分布式存储、模型训练这类流量对分片非常敏感。遇到这种业务我建议即使开了IPIP也要把MTU算准并显式设置避免因为自动探测和实际链路不一致导致偶发大包丢包。排查分片问题时ping命令很好用比如ping -M do -s 1472 10.10.0.12这条命令验证的是1500 MTU下不带分片的最大数据包。如果底层网络MTU是15001472字节payload加28字节ICMP头正好是1500。如果这条ping通说明二层链路支持1500如果通不了再逐级减小-s参数找实际上限。5. 踩坑实录IPIP常见故障与排查思路5.1 案例一跨子网Pod不通同子网却正常这种症状基本可以锁定问题出在IPIP封装或跨子网路由上。先检查IPPool的ipipMode是不是Always或CrossSubnet。如果配的是Never跨子网Pod根本不会有隧道路由自然不通。再检查BGP状态。用calicoctl查看calicoctl node status看BGP对端是否都是Established状态。如果有节点是Active或Connect说明BGP会话没建立成功可能是TCP 179端口被防火墙阻断也可能是节点AS号配置不一致。还有最容易被忽略的问题云安全组没有放行IPIP协议。跨子网流量要真正跑通底层必须允许IP-in-IP数据包也就是协议号4。一般云控制台的安全组规则里会有一项Custom Protocol选IP-in-IP协议号4放行而不是只放行某个TCP/UDP端口。这个坑我踩过不止一次每次都得提醒自己先看防火墙再查路由。5.2 案例二小包能通大包一直不通小包通、大包不通九成是MTU问题。IPIP模式下当Pod发送一个接近容器MTU大小的数据包封装后超过底层链路MTU如果设备不允许分片包就被丢了。TCP场景下表现更典型TCP握手能成但一旦开始传输大数据块就卡住或不断重传。解决办法分三步。第一步确认底层链路真实MTU用ping -M do逐级测底数第二步把IPPool的mtu字段改到理论值或略小一点第三步更新后重启受影响Pod让Pod内的接口拿到新MTU。注意只改IPPool不重建Podveth接口的MTU有可能不会立即更新我吃过这个亏业务方报障说还是不通最后发现是Pod还保留着老MTU。还需要检查宿主机上的tunl0接口MTU。tunl0的MTU决定了封装后外层包能否顺利发出。如果tunl0 MTU设得比物理网卡还大大包在物理出口一样被丢。一般建议tunl0 MTU和物理网卡保持一致或小一点配合IPPool里的容器MTU一起调。5.3 案例三BGP邻居建立不了路由表空空的如果路由表看不到远端Pod网段的路由说明BGP分发出了问题。常见原因有几个节点之间的BGP端口179被防火墙挡了全mesh模式下节点数量太多导致会话数爆炸或者某些节点配置了不同的AS号导致邻居拒绝建立。排查时先看BGP会话状态calicoctl node status再看节点的BIRD日志。calico-node容器的日志里会输出BGP连接失败的详细原因比如connection refused或hold timer expired。我遇到过一次比较隐蔽的情况某个节点因为内核参数conntrack冲突导致BGP连接被重置BGP会话反复建不起来。后来调整了节点上的conntrack相关设置才稳定下来。这类问题要靠日志一步步排查不能只盯着Calico配置看。如果节点数量超过50个全mesh模式就非常吃力了。BGP会话数是N乘N的级别建议尽早切换成路由反射器模式。Calico支持配置BGPPeer资源指定Route Reflector节点配置完成后普通节点只和RR建立会话网络压力会小很多。5.4 常用排查命令清单排查IPIP问题时我一般按下面这个顺序走效率最高# 1. 查看当前IPPool配置确认ipipMode和mtu kubectl get ippool -o yaml # 2. 查看BGP节点状态确认会话全Established calicoctl node status # 3. 查看路由表确认tunl0相关路由是否存在 ip route | grep tunl0 # 4. 确认tunl0接口状态 ip link show tunl0 ip addr show tunl0 # 5. 抓包看IPIP封装流量 tcpdump -i eth0 ip proto 4 -nn # 6. 测试底层链路MTU ping -M do -s 1472 节点IP这个顺序能覆盖绝大多数问题先看配置对不对再看控制平面通不通最后验证数据平面。抓包那一步特别关键如果你在eth0上看到正常数量的ip proto 4包说明IPIP封装已经工作了问题可能出在后端虚拟网络或Pod侧如果完全看不到说明流量根本没走到封装这一步。5.5 常见问题速查表现象可能原因处理办法跨子网Pod不通ipipMode为Never改IPPool为Always或CrossSubnet跨子网Pod不通安全组未放行IP-in-IP协议在安全组放行协议号4同子网正常跨子网失败底层路由器丢弃封装包检查核心交换机是否允许IPIP协议转发大包不通小包通MTU设置过大按底层MTU减去20重置IPPool的mtu路由表没有远端Pod路由BGP会话未建立检查TCP 179端口、节点AS号、BIRD日志BGP频繁断开conntrack或防火墙干扰调整节点conntrack参数或放行相关协议切换模式后路由未更新IPPool对象没改编辑IPPool的ipipMode并等待Calico重算路由tunl0不存在或DOWN内核模块缺失modprobe ipip确保内核加载ipip模块这几年维护Kubernetes集群我最大的体会是Calico IPIP本身不是特别复杂的东西但非常考验对网络细节的理解。很多网络问题不是配置写错而是底层链路、MTU、防火墙这些基础设施层面的因素和Calico的默认行为相互作用。遇到问题别急着改配置先把数据包路径完整捋一遍再对照命令清单一层层验证往往比瞎试来得快。特别是CrossSubnet模式它虽然聪明但依赖节点子网判断多网卡环境里一定要提前把IP检测规则配好否则会踩到同子网流量也走封装的暗坑。希望这篇指南能帮你少走几步弯路。