【容器网络】calico从入门到精通
第一章:Calico 整体架构概念
Calico 是一套基于 纯三层路由(L3) 的容器网络方案,区别于 overlay(如 Flannel VXLAN)与 BPF-first(如 Cilium)。
Calico 的核心是:
-
CNI 插件(容器入口)
-
Felix(数据面控制器)
-
Linux 内核(iptables / ipset / route / conntrack)
-
BGP 路由(跨节点 Pod 通信机制)
Calico 的网络行为完全由 datastore + 控制循环 驱动:
组件把对象写入 datastore(如 WEP)
-
Felix 监听到变化
-
Felix 渲染 iptables / ipset / 路由
-
BGP 宣告子网
-
Linux 内核执行业务流量
Calico 本质上是:
“对象驱动的路由 + 策略控制系统”。
┌──────────────────────────────────────────┐
│ Kubernetes API / Datastore │
│ (WEP / Policy / IPPool / IPAMBlock) │
└──────────────────────────────────────────┘
▲
│ 对象写入 / 更新
│
┌──────────────────────────────────────────┐
│ Felix(控制循环 / 数据面控制器) │
│ watch → diff → render → apply │
└──────────────────────────────────────────┘
│ │ │
│ │ │
iptables/ipset 路由表 BGP 宣告
│ │ │
└───────────────┴────────────────┘
▼
┌──────────────────────────────────────────┐
│ Linux Kernel(真实执行者) │
│ netfilter / route / conntrack │
└──────────────────────────────────────────┘
▼
Pod 实际流量
这张图表达的是一句话:
Calico 不“转发流量”,Calico 只“配置内核”,流量始终走 Linux 内核。
第一层:为什么说 Calico 是“纯三层路由(L3)”
Pod A (10.244.1.3)
│
│ 查路由表(L3)
▼
Node1 内核路由:
10.244.2.0/26 → via Node2-IP
│
│ 原生 IP 包(无封装)
▼
Node2 内核
│
▼
Pod B (10.244.2.5)
关键点你在图里要点出来:
-
❌ 没有 VXLAN / 没有隧道
-
✅ 路由表里直接存在 Pod 子网
-
✅ 下一跳是 Node IP(Underlay)
对比关系在图里是隐含的:
-
Flannel VXLAN:Pod → VXLAN 封装 → 解封 → Pod
-
Calico L3:Pod → 查路由 → 直达 → Pod
-
Cilium BPF-first:绕开 iptables,用 eBPF 程序接管数据面
第二层:Calico 的四大核心组件在图中的位置
[ Pod 创建 ]
│
▼
┌────────────┐
│ CNI 插件 │ ← 容器入口
│ - 分 IP │
│ - 建 veth │
│ - 写 WEP │
└────────────┘
│
▼
[ Datastore / API ]
│
▼
┌────────────┐
│ Felix │ ← 控制循环核心
│ watch 对象 │
│ 生成规则 │
└────────────┘
│
▼
┌────────────────────────────┐
│ Linux Kernel │
│ iptables / ipset / route │
│ conntrack │
└────────────────────────────┘
│
▼
[ 实际业务流量 ]
你在讲图的时候要强调:
- CNI 不转发流量
- Felix 不转发流量
- 只有 Linux 内核在转发流量
第三层:datastore + 控制循环(最容易被问“为什么这样设计”)
对象变化(WEP / Policy / IPPool)
│
▼
Felix Watch(事件)
│
▼
差异计算(calc)
│
▼
渲染阶段(render)
┌──────────┼───────────┐
▼ ▼ ▼
iptables ipset route
│
▼
内核状态收敛
这张图回答三个面试隐含问题:
-
为什么 Calico 稳定?
→ 控制循环,状态最终一致 -
为什么性能好?
→ 规则是“提前算好”的,不是 runtime 判断 -
为什么可扩展?
→ 对象模型 + 单向渲染
第四层:BGP 在整张图里的准确位置
Node1 Node2
┌────────────┐ ┌────────────┐
│ BGP Agent │◀────────▶│ BGP Agent │
│ 宣告 10.244│ │ 宣告 10.244│
└────────────┘ └────────────┘
│ │
▼ ▼
Node 路由表 Node 路由表
你要讲清楚这一点(非常关键):
BGP 不在数据路径上,它只负责“同步路由信息”。
真正的数据路径永远是:
Pod → Linux Kernel → Linux Kernel → Pod
最终“面试一句话配图总结”
你可以对着这张图这样说:
Calico 是一套基于纯三层路由的容器网络方案。 Pod 的网络身份以对象形式写入 datastore,Felix
通过控制循环监听对象变化,将策略和路由渲染为 Linux 内核的 iptables、ipset 和路由表;BGP 负责在节点之间同步 Pod
子网信息,最终由内核完成真实的数据转发。 本质上,Calico 是一个对象驱动的路由与策略控制系统。
Calico 极简架构图
┌─────────┐
│ Pod │
└────┬────┘
│ ① CNI 建网 / 分 IP
▼
┌──────────────┐
│ Datastore │ ← 对象(WEP / Policy)
└────┬─────────┘
│ ② Felix watch
▼
┌──────────────┐
│ Felix │ ← 控制循环
│ watch→render │
└────┬─────────┘
│ ③ 渲染规则
▼
┌─────────────────────────┐
│ Linux Kernel │
│ iptables / ipset / route│
└────┬─────────────┬──────┘
│ ④ 路由同步 │
│ ▼
│ ┌─────────┐
│ │ BGP │
│ │ 宣告子网 │
│ └────┬────┘
│ │
▼ ▼
实际流量 其他 Node
讲人话
1️⃣ Pod 创建时,CNI 只做一件事
分 IP、建 veth,把 Pod 的网络信息写成对象(WEP)。
2️⃣ Calico 的一切行为都从对象开始
所有网络状态都在 datastore,不是即时判断。
3️⃣ Felix 是核心控制循环
它 watch 对象变化,把高层策略翻译成内核规则。
4️⃣ 真正转发流量的只有 Linux 内核
iptables、ipset、route、conntrack 都在内核里跑。
5️⃣ BGP 不转发流量,只同步路由信息
把每个节点的 Pod 子网告诉其他节点。
6️⃣ 所以 Calico 的本质是
对象驱动的三层路由 + 策略控制系统。
一句话终极版
Calico 是基于纯三层路由的容器网络,通过对象驱动的控制循环,把策略和路由渲染到 Linux 内核,BGP 负责同步 Pod 子网,内核完成真实转发。
面试官常见追问
Q:为什么不用 overlay?
→ 不封包,性能更好,路由透明,易 debug。
Q:Felix 为什么要预渲染?
→ 网络策略必须高性能,不能 runtime 判断。
Q:BGP 在数据路径上吗?
→ 不在,只同步路由,数据全走内核。
WEP
好,只讲面试里“必须懂”的版本,不绕。
WEP 是什么?
WEP = WorkloadEndpoint
一句话:
WEP 是 Calico 世界里“Pod 的网络身份证”。
Kubernetes 里你看到的是 Pod,
Calico 里真正被用来算路由、算策略、下规则的,是 WEP。
为什么 Calico 需要 WEP?
因为 Calico 不直接依赖 Pod 对象。
Calico 要做的事是网络层面的:
- 这个 Pod 的 IP 是多少
- 挂在哪个 Node
- 对应哪个 网卡(veth)
- 应用哪些 NetworkPolicy
- 出入流量该怎么走
这些信息 Pod 对象本身不完整、也不稳定,
所以 Calico 把它们抽象成一个稳定的网络对象 —— WEP。
Pod 是应用概念,WEP 是网络概念。
WEP 里到底有什么?(面试必背)
一个 WEP 至少包含这些关键信息:
- IP 地址(Pod IP)
- MAC 地址
- Node 名称(Pod 跑在哪个节点)
- interfaceName(对应宿主机上的 veth)
- labels / profiles(用来匹配 NetworkPolicy)
- endpoint 名称(namespace + pod)
你可以理解成:
WEP = Pod 的“网络简历”
WEP 在 Calico 里的位置(白板版)
Pod 创建
│
▼
CNI 插件
│ 建 veth / 分 IP
│
▼
写 WEP 到 datastore
│
▼
Felix watch WEP
│
▼
渲染 iptables / ipset / route
关键一句:
Felix 不看 Pod,只看 WEP。
你怎么验证 WEP 真的存在?(非常重要)
直接在集群里跑:
calicoctl get wep -A -o wide
或者看详细内容:
calicoctl get wep -A -o yaml
你会看到每个 Pod 都对应一个 WEP。
如果一个 Pod 没有 WEP →
这个 Pod 在 Calico 看来“网络不存在”。
WEP 和 NetworkPolicy 的关系
NetworkPolicy 并不是“直接作用在 Pod 上”。
真实路径是:
NetworkPolicy
↓ selector
匹配 WEP(靠 labels)
↓
Felix 生成 ipset
↓
iptables 链绑定到 WEP 对应链
所以一句非常硬核的话你可以在面试说:
Calico 的 NetworkPolicy 实际是作用在 WEP 上,而不是 Pod 上。
WEP vs HEP(面试必问对比)
| 对象 | 作用 |
|---|---|
| WEP | Pod 的网络端点 |
| HEP(HostEndpoint) | Node 上网卡的网络端点 |
一句话区分:
WEP 管 Pod,HEP 管 Node。
面试官会怎么问?你怎么答?
❓ Q1:什么是 WEP?
标准答案:
WEP 是 Calico 定义的 WorkloadEndpoint,是 Pod 在 Calico 网络里的抽象表示,包含 IP、MAC、Node、接口和策略信息。
❓ Q2:Felix 为什么要用 WEP?
标准答案:
Felix 通过监听 WEP 的变化来感知 Pod 的网络状态,再把策略和路由渲染成内核规则,而不是直接依赖 Kubernetes Pod 对象。
❓ Q3:WEP 和 Pod 的关系是什么?
标准答案:
一个 Pod 对应一个 WEP,Pod 生命周期变化会导致 WEP 创建或删除,但 Calico 的控制逻辑完全基于 WEP。
❓ Q4:没有 WEP 会怎样?
标准答案:
Felix 无法为该 Pod 下发路由和策略,Pod 在 Calico 网络中等同于不可达。
一句话终极总结(请背)
WEP 是 Calico 用来描述 Pod 网络身份的核心对象,是 Felix 渲染路由和策略的唯一输入。
这句话你说出来,面试官基本不会再在 WEP 上追问了。
如果你愿意,我可以下一步把 “WEP 从创建到删除的完整生命周期” 画成一条时间线,专门用来应对追问。


