笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 接入与通信

    • 游戏基础架构与工具
    • 接入网关(长连接接入层)
    • 消息总线(共享内存 IPC)
    • 帧同步(Lockstep)
    • 全栈极限低延迟(物理层→应用层)
    • KCP 与 QUIC
  • 网络与服务网格

    • CNI 与 K8s 网络插件
    • 容器运行时:Docker 与 containerd
    • Istio 与 Cilium 服务网格
    • 服务网格:中心化 vs 去中心化
    • eBPF 原理与在网络/可观测/安全的落地
    • 自研 Mesh 服务网格 × K8s 部署
    • 自研 Mesh × K8s · 数据面深水区
    • Tco 协程框架 · 运行时深水区
    • K8s 异构 & 网络插件
  • 有状态服务

    • 分布式游戏存储(分布式 KV)
    • 有状态服务的数据迁移
    • 有状态服务的数据恢复与容灾
    • 有状态服务的 K8s 编排层治理(防漂移 + 节点崩溃恢复)
    • 内存配置热刷新与更新机制
  • 算法与协程

    • 一致性哈希四算法实现(Ring Hash / Ketama / Maglev / Jump Hash)
    • 蓄水池抽样(Reservoir Sampling)与加权扩展
    • C++ 协程:有栈 vs 无栈 vs C++20 原生(函数染色与 libco)
    • Raft 与 Gossip:CP 强一致共识 vs AP 最终一致传播
  • 流量与承载

    • 游戏秒杀场景承载
    • 令牌桶与漏桶
    • 限流与熔断
    • 限流:互联网 vs 游戏
    • 灰度发布 · Canary / BlueGreen / A-B / Shadow
    • 服务器日常系统开发与维护 SOP
    • 极限发压工具设计(多长连接 + 发压任务)
    • 性能分析与优化方法论(六条排查线)
  • 编译与排查

    • 编译优化与 LLVM/Clang/GCC
    • Sanitizer 工具链与内存泄漏定位

CNI 与 K8s 网络插件

CNI 规范 · Flannel / Calico / Cilium 三大流派 · Overlay vs Underlay · 数据包链路与 IPAM · 多网卡

一句话结论

CNI 用可执行+JSON 极简契约把组网外包给插件,游戏低延迟诉求把选型推向 BGP/eBPF。

场景问题

K8s 里每个 Pod 都要有一个 IP,且要满足 K8s 的网络模型铁律:所有 Pod 无需 NAT 即可互通、Pod 看到的自己的 IP 与别人看到它的 IP 一致。可是 kubelet 自己不管网络——它创建完 Pod 的 network namespace 后,就把"给这个 netns 配网卡、分 IP、通路由"这件事外包出去。

问题来了:不同环境网络差异巨大——公有云 VPC、裸金属 BGP、跨可用区、Overlay 隧道……不可能有一个内置实现通吃。于是 K8s 定义了 CNI(Container Network Interface)规范:只约定"kubelet 怎么调你、传什么参数、你返回什么",具体怎么组网由插件自由实现。

对游戏战斗集群还有额外诉求:低延迟、一机一 Pod(独占机器减少干扰)、有时需要多网卡(业务网 + 管理网分离)。这直接影响 CNI 选型。

打个比方:CNI 就像一纸"装修外包合同"。kubelet 只负责把毛坯房交出去(建好 Pod 的 network namespace),至于怎么通水电网(配网卡、分 IP、打通路由)它一概不碰,全甩给外包的装修队(CNI 插件)。合同(CNI 规范)只死板规定"我怎么叫你来、给你哪把钥匙(netns 路径和参数)、你干完回我一句成没成",具体用什么材料工艺完全随你发挥——Flannel 图省事拉个 Overlay 隧道、Calico 走 BGP 专业布线、Cilium 上 eBPF 玩智能家居。类比失效边界:契约极简是优点,但也意味着 kubelet 完全不为组网结果背书——插件配错、IP 撞车、路由不通,kubelet 只看到"外包回了个失败",真正排障得一头扎进插件内部。而且换插件≈换装修队重装,跨插件迁移一点都不平滑,选型时得想清楚再下手。

实现方案

CNI 规范:ADD / DEL / 可执行 + JSON

CNI 插件就是一个可执行文件,kubelet 通过环境变量传操作类型(CNI_COMMAND=ADD|DEL|CHECK)、netns 路径、容器 ID,通过 stdin 传 JSON 配置,插件通过 stdout 返回 JSON 结果。IPAM(IP 地址管理)是一个子职责,通常委托给独立的 IPAM 插件。

