第一章:Calico 整体架构概念

Calico 是一套基于 纯三层路由(L3) 的容器网络方案,区别于 overlay(如 Flannel VXLAN)与 BPF-first(如 Cilium)。

Calico 的核心是:

  • CNI 插件(容器入口)

  • Felix(数据面控制器)

  • Linux 内核(iptables / ipset / route / conntrack)

  • BGP 路由(跨节点 Pod 通信机制)

Calico 的网络行为完全由 datastore + 控制循环 驱动:

组件把对象写入 datastore(如 WEP)

  1. Felix 监听到变化

  2. Felix 渲染 iptables / ipset / 路由

  3. BGP 宣告子网

  4. Linux 内核执行业务流量

Calico 本质上是:
对象驱动的路由 + 策略控制系统”。

┌──────────────────────────────────────────┐
│               Kubernetes API / Datastore │
│   (WEP / Policy / IPPool / IPAMBlock)    │
└──────────────────────────────────────────┘
                     ▲
                     │ 对象写入 / 更新
                     │
┌──────────────────────────────────────────┐
│        Felix(控制循环 / 数据面控制器)    │
│  watchdiff → 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
              │
              ▼
          内核状态收敛

这张图回答三个面试隐含问题:

  1. 为什么 Calico 稳定?
    → 控制循环,状态最终一致

  2. 为什么性能好?
    → 规则是“提前算好”的,不是 runtime 判断

  3. 为什么可扩展?
    → 对象模型 + 单向渲染

第四层: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 从创建到删除的完整生命周期” 画成一条时间线,专门用来应对追问。

Image

Image

我给你一条白板可画、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 创建时:

  1. kubelet 调用 CNI 插件(calico CNI)

  2. CNI 执行:

    • 从 IPAM 分配 Pod IP

    • 创建 veth(host 侧 / pod 侧)

    • 写 WorkloadEndpoint(WEP)对象

  3. Felix 监听到 WEP

  4. Felix 计算:

    • iptables 规则

    • ipset 集合

    • 路由表更新

  5. BGP backend 宣告 Pod 网段 / 路由

  6. 内核开始转发流量

删除 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):

  1. kubelet 调用 CRI:
RunPodSandbox()
  1. CRI 插件(containerd-shim)发现:
  • 需要网络

  • sandbox 还没 network

  1. 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 判断。

流程:

  1. selector → 计算出匹配的 Pod

  2. 写入 ipset

  3. 为策略生成一段 iptables 链

  4. 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(最推荐入门)

实现:

  1. 保存 Pod 的上一次 IP(ConfigMap / 注解 / CRD)

  2. CNI Add 时优先 AssignIP

  3. 不成功再回退 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 连接所有节点。”

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