TCP/IP 底层基础

应用层数据如何被逐层封装

核心规则:从上往下,每一层都把上层整体当作自己的「Payload(载荷)」,加上本层头部;到接收端反向逐层解封装。

发送方的封装顺序(由上层至下层):

应用层 → 传输层(TCP/UDP)→ 网络层(IP)→ 数据链路层(以太网帧)→ 物理层(比特流)

平铺结构:

1
2
3
4
5
6
7
【应用层数据】
        ↓ 交给传输层
【TCP头 / UDP头】 + 【应用层数据】 → 传输层报文段(TCP) / 数据报(UDP)
        ↓ 交给网络层
【IP头】 + 【TCP头+应用数据】 → IP报文(三层包)
        ↓ 交给数据链路层
【以太网头(DMAC+SMAC+EtherType)】 + 【IP报文】 + 【FCS校验】 → 以太网帧(二层帧)

其中:

  • 应用 Payload:业务原始数据(HTTP 网页、MySQL 查询、JSON 等,纯业务字节,没有协议头)
  • TCP/UDP 眼里,应用数据就是它的 payload
  • IP 眼里,整个 TCP+应用数据都是 IP 的 payload
  • 以太网帧里,整个 IP 报文都是帧的 payload

标准无隧道、普通以太网帧结构

1
2
3
4
┌─────────┬─────────┬────────────┬──────────┬──────────────┬──────┐
 DMAC     SMAC     EtherType   IP Header TCP/UDP+Payload FCS  
 6B       6B       2B          20~60B    应用数据       4B   
└─────────┴─────────┴────────────┴──────────┴──────────────┴──────┘
  • DMAC:目标 MAC
  • SMAC:源 MAC
  • EtherType:0x0800 = IPv4;0x86DD = IPv6;0x8100 = 带 VLAN 标签
  • 后面就是三层 IP 头、传输层、应用层原始数据
  • FCS:Frame Check Sequence,帧校验序列,算法是CRC‑32(循环冗余校验),做差错校验,检查这个帧在传输过程中,字节有没有被干扰、比特发生翻转、传错、损坏

关于MAC 地址

MAC 地址是二层(以太网帧)专属,只有当 IP 包被装进以太网二层帧的时候,才会套上 DMAC/SMAC。换句话说:

  • 单独拎出来的 IP 报文(只有 IP 头 + TCP + 应用数据),本身没有 MAC!
  • MAC 是以太网链路专属,只有当这个 IP 包要跑在以太网上时,才会外面套一层以太网帧(加上 DMAC/SMAC)。
  • 如果不是以太网(比如串口链路),甚至根本不用 MAC 地址。

二层转发 vs 三层转发

不管是二层转发(交换机)还是三层转发(路由器),数据包长的样子不变,都是上面这套嵌套结构。二者区别不在于包结构,而在于:设备读取哪一层头部、依据什么字段决策转发。

二层转发(普通交换机,只看数据链路层)

只解析以太网头部,只关心 DMAC(目标 MAC):

  • 不拆 IP 头、不看 IP 地址
  • 行为:MAC 地址表匹配,直接从对应端口转发,原始帧从头到尾几乎不改
  • 唯一改动:FCS 校验重算,源目 MAC 一般不变

适合:同一个广播域、同一个网段内部转发。

结构如下:同网段 PC1 → 交换机 → PC2:

1
以太网头(DMAC=PC2 MAC, SMAC=PC1 MAC) + IP + TCP + 应用数据 + FCS

三层转发(路由器 / Linux 内核路由,看网络层 IP 头)

设备会解析到 IP 头部,根据目的 IP查路由表。

注意点:IP 头里的源/目的 IP,全程端到端不变;但是【二层以太网头部的 DMAC/SMAC,每一跳都会重写!】

举个跨网段最简例子:PC1(192.168.1.10) → 路由器 R → PC2(10.0.0.20)

  1. PC1 发给 R:
    • 以太网:DMAC=路由器入接口MAC,SMAC=PC1 MAC
    • IP:SIP=192.168.1.10,DIP=10.0.0.20
  2. 路由器路由查找后,重新封装二层帧,转发给 PC2:
    • 以太网:DMAC=PC2 MAC,SMAC=路由器出接口 MAC ,MAC地址变了
    • IP:SIP=192.168.1.10,DIP=10.0.0.20 ,IP 完全不变

即:三层转发是IP 端到端不变,二层 MAC 逐跳刷新,用来完成直连链路传递。

二层 vs 三层转发对比