// kubelet 通过 stdin 传给 CNI 插件的配置(ADD 时)
{
  "cniVersion": "1.0.0",
  "name": "mynet",
  "type": "bridge",              // 主插件类型
  "bridge": "cni0",
  "isGateway": true,
  "ipam": {                       // IPAM 子职责:谁来分配 IP
    "type": "host-local",
    "subnet": "10.244.1.0/24",
    "routes": [{ "dst": "0.0.0.0/0" }]
  }
}
// 插件 ADD 成功后通过 stdout 返回的结果
{
  "cniVersion": "1.0.0",
  "interfaces": [{ "name": "eth0", "sandbox": "/var/run/netns/cni-xxxx" }],
  "ips": [{
    "address": "10.244.1.7/24",
    "gateway": "10.244.1.1",
    "interface": 0
  }]
}

DEL 时同样传 netns 与容器 ID,插件负责回收 IP、删 veth、清路由(幂等,Pod 已删也要能安全返回)。

谁真正触发 CNI(易错点)

常说"kubelet 调 CNI",更精确的链路是:kubelet →(CRI)→ 容器运行时(containerd/CRI-O)→ 调 CNI 插件。kubelet 只通过 CRI 让运行时建好 Pod 的 sandbox 与 netns,由运行时按 CNI 配置去调插件配网。所以运行时那一层(见 容器运行时)和网络这一层是解耦的:换运行时不换 CNI,换 CNI 不换运行时。

网络平面三选一:Overlay / Underlay / hostNetwork

所有 CNI 方案,本质只在回答一个问题:底层网络(机房交换机 / 云上 VPC 路由表)认不认识 Pod IP? 三种答案就是三种平面。

平面Pod IP 从哪来底层认不认怎么跨节点典型实现代价
Overlay集群自造的虚拟 CIDR(10.244.0.0/16)不认识必须封装成底层认识的包(外层是 Node IP)Flannel VXLAN、Calico IPIP/VXLAN、Cilium tunnel(Geneve/VXLAN)每包封解包 CPU + MTU 缩水(VXLAN 50B / IPIP 20B)+ 排障多一层
Underlay真实网络的地址(VPC 子网 / 交换机网段 / BGP 宣告的 CIDR)认识原生二层或三层转发,不封装Calico BGP、Flannel host-gw、macvlan / ipvlan、SR-IOV、云厂商 VPC-CNI(弹性网卡/辅助 IP)依赖底层配合(允许 BGP/自定义路由、网卡与 IP 配额),且真实 IP 消耗快
hostNetwork没有 Pod IP,就是 Node IP不适用本来就是宿主机流量hostNetwork: true端口冲突、无网络隔离、可移植性差(详见 K8s 网络)

判据一句话:Pod IP 能不能被底层网络直接路由——不能就得套壳(Overlay),能就直接走(Underlay),懒得给 Pod 独立 IP 就共用宿主机(hostNetwork)。

打个比方:Overlay 像公司内部自编的"工位号"。快递员(底层交换机)根本不认工位号,所以每件快递出楼前都要再套一层写着"某某大厦 12 层"的外包装(VXLAN/IPIP 封装),到对面楼下前台拆开,再按工位号内投。Underlay 则是把工位号直接登记进城市邮政系统,快递员认,直投到人。hostNetwork 最省事——不设工位号,全公司共用前台一个地址,谁先占了分机 8000 别人就不能再占。

类比失效边界:外包装不是"多个盒子"这么无害——它每个包都要拆装(不是每批一次),还实打实吃掉 50 字节 MTU,忘了调 MTU 就是大包黑洞;而 Underlay 的"邮政登记"要底层点头(BGP 允许、VPC 配额够),不是想登记就能登记,公有云上常常直接被拒。

IPAM:谁来分配 IP

组网和"给谁哪个 IP"是两件事,后者由 IPAM(IP Address Management)子插件负责,主插件把 ipam 段委托出去:

IPAM 类型原理适用
host-local每节点从分到的网段里本地顺序分配,状态存节点本地磁盘最常见(Flannel 默认),节点网段互不重叠
DHCP起一个 dhcp daemon 向底层 DHCP 服务器租 IPPod 要拿底层网络真实 IP(underlay/macvlan)
插件自管CNI 自带集中式 IPAM(Calico IPAM、Cilium IPAM、云厂商 VPC-CNI 直接分 VPC 弹性网卡 IP)大规模、跨节点统一分配、防碎片