我给你一条白板可画、30 秒能讲清的 WEP 生命周期时间线。照着画就行。
WEP 生命周期(从出生到消失)
Pod 创建
│
▼
CNI 插件(Add)
│ 分 IP / 建 veth
│
▼
创建 WEP(写 datastore)
│
▼
Felix watch 到 WEP
│
├─ 生成 ipset(匹配策略)
├─ 生成 iptables 链(from-wl / to-wl)
└─ 下发路由(host route / 子网路由)
│
▼
Pod 正常通信(内核转发)
删除时:
Pod 删除
│
▼
CNI 插件(Del)
│ 回收 IP / 删 veth
│
▼
删除 WEP
│
▼
Felix watch 到删除事件
│
├─ 清理 iptables 链
├─ 清理 ipset 成员
└─ 清理路由
每个节点你在面试要点名的“职责”
- CNI:只负责接生(IP + veth + 写/删 WEP)
- Datastore:WEP 的“单点真相”
- Felix:watch → diff → render → apply
- Linux 内核:真正转发流量
- BGP:只同步路由信息,不在数据路径上
你怎么“现场验证”这条线?(面试加分)
- 看 WEP:
calicoctl get wep -A -o wide
- 看规则:
iptables-save | grep cali-
ipset list | grep cali
- 看路由:
ip route | grep 10.244
- 看 Felix 反应:
kubectl logs -n kube-system ds/calico-node -f
30 秒标准背诵版(请背)
Pod 创建时,Calico CNI 分配 IP 并创建 veth,同时把 Pod 的网络身份写成 WEP;Felix 监听到 WEP 的变化,把对应的策略、ipset、iptables 链和路由渲染到内核;Pod 删除时,WEP 被移除,Felix 清理所有相关规则。整个过程中 BGP 只负责同步 Pod 子网路由,数据转发始终由内核完成。
第二章:Pod 生命周期与 Calico 的链路
Pod 出生 → 网络配置 → 策略生效,这是学习 Calico 最重要的一条线。
当 Pod 创建时:
-
kubelet 调用 CNI 插件(calico CNI)
-
CNI 执行:
-
从 IPAM 分配 Pod IP
-
创建 veth(host 侧 / pod 侧)
-
写 WorkloadEndpoint(WEP)对象
-
-
Felix 监听到 WEP
-
Felix 计算:
-
iptables 规则
-
ipset 集合
-
路由表更新
-
-
BGP backend 宣告 Pod 网段 / 路由
-
内核开始转发流量
删除 Pod 时:
- IPAM 回收 IP
- WEP 被删除
- Felix 清理策略、路由
结论:Pod 生命周期是 Calico 的主干逻辑。
这里以及以后的章节,建议搭建集群安装calico
实验
Pod 创建时 —— 你怎么“看见”这条链路
实验 1:Pod 出生 → IP → WEP
kubectl run p1 --image=registry.aliyuncs.com/google_containers/pause:3.9 --restart=Never
kubectl get pod p1 -o wide