项目 二层转发(交换机) 三层转发(路由器)
解析到哪一层 只解析二层以太网头 解析到三层 IP 头
转发依据 目标 MAC 地址 目标 IP 地址
源目 IP 不变 不变
源目 MAC 不变 每一跳重新改写
适用范围 同一广播域/同一网段 跨网段、跨网关
数据包封装结构 不变 不变

VLAN——传统二层隔离技术

核心目的

在同一台物理交换机上,切出多个独立虚拟局域网,互相二层隔离

比如一台 48 口交换机:

  • 端口 1-10 → VLAN10(业务 A)
  • 端口 11-20 → VLAN20(业务 B)
  • VLAN10 和 VLAN20 默认二层不通,必须经过路由器/三层网关才能互通。

vlan报文结构–打Tag

原始普通以太网帧(无 VLAN):

1
DMAC | SMAC | Type | Payload | FCS

经过 802.1Q 打 VLAN 标签之后,在源 MAC 和 Type 之间插入4 字节 VLAN Tag:

1
DMAC | SMAC | TPID(0x8100)+TCI | Type | Payload | FCS

拆解 4 字节 Tag:

  • TPID:固定 0x8100,告诉交换机"这是 802.1Q VLAN 帧"
  • TCI 里面包含12bit VLAN ID

12bit最大4096个VLAN(0-4095,可用 1-4094),这是 VLAN 最致命短板!大规模虚拟化/云场景直接不够用。

交换机Access 口 / Trunk 口

示例拓扑:

1
PC1 ──Access(VLAN10)── SW ──Trunk── SW2 ──Access(VLAN10)── PC2
  • Access 口:接终端(PC/服务器),进出帧不带 VLAN 标签,交换机内部打上/剥掉 Tag
  • Trunk 口:交换机之间互联,带标签转发,可以同时透传多个 VLAN

流程:

  1. PC1 发出去的包没有标签,进入 SW 的 Access 口,交换机自动打上 VLAN10 标签
  2. Trunk 链路带着标签传给 SW2
  3. SW2 从 Access 口发往 PC2 前,把标签剥掉
  4. 终端本身感知不到 VLAN 存在

vlan短板

  1. 最多 4096 个,公有云/大规模虚拟化远远不够
  2. VLAN 是二层标签,只能在同一个连续二层广播域里跑;跨三层路由网络不能直接透传 VLAN 标签(VLAN 标签不能被路由器原生转发,一旦经过三层网关,802.1Q 标签就会被终结)。

BGP 协议

基本概念

AS(自治系统):一个独立管理、统一管控的大网络,有唯一 AS 号。比如电信是一个 AS、联通一个 AS、阿里云一个 AS。

BGP 邻居(Peer):两台路由器之间手动建立 TCP 连接(端口 179),然后互相交换路由,注意不是自动发现!

注意BGP 只管传递网段路由条目,不负责把数据包直接转发;转发还是靠路由器路由表。OSPF是自动找邻居,但BGP必须手动指定对端 IP 建邻居。BGP 全称Border Gateway Protocol 边界网关协议,现在主流是 BGP4。OSPF 选路径看带宽、延迟;BGP 主要靠各种路径属性做策略选路,叫“策略驱动型路由协议”,不是单纯算最短路径。

BGP 路由条目里带什么

BGP 传递的不是单纯"目标网段+下一跳",它会带一整串路径属性,最最重要就是:

AS-PATH(AS 路径):记录这条路由沿途经过了哪些 AS,是 BGP 的灵魂

AS-PATH 就是一串 AS 号列表,数据包去目标网段,需要依次穿过这些 AS,有两个巨大作用:

  1. 防环路:收到路由,如果 AS-PATH 里面已经包含自己的 AS 号,直接拒收,杜绝环路!(OSPF 靠拓扑防环,BGP 靠 AS-PATH 防环,这是根本性差异)

  2. 选路:优先挑 AS-PATH 更短的路径(只是规则之一,不是唯一)

    其他常用属性:

  • Next-Hop:下一跳 IP,数据包发到这个 IP
  • Local-Preference(本地优先级)AS 内部选路用,数字越大越优先,只在本 AS 传递,不会传给邻居 AS
  • MED(多出口区分):发给邻居 AS,告诉对方"走我这个出口更优",数字越小越好
  • Origin:路由来源,简单理解就是这条路由怎么进 BGP 的

BGP流程:AS1 和 AS2 怎么把路由互相传通

