news 2026/9/8 20:41:18

Calico IPIP隧道模式实战:原理、部署与排障全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Calico IPIP隧道模式实战:原理、部署与排障全解

在真实处理Kubernetes集群的日常中,网络问题几乎是每个运维都会碰到的“硬骨头”。尤其是节点一多、跨网段通信需求一上来,Pod 之间明明在一个集群里,却死活 ping 不通,最后发现是路由转发链路没打通。我在这条路上踩过不少坑,最终用得最顺手、也最稳定的一套组合,就是 Calico 配合 IPIP 隧道模式。这篇文章不打算复述官方文档,而是从实操角度把 Calico IPIP 的原理、版本下载、部署验证、故障排查和选型思考一次讲透,希望能让刚接触 Calico 的人快速上手,也能让已经在用的人少走几个弯路。

1. IPIP 模式的前因后果:Calico 为什么需要隧道封装

1.1 Calico 的两种通信模式:直接路由与 Overlay 封装

Calico 的核心设计理念,是把它当作一组分布式路由器来看待。每个运行了 calico-node 的节点,本质上就是一台带 BGP 能力的路由器,节点之间通过 BGP 协议交换 Pod 网段的路由信息。理解了这一点,就会明白 Calico 有两种截然不同的数据转发路径。

第一种是“直接路由”模式,也就是不开任何隧道。节点 A 上的 Pod 要访问节点 B 上的 Pod,数据包直接以宿主机 IP 作为下一跳,由底层的三层网络原样转发。这种模式的性能损耗最小,几乎做到了纯内核转发,吞吐量高、时延低。但它有一个前提条件:承载 Kubernetes 集群的物理网络或云网络,必须允许任意节点之间的数据包以 Pod IP 为源和目的直接路由。换句话说,中间路由器得知道 Pod 网段的路由条目,或者至少能对 Pod 网段做通配转发。

第二种就是 Overlay 封装模式,典型代表是 IPIP 和 VXLAN。数据包不是直接扔进底层网络,而是套上一层新的 IP 头或 UDP 头,外层地址是宿主机节点 IP,内层才是真正的 Pod IP。这样一来,底层网络唯一需要认识的就是各节点的宿主机 IP,完全不用感知 Pod 网段的存在。这种模式牺牲了一点性能,换来的是对底层网络的强兼容性。

很多刚开始接触 Calico 的人会问,既然直接路由性能好,为什么还要用封装?答案是现实网络往往不那么“配合”。比如说,Kubernetes 集群跨了几个子网,子网之间的路由器由其他团队或云厂商控制,你没法往这些路由器上塞一堆 Pod 网段路由。再比如在公有云环境里,VPC 默认不会帮你转发不属于网络自身的非标准网段,这时候 Overlay 就成了最省事的逃生通道。

1.2 IPIP 封装的工作过程:从一个数据包的视角来看

IPIP 全称是 IP-in-IP,标准的协议类型编号是 4。它的原理非常粗暴:在原始 IP 数据包外面再套一个完整的 IP 头,外层源地址是发送节点 A 的宿主机 IP,外层目的地址是接收节点 B 的宿主机 IP。中间网络设备看到的就是一份普通的 IP 通信,从一个宿主机到另一个宿主机,完全不知道里面还藏了一个 Pod 之间的数据包。

从数据流转来看,一次完整的 IPIP 通信大概分为四步:

  • Pod A 向 Pod B 发出请求,数据包从 Pod A 的 veth 对进入节点 A 的内核协议栈。
  • 节点 A 的路由规则命中 Calico 下发的到 Pod B 网段的路由条目,下一跳指向节点 B,出接口为 tunl0。
  • 数据包经过 tunl0 接口时被封装,外部增加一个 20 字节左右的 IP 头,外层源地址为节点 A IP,外层目的地址为节点 B IP,然后发往物理网络。
  • 底层网络按普通 IP 包将其送达节点 B,节点 B 内核收到后识别到这是 IPIP 封装包,解封装还原内层数据包,再根据原始 Pod 目的地址转发给 Pod B。