然后立刻:
kubectl get ippool
kubectl get ipamblock -A | grep 10.244

重点观察:
- Pod IP ∈ 哪个 block
你看到的不是“随便一个 Pod IP”,而是:
这个 IP 是从 某个 Node 持有的一个 IPAMBlock 里分出来的
也就是说,IP 的第一层归属不是 Pod,而是 Node。
逻辑是:
-
IPPool:全局地址池(10.244.0.0/16)
-
IPAMBlock:Node 级别“分包”的一小段
-
Pod:从自己 Node 的 block 里拿 IP
这一步的意义是:
👉 避免所有 Pod 在一个全局池里抢 IP
否则每创建一个 Pod:
-
都要全局锁
-
都要写 datastore
-
并发直接爆炸
如下图所示,pod 和分配的ipamblock 都是worker1 的

- block 是不是 /26
重点:为什么默认是 /26?
/26 = 64 个 IP(可用大概 62)
这是一个非常“工程味”的取值,不是拍脑袋。
1️⃣ 并发与冲突控制(最重要)
Calico 的 IPAM 是分布式的。
如果 block 太大(比如 /16):
所有 Node 都在同一个 block 里分 IP
-
冲突概率高
-
datastore 更新频繁
-
回收成本巨大
如果 block 太小(比如 /30):
-
一个 Node 很快就要申请新 block
-
block 创建/回收非常频繁
-
控制面压力大
/26 是一个折中点:
-
一个 Node 一次拿一小包
-
Pod 创建时不需要再碰 datastore
-
只有 block 用完 / 回收时才写
👉 大部分 Pod 创建 = 本地 bitmap 操作
2️⃣ 本地性(Locality)
/26 带来的另一个隐含好处是:
一个 Node 上的 Pod IP 大概率是连续的
这直接影响:
-
路由聚合
-
iptables / ipset 数量
-
BGP 宣告粒度
比如:
Node A: 10.244.1.0/26
Node B: 10.244.1.64/26
跨节点时:
-
宣告的是 block / Node
-
不是每个 Pod 一条路由
3️⃣ 路由规模控制(BGP 的视角)
Calico 的核心理念是:
Pod 子网直接进主机路由系统
如果你不用 block:
-
每个 Pod = 一条路由
-
BGP 表会炸
-
内核 fib 会炸
/26 的意义是:
-
一条路由 ≈ 一批 Pod
-
路由数量 ≈ Node 数量
-
可线性扩展
4️⃣ 回收与稳定性
Pod 删除时:
-
IP 只是回到 block 的 bitmap
-
block 不立刻销毁
这意味着:
-
短时间 Pod 波动不会导致 IP 抖动
-
减少“刚删又建拿不同 IP”的概率
这对:
-
有状态应用
-
日志/监控
-
策略缓存
都非常友好。
你现在这个实验,怎么“亲眼看到 /26 的价值”
你可以在你集群里直接验证:
kubectl get ipamblock -A -o yaml | grep cidr
你会看到一堆类似:
cidr: 10.244.235.128/26
再对照 Pod IP:
10.244.235.129
10.244.235.130
你会发现:
👉 Pod IP 在 block 内是连续的
👉 不同 Node 拿的是不同 /26
面试时一句话怎么说(非常重要)
你可以直接这样答:
Calico 通过 IPAMBlock 把全局 IPPool 切成 /26 的小块分配给 Node,本地用 bitmap 管理 IP。这样既避免全局锁,又减少 datastore 写入,同时控制 BGP 路由规模,是并发、性能和路由可控性的折中设计。
面试钩子
为什么 Calico 要有 WEP,而不是直接靠 Pod?
答:WEP 是 Calico 内部稳定抽象,Pod 是 K8s 对象,WEP 才是“网络身份”。
实验 2:veth 是什么时候创建的
Pod 所在 node
ip link | grep cali
你会看到:
- caliXXX(host 侧)