示例拓扑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
【AS1】 <--BGP--> 【AS2】
AS1 内部网段:10.1.0.0/24
AS2 内部网段:10.2.0.0/24

R1:AS1 的边界路由器(AS号 65001)
R2:AS2 的边界路由器(AS号 65002)

R1 直连IP:192.168.1.1
R2 直连IP:192.168.1.2
R1 <--网线--> R2,这一段是两个AS的边界互联链路

65001、65002 属于私有 AS 号,测试随便用;公网运营商用公有 AS 号。

目标:让 AS1 里面的机器能访问 10.2.0.0/24,AS2 里面能访问 10.1.0.0/24

  1. R1、R2 先建立 BGP 邻居

    BGP 底层先用 TCP 179 端口建连接,三次握手成功之后,才成为 BGP 邻居,然后交换路由,邻居建立成功不代表路由可以自动交换,要把内网网段注入 BGP。

  2. R1 把 AS1 内网 10.1.0.0/24 宣告进 BGP,发给 R2

    R1 构造一条 BGP 路由发给 R2,这条路由信息简化表示:

    1
    2
    3
    
     目标网段:10.1.0.0/24
     Next-Hop:192.168.1.1
     AS-PATH:65001
    

    即:目标 10.1.0.0/24,下一跳 192.168.1.1,要经过 AS65001

    R2 收到这条路由:

    • 检查 AS-PATH:里面没有自己 AS 号 65002 则 合法,收下

    • 放进 BGP 表,优选之后注入本机全局路由表

  3. 同理,R2 把 AS2 内网 10.2.0.0/24 发给 R1

    R2 发给 R1 的 BGP 路由:

    1
    2
    3
    
     目标网段:10.2.0.0/24
     Next-Hop:192.168.1.2
     AS-PATH:65002
    

    R1 校验没问题,收下,写入路由表。

    到这里,AS1 边界 R1 知道怎么去 10.2.0.0/24,AS2 边界 R2 知道怎么去 10.1.0.0/24,两个 AS 的网段就打通了

  4. 数据包实际转发路径

    1. 10.1.0.10 发包,目标 10.2.0.20,包先到 R1

    2. R1 查路由表:去 10.2.0.0/24 下一跳是 192.168.1.2 ,丢给 R2

    3. R2 收到包,查路由,转发到 AS2 内网 10.2.0.20

    4. 回程包反向走一样逻辑

AS-PATH 防环

再加一个AS说明环路,扩展拓扑:AS1(65001) <-> AS2(65002) <-> AS3(65003)

  1. AS1 把 10.1.0.0/24 发给 AS2,AS-PATH=[65001]

  2. AS2 再转发给 AS3,转发时把自己 AS 号加到 AS-PATH 最前面 ,AS-PATH=[65002, 65001]

规则:路由跨 AS 传递时,下一跳 AS 会把自身 AS 号追加进 AS-PATH 头部

  1. 假设 AS3 又把这条路由发回给 AS1,此时 AS-PATH=[65003, 65002, 65001]

  2. AS1 收到后一看:AS-PATH 里面存在 65001(自己),则直接丢弃,环路直接扼杀,这就是 BGP 防环机制。

BGP 选路极简顺序

当一条目标网段收到多条 BGP 路由,路由器按顺序挑最优:

  1. 优先 Local-Preference 越大越好(本 AS 内部优先,不传外部)
  2. 优先本地注入的路由 > 从邻居学来
  3. 优先 AS-PATH 长度更短
  4. Origin:i(IGP 注入) 优于 incomplete
  5. MED 越小越好(只对比同一个相邻 AS 传来的路由)
  6. EBGP 路由优先于 IBGP
  7. 再看下一跳 IGP 开销……后面还有一堆

IBGP——同一个 AS 内部的 BGP

概述

EBGP是不同 AS 之间的 BGP,IBGP是同一个 AS 内部,两台路由器之间跑的 BGP。IBGP最核心规则是:IBGP 学到的路由,默认不能再转发给其他 IBGP 邻居(IBGP 水平分割)。IBGP 传递路由时,不会追加 AS 号到 AS-PATH!(这是和 EBGP 最明显区别)

EBGP / IBGP 核心对比

项目 EBGP(跨 AS) IBGP(同 AS 内)
邻居位置 不同自治系统 同一个自治系统
AS-PATH 传递路由时,把自己 AS 号加到 AS-PATH 最前面 不会修改、不会追加 AS-PATH
水平分割规则 EBGP 没有这个限制(EBGP 学到可以传给其他 EBGP) IBGP 核心:从 IBGP 邻居收到的路由,不能再发给另一个 IBGP 邻居
Next-Hop 默认 发给 EBGP 对端时,下一跳改成自己和对端直连 IP 默认不修改 Next-Hop,原样传递