整个过程对 Kubernetes 层面完全透明,Pod 内的应用感知不到封装的存在。如果你用 tcpdump 在宿主机物理网卡上抓包,会看到很多 IP 协议类型为 4 的报文,那基本就是 IPIP 封装的流量。

这里有一个值得注意的操作细节:在启用 IPIP 后,节点上会出现一个名为 tunl0 的隧道接口。它不依赖具体的物理网卡,而是以内核模块方式工作。如果节点的内核没有加载 ipip 相关模块,tunl0 就不会正常工作,这往往是很多部署问题的根源之一。

1.3 IPIP 与 VXLAN 模式怎么选:一张表看清差别

IPIP 和 VXLAN 都是 Calico 支持的 Overlay 封装方式,但在实际选型上,两者的权衡点还挺明显的。我根据自己的使用经验,把它们的关键差异整理在下面。

对比维度IPIPVXLAN
封装方式IP-in-IP,外层直接套 IP 头UDP 封装,外层套 UDP/IP 头
额外开销约 20 字节约 50 字节
IPv6 支持不支持,仅支持 IPv4支持 IPv6
网络要求依赖宿主机 IP 路由可达依赖宿主机 IP 路由可达,也支持单播组播等
内核支持需 ipip 模块,绝大多数系统默认支持需 VXLAN 支持,主流内核都有
典型场景跨网段、跨子网、底层路由可控大型集群、混合网络、IPv6 环境

选择建议其实没什么玄学。如果集群规模不大,节点数在几十台以内,底层网络就是标准的三层网络,那 IPIP 足够了,封装开销小,排错也更直观。如果集群规模上了几百台,或者网络环境比较复杂,尤其是有多租户、多网络平面、IPv6 需求的时候,VXLAN 的灵活性和可管理性会更好。同一个集群里实际上也可以让不同 IP 池分别使用不同封装模式,这个后面配置部分会讲到。

2. 动手前先搞定版本:Calico 指定版本下载全解

2.1 版本这东西为什么值得较真

很多人觉得下载 Calico 无非就是拿个最新 yaml 往下应用,省事。但到实际生产环境里,版本这事讲究得很。一方面,Kubernetes 的大版本在快速推进,Calico 的每个版本都有对应的 Kubernetes 兼容范围,盲目用新版不一定适配你现有的集群,用太老的版本又可能缺少新的 API 支持。另一方面,很多企业环境是离线的,内网没有外网权限,所有镜像和二进制都得提前准备好,一旦在某个固定版本上稳定运行了,后续扩容也不能随随便便拉一个 latest 回来。

因此我强烈建议:安装之前先明确两个变量,一个是 Kubernetes 集群的确切版本,另一个是你打算安装的 Calico 主版本号。然后去官方文档的兼容性表格里核对一下,确认 Kubernetes API 版本、kubelet 参数等都没有冲突,再把 yaml 和镜像下载下来。

2.2 下载 calicoctl / calico 命令行版本

Calico 的命令行工具有点“历史包袱”。在较早的版本里,它叫 calicoctl,当时是直接归档在 projectcalico/calico 仓库下的。后来官方拆分出了独立的 projectcalico/calicoctl 仓库,v3.19 到 v3.26 左右这段时间,要下载对应版本的独立 calicoctl。再往后,新版本又回归为 projectcalico/calico 仓库直接提供二进制,工具名简化为 calico。所以如果你在搜索引擎里看到命令一会是 calicoctl 一会是 calico,不用懵,两者基本一脉相承,只是发布形式不同。

以 v3.25.0 为例,下载独立 calicoctl 的命令如下:

curl -LO https://github.com/projectcalico/calicoctl/releases/download/v3.25.0/calicoctl-linux-amd64 mv calicoctl-linux-amd64 /usr/local/bin/calicoctl chmod +x /usr/local/bin/calicoctl calicoctl version

如果你用的是较新的 v3.27.0 及以上版本,下载命令类似,只是仓库路径和二进制名变了:

curl -LO https://github.com/projectcalico/calico/releases/download/v3.27.0/calico-linux-amd64 mv calico-linux-amd64 /usr/local/bin/calico chmod +x /usr/local/bin/calico calico version