- Pod 内是 eth0
追问点
Felix 创建 veth 吗?
不。veth 是 CNI 插件干的,Felix 只管规则和路由。
一句话结论(先给时间点)
veth 是在 kubelet 调用 CNI 的 ADD 阶段,由 Calico CNI 插件创建的,发生在容器进程启动之前、镜像解压之后。
顺序非常严格,错一位都不会工作。
精确时间轴(这是重点)
以你 kubectl run p1 为例,真实顺序是:
kubectl apply
↓
kube-apiserver 写 Pod 对象
↓
scheduler 选 node
↓
kubelet(目标 node)开始处理 Pod
↓
container runtime 拉镜像 / 解压 rootfs
↓
❗ kubelet 调用 CNI ADD(这里创建 veth)
↓
pause / 容器进程启动
↓
容器里 eth0 ready
👉 veth 创建发生在容器“还没跑起来”的时候
👉 容器一启动,就已经看到 eth0 了
kubelet 到底是怎么触发 CNI 的?
在你现在的环境里(containerd + kubelet):
- kubelet 调用 CRI:
RunPodSandbox()
- CRI 插件(containerd-shim)发现:
-
需要网络
-
sandbox 还没 network
- containerd 调用:
CNI ADD
并把这些参数传进去:
-
Pod name / namespace
-
Pod UID
-
Pod netns 路径(比如 /proc/12345/ns/net)
-
CNI 配置(/etc/cni/net.d/10-calico.conflist)
Calico CNI 在 ADD 里具体干了什么(逐步)
下面是真正发生的步骤,不是抽象描述。
1️⃣ 创建 veth pair(此时 Pod 进程还不存在)
在 host namespace 执行:
ip link add caliXXXX type veth peer name eth0
-
caliXXXX:留在 host
-
eth0:准备放进 Pod netns
注意:eth0 此时还不在 Pod 里
2️⃣ 把 eth0 移入 Pod netns
ip link set eth0 netns <pod-netns>
这一步完成后:
-
host 只剩 caliXXXX
-
Pod netns 里出现 eth0(但还没 IP)
3️⃣ 给 eth0 配 IP / MAC / route(在 Pod netns 内)
ip addr add 10.244.x.y/32 dev eth0
ip link set eth0 up
ip route add default via 169.254.1.1
Calico Pod IP 是 /32,不是子网
路由由 host 决定怎么转
4️⃣ host 侧 caliXXXX up + route
ip link set caliXXXX up
ip route add 10.244.x.y dev caliXXXX
5️⃣ 写 Calico datastore(WEP)
CNI 插件这时会创建:
WorkloadEndpoint
里面包含:
Pod IP
interfaceName = caliXXXX
node
labels
👉 这一步是 Felix 后续工作的触发点
Felix 是什么时候介入的?
veth 创建 ≠ Felix 的事
Felix 的时间点是:
CNI 写 WEP
↓
datastore 变化
↓
Felix watch 到 WEP
↓
生成 iptables / ipset / route
所以你要记住一句关键区分:
CNI 负责“线接好”,Felix 负责“路怎么走、能不能走”
用你现在的集群,怎么“亲眼看到这个过程”
实验 1:Pod Pending 时 veth 已存在
kubectl run p2 --image=pause --restart=Never
在 Pod 还没 Running 时(甚至 ErrImagePull):
ip link | grep cali
你会发现:
-
cali 接口已经出现
-
即使容器没起来
👉 证明:veth 在容器启动前创建
实验 2:Pod netns 里 eth0 的来源
找 pause 进程 pid(在 Pod 所在 node):
crictl ps | grep p2
crictl inspect <container-id> | grep pid
进 netns:
nsenter -t <pid> -n ip link
你会看到:
eth0
它不是 magic,是 CNI 提前放进去的。
面试必问 & 标准回答
Q:veth 是什么时候创建的?谁创建的?
veth 是在 kubelet 调用 CNI ADD 阶段,由 Calico CNI 插件创建的,发生在容器启动之前。CNI 负责创建 veth、配置 IP 并写 WEP,Felix 后续再根据 WEP 渲染策略和路由。
再给你一个“更高阶”的理解(你已经能接住)
Calico 的设计是:
-
CNI:一次性、同步、创建型
-
Felix:持续监听、异步、控制型
这就是为什么:
-
Pod 一启动就有 eth0
-
策略可能稍后才完全生效(但结构已就绪)
先启动eth0后有策略?
Calico 接受“eth0 先于策略存在”这个事实,但通过 同步 CNI + 预置链 + 默认全通语义,保证在 Pod 真正开始发流量之前,数据面已经处于一个确定状态。
面试时怎么说(非常关键)
如果有人问你类似:
Calico 会不会有策略生效不一致的时间窗口?
你可以直接答:
从时序上看,veth 和 IP 是在 CNI ADD 阶段创建的,策略由 Felix 异步渲染。但 kubelet 在 CNI 完成前不会启动容器进程,同时 Calico 的数据面主干链是预先存在的,因此不存在业务流量在“无策略状态”下发包的问题。只有在显式 default-deny 场景下才会短暂 fail-closed,这是预期行为。
实验 3:Pod 删除 → 回收
kubectl get ipamblock 10-244-235-128-26 -o yaml
kubectl delete pod p1
kubectl get ipamblock 10-244-235-128-26 -o yaml
你会看到 bitmap 变化(已用 IP 回收)。
bitmap 在 spec.allocations / spec.unallocated 里
一个真实的 IPAMBlock(简化)是这样的结构:
spec:
cidr: 10.244.2.0/26
allocations:
- null
- 123456
- null
- null
- ...
unallocated:
- 5
- 6
- 7
attributes:
- handle_id: k8s-pod-network.xxxx
secondary:
namespace: default
pod: p1
node: worker1
这里有三件极重要的事:
1️⃣ allocations 是一个数组(bitmap 的本体)
index = IP 偏移
value = handle ID
null = 空闲
非 null = 已占用
2️⃣ unallocated 是“空闲 index 列表”(压缩存储)
为了节省空间:
Calico 不存完整 bitmap
而是用 allocations + unallocated 混合表示
3️⃣ handle_id 决定“谁用了这个 IP”
Pod 删除 → handle 释放 → allocation 置空 → index 回到 unallocated
实验 4:pod A 发到pod B (跨节点)
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: p2
spec:
nodeName: worker2
containers:
- name: netshoot
image: docker.io/nicolaka/netshoot:latest
imagePullPolicy: IfNotPresent
command: ["sleep","365d"]
EOF
场景假设(你可以用自己的实际 IP 对照)
PodA:10.244.235.134 在 worker1
PodB:10.244.189.72 在 worker2
worker1 IP:192.168.10.12
worker2 IP:192.168.10.13
Calico:非 overlay,BGP 模式
一、PodA 发出第一个包(Pod 网络命名空间)
你在 PodA 里执行:
ping 10.244.189.72
1️⃣ Pod 内的网络视角
ip addr
ip route
你会看到:
eth0: 10.244.235.134/32
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