IBGP数据流

基础拓扑示例入下:

1
2
3
4
5
┌──────────────────────────────── AS65001 ───────────────────────────────┐
│                                                                         │
│  R1(边界路由器,EBGP邻居连外面AS2) <-- IBGP --> R3(AS内部路由器)      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
  • AS65002: 外部AS,网段10.2.0.0/24,R2 192.168.1.2在此AS
  • AS65001:R1/R3 IBGP内部统一
  • R1:192.168.1.1,和外部 AS65002 的 R2 建立 EBGP,拿到外部网段 10.2.0.0/24,内网口10.0.0.1
  • R3:内网口是10.0.0.3,R1 和 R3 在同一个 AS 内部,建立 IBGP 邻居

目标: R1 从 EBGP 学到 10.2.0.0/24,通过 IBGP 传给 R3,让 AS 内部 R3 也能知道怎么去 10.2.0.0/24

步骤 1:R1 通过 EBGP,从 R2 学到路由

R2 发给 R1(EBGP 路由):

1
2
3
目标:10.2.0.0/24
Next-Hop:192.168.1.2
AS-PATH:65002

R1 收下,放进 BGP 表

步骤 2:R1 把这条路由通过 IBGP 发给同 AS 的 R3

⚠️ IBGP 转发规则生效AS-PATH 原样不动,不追加 6500

R1 发给 R3 的 IBGP 路由条目:

1
2
3
目标:10.2.0.0/24
Next-Hop:192.168.1.2   ←【默认直接继承,不会改成R1的10.0.0.1】
AS-PATH:65002          ← AS-PATH完全没变!

那么问题来了 : R3 收到路由:想去 10.2.0.0/24,下一跳是 192.168.1.2 R3 本地必须要有路由可达 192.168.1.2,否则这条 BGP 路由在 R3 上无效、不进路由表!

两种解决办法:

  1. 在 R1 配置 next-hop-self(最常用) R1 再发给 R3 的时候,自动把 Next-Hop 改成 R1 自己 10.0.0.1,R3 内部自然可达。
  2. 把边界直连网段 192.168.1.0/24 宣告进 AS 内部 OSPF,让 R3 能到达 192.168.1.2

打开 next-hop-self 之后,R1 发给 R3 的 IBGP 路由变成:

1
2
3
目标:10.2.0.0/24
Next-Hop:10.0.0.1
AS-PATH:65002

R3 校验下一跳可达,路由生效

IBGP 水平分割

扩展拓扑,演示水平分割:

1
2
AS65001
R1 <IBGP> R3 <IBGP> R4

默认规则:R1(EBGP 学到路由),并通过IBGP 发给 R3,允许;R3(从 IBGP 邻居 R1 学到的路由)再通过 IBGP 转发给 R4,默认禁止。

为什么要有这个规则?IBGP内部AS号一样,不追加 AS-PATH,没法靠 AS-PATH 防环,EBGP 靠 AS-PATH 天然防环;IBGP 没有这个机制,所以强制加一条硬性枷锁:IBGP 收到的路由,不能再传给别的 IBGP 邻居,避免 AS 内部路由环路。

问题又来了:一个 AS 里面很多台路由器怎么办?

如果 AS 内部有 4 台路由器:R1、R3、R4、R5,如果严格遵守 IBGP 水平分割,最简单粗暴方案:全互联 IBGP,也就是两两之间全部建立 IBGP 邻居:R1-R3、R1-R4、R1-R5、R3-R4、R3-R5、R4-R5,两两建连宣告自己的网络,不存在环路,也能全部打通。

缺点显而易见:节点一多,邻居数量爆炸,N 台设备需要 N*(N-1)/2 个 IBGP 邻居,没法大规模部署。所以工程上衍生两种方案,专门解决 IBGP 全互联痛点:

  1. 路由反射器 RR(Route Reflector)
  2. BGP 联邦 Confederation(相对少用,大型网络)

简单说明联邦 Confederation,就是把一个大 AS,逻辑上切割成多个小子 AS,子 AS 之间跑 EBGP、外层对外还是同一个 AS 号,一般超大型骨干网才会用。

路由反射器 RR

角色划分:

  • RR 路由反射器(中心点)

  • Client 客户端路由器