下载完之后,建议用官方发布的 SHA256SUMS 文件做一次校验。我见过不止一次因为下载中断或者镜像源污染,导致二进制无法运行的情况。校验命令也很简单:

curl -LO https://github.com/projectcalico/calico/releases/download/v3.27.0/sha256sum.txt sha256sum -c sha256sum.txt 2>/dev/null | grep calico-linux-amd64

2.3 下载指定版本的 calico.yaml 部署清单

部署 Calico 时最常用的方式,是直接把官方准备好的清单文件应用到集群里。默认文档给出的命令往往是:

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/master/manifests/calico.yaml

这种写法最大的问题就是“master”分支随时在变,你无法保证和生产环境的一致性。正确做法是把清单固定到具体版本对应的 tag 上。GitHub 上每个 Calico 发布版本都会保留对应的 manifest 目录,所以只要把 URL 里的版本号换掉即可:

curl -LO https://raw.githubusercontent.com/projectcalico/calico/v3.25.0/manifests/calico.yaml

如果你所在网络访问 GitHub 不稳,官方文档站点也提供了归档版本路径,同样可以下载:

curl -LO https://docs.tigera.io/archive/v3.25/manifests/calico.yaml

下载下来之后不要急着直接 apply,先打开 calico.yaml 检查几个关键内容。首先是镜像版本,用 grep 看下 cni、node、kube-controllers 等镜像的 tag,确认与你想要安装的版本一致。其次看安装模式,这个清单会把项目相关的 CRD、RBAC、ServiceAccount、DaemonSet、Deployment 全部打包在一起,结构很长,但核心是 calico-node 这个 DaemonSet 和后面的 IPPool 配置。提前把内容过一遍,后面出问题时会省很多事。

2.4 离线环境不能缺的一环:镜像拉取与导入

生产环境里离线部署实在太常见了,这里把镜像准备方法一并说了。根据 calico.yaml 里的镜像列表,通常会涉及下面几个镜像:

  • calico/cni
  • calico/node
  • calico/kube-controllers
  • calico/pod2daemon-flexvol

在能访问外网的机器上,按版本号把镜像拉下来,打成 tar 包,再传到目标节点或私有镜像仓库:

docker pull calico/node:v3.25.0 docker pull calico/cni:v3.25.0 docker pull calico/kube-controllers:v3.25.0 docker pull calico/pod2daemon-flexvol:v3.25.0 docker save calico/node:v3.25.0 calico/cni:v3.25.0 calico/kube-controllers:v3.25.0 calico/pod2daemon-flexvol:v3.25.0 -o calico-images.tar

到了内网环境,导入镜像:

docker load -i calico-images.tar

如果内网有自建的镜像仓库,更推荐的做法是改掉 calico.yaml 里的 image 前缀,指向内网仓库地址,这样所有节点都会从内网统一拉取,不再依赖每个节点单独导入。改的时候注意,yaml 里同一镜像可能出现在多处,用 sed 批量替换比手改更可靠,但替换完一定要抽查几个位置,防止替换出错。

3. 完整实操:从零部署一套 IPIP 模式的 Calico

3.1 最方便的上手方式:直接应用默认清单

如果你是第一次搭建环境,或者集群属于测试环境,最快速的方式就是直接应用上一节下载好的 calico.yaml。

kubectl apply -f calico.yaml

应用之后,等上一分钟左右,检查 Pod 状态:

kubectl get pods -n calico-system

版本不同,命名空间可能不一样。早期的 Calico 会把组件放在 kube-system,v3.16 之后的版本则默认使用独立的 calico-system 命名空间。看到 calico-node 在每个节点上都是 Running,calico-kube-controllers 也稳定运行,说明基本组件已经起来了。

但注意,“能跑”不代表“按你预期的模式跑”。默认清单里的 IPPool 设置可能不是 IPIP Always,也可能是 CrossSubnet,甚至在某些安装方式下根本不开 IPIP。所以真正要紧的是确认当前实际生效的 IPPool 配置。

3.2 修改 IPPool 配置,把 IPIP 模式调整到符合预期