IPAM 选错是IP 泄漏/耗尽的常见根因:host-local 状态在节点本地,Pod 异常删除未回收就会泄漏;集中式 IPAM 更好回收但依赖控制面。

IPAM 到底在干什么:两级分配

"Pod 的 IP 是谁给的"这题的完整答案是两级,只答一级就丢分:

  1. 集群级切网段:kube-controller-manager 带 --allocate-node-cidrs --cluster-cidr=10.244.0.0/16 --node-cidr-mask-size=24,给每个 Node 切一块 Node.spec.podCIDR(如 NodeA 拿 10.244.1.0/24)。Calico 换成 IPPool + block、Cilium 换成 CiliumNode 资源,思路一样。
  2. 节点级分单 IP:CNI 主插件把 ipam 段委托给子插件(host-local),它在本节点那块网段里取一个空闲 IP,落盘 /var/lib/cni/networks/<网络名>/<IP> 记录占用,把 IP + 网关 + 要写进 Pod netns 的路由一起返回给主插件。

所以 IPAM 交付的不只是一个 IP,而是四件事:① 分配全局唯一的 Pod IP(K8s 铁律,冲突即灾难);② 给出网关地址(10.244.1.1,即网桥持有的地址);③ 给出要写进 Pod 的路由(default via 网关);④ DEL 时回收,以及回收失败后的 GC。

为什么必须按节点切段,而不是全局随手分? 因为只有"一个节点 = 一段连续前缀",跨节点的可达性才能聚合成一条路由(10.244.2.0/24 via NodeB)。否则宿主机路由表 / VXLAN fdb 的条目数就等于 Pod 数而不是节点数,几万 Pod 时内核表和控制面同步都会崩。IPAM 的网段划分方式,直接决定了跨节点转发能不能做得高效——这也是下一节 cross 模块的前提。

规模账(经典面试题)

--cluster-cidr=10.244.0.0/16 + --node-cidr-mask-size=24 ⇒ 最多 256 个节点、每节点最多 254 个 Pod。要上千节点就得把 cluster-cidr 放大到 /12,或把 mask 收到 /25(节点翻倍、单机 Pod 减半)。这个参数建集群时定死,事后改基本等于重建,属于开集群前必须先算的账。

两个高频 IPAM 事故:① host-local 状态只在节点本地磁盘,kubelet 被强杀或节点异常重启后残留占用,ls /var/lib/cni/networks/cbr0 全是幽灵 IP,新 Pod 卡 ContainerCreating 并报 failed to allocate for range 0: no IP addresses available;② Calico 按 block(默认 /26,64 个 IP)向节点批发 IP 以减少访问控制面,碎片化时会出现"总量还够但本节点借不到"的假性耗尽,用 calicoctl ipam show --show-blocks 查借用情况。

三大流派原理对比

插件数据面原理类型特点
FlannelVXLAN 把二层帧封进 UDP 隧道,跨节点 overlayOverlay简单通用、任意底层网络能跑;但封包开销 + MTU 减少 50 字节
Flannel host-gw同网段节点间直接写宿主机路由表,二层直达不封装Underlay(路由)无封包税;但要求节点二层可达(同子网),跨子网不行
Calico每节点跑 BGP,把 Pod 网段路由宣告给对端,三层直达;跨子网用 IPIP 兜底Underlay(路由)无封包、性能好、支持 NetworkPolicy;依赖底层可跑 BGP
CiliumeBPF 在内核态挂载点直接转发与执行 L3-L7 策略,可替代 kube-proxyeBPF高性能、O(1) 策略、丰富可观测(见 eBPF);需较新内核

封装开销与 MTU(面试必答的性能账):VXLAN/IPIP 每个包都要套一层外层头(VXLAN 约 50 字节、IPIP 约 20 字节),带来两笔成本:① CPU 封解包;② 有效 MTU 变小——底层 1500,overlay 后 Pod 侧要降到 1450(VXLAN),忘了改就会大包被分片或直接黑洞,表现为"小包通、大包/长连接 gRPC 偶发超时"。underlay 路由(host-gw / Calico BGP)不封包,无此税。

NetworkPolicy 落地差异:NetworkPolicy 是 K8s 的声明,但执行靠 CNI——不是所有 CNI 都实现。Flannel(纯 VXLAN)不支持,配了也不生效;Calico 用 Felix 下发 iptables/eBPF 规则执行;Cilium 用 eBPF 执行且支持到 L7(HTTP method/path)。选 CNI 前先确认它是否落地 NetworkPolicy,否则"策略写了个寂寞"。