规则:

  • RR 收到 Client 的 IBGP 路由,可以反射转发给其他 Client(打破原生 IBGP 水平分割限制)

  • Client 和 Client 之间不需要建立 IBGP 邻居,只需要和 RR 建邻居

即:RR 做转发枢纽,不用全网全互联。但是需要注意,RR 反射路由依然不会追加 AS-PATH,只是放宽转发限制,不是改变底层原理。

举个极简 RR 例子:

1
R1(EBGP边界)→ IBGP → RR → IBGP → R3、R4、R5

R1 把外部路由发给 RR,RR 反射给 R3/R4/R5, R3/R4/R5 只和 RR 建立 IBGP,互相不用建邻居,大幅减少邻居数量。

总结 RR:专门用来解决 IBGP 全互联爆炸问题,可控地打破 IBGP 水平分割。

IBGP 路由防环方式:

  1. 原生 IBGP 水平分割(默认):IBGP 学到的路由,禁止转发给其他 IBGP 邻居
  2. RR 反射场景额外防环规则
    • 路由反射时,会带上 Originator-ID(路由原始始发路由器)+ Cluster-List(反射簇列表)
    • 如果收到路由,Originator-ID 等于自己,或者 Cluster-List 包含本簇 ID,直接丢弃,防止反射环路

IBGP 选路说明

IBGP 路由和 EBGP 路由放在一起比较优先级时:EBGP 路由天生优于 IBGP 路由(选路规则靠前)

举个例子:同一条网段 10.2.0.0/24,R1 同时收到:

  • 一条 EBGP 路由(来自 AS2)

  • 一条 IBGP 路由(来自同 AS 另一台路由器)

    路由器会优选 EBGP 那条。

    其他属性(Local-Pref、MED、AS-PATH 长度等)IBGP 完全沿用 BGP 通用选路逻辑。

Local-Preference 这个属性就是设计给 IBGP 场景用的:在本 AS 内部所有 IBGP 邻居之间传递,用来统一控制整个 AS 的出站选路,不会传给外部 EBGP 邻居。

IBGP 核心规则总结

  1. IBGP = 同一个 AS 内部的 BGP 邻居,底层 TCP端口 179

  2. IBGP 转发路由:不追加 AS 号,AS-PATH 保持原样

  3. 默认硬规则:IBGP 学到的路由,不能再转发给别的 IBGP 邻居(水平分割)

  4. IBGP 传递路由默认不改 Next-Hop,极易下一跳不可达;工程常规配置 next-hop-self

  5. IBGP 大规模部署解决方案:路由反射器 RR(主流)、BGP 联邦(小众)

  6. IBGP 防环不靠 AS-PATH:原生靠水平分割;RR 场景靠 Originator-ID + Cluster-List

  7. Local-Preference 作用域就是整个 AS 内部,随 IBGP 传递,用来控制本 AS 出口方向


VXLAN——跨三层的虚拟大二层

VLAN & VXLAN

VLAN:二层隔离,给同一个交换机里打标签,范围局限在本地二层域;标准 802.1Q VXLAN:Overlay 隧道(在原有底层网络Underlay之上,再虚拟一层网络),把二层帧封装在 UDP 包里,跨三层网络传输,解决 VLAN 数量不够、跨机房跨云二层迁移问题

VLAN 是传统物理二层标签;VXLAN 是"VLAN 的跨三层升级版隧道"

核心概念

  • VNI(VXLAN Network Identifier):对应 VLAN 的 VLAN ID,但是 24bit,2^24 = 16777216 个,一千六百多万

  • VTEP(VXLAN Tunnel End Point):VXLAN 隧道两端的封装/解封装节点,在 K8s Flannel 里,每个 Node 就是一个 VTEP

  • Underlay 网络:底层承载网络(就是 Node 宿主机之间可达的 IP 网络,三层)

  • Overlay 网络:覆盖网络,此处理解为VXLAN 虚拟出来的大二层,Pod 属于这个 Overlay

完整报文封装结构

顺序:原始二层帧 → VXLAN 头 → UDP 头 → 外层 IP 头 → 外层 MAC 头

1
2
3
4
5
6
7
8
9
【内层原始以太网帧(带DMAC/SMAC,就是Pod之间原生二层帧)】
【8字节 VXLAN Header】  里面放24bit VNI
【UDP Header】 目的端口固定 4789(VXLAN标准端口)
【外层IP Header】 源=本Node宿主机IP,目的=对端Node宿主机IP
【外层MAC Header】 底层物理MAC,Underlay转发使用