IPPool 是 Calico 用来管理 Pod IP 地址段的核心资源。你可以通过命令行工具查看当前 IP 池:

calicoctl get ippool -o yaml

输出里能看到一个或几个 IPPool 对象。默认创建的 IPPool 通常长这样:

apiVersion: projectcalico.org/v3 kind: IPPool metadata: name: default-ipv4-ippool spec: blockSize: 26 cidr: 192.168.0.0/16 ipipMode: Always natOutgoing: true disabled: false nodeSelector: all()

重点就是 spec.ipipMode 这一项,可取值有三个:Always、CrossSubnet、Never。

  • Always 表示所有跨节点的 Pod 流量都走 IPIP 隧道,简单粗暴,底层网络完全不用感知 Pod 网段。
  • CrossSubnet 表示只有跨子网的流量才封装,同一子网内的节点之间走直接路由,性能更好。
  • Never 表示完全禁用 IPIP,也就是使用纯直接路由。

我自己的经验是,如果节点都集中在同一子网,CrossSubnet 是最优选择。如果节点分布在不同子网,且云平台对网络管控比较严,Always 更省心。修改方式也很简单,直接 patch 默认 IPPool 即可:

kubectl patch ippool default-ipv4-ippool --type merge -p '{"spec":{"ipipMode":"CrossSubnet"}}'

修改之后,再过几秒钟,Calico 会自动重建对应的路由和隧道策略。你不需要重启节点,但我遇到过个别情况需要重启一下 calico-node Pod 才能让 tunl0 接口上的策略完全刷新。如果改了模式后路由没有如期变化,就重启对应节点的 calico-node Pod。

3.3 验证 IPIP 是否真正生效:三条命令看穿真相

部署完成后,不能只看 Pod 是 Running 就收工,必须验证 IPIP 确实在按预期工作。我通常会在每个节点上依次做三件事。

第一件事,检查隧道接口 tunl0 是否存在且状态正常:

ip addr show tunl0 ip link show tunl0

如果 tunl0 存在,且链路状态是 UP,说明 IPIP 隧道通道已经建立。有些系统默认 tunl0 存在但状态是 DOWN,得确认它变成了 UP。

第二件事,查看路由表,确认 Pod 网段的路由条目指向了 tunl0:

ip route | grep tunl0

如果你看到类似下面的输出,说明跨节点 Pod 网段已经通过 IPIP 转发:

192.168.1.0/26 via 192.168.20.101 dev tunl0 proto bird onlink 192.168.2.0/26 via 192.168.20.102 dev tunl0 proto bird onlink

注意这里的 onlink 标志,它意味着即使目标地址不在当前网卡直连网段内,也强制走这个接口,这是 Calico 驱动隧道路由的一种手段,是正常现象。

第三件事,选两个不同节点上的 Pod,做一次真实通信测试。先在节点 A 上的 Pod 里 ping 节点 B 上 Pod 的 IP,同时我习惯在节点 A 的物理网卡上抓一下包,看是否出现了协议类型为 4 的 IPIP 报文:

tcpdump -i eth0 -nn 'ip proto 4' -c 10

能抓到 IPIP 报文,说明封装确实发生了;从 Pod 里能 ping 通,说明解封装和回程路由也没问题。整个链路到这里才算真正闭环。

4. IPIP 实战排坑:MTU、rp_filter 与路由黑洞

4.1 MTU 调整:很多“时通时不通”的元凶

MTU 问题是 IPIP 隧道模式里最经典、也最容易被忽视的坑。默认情况下,物理网卡的 MTU 是 1500,而 IPIP 封装会额外占用约 20 字节,如果隧道接口或者 Pod 的 MTU 没有相应调小,就会出现一个现象:小包能通,大包不通。典型的症状是某些应用访问时正常,但一旦涉及大文件传输或大包请求,连接直接卡死或超时。

解决思路很清晰,把链路里每一跳的 MTU 都调整到与封装开销匹配。如果物理网络 MTU 是 1500,那么 IPIP 隧道的有效载荷 MTU 应该设为 1480,对应的 Pod 网卡 MTU 也应设为 1480。