这两条路由是一组“配套设计”,目的是让 Pod 的默认路由既可用、又不依赖真实网关,还能被宿主机完全接管。
先看第二条(这是基础)
169.254.1.1 dev eth0 scope link
这条的意思是:
169.254.1.1 在 eth0 的二层直连范围内
也就是说:
不需要 ARP 之外的任何东西
直接往 eth0 发就行
注意一个关键点:
169.254.0.0/16 是 link-local 地址段,RFC 定义为“只在本链路有效”。
👉 这个 IP 从来不会真的存在在网络中
再看第一条(这是“钩子”)
default via 169.254.1.1 dev eth0
这条的真实含义是:
Pod 内所有非本地流量,都“假装”发给 169.254.1.1
但真相是:
-
Pod 并不关心 169.254.1.1 是谁
-
它只需要一个“形式合法的 next-hop”
-
真正处理包的是 宿主机的 veth 对端
关键转折点:veth 是二层直连,不需要真实网关
Pod → eth0 → veth → host
这里发生的事是:
-
Pod 把包丢给 eth0
-
内核发现:169.254.1.1 在 scope link
-
不做三层转发
-
直接 L2 丢给 veth 对端
👉 包根本不会“去找”169.254.1.1
这只是一个触发默认路由的占位符
那为什么不用 default dev eth0?
这是个非常关键的设计选择。
如果写成:
default dev eth0
会有几个严重问题:
-
无法表达“这是一个三层转发点”
-
路由行为在不同内核版本不一致
-
很多工具(ip rule / policy routing)不好配合
-
CNI 很难统一控制行为
用 via 169.254.1.1 的好处是:
明确告诉内核:这是 L3 路由语义
但又不依赖真实设备
所有 CNI(Calico / Flannel / Cilium)都用这一招
👉 这是一个Linux 网络工程里的经典技巧
再往深一层:谁“真的”是网关?
答案是:宿主机自己
-
Pod 认为网关是 169.254.1.1
-
实际上:
-
veth 对端在 host
-
host 根据 iptables / route 决定下一跳
-
-
Pod 永远不会看到真实拓扑
一句话总结:
Pod 的默认网关是“逻辑网关”,不是物理网关
面试级一句话总结(可以直接用)
Pod 内看到的 169.254.1.1 并不是真实网关,而是 CNI 设置的 link-local 占位 next-hop。它的作用是触发默认路由,将所有流量通过 veth 交给宿主机,由宿主机完成真实的路由和策略处理。
关键点:
-
Pod IP 是 /32
-
Pod 根本不知道什么 worker2
-
所有非本地流量 → 丢给 eth0
👉 Pod 的世界非常简单
二、eth0 → veth → host(worker1)
这个包从 Pod 的 eth0 出来:
ICMP echo-request
src=10.244.235.134
dst=10.244.189.72
2️⃣ veth 对的作用
-
Pod 侧:eth0
-
Host 侧:caliXXXX
在 worker1 上:
ip link | grep cali
你会看到对应的 caliXXXX@ifYYY
这一步没有任何路由判断,只是 L2 veth 转发。
三、进入 host 网络栈(worker1)
包现在进入 worker1 的 Linux 网络栈,开始真正“走 Calico”。
3️⃣ 路由决策(最关键的一步)
在 worker1 上:
ip route get 10.244.189.72
你会看到类似:
10.244.2.0/24 via 192.168.10.13 dev eth0 proto bird
下图里面ens33是我虚拟机真实接口名,eth0:泛指宿主机对外网卡
下图就是路由表原貌
这一步非常重要:
👉 worker1 知道 PodB 在 worker2
👉 这是 BGP 写进内核的路由
Calico 在这里已经完成了 90% 的工作
四、iptables:策略检查(Felix 的地盘)
在真正发包前,Linux 会过 iptables。
4️⃣ FORWARD 链(Calico 主战场)
iptables-save | grep cali-FORWARD -n