eBPF 替代 kube-proxy 的取舍:Cilium 可开 kube-proxy replacement,把 Service→Endpoint 转发做进 eBPF map(O(1)),省掉 iptables/ipvs 整条链(kube-proxy 演进原理见 K8s 网络)。收益是大规模 Service 下延迟/同步成本骤降;代价是内核版本要求高(≥4.19、强推荐 ≥5.10)、排障需懂 eBPF、生态较新。

一个包的完整旅程:为什么要过"0 号网卡"

面试里常说的"0 号网卡"其实是一串不同的设备,先把名字对上号,后面的链路才不会乱:

设备在哪是什么谁创建
loPod netns 内回环,容器内 127.0.0.1内核建 netns 时自带
eth0Pod netns 内Pod 唯一的业务网卡,veth pair 的容器侧CNI 插件 ADD 时创建
vethxxxx宿主 netnsveth pair 的宿主侧,与 Pod 的 eth0 是"一根网线两头"CNI 插件
cni0 / docker0宿主二层网桥:同节点 Pod 的虚拟交换机 + 网关(10.244.1.1)bridge 类插件(Flannel)
flannel.1 / vxlan.calico / tunl0宿主隧道设备(VTEP):封装的出入口flanneld / Calico
eth0(宿主)宿主真实物理网卡,最终出口系统

三条路径分开看:

  • 同节点 Pod ↔ Pod:Pod A 的 eth0 → veth → cni0。目标 10.244.1.8 与自己同网段,二层查 MAC 直接交换,压根不进宿主机的 IP 路由表。最短路径,没有任何封装。
  • 跨节点 Pod ↔ Pod:Pod A → cni0 → 宿主路由表命中 10.244.2.0/24 dev flannel.1 → 内核在 VXLAN 设备上查 neigh(对端 VTEP 的 MAC)与 fdb(这个 MAC 在哪台 Node 上)→ 套上 VXLAN + UDP:8472 + 外层 IP(NodeA→NodeB)→ 从宿主 eth0 发出 → NodeB 收到 8472 → 解封 → flannel.1 → 路由到 cni0 → veth → Pod C。
  • Pod → 公网:Pod → cni0 → 宿主默认路由 → iptables 的 MASQUERADE 把源地址 SNAT 成 Node IP(否则回包不认识 10.244.x.x)→ 物理网卡出去。

典型路由表长这样:

# Pod 内(netns 里)
default via 10.244.1.1 dev eth0            # 网关就是 cni0 持有的地址
10.244.1.0/24 dev eth0 scope link          # 同节点邻居走二层

# 宿主机上
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1   # 本机 Pod 网段
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink               # 对端节点整段,一条路由搞定

为什么要过 cni0 这个网桥? veth 是一根点对点的"虚拟网线",一端在 Pod、一端在宿主。如果宿主这端不插网桥,就得为每个 Pod 在宿主上单独加一条 /32 路由,同节点两个 Pod 通信还要绕三层。网桥把 N 根 veth 汇成一台虚拟交换机,一次买下三样东西:① 同节点 Pod 二层直通,不进 IP 路由;② 网桥自己持有网段网关 IP,于是 Pod 里只需要一条 default via 网关 的通用路由,不必给每个 Pod 定制;③ 出节点的流量在这里收敛成一个点再交给宿主路由表。一句话:网桥 = 同节点二层交换机 + 默认网关 + 流量收敛点。

但网桥并不是必须的(这条最能拉开区分度)

Calico 没有 cni0。它给每个 Pod 一根 veth 直插宿主(宿主侧叫 caliXXXX),宿主侧开 proxy_arp 冒充网关(Pod 里的默认路由指向假地址 169.254.1.1),并为每个 Pod 装一条 /32 主机路由——全三层、零网桥,没有 MAC 学习与广播域,隔离更强、排障更直白;代价是宿主路由条目数等于本机 Pod 数(跨节点部分仍靠 /26 网段聚合)。

所以"要过 0 号网卡"是 bridge 流派(Docker/Flannel)的实现选择,不是 K8s 网络模型的要求。答面试题时把这句补上,档次立刻不一样。

为什么要过 flannel.1? 因为封装必须发生在某个网卡上。它就是内核里的 VXLAN 设备(VTEP):路由把对端 Pod 网段的下一跳指向它,内核在这个设备上查 neigh/fdb 拿到"对端 VTEP MAC + 对端 Node IP",套壳后再交回宿主路由从物理网卡发走。它是 Pod-IP 平面 → Node-IP 平面的换乘站,没有它,虚构的 Pod IP 无处可去。tunl0 是 Calico IPIP 的同位角色,只套一层 20 字节外层 IP 头,没有 UDP 和 VNI。