判断 MTU 问题最直接的办法,是带 DF 标志去 ping 对端宿主机 IP,逐步减小包大小,找到通与不通的临界值。比如先发 1472 字节的包:

ping -M do -s 1472 对端节点IP

如果这个包能通,说明物理网络 MTU 是 1500。然后再从 Pod 内部测试跨节点大包,如果发现包在 1480 到 1500 之间丢包,基本就能确认是封装后的 MTU 超限。此时需要修改 calico.yaml 中的全局 MTU 配置,或者用 IPPool / Felix 配置把 MTU 固定到合理值。

修改后重启 calico-node Pod,让新配置生效。这里有个容易忽略的细节:光改 Calico 的 MTU 不够,节点物理网卡如果启用了巨型帧,MTU 是 9000,那隧道 MTU 可以调到 8980 附近。总之要先摸清物理网络允许的最大包,再减去封装开销。

4.2 内核模块与 rp_filter:路由对,但包就是不通

另一种很常见的情况是,你查看路由表,路由条目完全正确,tunl0 也起来了,但跨节点 Pod 通信就是不通。这时候十有八九是内核的反向路径过滤,也就是 rp_filter 在搞鬼。

rp_filter 是 Linux 内核提供的一种防伪造 IP 的机制,它会检查数据包的源 IP 是否符合当前接口的路由规则,不符合就直接丢弃。在 Calico 的多网卡、多隧道环境下,来自 tunl0 的解封装包很容易被系统判断为“源地址与入接口不匹配”,然后被静默丢弃。

官方清单里的 calico-node 在其初始化容器或安全上下文中已经把 rp_filter 设置过一轮,但在某些内核参数被覆盖、或你修改了默认 sysctl 的环境里,问题会反复出现。如果怀疑是这个问题,可以在每个节点上执行:

sysctl -w net.ipv4.conf.all.rp_filter=0 sysctl -w net.ipv4.conf.default.rp_filter=0

改为 0 是彻底关闭,改成 2 是设为宽松模式。我个人建议生产环境不要直接改成 0,而是评估一下集群的安全性,再决定是开 2 还是关掉。改完后理论上即时生效,如果还不通,再结合 tcpdump 抓包看看是不是包在入接口就被丢了。

4.3 BGP 对等与路由黑洞:黑洞路由为什么会出现在路由表里

使用 Calico 的纯 BGP 直接路由或 CrossSubnet 模式时,节点之间需要通过 BGP 交换路由。如果你配置了 IPIP 但路由表里出现了黑洞路由,比如:

blackhole 192.168.1.0/26 proto bird

这就说明 BGP 会话虽然建立了,但下一跳信息没有正确填充。出现这种情况,至少要从两个方向排查。

一是看 calico-node 的 BGP 状态。用命令行工具查看节点状态:

calicoctl node status

正常输出会显示每个 BGP 对等端的连接状态,如果是 Established,说明 BGP 层面正常。如果是 Idle 或 Active,就要检查节点间的 179 端口连通性,以及集群的 AS 号配置是否一致。

二是看 Felix 的日志。calico-node Pod 的日志里一般会留下 BGP 对等失败、路由计算异常等线索。日志级别可以通过环境变量调整,排查时可以临时把日志调成 debug,定位问题后再改回来。

还有一种路由黑洞的原因比较隐蔽:IPPool 的 nodeSelector 或者 disable 配置导致某些节点的 Pod 网段没有被正常宣告。遇到过有人在 IPPool 上设置了一堆节点选择器,结果新加的节点不在选择器范围内,Pod 能创建但路由一直缺失。这种问题从路由表看就是黑洞条目,检查 IPPool 的节点选择器往往能一击命中。

4.4 常见问题速查表:症状与排查方向

把实战中遇到的高频问题整理成一张表,方便大家快速对照定位。