逻辑结构是(简化):
FORWARD
└─ cali-FORWARD
├─ cali-from-wl
│ └─ cali-fw-caliXXXX
└─ cali-to-wl
Felix 在这里做了什么?
-
根据 WEP
-
把 PodA 对应的 veth
-
jump 到它专属的策略链
如果你 没写 NetworkPolicy:
👉 规则是 ACCEPT
如果你写了 deny-all:
👉 在这里 DROP
五、包从 worker1 发向 worker2(真实三层)
包现在被路由到:
dev eth0
next-hop 192.168.10.13
这是一个 普通的三层包:
src=10.244.1.10
dst=10.244.2.20
L2 dst = worker2 MAC
⚠️ 没有 VXLAN
⚠️ 没有封装
⚠️ 没有 overlay
👉 Calico 的核心哲学:
Pod IP 就是路由 IP
六、包到达 worker2(反向对称)
包进入 worker2:
6️⃣ worker2 的路由判断
ip route get 10.244.189.72

结果:
10.244.189.72 dev caliYYYY
说明:
-
PodB 的 IP 是本机直连
-
不再走 eth0
七、iptables(worker2 的入站策略)
在 worker2:
iptables-save | grep cali-to-wl -n

逻辑是:
FORWARD
└─ cali-to-wl
└─ cali-fw-caliYYYY
这里检查的是:
- PodB 的 ingress policy
通过 → 放行
拒绝 → DROP
八、veth → eth0 → PodB
包被送入:
caliYYYY → eth0 (Pod netns)
PodB 收到:
ICMP echo-request
然后原路返回 echo-reply,路径完全对称。
九、你现在可以做的“验证级命令”
在 worker1:
ip route | grep 10.244
iptables-save | grep caliXXXX
在 worker2:
ip route | grep 10.244
iptables-save | grep caliYYYY
抓包(任选一个点):
tcpdump -i eth0 host 10.244.2.20
tcpdump -i caliXXXX
十、一句话把整条链路说清(面试级)
你可以这样总结:
在 Calico 的 BGP 模式下,Pod 发出的包先经 veth 进入宿主机,由内核根据 BGP 下发的路由决定下一跳,iptables 中的 Calico 链完成策略校验,随后以三层方式直接转发到目标节点,再通过目标节点的 veth 进入 Pod。整个过程不依赖 overlay,Pod IP 即路由 IP。
和同节点pod 的对比
在 Calico 中,同节点 Pod 通信和跨节点 Pod 通信在 Pod 内、veth 以及 iptables 策略阶段完全一致,差异只体现在宿主机的路由决策:同节点命中本机 veth,跨节点命中 BGP 下发的下一跳并经由物理网卡转发。策略在路由之前生效,因此不存在跨节点绕过 NetworkPolicy 的情况。
一、先给你一张“对照心智图”(不抽象,按阶段)
| 阶段 | 同节点 Pod | 跨节点 Pod |
|---|---|---|
| Pod 内 | 一样 | 一样 |
| veth → host | 一样 | 一样 |
| cali-from-wl / to-wl | 一样 | 一样 |
| 宿主机路由 | 本机直连 | 下一跳是对端 Node |
| 出口设备 | caliXXXX(veth) | ens33(物理网卡) |
| 是否封装 | 否 | 否(你现在是纯 BGP) |
| 是否经过物理网络 | 否 | 是 |
真正的分叉点只有一个:宿主机路由表命中哪条路由。
二、同节点 Pod:一切都发生在“本机”
场景
p1:10.244.1.10(worker1)
p1b:10.244.1.11(worker1)
路由判断(worker1)
ip route get 10.244.1.11
你会看到类似:
10.244.1.11 dev caliYYYY src 10.244.1.10
这一步意味着什么?
目标 Pod IP 在本机
内核知道:这是一个 直连 endpoint
不走物理网卡
不离开内核
包的真实路径
PodA eth0
→ veth
→ iptables (cali-from-wl)
→ 路由命中 caliYYYY
→ iptables (cali-to-wl)
→ veth
→ PodB eth0
完全没有走 ens33
完全没有经过物理网络
👉 本质:宿主机承载的 L2/L3 转发
三、跨节点 Pod:从“本机”切到“网络”
场景
p1:10.244.235.128(worker1)
p2:10.244.189.72(worker2)
路由判断(worker1)
ip route get 10.244.189.72
你已经看到:
10.244.189.72 via 192.168.10.13 dev ens33 src 192.168.10.12
这一步发生了什么?
目标 Pod IP 不在本机
命中 BGP 下发的路由
下一跳 = 对端 Node IP
出口设备 = ens33
包的真实路径(纯 BGP)
PodA eth0
→ veth
→ iptables (cali-from-wl)
→ 路由命中 via worker2
→ ens33
→ 物理网络
→ ens33(worker2)
→ iptables (cali-to-wl)
→ veth
→ PodB eth0
👉 唯一多出来的东西:物理网络
四、iptables:两种情况几乎完全一样(关键认知)
这是很多人会误判的地方。
不管同节点还是跨节点:
都会进:
cali-from-wl
cali-to-wl
NetworkPolicy 不关心你是不是跨节点
Felix 渲染的规则结构 一模一样
也就是说:
策略判断发生在“是否离开节点”之前
这就是为什么:
同节点 Pod 也能被 NetworkPolicy 拦住
跨节点 Pod 不会“绕过策略”
五、你可以亲手验证的“最直观对照实验”
1️⃣ 同节点
在 worker1:
ip route get <同节点-pod-ip>
tcpdump -i ens33 icmp # 什么都看不到
2️⃣ 跨节点
在 worker1:
ip route get <跨节点-pod-ip>
tcpdump -i ens33 icmp # 能看到包
iptables 的计数器变化是一样的,ens33 才是差异点。
Q&A
一、“一个包怎么走”
标准答案:
Pod 发出的包先通过 veth 进入宿主机网络栈,在进入宿主机后先经过 Calico 注入的 iptables 策略链进行策略校验,随后由宿主机路由表决定下一跳:同节点命中本地 veth,跨节点命中 BGP 下发的对端 Node 路由并从物理网卡转发,最终在目标节点再次经过策略链后进入目标 Pod。
一句话版:
Pod → veth → iptables(策略)→ 路由 →(同节点 veth / 跨节点物理网卡)→ iptables → veth → Pod。
二、“为什么 NetworkPolicy 一定会生效”
标准答案:
因为 Calico 的策略不是在转发路径中动态判断,而是提前由 Felix 将 NetworkPolicy 渲染成静态 iptables/ipset 规则,并挂接在 Pod 对应的 veth 接口上。所有 Pod 流量在离开或进入宿主机前都必须经过这些链,因此无论是同节点还是跨节点通信,都无法绕过策略。
关键点强调(加分):
-
策略 发生在路由之前
-
同节点 / 跨节点 共享同一套策略路径
-
没有 runtime decision,只有内核规则匹配
一句话版:
NetworkPolicy 一定生效,是因为所有 Pod 流量在路由前就被 iptables 强制拦截,根本没有“绕路”的可能。
三、“overlay 和非 overlay 的真实差异”
标准答案:
Overlay 和非 overlay 的差异只体现在跨节点转发阶段:
- 在 overlay(IPIP/VXLAN)模式下,宿主机将 Pod 流量封装后再通过物理网络转发;
- 在非 overlay(纯 BGP)模式下,宿主机直接基于 BGP 下发的三层路由转发 Pod 原始 IP 包。
Pod 内、veth、策略链路在两种模式下完全一致。
对照一句话:
Overlay 多了一层“封装/解封装”,非 overlay 直接走三层路由,其余路径完全相同。
四、“为什么 Calico 可以 scale”
标准答案:
Calico 能 scale 的核心原因在于它把网络问题拆成了三层:
1)用 IPAMBlock 做分片,避免全局 IP 竞争;
2)用 BGP 分布式发布路由,避免中心化转发表;
3)把策略预编译成内核规则,让转发路径不依赖控制面。
控制面变动不会阻塞数据面,节点之间没有集中瓶颈。
展开但仍是面试级的版本:
-
IPAM:/26 block 降低并发冲突
-
路由:每节点只关心“去哪个 Node”,而不是每个 Pod
-
数据面:iptables/ipset O(1) 匹配
-
控制面:Felix 异步、最终一致
一句话版:
Calico 能 scale,是因为它把复杂度前移到控制面,把转发压缩成“内核里的静态规则 + 分布式路由”。
五、终极四句总结(非常推荐背)
1️⃣ 包先过策略,再过路由。
2️⃣ 同节点和跨节点只差一个路由出口。
3️⃣ Overlay 只是多了一层封装,不改变策略和模型。
4️⃣ Calico 的扩展性来自分片 IPAM、分布式路由和内核数据面。
第三章:Calico Datastore 深度解析
Calico 的所有行为都源自其数据模型,核心对象包括:
-
IPPool:Pod IP 分配池
-
IPAMBlock:实际的 IP 分配单元,默认 /26
-
WorkloadEndpoint(WEP):Pod 的网络身份
-
HostEndpoint:Node 的网络接口策略控制
-
BGPPeer:节点间 BGP Peer 配置
-
NetworkPolicy / GlobalNetworkPolicy
-
FelixConfiguration / BGPConfiguration
其中最关键两个是:
1. WorkloadEndpoint
包含:
-
Pod IP
-
MAC
-
endpoint 名称
-
所属策略(Profiles / Policies)
-
node
-
interfaceName
Felix 依据 WEP 决定流量从哪里来/去。
2. IPAMBlock
-
默认一个 block 是 /26
-
使用 BITMAP 管理可用 IP
-
CNI 每次分配时会更新 block
理解 WEP 和 IPAMBlock = 理解 Calico 的本质。
第四章:Felix 深度解析(Calico 的灵魂)
Felix 的作用是:
监听 datastore → 计算差异 → 渲染内核数据面 → 写入内核。
Felix 的主要模块:
-
watchersyncer/:监听 datastore
-
calc/:将对象计算成低层策略
-
iptables/:生成 iptables 规则
-
ipsets/:维护 ipset 集合
-
routetable/:管理路由表
-
dataplane/linux/:真正操作内核
Felix 存在的意义是——
把高层对象(如 NP、WEP)转化为内核的规则和路由。
第五章:BGP 与跨节点路由
Calico 默认用 BGP 广播 Pod 网段。
每个 Node 会宣告:
10.244.x.0/26 via node-ip proto bird
你可以通过:
calicoctl node status
看到:
-
Peer
-
AS
-
状态
-
已学习的路由
抓包验证更直观:
tcpdump -i any port 179
你会看到:
-
OPEN
-
KEEPALIVE
-
UPDATE(子网宣告)
这就是 Calico 不需要 overlay 的原因:
它把 Pod 子网直接加入主机路由系统。
第六章:NetworkPolicy 工作原理
Calico 对策略的处理是:提前渲染,不进行 runtime 判断。
流程:
-
selector → 计算出匹配的 Pod
-
写入 ipset
-
为策略生成一段 iptables 链
-
endpoint 引用对应链
优势:
-
高性能
-
可预期
-
静态结构
关键链:
-
cali-from-wl:From workload chain
-
cali-to-wl:To workload chain
NetworkPolicy 的本质是:
selector → ipset → iptables chain → accept/drop
第七章:二次开发入口(由浅入深)
Calico 的源码能支持你自由扩展,这里列出最常见的二开方向:
1. IPAM 扩展(最容易)
入口:
-
cni-plugin/pkg/ipamplugin
-
libcalico-go/lib/ipam
可做:
-
粘性 IP(sticky IP)
-
多 IPPool 策略
-
特定 namespace 固定网段
2. 策略增强(中等)
入口:
-
felix/iptables/
-
felix/calc/
可做:
-
新 selector(时间、标签、端口范围)
-
geoip / ip-based policy
-
动态策略渲染
3. dataplane 扩展(最难)
入口:
-
felix/dataplane/linux/
-
felix/intdataplane/
可做:
-
mark → rule → table 的策略路由
-
自定义 NAT 行为
-
定制 SNAT / DNAT
-
BPF 加速(高级玩法)
4. API 扩展
入口:
-
libcalico-go/apis/v3/…
-
Felix calc → 渲染流程
第八章:真实二开案例
案例 1:Pod 粘性 IP(最推荐入门)
实现:
-
保存 Pod 的上一次 IP(ConfigMap / 注解 / CRD)
-
CNI Add 时优先 AssignIP
-
不成功再回退 AutoAssign
简单、实用、风险低。
案例 2:策略分流(mark → rule → table)
效果:
-
给某些 Pod 打 mark
-
通过 ip rule 选择不同路由
-
实现“业务分流”“旁路网关”
这是常见的高阶需求。
案例 3:扩展 NP 功能
例如:
-
增加 time-based
-
增加 CIDR 智能匹配
-
动态调整优先级
第九章:Calico 调试方法合集(必背)
你的排障七件套:
iptables-save | grep cali-
ipset list | grep cali
ip route
ip rule
calicoctl get wep -A -o wide
calicoctl node status
kubectl logs -n kube-system ds/calico-node -f
tcpdump -i any port 179
掌握这几点,你已能 debug 90% 的网络问题。
第十章:学习路线总结
跑 kind 实验(建立直觉)
看 datastore 模型(建立对象概念)
看 Felix 行为(建立数据面理解)
看 BGP(建立跨节点视图)
做一个二开(建立实战)
最终,你会明白 Calico 的本质:
“用对象驱动 Linux 内核,用 BGP 连接所有节点。”
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)