ip -d link show flannel.1        # 看 vxlan id / 本地 VTEP IP
bridge fdb show dev flannel.1    # 对端 VTEP MAC → 对端 Node IP 的映射
ip neigh show dev flannel.1      # 对端 Pod 网段网关 → VTEP MAC
ip route get 10.244.2.9          # 内核实际会怎么走这个包
tcpdump -i any -nn udp port 8472 # 抓 VXLAN 外层,确认封装真发出去了

跨节点转发(cross)模块:Overlay 的必备零件

同节点靠网桥就够了,问题全出在跨节点:Pod IP 是集群自造的地址,底层交换机的路由表里根本没有 10.244.x.x 这一条,云上 VPC 还会开源地址校验,把它当伪造包直接丢掉。所以每个 Overlay CNI 都必须常驻一个"跨节点模块"(flanneld / Calico 的 Felix + BIRD / cilium-agent),干三件事:

  1. 拓扑发现——谁负责哪一段:watch API Server 或 etcd 里的 Node.spec.podCIDR / CRD,维护 10.244.2.0/24 → NodeB(192.168.1.12) 这张映射表。
  2. 数据面下发——把映射翻译成内核状态:写宿主机路由(via ... dev flannel.1)、写 VXLAN 的 neigh 与 fdb 表项;换成 Calico 就是用 BGP 把网段宣告给对端;换成 Cilium 就是写 eBPF map。
  3. 持续对账——节点会变:节点上下线、网段回收、agent 重启后从期望状态幂等重建内核表项,而不是靠一次性初始化。

一句话分工:IPAM 决定"谁是谁"(地址归属),cross 模块决定"怎么到"(可达性)。两者必须对齐——IPAM 按节点切连续网段,cross 模块才能用一条聚合路由覆盖整台机器的 Pod。

CrossSubnet:只对真正跨子网的流量收封装税

  • Calico:ipipMode: CrossSubnet 或 vxlanMode: CrossSubnet——同一个二层子网内的节点之间直接写路由(via 对端NodeIP dev eth0,零封装),只有跨子网/跨机房才落到 IPIP/VXLAN。
  • Flannel:"Backend": {"Type": "vxlan", "DirectRouting": true},同样是"同子网直路由、跨子网才封装"。
  • 收益:绝大多数流量在同机架/同可用区内,这部分完全不付封包 CPU 与 MTU 的税,同时跨子网依旧可达——对同 AZ 部署的游戏战斗集群是性价比最高的一档。
  • 前提:同子网节点二层可达,且底层不做源地址校验(公有云上常常不满足,只能老实全程封装)。

什么时候需要自研 cross:标准 CNI 只保证单集群内一个平面。一旦多地域/多集群——Pod 网段可能重叠、跨 VPC 不通、云厂商禁自定义路由——就得另加一层:要么上 Submariner(网关节点 + IPsec/VXLAN 隧道打通集群间,网段冲突时用 GlobalNet 做 NAT),要么自研跨集群网关。本项目走的是更省事的第三条路:干脆不做跨集群 Pod 平面,用 hostNetwork + 业务级 Mesh 直接连宿主机内网 IP(见 K8s 网络)。

多插件链与多网卡

  • CNI chaining:主插件(组网)+ 链式插件(如 portmap 做端口映射、bandwidth 限速、tuning 调 sysctl),按顺序执行。
  • Multus:给一个 Pod 挂多张网卡——比如战斗服 eth0 走业务 overlay、net1 直连高性能 underlay/SR-IOV 做玩家流量。游戏"一机一 Pod + 业务/管理网分离"常用。

为什么这么做

  • 为什么 K8s 把网络外包给 CNI:网络环境千差万别,内置死一种实现会限制所有人。可执行 + JSON 的极简契约让插件生态百花齐放,谁都能实现。
  • 为什么大规模/低延迟倾向 BGP/eBPF 而非 VXLAN:VXLAN 每个包都要封装外层 UDP/IP 头,CPU 花在封解包上,且 MTU 变小易触发分片/性能悬崖。BGP 路由是原生三层转发无封包;eBPF 在内核态直接查 map 转发,两者都省掉 overlay 税,延迟与吞吐更好。游戏战斗流量对延迟敏感,优先它们。

一机一 Pod 的诉求