现象可能原因排查方向
跨节点 Pod 完全不通隧道接口未生效、BGP 未建立检查 tunl0 状态、calicoctl node status
小包通大包不通MTU 超限ping -M do 测试,调整隧道 MTU
路由有 blackhole 条目BGP 下一跳没收敛、节点选择器不匹配查看 BGP 对等状态、IPPool 节点选择器
通但不稳定、时断时续rp_filter 丢弃、底层网络丢包检查 rp_filter、抓包分析链路质量
宿主机能通 Pod,Pod 间不通NetworkPolicy 或者 iptables 规则拦截检查策略、calico-node 日志
修改 ipipMode 后无变化calico-node 未刷新重启 calico-node Pod 或重建隧道接口

5. 长期实战下来的选型心得与版本管理建议

5.1 什么时候无脑上 IPIP,什么时候换 VXLAN

经过几次大规模环境折腾,我形成了一个比较务实的选型原则。如果你的集群规模不大,节点数量在百台以内,底层是标准三层网络,没有 IPv6 的硬性诉求,那 IPIP 基本是最优解。它的封装开销小、排错直观、依赖简单,内核支持也好。尤其是当你需要做跨子网通信,且子网间路由器不方便添加大量明细路由时,IPIP 几乎是无脑选择。

但是一旦涉及 IPv6,或者集群规模膨胀到几百上千节点,网络路径复杂,中间设备对协议类型的管控比较多,我会优先考虑 VXLAN。VXLAN 使用 UDP 封装,很多网络设备对 UDP 流量的兼容性天然比 IP-in-IP 协议好,而且在 overlay 网络中做隔离、多租户也更灵活。代价就是每包多 30 字节左右的开销,但在现代数据中心普遍开启巨型帧的背景下,这点开销基本可以忽略。

还有一点值得提,Calico 的 IPPool 是支持同一个集群内不同 IP 池用不同模式的。我曾经在一个混合集群里,一部分节点在同一子网,我用 CrossSubnet 让它们走直接路由,另一部分节点跨了子网,我对它们所在的 IPPool 改成 Always。这样既把性能留在了同网段,又保证了跨网段的可达性。

5.2 版本管理的心得:固定版本,记录变更

很多线上事故其实是“手滑升级”造成的。我个人强烈建议,把 Calico 的版本作为一个独立参数纳入集群资产清单。部署时写清楚 Kubernetes 版本、Calico 版本、使用的 IPPool 配置、下载来源。升级前先在测试环境完整验证,生产环境尽量使用和测试环境一致的版本。Calico 的版本迭代速度不慢,但没必要追求最新,稳定压倒一切。

下载指定版本这个操作,看起来只是多带个版本号的问题,但在真实环境里,能救命。我曾经因为图省事用了默认的 master 分支清单,结果一个月后再去排查另一个问题时,发现 Calico 版本已经悄悄变了,两个环境的行为不一致,排查半天才回过神。从那以后,我所有环境的 Calico 安装和升级都有了固定的版本锁定流程,calico.yaml 也会和代码一起纳入版本管理,变更记录清清楚楚。

5.3 最后分享一个小习惯:每次动 IPIP 配置前先留快照

在调整 IPPool 或切换隧道模式之前,我习惯先把当前的路由表、tunl0 状态、calicoctl node status 全部记录一份。这看上去多花了一两分钟,但回滚时能免掉很多猜测。毕竟 Calico 的配置变更大多是即时生效的,一旦出问题,第一时间回退或者重新配置,都比翻日志猜原因要快得多。特别是刚上手 IPIP 的读者,建议每次改动都做一次前后对比,配置项很快就熟了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 20:40:20

LivePortrait 实操指南:三条命令把静态照片变成动态肖像

LivePortrait 实操指南:三条命令把静态照片变成动态肖像 【免费下载链接】LivePortrait Bring portraits to life! 项目地址: https://gitcode.com/GitHub_Trending/li/LivePortrait LivePortrait 是一个基于 PyTorch 的开源人像动画工具:它从一段…

作者头像 李华
网站建设 2026/9/8 20:38:25

Windows Terminal 自动补全实战:PSReadLine 与 Clink 组合配置指南

1. 先搞明白:Windows Terminal 的自动补全到底缺什么这些年不管是从 cmd 迁移过来,还是从 macOS 的 iTerm2 转战 Windows,很多人装上 Windows Terminal 的第一反应都是:界面是漂亮了,字体渲染也舒服了,可这…

作者头像 李华