十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Calico IPIP 深度解析:原理、模式选型与生产排障实战

Calico IPIP 深度解析:原理、模式选型与生产排障实战 有一回我们集群跨节点 Pod 突然大面积 ping 不通BGP 对等体全军覆盖、路由表里该有的条目也都在看起来一切正常业务就是死活不通。查到凌晨才靠 tcpdump 发现底层交换机把协议号 4 的 IPIP 报文当成异常包丢弃了。那次排障之后我才认真把 Calico 的 IPIP 完整吃透从工作模式、封装流程到版本选择、排障手段全部重新过了几遍。这篇内容就从实际运维视角出发把 Calico IPIP 的来龙去脉讲清楚。内容包括 IPIP 报文从源 Pod 到目标 Pod 的流转路径、三种模式的选型分析、指定版本下载安装的完整操作以及我在生产环境里踩过的一堆坑。适合觉得自己“会用 Calico 但没真正搞懂封装细节”的 Kubernetes 网络维护者也适合准备把集群搬入新机房或云上 VPC、正在纠结封装模式怎么选的同学。1. 纯三层 Calico 为什么还要默认开启 IPIPCalico 对外宣传的标签是“纯三层网络方案”数据面走 Linux 内核路由、控制面走 BGP不像早期 Flannel、Weave 那样非得上 overlay。但实际部署时你会发现默认创建的 IPPool 经常带着 IPIP 封装。看似矛盾背后其实是生产网络环境太复杂纯路由在裸奔时根本兜不住。1.1 Calico 的基础数据路径BGP 学习路由内核直接转发要理解 IPIP 存在的意义先把 Calico 的正常数据路径捋一遍。每个 Node 上的 calico-node 会通过 BGP 把自己负责的 Pod 网段通告出去其他节点学到之后在 Linux 路由表里生成对应条目。Pod A 访问 Pod B 时源节点内核根据路由表找到目标 Pod 对应的下一跳也就是目标节点的 IP直接把数据包从物理网卡发过去。这种模式下没有隧道封装数据包在底层网络里是“裸奔”的。代价最小性能损耗基本为零这也是 Calico 在性能测试里表现普遍优于 VXLAN 类方案的原因。但这里有个隐含前提目标节点的 IP 是底层网络可达的而且从源节点发出的、带着 Pod 源地址的数据包底层网络也愿意转发。1.2 多数底层网络做不到“对 Pod 网段透明”云上的 VPC、物理机房的交换机它们认识的是节点 IP比如192.168.1.10、10.0.2.20这类网段。Pod 网段是集群内部自己规划出来的比如172.20.0.0/16底层路由表根本没有它的条目也没人愿意为这个内部分配动态路由协议。就算你愿意跟网络管理员协调把 Pod 网段加进底层路由表后续扩容拓扑一变又是一番折腾。更别提跨机房、跨 VPC 场景网络边界根本不归你管。所以 Calico 设计了一个“保底机制”当底层网络不可控时把 Pod 发出的数据包外面再套一层 IP 头。外层源地址是源节点 IP、外层目的地址是目标节点 IP底层网络只看到“节点和节点在通信”完全不需要理解 Pod 网段。这套机制就是 IPIP。1.3 为什么默认偏向 IPIP 而不是 VXLAN常见容器网络里IPIP 和 VXLAN 都有封装能力但 Calico 默认首选 IPIP核心原因是开销和实现复杂度。封装开销方面IPIP 只在原始 IP 包外面加一个 20 字节的 IPv4 头数据面改动小性能损耗低。VXLAN 则要加 VXLAN 头、UDP 头和外部 IP 头总共多出 50 字节左右而且多一次 UDP 封装与解封装CPU 消耗也更高。实现上IPIP 直接复用 Linux 内核的ipip模块节点上出现一个tunl0隧道接口用的也是内核原生路由机制不依赖用户态进程处理数据面。生产环境里纯物理机机房、自建虚拟化平台、绝大多数传统 IDCIPIP 都能稳稳跑起来。不过 IPIP 也有天然短板最典型的是协议号不常见。IPIP 外层头的协议号固定是 4部分网络设备或云平台安全组没有放行这个协议就会直接丢包。遇到这类环境就得改成 VXLAN 或者关闭封装这也是后文排障章节要重点讲的内容。2. IPIP 报文从 Pod 到 Pod 到底经历了什么很多文章喜欢把 IPIP 称为隧道但从数据面实现看它更像是“路由表里一个特殊的 dev”。要真理解 IPIP跟着一个数据包从源 Pod 走到目标 Pod 最靠谱。2.1 源节点上发生的事路由命中 tunl0内核完成封装假设集群节点和 Pod 网段如下资源地址节点 A192.168.1.10节点 B192.168.2.20Pod A在节点 A172.20.1.2Pod B在节点 B172.20.2.3节点 A 上BGP 已经把 Pod B 所属网段的路由同步了下来。执行ip route通常能看到类似这样的条目172.20.2.0/26 via 192.168.2.20 dev tunl0 proto bird其中via 192.168.2.20是下一跳也就是节点 B 的 IPdev tunl0告诉内核匹配这条路由的数据包要走 tunl0 隧道接口。Pod A 发出的原始数据包到了内核协议栈完成路由查找后发现出口设备是tunl0。内核的ipip模块会立刻在外面套一个新的 IPv4 包头协议号填 4。外层头部关键字段大概长这样Outer Source IP: 192.168.1.10 Outer Destination IP: 192.168.2.20 Protocol: 4 (IP-in-IP)此时数据包的长度比原始包多了 20 字节。内核再把封装后的包交给物理网卡 eth0 发出去。2.2 目标节点上发生的事解封装后二次路由节点 B 的物理网卡收到外层 IP 包后先把外层的 IP 头剥掉把原始 Pod 数据包留给本机协议栈继续处理。这里要特别注意解封装后的包并没有立刻送到 Pod B 的 veth而是再次进行路由查找。节点 B 上同样有一条自己维护的直连路由比如172.20.2.0/26 dev eth0 scope link这里的 dev 是 Pod B 所在节点上的 veth 设备侧具体设备名可能是vethxxx。内核第二次路由查找命中后数据包被送入对应 veth最终到达 Pod B 的协议栈。在外界看来整个过程就是节点 A 发了一个外层 IP 包给节点 BB 收到后解包再转给内部 Pod。底层网络始终感觉不到 Pod 网段的存在。2.3 紧绑定的 MTU 变化1500 变 1480 不是玄学IPIP 多了 20 字节外层头这直接影响链路 MTU。物理网卡 MTU 是 1500 时封装后的包最大只能是 1500那原始 Pod 内层数据包就只能控制在 1480否则就会分片。Calico 在启用 IPIP 时会自动计算 Pod 接口的 MTU通常把veth的 MTU 设置成 1480。因此 Pod 里ip link看到的 MTU 仍然是一个相对整的值不需要手工调整。但如果物理网卡启用了 jumbo frame比如把 MTU 调成了 9000那就得同步调整 Calico 对 Pod MTU 的配置否则内层浪费了不少空间。实际排查时如果发现小包能通、大包经常卡住第一个就要想到 MTU 问题。用 ping 测试时最直接的办法是从1450开始逐步调大 payload一点一点试探临界点。如果你想在节点上直接观察 IPIP 报文而不是看 Pod 内的包可以这样抓包tcpdump -i eth0 ip proto 4 -nn -c 100抓到以后展开外层 IP 头能看到protocol: 4源目地址都是节点 IP这就说明 IPIP 封装确实在跑。3. Always、CrossSubnet、Never 到底该怎么挑不少人在配 Calico IPPool 时对ipipMode的三个值只停留在“能用就行”的层面随手填个 Always生产环境跑出问题才知道疼。模式选型不是看哪个名头好听而是要看底层网络到底能为你承担多少。3.1 三种模式的行为差异与典型场景模式行为典型场景主要代价Always对任何目标 Pod 的数据包都做 IPIP 封装底层网络完全不可控依赖路由不可用同子网节点间也多 20 字节开销CrossSubnet目标节点与源节点不在同一子网时封装同子网不封装底层网络收留节点之间但不收留 Pod 网段同机房同网段带宽敏感需要能识别节点子网依赖底层二层域Never从不封装纯裸路由底层路由完全支持 Pod 网段一旦底层网络不认 Pod 网段立刻断连Always 的最大问题是哪怕节点 A 和节点 B 就在同一个交换机下、二层网络本来就能把数据包送过去它仍然要强行封装白白增加开销。如果你在性能敏感的物理机房内网All Pod 都在同一大二层里依旧用 Always延迟多了、CPU 多了但收益几乎为零。Never 恰恰反过来。它要求底层网络真的能直接转发 Pod 网段流量要么你已经把 Pod 网段路由下发到三层交换机和云路由表里要么全网全网其实跑在同个子网。实际生产里很多新落地的小集群节点间确实有完整路由但 Pod 网段没有同步到网络设备这时用 Never 就是自找麻烦。CrossSubnet 最像“中间路线”同子网流量不封装利用二层交换机的原生转发跨子网流量再封装解决底层网关不认 Pod 网段的问题。在多数本机房多网段、自建 VPC 多子网的场景下CrossSubnet 是性价比最平衡的选项。3.2 CrossSubnet 看起来很完美但有一个前提CrossSubnet 判断是否封装的依据是源节点和目标节点的 IP 是否在同一个子网。同子网时走裸路由跨子网时走 IPIP 封装。听起来很适合云上 VPC但我必须提醒你CrossSubnet 并不能解决跨子网流的底层路由问题。举个例子节点 A 在192.168.1.0/24节点 B 在192.168.2.0/24中间隔了一台三层交换机。如果这台交换机不允许转发协议号 4 的报文那 CrossSubnet 模式下跨子网流量照样会断。所谓“CrossSubnet 自动解决跨子网问题”前提是底层允许 IPIP 报文通过。所以生产环境的最佳实践是先把底层设备安全策略搞清楚。如果设备不支持协议号 4那跨子网流量就不该寄希望于 CrossSubnet要么关封装然后打通底层路由要么直接用 VXLAN 这类基于 UDP 的封装模式因为 UDP 4789 在大多数网络设备里英文文件更容易放行。3.3 云环境里更要谨慎安全组不认识 IPIP我见过好几个团队把 Calico 从物理机迁到公有云时沿用 IPIP 默认配置结果跨节点 Pod 一开机就不通。原因高度统一云安全组入站规则没有放行协议号 4。云平台的安全组操作界面一般默认放行 TCP、UDP、ICMP很少人意识到还要单独放“IP-in-IP”或者协议号 4。如果你在云上建议优先确认安全组的协议支持情况。开通协议号 4 以后IPIP 基本能正常跑但出于稳定性和后续可维护性考虑纯云原生环境我更推荐把 IPPool 切到 VXLAN 模式业务侵入更小。4. 指定版本 Calico 的下载与 IPIP 模式落地Calico 的安装方式相对灵活但“下载指定版本”这个问题不少初次接触的人很容易踩坑点进 GitHub 默认分支下载的 manifests 可能是还在开发中的版本跑起来跟当前 Kubernetes 版本对不上。这篇单独拿出一个章节写清楚版本选择和下载方法。4.1 版本选择先看兼容矩阵再定版本号Calico 和 Kubernetes 之间有明确的版本兼容矩阵通常发布在 Calico 官方文档的 “Product compatibility” 页面。选版本的第一个动作不是拉最新 Release而是确认你的 Kubernetes 小版本在哪个范围内兼容。大致规律是Calico v3.26 对应 Kubernetes 1.26 到 1.30 附近v3.27 会覆盖更后面的版本v3.28 再往后扩展。具体到某个 K8s 小版本应该以官方兼容矩阵为准。我的建议是选择比 Kubernetes 小版本落后一两个版本的 Calico既稳定相关排障案例也相对多。另外提醒一点尽量折腾“官方 Release tag”里的文件而不是默认分支master或main。默认分支的 manifests 可能包含尚未正式发布的特性放到生产环境一旦出问题资料都很难找。4.2 从 GitHub Release 下载指定版本需要calicoctl或者 Calico manifests 文件时直接从 GitHub Release 页下载固定 tag 的资源即可。比如要下载 v3.27.2 的 calicoctlcurl -sSLO https://github.com/projectcalico/calico/releases/download/v3.27.2/calicoctl-linux-amd64 mv calicoctl-linux-amd64 calicoctl chmod x calicoctl同时把对应版本的 manifests 下载下来便于离线检查或重复安装wget https://github.com/projectcalico/calico/archive/refs/tags/v3.27.2.tar.gz tar xvf v3.27.2.tar.gz cd calico-3.27.2如果更习惯 Helm也可以从官方 Helm 仓库拉取指定 chart 版本helm repo add projectcalico https://projectcalico.docs.tigera.io/charts helm pull projectcalico/tigera-operator --version v3.27.24.3 安装时直接把 IPIP 模式定下来Calico 的新版本推荐通过 Operator 方式安装。安装前把 operator 和自定义资源准备好然后在Installation自定义资源里指定 IPPool 封装模式即可。先创建命名空间并应用 operatorkubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/tigera-operator.yaml然后准备一个custom-resources.yaml这是决定 IPIP 模式的关键文件apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - name: default-ipv4-ippool blockSize: 26 cidr: 172.20.0.0/16 encapsulation: IPIP natOutgoing: Enabled - name: default-ipv4-ippool blockSize: 26 cidr: 172.20.0.0/16 encapsulation: IPIP natOutgoing: Enabled nodeSelector: all()注意encapsulation字段支持IPIP、VXLAN、None三种值。如果希望使用CrossSubnet需要在 IPPool 里显式配置ipipMode: CrossSubnetOperator 安装后也可以通过修改 IPPool 完成调整。如果你使用的是较老的calico.yaml直接安装方式则通常要通过环境变量或在 manifests 中的 IPPool 资源里改ipipMode。应用自定义资源kubectl apply -f custom-resources.yaml这套方式的好处是版本明确、IPIP 模式从一开始就固定后续不容易出现模式漂移。4.4 安装后检查现有 IPPool 的封装状态已经跑了一段时间的集群想确认当前用的是不是 IPIP直接查看 IPPool 即可。新版 Calico 把 IPPool 做成了 CRD可以直接用 kubectl 查询kubectl get ippool -o yaml重点看ipipMode和vxlanMode两个字段。如果ipipMode: Always那说明当前确实是彻底封装。如果ipipMode: CrossSubnet那就是按子网条件进行封装。同时检查节点上的隧道接口ip addr show tunl0 ip tunnel show启用 IPIP 后每个节点通常能看到tunl0状态为 UP且隧道类型为ipip。如果 IPIP 没有启用可能看不到这个接口或者接口处于 DOWN 状态。4.5 跑完安装后如何验证 IPIP 真的生效验证不能只看配置还要看数据面行为。第一步跑两个位于不同节点的测试 Pod从 Pod A 去 ping Pod B。接着在源节点上抓包tcpdump -i eth0 ip proto 4 -nn -c 20如果能抓到协议号为 4 的 IPIP 报文就能确认封装确实发生在源节点上。如果只看到普通 ICMP 包可能是 IPIP 模式没有真正开启或者流量走了别的方式。另一个侧证是查看节点上的 calico-node 日志kubectl logs -n kube-system calico-node-xxxxx | grep -i ipip不过这个日志信息比较有限最终以tcpdump抓包为准。5. IPIP 生产环境排障复盘配置我们都会配真正拉开差距的永远是问题出现时的定位能力。这里把我在生产环境里跟 IPIP 反复搏斗的几条经验完整梳理一遍希望能帮你少走点弯路。5.1 跨节点 Pod 不通从 BGP 状态到协议号的完整排查链路跨节点 Pod 不通很多人第一反应是查 CNI 配置但 IPIP 场景下有一套更高效的排查顺序。第一步先看 BGP 对等体是否正常。在安装了 calicoctl 的节点执行calicoctl node status如果 BGP 对等体不是 Established先解决 BGP 问题否则后面所有路由都是空谈。第二步看源节点路由表是否学到目标 Pod 网段ip route | grep 172.20.2.0第三步检查 tunl0 接口状态ip addr show tunl0如果接口 DOWNIPIP 封装就没法落地。可以尝试加载内核心模块看看modprobe ipip第四步是抓包。这一步能把问题从路由层推向策略层。在源节点执行tcpdump -i eth0 ip proto 4 -nn -c 50如果发现 IPIP 报文有出无回或者根本抓不到协议号 4 的包重点指向中间网络设备或云安全组。有一次我们排查时BGP 和路由全部正常tunl0 也开着但跨子网的包始终无法到达。反复确认后发现是对端机房的核心交换机 ACL 没有放行协议号 4。底层网络团队增加一条允许 IP protocol 4 的规则后流量瞬间通了。这种问题从应用层往下看不到任何异常只有从抓包里才能发现外层 IP 包被静默丢弃。5.2 大包不通小包通MTU 问题最常见IPIP 的固定 20 字节开销让 MTU 问题成为高频故障。现象通常是同一节点内 Pod 互通正常跨节点小流量正常业务传输大文件时卡住或者 TCP 连接频繁超时。定位方法是从业务 Pod 里向目标 Pod 发包逐步试探 MTU 临界点kubectl exec -it pod-a -- ping -M do -s 1450 pod-b-ip如果 1450 字节的包能通把 payload 逐步加大到 1472 或更大很可能就出现不通。因为 Pod 内 MTU 是 1480ICMP 消息本身有 8 字节 ICMP 头加上 20 字节 IP 头能承载的最大 payload 通常是 1452 左右再往大就会引发分片或丢弃。解决思路有三种根据线上情况挑一个调整底层物理网卡 MTU把节点物理链路调大Calico 才能顺势增加内层 MTU。直接改 Calico 的 Pod MTU 配置让 Pod 接口与封装后的 MTU匹配。改用 VXLAN 或关闭封装从源头减少额外头部带来的压力。如果只是测试阶段直接把物理网卡 MTU 调成 9000 并同步调整 Calico MTU 相关参数大包基本不会再出问题。5.3 修改 IPIP 模式后存量 Pod 不重建迟早踩坑有一种操作特别危险集群已经稳定运行你为了调性能直接把 IPPool 的ipipMode从Always改成CrossSubnet只改了配置而没有重建存量 Pod。结果会出现一部分老 Pod 的路由行为和新配置不一致流量走不通然后又去查了半天 BGP。原因不复杂。IPIP 模式切换会影响节点上的路由走向和 tunl0 的启用状态但已经创建出来的 Pod 的 veth 网卡、节点上的 conntrack 状态并不会立刻刷新。新旧状态叠加就可能出现“同一个集群里一部分 Pod 能通一部分不能通”的诡异现象。正确的操作流程是修改 IPPool 的ipipMode或新建 IPPool。保存并应用配置。滚动重启 calico-node确保节点侧路由重建。对存量业务 Pod 做一次滚动重建让数据面状态彻底对齐。再用kubectl get ippool -o yaml确认最终模式。如果你担心业务中断建议在低峰期操作还应该准备一个快速回滚方案保留旧的 IPPool 定义改动后一旦发现问题恢复旧模式并重启节点即可。5.4 别忽视 conntrack 对 IPIP 流量的影响IPIP 场景下conntrack 导致的问题也很常见尤其当你切换 IPIP 模式或者重建大量业务 Pod 后。Linux 的 conntrack 表会保留旧连接状态而 IPIP 报文经过两次 IP 头处理conntrack 对外层连接和内层连接的处理逻辑不完全一样。某些场景下你会看到内层 Pod 之间的 TCP 连接被为 INVALID 状态被防火墙规则直接丢掉。如果遇到这种问题可以尝试清理节点上的 conntrack 表项或者重启对应节点的 calico-node让数据面重新建连。当然最省事的办法还是建立固定的运维流程每次改动 IPIP 模式后都主动做一次节点滚动重启把 conntrack 残留清干净。5.5 从 IPIP 切换到 VXLAN 的注意事项当底层网络明确不允许协议号 4或者云厂商只放行 UDP 时就需要考虑把封装模式从 IPIP 切到 VXLAN。切换前先确认节点上是否支持 VXLAN 隧道Linux 内核一般自带了vxlan模块不用额外编译。操作上还是先改 IPPool 的vxlanMode字段。这和 IPIP 模式改动一样都建议先试验再铺开并且重建相关业务 Pod。切换期间BGP 路由信息并不会消失但数据面行为会变要特别关注 conntrack 残留和防火墙策略对 UDP 4789 的放行情况。VXLAN 的排障抓包与 IPIP 的最大区别在于抓包过滤器不再是ip proto 4而改成tcpdump -i eth0 udp port 4789 -nn -c 100看到 UDP 4789 的包说明 VXLAN 封装在跑。VXLAN 的头部开销比 IPIP 多约 50 字节MTU 相应要再减一次这一点千万记得同步配置。写在最后的几个小建议把 Calico IPIP 真正搞熟之后最大的感受是网络问题往往不是“配错机密”那么简单更多是底层网络、内核路由、隧道封装三者叠加后的系统性复杂。无论是模式选错导致性能下降还是安全组没放行协议号导致断连定位问题的关键都是先想清楚“数据包在哪个环节变成了什么样子”。如果你现在正在处理一个稳定的生产集群建议先抽空执行一次kubectl get ippool -o yaml把你当前的 IPIP 模式记录下来再确认一遍 tunl0 状态和物理链路 MTU。这些日常巡视动作看起来不起眼但真要出了故障能帮你省下好几个小时的排障时间。等踩过几次坑就会发现 IPIP 本身并不复杂复杂的是它背后那只“底层网络的手”。
返回列表