游戏战斗服常要独占物理机(避免邻居 Pod 抢 CPU/网络导致帧抖动),此时 overlay 的多租户隔离价值下降,而低延迟直连价值上升,进一步推向 Calico BGP / Cilium eBPF,甚至 hostNetwork。

为什么别的选择不行

各流派的不适用场景

想用…但是…
Flannel VXLAN 通吃封包开销 + MTU 缩水,大规模高吞吐/低延迟战斗集群性能不划算;早期无 NetworkPolicy
Calico BGP 到处跑依赖底层网络允许 BGP/路由宣告;公有云 VPC 常禁自定义路由,被迫回退 IPIP(又变相 overlay)
纯 hostNetwork 图省事Pod 直接用宿主 IP,端口冲突、隔离全无、可移植性差,只适合极特殊独占场景
不用 IPAM 手工分 IP规模一大必冲突、泄漏,无法回收;IPAM 是 CNI 明确的子职责,交给它

排障应对清单(现象 → 定位路径)

CNI 层最常见的四类故障与定位顺序(更宏观的集群踩坑见 K8s 网络 的生产踩坑清单,此处只给网络插件侧的定位路径):

现象应对 / 定位路径
跨节点 Pod 不通(同节点通)先看是 overlay 还是路由:Flannel 查 VXLAN 设备 flannel.1 与 fdb、UDP:8472 是否被防火墙拦;Calico 查 calicoctl node status BGP 邻居是否 Established、宿主机路由表有无对端 Pod 网段;再排安全组/防火墙
小包通、大包/gRPC 偶发超时(MTU 黑洞)典型封装 MTU 未减。ping -M do -s <size> 二分探测实际 MTU,把 Pod/CNI MTU 显式设为 1450(VXLAN);检查中间设备是否丢 ICMP 导致 PMTUD 失效
Pod 起不来 / ContainerCreating 卡在分 IP(IP 耗尽或泄漏)crictl 看事件报 IPAM 失败;host-local 检查节点上 /var/lib/cni 残留分配、清僵尸;扩子网或换集中式 IPAM;根因常是 Pod 异常删除未回收
封装导致吞吐/延迟回退确认是否在付 overlay 税:能三层直达就切 host-gw/Calico BGP;公有云被迫 IPIP 时评估换 VPC-CNI(直接分 VPC IP,无封装)
NetworkPolicy 不生效先确认 CNI 是否实现 NetworkPolicy(Flannel 纯 VXLAN 不支持!);再查策略选择器是否命中 Pod label、是否漏放 ingress-nginx/DNS(53);Cilium 可用 hubble 看策略裁决

沉淀结论

结论

  • CNI = 极简契约:可执行文件 + ADD/DEL/CHECK + stdin/stdout JSON,IPAM 是其子职责。kubelet 只管调用,组网自由实现。
  • 三流派:Flannel(VXLAN overlay,简单但有封包税) / Calico(BGP underlay 路由,快但依赖底层) / Cilium(eBPF 内核态,高性能可替代 kube-proxy)。
  • Overlay vs Underlay 本质是"通用性"换"性能":能跑三层路由/eBPF 就别付 overlay 的封包与 MTU 税;判据是底层网络认不认识 Pod IP。
  • 数据面链路:Pod eth0 →veth→ cni0(同节点二层直通)→宿主路由→ flannel.1 封装 →宿主 eth0。网桥是 bridge 流派的选择而非模型要求(Calico 用 proxy_arp + /32 路由全三层);隧道设备则是封装必须落脚的换乘站。
  • IPAM 两级 + cross 模块:IPAM 决定"谁是谁"(controller-manager 切 podCIDR、host-local 分单 IP),cross 模块决定"怎么到"(拓扑发现 + 数据面下发 + 幂等对账)。按节点切连续网段,跨节点才能一条聚合路由搞定。
  • 游戏低延迟 + 一机一 Pod + 多网卡(Multus)诉求,把选型推向 BGP / eBPF。

相关专题:eBPF 原理与落地 · Istio 与 Cilium 服务网格 · K8s 网络 · 容器运行时 · 全栈极限低延迟