按Flannel场景说明封装过程,PodA(node1)发包给 PodB(node2):

  1. node1 Flannel VTEP 拿到 Pod 原始二层帧

  2. 套 VXLAN 头,打上 VNI

  3. 套 UDP 头 4789

  4. 套外层 IP:源 node1 宿主机 IP,目的 node2 宿主机 IP

  5. 底层物理网络按照外层 IP 路由,把包送到 node2

  6. node2 的 VTEP 解封装,剥掉外层 UDP/IP/MAC,拿出内层原始二层帧,交给 PodB

关键点:

底层 Underlay 只需要node1 和 node2 宿主机三层 IP 可达就行,不需要大二层、不需要透传 VLAN。哪怕 node1 和 node2 跨网关、跨机房、跨云,只要 IP 能通,VXLAN 隧道就能跑。

对比 VLAN:VLAN 标签是插在二层帧中间;VXLAN 是把整个内层二层帧打包塞进 UDP 包里走三层

VXLAN 组播 / 单播(Flannel 两种模式)

VXLAN 本身要解决一个问题:ARP 广播(二层网络里查 MAC)

  • 标准传统 VXLAN:依靠 Underlay 组播,同一个 VNI 的所有 VTEP 加入同一个组播组,ARP 广播泛洪给所有 VTEP。缺点是底层网络要支持组播,很多公有云不开放组播,所以 Flannel 默认不用这个

  • Flannel VXLAN(直接路由/单播模式):预先知道所有对端 VTEP IP,ARP 不泛洪,直接点对点单播封装

注意:VXLAN 本质依然是大二层,广播/未知单播会泛洪,规模很大之后广播风暴风险会上来

VXLAN 优缺点

优点

  1. VNI 1600 万+,彻底摆脱 4096 VLAN 限制

  2. 跨三层 Underlay 构建虚拟大二层,云/虚拟化标配

  3. 公有云友好,只需要节点 IP 互通,不用底层改路由、不用 BGP

缺点

  1. 封装开销:额外 50 字节左右头部(VXLAN+UDP+外层 IP),小包场景 CPU 损耗明显

  2. 属于 Overlay 大二层,广播泛洪风险,不适合超大规模集群

  3. 原生只是连通,没有网络策略