记忆口诀

  • CNI 契约:可执行文件 / stdin JSON 入 / stdout JSON 出 / ADD·DEL·CHECK
  • 触发链:kubelet →CRI→ 运行时 →调 CNI(不是 kubelet 直接调)
  • 三平面判据:底层认不认 Pod IP —— 不认=Overlay要封装 / 认=Underlay原生转发 / 不给Pod IP=hostNetwork
  • 包的旅程:Pod eth0 →veth→ cni0(同节点二层直通) →宿主路由→ flannel.1 封 VXLAN →宿主 eth0;出公网加 MASQUERADE
  • 网桥非必需:Calico 无 cni0 / veth + proxy_arp(169.254.1.1) + 每 Pod /32 路由,全三层
  • cross 三件事:拓扑发现(谁管哪段) / 数据面下发(路由·fdb·neigh·BGP·eBPF map) / 幂等对账
  • CrossSubnet:同子网直路由零封装 / 跨子网才 IPIP·VXLAN
  • IPAM 两级:controller-manager 切 podCIDR / host-local 节点内分单 IP;按节点切段才能聚合路由
  • 规模账:cluster-cidr /16 + mask /24 = 256 节点 × 254 Pod,建集群时定死
  • IPAM:host-local 本地顺序 / DHCP 租底层 / 集中式(Calico·Cilium·VPC-CNI)
  • 三流派:Flannel=VXLAN overlay / Calico=BGP underlay 路由 / Cilium=eBPF 内核态
  • Overlay vs Underlay:通用性换性能 / 封包税 + MTU 缩水 / 能三层路由就别付税
  • NetworkPolicy:K8s 声明·CNI 执行 / Flannel 不支持 / Calico·Cilium 支持(Cilium 到 L7)
  • 排障:跨节点看 VXLAN/BGP / 大包超时查 MTU / 起不来查 IPAM / 策略不生效先看 CNI 支不支持
  • 游戏诉求:低延迟 / 一机一 Pod / Multus 多网卡 → 推向 BGP·eBPF

内容来源

综合整理。参考方向:CNI 官方规范(containernetworking/cni SPEC,ADD/DEL/CHECK、IPAM 约定)、Flannel / Calico / Cilium 官方文档与数据面原理、VXLAN(RFC 7348) 与 BGP 路由基础、Multus / CNI chaining 文档、K8s 网络模型(Pod 无 NAT 互通)官方说明。

自测:合上资料能说清楚吗?

  1. K8s 为什么不内置一种网络实现,而要把组网外包给 CNI?CNI 的契约到底约定了什么?
参考答案

网络环境千差万别(VPC/裸金属/跨区),内置死一种会限制所有人。CNI 只约定极简契约:一个可执行文件,kubelet 传 CNI_COMMAND=ADD/DEL/CHECK + netns + stdin JSON,插件回 stdout JSON,组网自由实现,IPAM 是其子职责。

  1. Flannel(VXLAN) 与 Calico(BGP) 的数据面原理差在哪?为什么低延迟战斗集群更偏向后者?
参考答案

Flannel 把二层帧封进 UDP/VXLAN 隧道(overlay),每包付封包 CPU + MTU 缩水 50 字节税。Calico 每节点跑 BGP 宣告 Pod 路由,原生三层直达无封包。战斗流量对延迟敏感,省掉 overlay 税更划算。

  1. 为什么在公有云上 Calico 常被迫回退 IPIP?这带来什么代价?
参考答案

公有云 VPC 常禁止自定义路由/BGP 宣告,Pod 网段路由无法被底层接受,Calico 只能用 IPIP 封装跨子网兜底——又变相成了 overlay,重新付封包税,失去纯路由的性能优势。

  1. CNI chaining 与 Multus 各解决什么问题?游戏场景怎么用?
参考答案

chaining:主插件组网后串接链式插件(portmap 端口映射、bandwidth 限速、tuning)。Multus:给一个 Pod 挂多张网卡。游戏"一机一 Pod + 业务/管理网分离",如 eth0 走 overlay、net1 直连 SR-IOV/underlay 承载玩家流量。

  1. Cilium 相比 Calico 的核心差异是什么?各自的前提约束?
参考答案

Cilium 用 eBPF 在内核态挂载点转发并执行 L3-L7 策略,可替代 kube-proxy、O(1) 策略、可观测强,但需较新内核。Calico 靠 BGP 三层路由,成熟稳定,但依赖底层网络允许 BGP。

  1. Overlay 与 Underlay 的判据到底是什么?各自的代价是什么?
参考答案

判据只有一句:底层网络(交换机/VPC 路由表)认不认识 Pod IP。不认识 → Overlay,Pod IP 来自集群自造的虚拟 CIDR,跨节点必须封装成外层 Node IP 的包(VXLAN/IPIP/Geneve),代价是每包封解包 CPU + MTU 缩水(VXLAN 50B、IPIP 20B)+ 排障多一层。认识 → Underlay,Pod IP 是真实网络地址(VPC 辅助 IP、BGP 宣告网段、macvlan/SR-IOV),原生转发不封装,代价是依赖底层配合(允许 BGP/自定义路由、网卡与 IP 配额)且真实 IP 消耗快。第三条路是 hostNetwork:干脆不给 Pod 独立 IP,直接共用宿主机网络栈。

  1. 一个包从 Pod A 到另一台节点的 Pod C,完整经过哪些网卡?为什么要过 cni0 和 flannel.1?
参考答案

链路:Pod A 的 eth0(veth 容器侧)→ 宿主侧 vethXXXX → cni0 网桥 → 宿主机路由表命中 10.244.2.0/24 dev flannel.1 → flannel.1(VXLAN VTEP) 查 neigh/fdb 得到对端 VTEP MAC 与 Node IP,套 VXLAN+UDP:8472+外层 IP → 宿主 eth0 发出 → 对端解封 → flannel.1 → cni0 → veth → Pod C 的 eth0。

cni0 的价值=同节点 Pod 二层直通(不进 IP 路由)+ 持有网段网关地址(Pod 里只要一条 default via 通用路由)+ 出节点流量的收敛点。flannel.1 的价值=封装必须发生在一张网卡上,它是 Pod-IP 平面到 Node-IP 平面的换乘站。

关键补充:网桥不是必须的——Calico 没有 cni0,用 veth + proxy_arp(Pod 默认路由指向 169.254.1.1)+ 每 Pod 一条 /32 主机路由,全三层无广播域。"要过 0 号网卡"是 bridge 流派的实现选择,不是 K8s 网络模型的要求。

  1. Overlay 为什么必须有一个"跨节点(cross)模块"?它具体做哪三件事?CrossSubnet 又优化了什么?
参考答案

因为 Pod IP 是集群自造的地址,底层交换机路由表里没有这条路由,云上 VPC 还会因源地址校验直接丢包——单靠内核路由跨不了机。所以要常驻一个模块(flanneld / Calico Felix+BIRD / cilium-agent)做三件事:① 拓扑发现:watch Node.spec.podCIDR,维护"网段 → Node IP"映射;② 数据面下发:翻译成宿主机路由 + VXLAN 的 neigh/fdb,或 BGP 宣告,或 eBPF map;③ 持续对账:节点上下线、重启后幂等重建。

分工记忆:IPAM 决定"谁是谁",cross 模块决定"怎么到"。

CrossSubnet(Calico ipipMode/vxlanMode: CrossSubnet、Flannel DirectRouting: true):同一二层子网内的节点直接写路由零封装,只有跨子网才封装——让绝大多数同 AZ 流量不付封包与 MTU 税。前提是同子网二层可达且底层不做源地址校验。

  1. Pod 的 IP 到底是谁分的?为什么必须按节点切连续网段?--cluster-cidr=10.244.0.0/16 能带多少节点?
参考答案

两级分配:① kube-controller-manager(--allocate-node-cidrs --cluster-cidr --node-cidr-mask-size)给每个 Node 切一块 spec.podCIDR;② CNI 的 IPAM 子插件(host-local)在这块网段内取空闲 IP,落盘 /var/lib/cni/networks/ 记录占用,返回 IP + 网关 + 路由。

必须按节点切连续段,是因为只有"一节点 = 一段前缀",跨节点才能用一条聚合路由(10.244.2.0/24 via NodeB)覆盖整机;否则路由/fdb 条目数等于 Pod 数而非节点数,规模一大就崩。

规模账:/16 + node-cidr-mask-size=24 ⇒ 最多 256 节点 × 每节点 254 Pod,且建集群时定死、事后改约等于重建。

  1. 配了 NetworkPolicy 却不生效,排查顺序是什么?大包偶发超时又怎么定位?
参考答案

NetworkPolicy 不生效:先确认 CNI 是否实现它——Flannel 纯 VXLAN 不支持,配了也白配;支持的(Calico/Cilium)再查选择器是否命中 Pod label、是否漏放 DNS(53)/ingress-nginx,Cilium 可用 hubble 看裁决。大包偶发超时:多是 MTU 黑洞——overlay 封装后有效 MTU 变小(VXLAN 减 50 字节)却没改,大包被分片/黑洞;用 ping -M do -s 二分探测,显式把 MTU 设成 1450,并检查中间设备是否吞 ICMP 致 PMTUD 失效。

最近更新: 2026/9/10 11:38
Next
容器运行时:Docker 与 containerd