VXLAN 有硬件和软件实现:

  1. 硬件 VXLAN:高端交换机/路由器(华为、H3C、Cisco 数据中心款)内置 VXLAN 处理芯片,VTEP 跑在硬件里,不需要服务器上装 agent,多用于传统大型虚拟化/云厂商底层。
  2. Linux 内核 VXLAN(K8s Flannel 这种)**:**内核原生支持 VXLAN 模块,但是必须由用户态程序(flanneld/cilium-agent)来控制配置。 Linux 内核自带 VXLAN 驱动(vxlan.ko),封装解包是内核做,flanneld 不做报文封装,它只干控制面:
    • 监听 apiserver,拿到所有 Node、PodCIDR 信息

    • 在本机自动创建 vxlan 网卡(比如 flannel.1

    • 动态填充内核 FDB 转发数据库(把"对端 VTEP IP ↔ MAC"写到内核)

    • 维护路由


IPIP——轻量三层隧道

IPIP(IP-in-IP)是一种最简单的三层隧道:把一个 IP 包,外面再套一层 IP 头。

它**不保留内层二层 MAC 头,不是大二层。**这是和 VXLAN 本质区别,VXLAN 要保留完整二层帧、虚拟大二层,IPIP 只封装三层 IP 包,直接砍掉内层二层。

IPIP 报文结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
┌─────────────────────────────────────────────────────────────────────┐
│ 外层以太网帧(Underlay物理链路)                                      │
│ DMAC SMAC EtherType=0x0800                                          │
├─────────────────────────────────────────────────────────────────────┤
│ 外层IPv4头:Src=Node1宿主机IP,Dst=Node2宿主机IP                    │
├─────────────────────────────────────────────────────────────────────┤
│ IPIP标记(协议号4,没有UDP、没有VNI、没有内层二层帧!)              │
├─────────────────────────────────────────────────────────────────────┤
│ 内层IPv4头:SrcPodIP DstPodIP                                       │
├─────────────────────────────────────────────────────────────────────┤
│ TCP/UDP + 【应用层原始数据】                                         │
└─────────────────────────────────────────────────────────────────────┘

IPIP 关键:只有外层 MAC,不存在内层 MAC,没有虚拟大二层。这就是为什么 IPIP 不需要跨节点 ARP,而 VXLAN 必须处理 ARP 代理

IPIP vs VXLAN 差异

项目 IPIP VXLAN
封装内容 只封装三层 IP 包 完整封装内层二层以太网帧
内层 MAC 没有
传输协议 IP 协议号 4(直接 IP over IP) UDP 4789
VNI/标识 24bit VNI
模型 三层隧道 大二层 Overlay
额外头部开销 约 20 字节 约 50 字节
跨节点 ARP 不需要 需要(ARP 代理或组播泛洪)

Calico——基于 BGP 的 CNI

Calico 是基于纯三层路由(BGP)的 K8s CNI,默认方案没有 Overlay 封装,Pod 之间互通靠路由表转发。

概念说明:

  • CNI:容器网络接口,K8s 用来给 Pod 分配网络、连通的插件标准。解耦了容器运行时(如 Kubernetes 或 Containerd)与底层网络实现,让任何网络插件都可以通过统一的 JSON 格式配置和管理网络,点此参考官方仓库

  • Calico 两大核心组件:Felix + BGP Client(bird)+ etcd

Calico 支持两种模式:

  1. BGP 模式(默认经典模式,无封装,三层直路由)

  2. IPIP/VXLAN Overlay 隧道模式(跨子网节点、云厂商禁止 BGP 时需要)

Calico 核心组件

Felix

Calico 的大脑代理,最核心组件 职责:

  1. 监听 apiserver/etcd:看新增 Pod、删除 Pod、NetworkPolicy 网络策略
  2. 在宿主机 Linux 上编程:配置路由表、iptables/nftables
  3. 通知本机 BIRD:要宣告哪些 Pod 网段路由出去

Felix 本身不跑 BGP,只管写内核路由+防火墙规则。

BIRD

就是一个轻量级 BGP 路由器实现(BIRD Internet Routing Daemon) Felix 告诉 BIRD:把本机 PodCIDR 10.244.1.0/24 宣告给集群内其他节点 BIRD,所有节点 BIRD 之间建立 IBGP 邻居,互相交换 PodCIDR 路由。所有 BIRD 属于同一个 AS,互相 IBGP,传递路由时不修改 AS-PATH

etcd

存储 Calico 全部状态,两种部署形态:

  1. Calico 直连独立 etcd(老方案)

  2. K8s APIServer 作为数据存储(现在默认,CalicoDatastore=kubernetes)

    存的内容:节点信息、PodCIDR、BGP 配置、NetworkPolicy、IPPool等

calicoctl

命令行管理工具,用来手动改 BGP 配置、查看路由、BGP 邻居状态等,参考官网文档

数据流

示例 K8s 拓扑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
K8s集群 2个节点
node1:10.0.0.11  宿主机IP
node2:10.0.0.12  宿主机IP

Calico 给Pod网段规划:
node1上PodCIDR:10.244.1.0/24
node2上PodCIDR:10.244.2.0/24

node1跑PodA:10.244.1.10
node2跑PodB:10.244.2.20
目标:PodA <----> PodB 互通

核心思想: 每个 Node 上的 Felix+BIRD,互相建立 BGP 邻居,把本节点的 PodCIDR 当作一条 BGP 路由,发给其他所有节点,整个 K8s 集群可以看成一个自治系统 AS(比如 AS 64512),每个 K8s Node,等价于一台小型 BGP 路由器,所有 node 上的 bird 进程互相建立 IBGP 邻居。

前面提到的IBGP 水平分割、next-hop,在 Calico 里面全部存在

BGP 无 overlay 模式步骤

步骤 1:集群初始化,node1/node2 的 BIRD 建立 IBGP 邻居(TCP 179),同 AS

步骤 2:node1 把自己 PodCIDR 10.244.1.0/24 通过 BGP 宣告出去

node1-BIRD 发给 node2-BIRD 的 BGP 路由条目:

1
2
3
目标网段:10.244.1.0/24
Next-Hop:10.0.0.11   ← node1宿主机IP
AS-PATH:空(同AS IBGP,不追加AS号!)

同理 node2-BIRD 发给 node1:

1
2
3
目标网段:10.244.2.0/24
Next-Hop:10.0.0.12
AS-PATH:空

步骤 3:每个节点内核路由表自动写入这条路由

node1 宿主机路由表新增: 10.244.2.0/24 via 10.0.0.12

node2 宿主机路由表新增: 10.244.1.0/24 via 10.0.0.11

步骤 4:PodA 发包给 PodB

  1. PodA(10.244.1.10)发包,目标 IP 10.244.2.20

  2. 包先出 Pod 的 veth 虚拟网卡,到达 node1 宿主机内核

  3. node1 查内核路由表:去 10.244.2.0/24 的下一跳 10.0.0.12(node2 宿主机 IP)

  4. 直接走底层物理网络/交换机,裸 IP 包转发到 node2

  5. node2 收到包,查本地路由,转给 PodB

    全程没有封装,标准三层 IP 路由

对比 Flannel VXLAN:Flannel 会在宿主机之间包一层 UDP 隧道头,对上层透明,有额外开销。

Calico BGP 两种对等模型

  1. Node-to-Node Mesh(全互联 IBGP,默认)

    由于IBGP水平分割限制,集群里所有节点 BIRD 两两建立 IBGP 邻居,如3 节点集群:node1<->node2,node1<->node3,node2<->node3

    缺点:节点数量较多后,BGP 邻居数量爆炸,BIRD 压力大,仅适合小规模集群

  2. Route Reflector(RR 路由反射器)

    不需要所有节点两两 IBGP,挑选 1~3 台节点做 Calico BGP RR,其他普通节点只和 RR 建立 IBGP 邻居,普通节点之间不建邻居。

1
2
3
node1(Client) ──IBGP──┐
node2(Client) ──IBGP── RR节点
node3(Client) ──IBGP──┘
  • Client 把本机 PodCIDR 发给 RR

  • RR 反射路由给所有其他 Client

    完美解决 IBGP 全互联爆炸问题,生产 100+ 节点集群标配

上面裸 BGP 有个硬性前提:所有 K8s Node 宿主机之间,三层可达,网络设备允许 BGP、允许自定义路由。

很多公有云(阿里云/AWS)底层网络不允许你自定义私网 BGP、不允许随意注入路由,这种场景不能直接裸 BGP,Calico 开启 IPIP 隧道,IPIP 是简单 IP-in-IP 封装,原始 IP 包外面再套一层宿主机 IP 头,点对点隧道,此时路由逻辑不变,只是包在跨节点时加了封装,底层网络只需要宿主机互通即可。

Calico IPIP 模式说明

Calico IPIP 依然是「三层路由优先」架构,控制平面还是标准 BGP(BIRD IBGP 交换 PodCIDR);IPIP 只是一个可选的三层隧道封装,不保留内层二层 MAC 头,不是大二层

继续沿用之前拓扑:

  • node1:10.0.0.11,PodCIDR 10.244.1.0/24
  • node2:10.0.0.12,PodCIDR 10.244.2.0/24
  • Calico IPIP 开启

Calico IPIP 完整数据流(PodA访问PodB)

  1. PodA 发包:源 10.244.1.10,目标 10.244.2.20(纯 IP 包,不需要 ARP 跨节点!)

    因为 Calico 是三层路由模型,跨节点不需要二层 ARP 广播,根本不需要知道远端 Pod MAC! 这和 Flannel VXLAN 完全两条路。

  2. node1 内核路由表(由 BIRD+BGP 同步过来):10.244.2.0/24 via 10.0.0.12,并且命中 IPIP 策略:去往这个网段需要封装

  3. 内核 ipip 模块封装:内层 PodIP 包外面套一层外层宿主机 IP 头

  4. 底层网络把封装包送到 node2

  5. node2 内核解封装,剥离外层 IP,拿到原始 Pod 之间 IP 包,直接转发给 PodB

Calico NetworkPolicy

底层本质

Felix 监听 K8s NetworkPolicy 资源,翻译成宿主机上 iptables/nftables 规则,在节点上直接拦截 Pod 流量。

简单示例

声明策略:只允许 PodA 访问 PodB,其他全部拒绝,Felix 在 node2 生成 iptables 规则,源 IP 不是 10.244.1.10 直接丢弃。

常见问题

  1. BGP Mesh 模式节点量大之后 BIRD 负载高 上 Calico BGP Route Reflector

  2. 底层网络不支持自定义路由(公有云默认限制) 开启 IPIP 隧道

  3. 节点 BGP 邻居起不来:防火墙是否封禁 TCP 179 端口

  4. 跨节点 Pod 不通,但宿主机互通:

    大概率底层网络丢弃 Calico 注入的 PodCIDR 路由,需要切 IPIP

  5. 不要混淆:Calico 的 BGP 只负责宣告 Pod 网段,不管 Service,Calico 只管 Pod 到 Pod 底层连通。