Kubernetes 容器网络学习笔记¶
本文档是基于容器网络课程整理的系统性学习笔记,面向运维工程师/SRE,帮助深入理解 Kubernetes 容器网络。
📚 目录¶
第一部分:网络基础¶
- 第1章 TCP/IP 协议栈
- 第2章 Linux 网络命名空间
- 第3章 Veth Pair 虚拟网络设备
- 第4章 Linux Bridge 网桥
- 第5章 路由与 ARP
- 第6章 iptables 防火墙
- 第7章 IPVS 负载均衡
第二部分:Kubernetes 网络核心¶
- 第8章 Docker 网络模型
- 第9章 Kubernetes 网络模型
- 第10章 CNI 容器网络接口
- 第11章 Pod 网络
- 第12章 Service 网络
- 第13章 DNS 与服务发现
- 第14章 Ingress 与 Gateway API
第三部分:Cilium CNI 深度实践¶
- 第15章 Cilium 概述与架构
- 第16章 Cilium 安装与部署
- 第17章 Cilium 网络策略
- 第18章 Cilium Observability
- 第19章 Cilium Service Mesh
- 第20章 Cilium Cluster Mesh
- 第21章 Cilium BGP
- 第22章 Cilium Egress Gateway
- 第23章 Cilium Bandwidth Manager
- 第24章 Cilium Host Firewall
- 第25章 Cilium 透明加密
- 第26章 Cilium NAT46/NAT64
- 第27章 Cilium Local Redirect Policy
- 第28章 Cilium Health Checking
- 第29章 Cilium 性能调优
- 第30章 Cilium 故障排除
- 第31章 Cilium 升级与运维
- 第32章 Cilium 生产最佳实践
- 第33章 Cilium eBPF Internals
- 第34章 Cilium Datapath
- 第35章 Cilium Identity 与安全
- 第36章 Cilium IPAM
- 第37章 Cilium LB IPAM
- 第38章 Cilium Envoy
- 第39章 Cilium Tetragon
- 第40章 Cilium Hubble 深度解析
- 第41章 Cilium 与 Istio 集成
- 第42章 Cilium 未来展望
第四部分:Flannel CNI 实践¶
- 第43章 Flannel 概述与架构
- 第44章 Flannel VXLAN 模式
- 第45章 Flannel VXLAN DirectRouting
- 第46章 Flannel Host-GW 模式
- 第47章 Flannel UDP 模式
- 第48章 Flannel Alloc 模式
- 第49章 Flannel 多网卡与 Public IP
- 第50章 Flannel IPsec 模式
- 第51章 Flannel WireGuard 模式
第五部分:高性能 CNI(Multus/SR-IOV/DPDK)¶
- 第52章 Multus 多网卡方案
- 第53章 Multus IPVLAN L2 模式
- 第54章 Multus IPVLAN L3 模式
- 第55章 Multus IPVLAN SBR 模式
- 第56章 IPVLAN-SBR 深度解析
- 第57章 MACVLAN-SBR 实践
- 第58章 Multus-with-SRIOV-Kernel
- 第59章 Multus-with-SRIOV-DPDK-VPP
- 第60章 K8s-CNI-IPAM 机制详解
第一部分:网络基础¶
第1章 TCP/IP 协议栈¶
🎯 学习目标¶
- 理解 OSI 七层模型与 TCP/IP 四层模型的区别与联系
- 掌握网络分层架构的设计思想
- 理解 TCP 和 UDP 协议的核心差异
- 学会使用 tcpdump 和 Wireshark 进行网络抓包分析
1.1 OSI 七层模型与 TCP/IP 四层模型¶
背景¶
在计算机网络发展早期,不同厂商的网络设备和协议互不兼容,导致设备之间无法互联互通。为了解决这个问题,国际标准化组织(ISO)提出了 OSI(Open Systems Interconnection)参考模型,将网络通信划分为七个层次,每一层负责特定的功能。
[!NOTE] OSI 模型是一个理论参考模型,而实际生产环境中更多使用的是简化后的 TCP/IP 四层模型。
原理¶
分层架构的核心思想:术业有专攻
网络分层的本质是解耦,使得:
- 每一层专注于自身的协议实现
- 不同厂商的设备只要遵循同一标准,就可以互联互通
- 便于问题定位和排查
graph TD
subgraph OSI["OSI 七层模型"]
L7[应用层<br/>Application]
L6[表示层<br/>Presentation]
L5[会话层<br/>Session]
L4[传输层<br/>Transport]
L3[网络层<br/>Network]
L2[数据链路层<br/>Data Link]
L1[物理层<br/>Physical]
L7 --> L6 --> L5 --> L4 --> L3 --> L2 --> L1
end
subgraph TCPIP["TCP/IP 四层模型"]
T4[应用层]
T3[传输层]
T2[网络层]
T1[网络接口层]
T4 --> T3 --> T2 --> T1
end
L7 -.-> T4
L6 -.-> T4
L5 -.-> T4
L4 -.-> T3
L3 -.-> T2
L2 -.-> T1
L1 -.-> T1
图解说明:
上图展示了 OSI 七层模型与 TCP/IP 四层模型的对应关系:
- OSI 的应用层、表示层、会话层统一为 TCP/IP 的应用层
- OSI 的传输层对应 TCP/IP 的传输层
- OSI 的网络层对应 TCP/IP 的网络层
- OSI 的数据链路层、物理层合并为 TCP/IP 的网络接口层
各层功能与核心协议¶
| 层级 | OSI 模型 | TCP/IP 模型 | 核心功能 | 代表协议/设备 |
|---|---|---|---|---|
| 7 | 应用层 | 应用层 | 为用户提供网络服务 | HTTP、FTP、DNS、DHCP |
| 6 | 表示层 | 应用层 | 数据编解码、压缩、加密 | SSL/TLS、JPEG |
| 5 | 会话层 | 应用层 | 建立、管理、终止会话 | RPC、NetBIOS |
| 4 | 传输层 | 传输层 | 端到端的可靠传输 | TCP、UDP、SCTP |
| 3 | 网络层 | 网络层 | 路由选择、逻辑寻址 | IP、ICMP、路由器 |
| 2 | 数据链路层 | 网络接口层 | 物理寻址、帧封装 | MAC、交换机、ARP |
| 1 | 物理层 | 网络接口层 | 比特流传输 | 光纤、双绞线、无线 |
要点解读:
对于容器网络学习,我们需要重点关注以下三层:
- 传输层(L4):TCP/UDP 端口,这是应用开发最常接触的层
- 网络层(L3):IP 地址、路由表,跨节点通信主要依赖此层
- 数据链路层(L2):MAC 地址、交换机,同网段通信的基础
关键点¶
[!IMPORTANT] 运维排障思路:当服务不可达时,应该逐层排查:
- 物理层:网线是否连接正常?
- 数据链路层:MAC 地址是否正确?ARP 表是否正确?
- 网络层:IP 是否可达?路由表是否正确?
- 传输层:端口是否监听?防火墙是否放行?
- 应用层:服务是否正常启动?
1.2 数据封装与解封装流程¶
背景¶
当应用程序需要发送数据时,数据会从上层向下层逐级传递,每经过一层都会添加该层的头部信息(Header),这个过程称为封装(Encapsulation)。接收端则进行相反的操作,称为解封装(Decapsulation)。
原理¶
graph TB
subgraph 发送端["发送端封装过程"]
direction TB
A1[应用数据] --> A2[TCP/UDP 头 + 数据<br/>段 Segment]
A2 --> A3[IP 头 + 段<br/>包 Packet]
A3 --> A4[MAC 头 + 包 + MAC 尾<br/>帧 Frame]
A4 --> A5[比特流 Bits]
end
subgraph 接收端["接收端解封装过程"]
direction TB
B5[比特流 Bits] --> B4[帧 Frame]
B4 --> B3[包 Packet]
B3 --> B2[段 Segment]
B2 --> B1[应用数据]
end
A5 -->|网络传输| B5
图解说明:
数据在发送端逐层封装,每层添加自己的协议头:
- 应用层:生成原始数据
- 传输层:添加 TCP/UDP 头(包含源端口、目的端口)
- 网络层:添加 IP 头(包含源 IP、目的 IP、TTL)
- 数据链路层:添加 MAC 头和尾(包含源 MAC、目的 MAC)
- 物理层:转换为 0/1 比特流进行传输
数据包结构示意¶
+------------------+----------+--------+----------+-----------------+
| 以太网帧头 | IP 头 | TCP 头 | 数据 | 以太网帧尾 |
| 14 字节 | 20 字节 | 20 字节| N 字节 | 4 字节 |
+------------------+----------+--------+----------+-----------------+
|←-- 二层 MAC --→|←-三层IP-→|←-四层-→| |
代码说明:
以上结构展示了一个完整的以太网帧:
- 以太网帧头(14 字节):包含目的 MAC(6B)+ 源 MAC(6B)+ 类型(2B)
- IP 头(20 字节):包含版本、TTL、源 IP、目的 IP 等
- TCP 头(20 字节):包含源端口、目的端口、序列号、标志位等
- 以太网帧尾(4 字节):FCS 帧校验序列
1.3 TCP 协议详解¶
背景¶
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的传输层协议。它通过三次握手建立连接、四次挥手断开连接,并提供流量控制、拥塞控制等机制来保证数据可靠传输。
原理¶
1.3.1 套接字(Socket)概念¶
套接字 = IP 地址 + 端口号
例如:192.168.1.100:8080 表示一个套接字
[!NOTE] TCP 端口和 UDP 端口是独立的。同一个端口号(如 53)可以同时被 TCP 和 UDP 监听,因为它们属于不同的协议。
1.3.2 TCP 三次握手¶
sequenceDiagram
participant C as 客户端
participant S as 服务端
Note over S: LISTEN 状态
C->>S: SYN (seq=x)
Note over C: SYN_SENT
S->>C: SYN + ACK (seq=y, ack=x+1)
Note over S: SYN_RCVD
C->>S: ACK (ack=y+1)
Note over C,S: ESTABLISHED 连接建立
图解说明:
三次握手的过程:
- 第一次握手:客户端发送 SYN 包(同步序列号),进入 SYN_SENT 状态
- 第二次握手:服务端收到 SYN 后,回复 SYN+ACK 包,进入 SYN_RCVD 状态
- 第三次握手:客户端收到后回复 ACK,双方进入 ESTABLISHED 状态,连接建立
1.3.3 TCP 标志位¶
| 标志位 | 名称 | 作用 |
|---|---|---|
| SYN | Synchronize | 发起连接,同步序列号 |
| ACK | Acknowledge | 确认收到数据 |
| FIN | Finish | 请求关闭连接 |
| RST | Reset | 重置连接(异常终止) |
| PSH | Push | 立即推送数据给应用层 |
| URG | Urgent | 紧急数据 |
1.3.4 MSS 与分片机制¶
MSS(Maximum Segment Size):最大报文段长度,指 TCP 数据部分的最大长度。
MTU = 1500 字节(以太网默认)
MSS = MTU - IP头(20) - TCP头(20) = 1460 字节
要点解读:
[!IMPORTANT] 为什么分片在 TCP 层做而不是 IP 层?
- TCP 有序列号,可以精确知道哪个片丢失,只需重传丢失的片
- IP 没有重传机制,如果一个分片丢失,需要重传整个原始数据
- 在 TCP 层分片可以减少重传开销
1.3.5 TCP 卸载技术¶
当数据量较大时,TCP 分片等操作会消耗大量 CPU 资源。现代网卡支持将这些操作卸载(Offload)到硬件,减轻 CPU 负担:
| 技术 | 全称 | 作用 |
|---|---|---|
| TSO | TCP Segmentation Offload | 将 TCP 分片卸载到网卡 |
| GSO | Generic Segmentation Offload | 通用分片卸载 |
| GRO | Generic Receive Offload | 接收端合并小包 |
| LRO | Large Receive Offload | 大包接收卸载 |
代码说明:
查看网卡 offload 状态:
# 查看网卡 offload 配置
ethtool -k eth0
# 输出示例:
# tcp-segmentation-offload: on
# generic-segmentation-offload: on
# generic-receive-offload: on
# large-receive-offload: off
1.4 UDP 协议特点¶
背景¶
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的、不可靠的传输层协议。它没有握手过程,发送数据时不保证对方能收到。
TCP 与 UDP 对比¶
| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接 | 无连接 |
| 可靠性 | 可靠(有确认、重传) | 不可靠 |
| 有序性 | 保证顺序 | 不保证 |
| 速度 | 较慢(握手开销) | 较快 |
| 头部大小 | 20 字节 | 8 字节 |
| 应用场景 | HTTP、数据库、文件传输 | DNS、视频流、游戏 |
graph LR
subgraph TCP["TCP 通信"]
C1[客户端] -->|1. SYN| S1[服务端]
S1 -->|2. SYN+ACK| C1
C1 -->|3. ACK| S1
C1 -->|4. 数据| S1
end
subgraph UDP["UDP 通信"]
C2[客户端] -->|直接发送数据| S2[服务端]
end
图解说明:
- TCP:需要三次握手建立连接后才能传输数据
- UDP:直接发送数据,无需建立连接,简单高效但不可靠
1.5 使用 nc 工具进行网络测试¶
背景¶
nc(netcat)是一个功能强大的网络工具,可以用于 TCP/UDP 端口监听、网络调试、数据传输等场景。
常用命令¶
# ============ TCP 监听与连接 ============
# 服务端:监听 TCP 8088 端口(-l 表示 listen,-k 表示保持监听)
nc -lk 192.168.1.100 8088
# 客户端:连接 TCP 8088 端口
nc 192.168.1.100 8088
# ============ UDP 监听与连接 ============
# 服务端:监听 UDP 8088 端口(-u 表示使用 UDP)
nc -lu 192.168.1.100 8088
# 客户端:连接 UDP 8088 端口
nc -u 192.168.1.100 8088
代码说明:
-l:listen 模式,作为服务端监听端口-k:keep-open,连接断开后保持监听-u:使用 UDP 协议(默认为 TCP)
1.6 使用 tcpdump 和 Wireshark 进行抓包分析¶
背景¶
网络抓包是排查网络问题的核心技能。tcpdump 是 Linux 下的命令行抓包工具,Wireshark 是图形化的抓包分析工具。
tcpdump 常用命令¶
# 基础抓包:在 eth0 接口抓包并保存到文件
tcpdump -i eth0 -w capture.pcap
# 抓取指定端口的 TCP 包
tcpdump -i eth0 port 8088
# 抓取指定 IP 的包
tcpdump -i eth0 host 192.168.1.100
# 详细显示包内容(-nn 不解析主机名和端口名)
tcpdump -i eth0 -nn -vv port 8088
代码说明:
| 选项 | 说明 |
|---|---|
-i |
指定网络接口 |
-w |
写入文件(.pcap 格式) |
-nn |
不解析主机名和端口名 |
-vv |
详细输出 |
Wireshark 过滤技巧¶
| 过滤条件 | 说明 |
|---|---|
tcp.port == 8088 |
过滤 TCP 8088 端口 |
tcp.stream eq 0 |
过滤第一个 TCP 流 |
tcp.flags.syn == 1 |
过滤 SYN 包 |
tcp.len == 0 |
过滤无数据的 TCP 包(握手包) |
ip.addr == 192.168.1.100 |
过滤指定 IP |
1.7 实战:TCP 与 UDP 抓包分析¶
实验目的¶
通过 nc 工具模拟 TCP 和 UDP 通信,使用 tcpdump 抓包,用 Wireshark 分析数据包结构。
实验步骤¶
步骤一:TCP 抓包实验
# 终端 1:启动 tcpdump 抓包
tcpdump -i eth0 -w tcp_capture.pcap port 8088
# 终端 2:启动 TCP 服务端
nc -lk 192.168.1.100 8088
# 终端 3:启动 TCP 客户端并发送数据
nc 192.168.1.100 8088
# 输入:hello CNI
步骤二:用 Wireshark 分析 TCP 包
使用 Wireshark 打开 tcp_capture.pcap,可以看到:
- 三次握手:前三个包(SYN → SYN+ACK → ACK)
- 数据传输:带有 PSH 标志的包,包含 "hello CNI" 数据
- 四次挥手:断开连接的 FIN 包
步骤三:UDP 抓包实验
# 终端 1:启动 tcpdump 抓包
tcpdump -i eth0 -w udp_capture.pcap udp port 8088
# 终端 2:启动 UDP 服务端
nc -lu 192.168.1.100 8088
# 终端 3:启动 UDP 客户端并发送数据
nc -u 192.168.1.100 8088
# 输入:hello UDP
步骤四:对比分析
| 对比项 | TCP | UDP |
|---|---|---|
| 握手过程 | 有三次握手 | 无握手 |
| 数据发送 | 有确认机制 | 直接发送 |
| 包数量 | 多(握手 + 数据 + 确认) | 少(仅数据包) |
📝 章节小结¶
本章介绍了网络通信的基础知识:
- OSI 七层模型与 TCP/IP 四层模型:理解网络分层架构的设计思想,掌握各层的职责
- 数据封装与解封装:理解数据在网络中传输时的打包和拆包过程
- TCP 协议:掌握三次握手、四次挥手、标志位、MSS 分片等核心概念
- UDP 协议:理解无连接、不可靠传输的特点和应用场景
- 网络抓包:学会使用 tcpdump 和 Wireshark 进行网络问题分析
[!TIP] 学习建议:
- 动手实践 nc 和 tcpdump 命令
- 使用 Wireshark 分析真实的网络包
- 理解每一层的头部信息,这对后续学习容器网络非常重要
第2章 IP 地址与 MAC 地址精讲¶
🎯 学习目标¶
- 掌握 IP 地址的点分十进制表示与二进制转换
- 理解有类地址(A/B/C/D/E)与无类地址(CIDR)的区别
- 熟练掌握 VLSM(可变长子网掩码)的计算方法
- 理解 MAC 地址的结构与 OUI 厂商标识
- 掌握二层交换与三层路由的核心区别
- 理解路由转发过程中 IP 和 MAC 地址的变化规律
2.1 IP 地址基础¶
背景¶
IP 地址是网络层的核心标识,用于在互联网中唯一标识一台设备的逻辑位置。与 MAC 地址不同,IP 地址是逻辑地址,可以根据网络规划进行分配和更改。
[!NOTE] IP 地址是逻辑地址,它不与设备绑定。例如,你的手机今天在家获得
192.168.1.100,明天同事来你家用他的手机也可能获得这个地址。
原理¶
2.1.1 点分十进制与二进制转换¶
IPv4 地址是一个 32 位的二进制数,为了便于人类阅读,将其分为 4 组,每组 8 位(1 字节),转换为十进制后用点分隔,称为点分十进制。
graph LR
subgraph Binary["二进制表示 (32位)"]
B1["11000000"] --- B2["10101000"] --- B3["00000010"] --- B4["01001000"]
end
subgraph Decimal["点分十进制"]
D1["192"] --- D2["168"] --- D3["2"] --- D4["72"]
end
B1 --> D1
B2 --> D2
B3 --> D3
B4 --> D4
图解说明:
上图展示了 IP 地址 192.168.2.72 的二进制与十进制转换:
- 每 8 位二进制对应一个十进制数
- 每个十进制数的范围是 0-255
二进制权重计算表:
| 位置 | 第7位 | 第6位 | 第5位 | 第4位 | 第3位 | 第2位 | 第1位 | 第0位 |
|---|---|---|---|---|---|---|---|---|
| 权重 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
| 示例 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| 计算 | 128 | 64 | 0 | 0 | 0 | 0 | 0 | 0 |
结果:128 + 64 = 192
2.1.2 IP 地址分类(有类地址)¶
早期的 IP 地址按照有类方式划分为 A、B、C、D、E 五类:
| 类别 | 首位模式 | 网络位 | 主机位 | 地址范围 | 用途 |
|---|---|---|---|---|---|
| A 类 | 0xxxxxxx | 8 位 | 24 位 | 1.0.0.0 ~ 127.255.255.255 | 大型网络 |
| B 类 | 10xxxxxx | 16 位 | 16 位 | 128.0.0.0 ~ 191.255.255.255 | 中型网络 |
| C 类 | 110xxxxx | 24 位 | 8 位 | 192.0.0.0 ~ 223.255.255.255 | 小型网络 |
| D 类 | 1110xxxx | - | - | 224.0.0.0 ~ 239.255.255.255 | 组播地址 |
| E 类 | 1111xxxx | - | - | 240.0.0.0 ~ 255.255.255.255 | 保留/实验 |
要点解读:
[!NOTE] 有类地址划分已经过时,现代网络普遍使用无类域间路由(CIDR)。但理解有类地址有助于理解网络发展历史和一些遗留配置。
2.1.3 公有地址与私有地址¶
| 类别 | 私有地址范围 | 可用主机数 |
|---|---|---|
| A 类 | 10.0.0.0 ~ 10.255.255.255 | 约 1677 万 |
| B 类 | 172.16.0.0 ~ 172.31.255.255 | 约 104 万 |
| C 类 | 192.168.0.0 ~ 192.168.255.255 | 约 6.5 万 |
要点解读:
- 私有地址只能在内网使用,不能直接访问互联网
- 家庭路由器通常使用
192.168.0.x或192.168.1.x - 大型企业通常使用
10.x.x.x段,因为地址空间更大
2.2 子网划分与 VLSM¶
背景¶
在实际网络规划中,很少严格按照 A/B/C 类来划分网络。VLSM(Variable Length Subnet Mask,可变长子网掩码)允许我们根据实际需求灵活划分子网,这就是无类域间路由(CIDR)的核心思想。
原理¶
2.2.1 网络位与主机位¶
一个 IP 地址由两部分组成:
- 网络位:标识所属网络(固定不变的部分)
- 主机位:标识网络内的具体主机(可变的部分)
graph TD
subgraph IP["IP 地址: 192.168.1.72/24"]
NET["网络位: 192.168.1"] --- HOST["主机位: 72"]
end
subgraph Mask["子网掩码: 255.255.255.0"]
M1["255"] --- M2["255"] --- M3["255"] --- M4["0"]
M1_B["11111111"] --- M2_B["11111111"] --- M3_B["11111111"] --- M4_B["00000000"]
end
NET -.-> |"前 24 位固定"| M1
HOST -.-> |"后 8 位可变"| M4
图解说明:
/24表示前 24 位是网络位(固定),后 8 位是主机位(可变)- 子网掩码
255.255.255.0的二进制表示中,1表示网络位,0表示主机位
2.2.2 VLSM 计算实例¶
问题:如何将一个 /24 网络 192.168.1.0/24 划分为两个子网?
解答:
将掩码从 /24 变为 /25,即多借用 1 位作为网络位:
| 子网 | 网络地址 | 地址范围 | 广播地址 | 可用主机数 |
|---|---|---|---|---|
| 子网1 | 192.168.1.0/25 | 192.168.1.1 ~ 192.168.1.126 | 192.168.1.127 | 126 |
| 子网2 | 192.168.1.128/25 | 192.168.1.129 ~ 192.168.1.254 | 192.168.1.255 | 126 |
计算过程:
/24 掩码:11111111.11111111.11111111.00000000
/25 掩码:11111111.11111111.11111111.10000000
↑
多借 1 位
第 25 位 = 0 → 子网1:主机位范围 0~127(0000000 ~ 1111111)
第 25 位 = 1 → 子网2:主机位范围 128~255(10000000 ~ 11111111)
2.2.3 常用掩码速查表¶
| CIDR | 子网掩码 | 可用主机数 | 常用场景 |
|---|---|---|---|
| /30 | 255.255.255.252 | 2 | 点对点链路(路由器互联) |
| /29 | 255.255.255.248 | 6 | 小型服务器组 |
| /28 | 255.255.255.240 | 14 | 小型 VLAN |
| /27 | 255.255.255.224 | 30 | 小型办公室 |
| /26 | 255.255.255.192 | 62 | 中型 VLAN |
| /25 | 255.255.255.128 | 126 | 大型 VLAN |
| /24 | 255.255.255.0 | 254 | 标准子网 |
| /16 | 255.255.0.0 | 65534 | 大型网络 |
关键点¶
[!IMPORTANT] CNI 中的 /32 掩码
在 Kubernetes CNI 中,Pod IP 经常使用
/32掩码。这意味着:
- 所有 32 位都是网络位,没有主机位
- 每个 Pod IP 都是一个独立的"网络"
- 同节点的两个 Pod 之间也需要通过路由通信,而非二层交换
这是理解 Calico 等 CNI 路由模式的关键!
2.3 MAC 地址详解¶
背景¶
MAC(Media Access Control)地址是数据链路层的物理地址,用于在同一局域网内唯一标识一个网络设备。与 IP 地址不同,MAC 地址通常与网卡硬件绑定,也称为硬件地址或物理地址。
原理¶
2.3.1 MAC 地址结构¶
MAC 地址是一个 48 位(6 字节) 的地址,通常用冒号分十六进制表示:
AA:BB:CC:DD:EE:FF
└──┬──┘ └──┬──┘
OUI 设备标识
(厂商) (序列号)
| 部分 | 位数 | 说明 |
|---|---|---|
| OUI | 前 24 位 | Organizationally Unique Identifier,厂商标识 |
| 设备标识 | 后 24 位 | 由厂商分配的设备序列号 |
常见厂商 OUI:
| OUI 前缀 | 厂商 |
|---|---|
00:50:56 |
VMware |
FA:16:3E |
OpenStack |
52:54:00 |
QEMU/KVM |
00:0C:29 |
VMware |
00:1A:A0 |
Dell |
2.3.2 特殊 MAC 地址¶
| 类型 | 地址 | 说明 |
|---|---|---|
| 广播 MAC | FF:FF:FF:FF:FF:FF |
发送给同一广播域内所有设备 |
| 组播 MAC | 01:xx:xx:xx:xx:xx |
第 8 位为 1,发送给一组设备 |
2.3.3 MAC 地址表与学习机制¶
交换机通过MAC 地址表来转发数据帧:
graph TD
subgraph Switch["交换机 MAC 地址表"]
T[MAC 地址表]
E1["端口1 → AA:BB:CC:11:22:33"]
E2["端口2 → AA:BB:CC:44:55:66"]
E3["端口3 → AA:BB:CC:77:88:99"]
end
P1[主机 A] -->|端口1| Switch
P2[主机 B] -->|端口2| Switch
P3[主机 C] -->|端口3| Switch
图解说明:
交换机的学习过程:
- 收到数据帧后,记录源 MAC 地址与入端口的对应关系
- 查找目的 MAC 地址对应的端口,从该端口转发
- 如果找不到目的 MAC,则泛洪到所有端口(除入端口外)
- 表项有老化时间,长时间无流量会被删除
2.4 二层交换与三层路由¶
背景¶
网络通信的核心问题是:数据包应该发给谁?如何到达目的地?
根据通信双方是否在同一网段,分为:
- 同网段通信:走二层交换(基于 MAC 地址)
- 跨网段通信:走三层路由(基于 IP 地址)
原理¶
graph TD
subgraph Layer2["二层交换(同网段)"]
H1["主机 A<br/>192.168.1.10"] <-->|MAC 直接通信| SW[交换机]
SW <-->|MAC 直接通信| H2["主机 B<br/>192.168.1.20"]
end
subgraph Layer3["三层路由(跨网段)"]
H3["主机 C<br/>192.168.1.10"] -->|经过网关| R[路由器]
R -->|路由转发| H4["主机 D<br/>192.168.2.20"]
end
图解说明:
| 场景 | 判断依据 | 转发方式 | 地址变化 |
|---|---|---|---|
| 同网段 | 目的 IP 与源 IP 在同一子网 | 二层交换 | MAC 不变 |
| 跨网段 | 目的 IP 与源 IP 不在同一子网 | 三层路由 | MAC 每跳改变 |
判断是否同网段的方法¶
已知:
- 主机 A:
192.168.1.2/24 - 主机 B:
192.168.1.130/24 - 主机 C:
192.168.1.130/25
分析:
| 比较 | A 的网段 | B 的网段 | 是否同网段 |
|---|---|---|---|
| A 和 B(/24) | 192.168.1.0 | 192.168.1.0 | ✅ 是 |
| A 和 C(不同掩码) | - | - | 需按各自掩码计算 |
如果 A 使用 /25 掩码:
- A (192.168.1.2/25):网段 192.168.1.0,范围 0~127
- C (192.168.1.130/25):网段 192.168.1.128,范围 128~255
- 结论:A 和 C 不在同一网段!
2.5 ARP 协议与 IP-MAC 映射¶
背景¶
当主机知道目的 IP 但不知道目的 MAC 时,需要通过 ARP(Address Resolution Protocol) 协议来获取 MAC 地址。
原理¶
sequenceDiagram
participant A as 主机 A<br/>192.168.1.10
participant B as 主机 B<br/>192.168.1.20
Note over A: 我要发数据给 192.168.1.20<br/>但不知道它的 MAC 地址
A->>B: ARP Request 广播<br/>谁是 192.168.1.20?
Note over B: 是我!
B->>A: ARP Reply 单播<br/>我是 192.168.1.20<br/>我的 MAC 是 AA:BB:CC:DD:EE:FF
Note over A: 记录到 ARP 缓存表<br/>192.168.1.20 → AA:BB:CC:DD:EE:FF
图解说明:
- 主机 A 发送 ARP 请求广播,询问
192.168.1.20的 MAC 地址 - 主机 B 收到请求后,单播回复自己的 MAC 地址
- 主机 A 将 IP-MAC 映射缓存到 ARP 表,后续通信直接使用
查看 ARP 缓存:
# Linux 查看 ARP 表
ip neigh show
# 或使用传统命令
arp -a
2.6 路由转发过程中的 IP/MAC 变化规律¶
背景¶
这是理解网络转发的最核心知识点。很多从业多年的工程师也不一定能说清楚:在路由转发过程中,哪些地址在变化,哪些不变?
原理¶
核心结论(非 NAT 场景):
| 地址类型 | 是否变化 | 说明 |
|---|---|---|
| 源 IP | 不变 | 始终是发送方的 IP |
| 目的 IP | 不变 | 始终是接收方的 IP |
| 源 MAC | 每跳改变 | 变为上一跳设备的出接口 MAC |
| 目的 MAC | 每跳改变 | 变为下一跳设备的入接口 MAC |
graph LR
subgraph S1["Server 1"]
IP1["IP: 10.1.5.10"]
MAC1["MAC: AA:11"]
end
subgraph R["Router"]
R_E1["eth1<br/>MAC: BB:11"]
R_E2["eth2<br/>MAC: BB:22"]
end
subgraph S2["Server 2"]
IP2["IP: 10.1.8.10"]
MAC2["MAC: CC:11"]
end
S1 -->|1| R
R -->|2| S2
图解说明:
当 Server 1 ping Server 2 时:
| 阶段 | 源 IP | 目的 IP | 源 MAC | 目的 MAC |
|---|---|---|---|---|
| S1 → R (eth1) | 10.1.5.10 | 10.1.8.10 | AA:11 | BB:11 |
| R (eth2) → S2 | 10.1.5.10 | 10.1.8.10 | BB:22 | CC:11 |
要点解读:
[!IMPORTANT] 关键理解:
- IP 地址决定最终目的地,在整个路由过程中始终不变(NAT 除外)
- MAC 地址决定下一跳,每经过一个路由器都会改变
- 发送数据时,目的 MAC 是网关的 MAC,不是最终目标的 MAC
- 这就是为什么同网段走交换(MAC 直达),跨网段走路由(MAC 逐跳变化)
实验验证¶
步骤一:创建测试环境(使用 Containerlab)
name: routing-lab
topology:
nodes:
router:
kind: linux
image: vyos/vyos:1.2.8
server1:
kind: linux
server2:
kind: linux
links:
- endpoints: ["router:eth1", "server1:net0"]
- endpoints: ["router:eth2", "server2:net0"]
步骤二:在 Server 2 上抓包
# 在 Server 2 的 net0 接口抓包
tcpdump -i net0 -e -nn icmp
# 输出示例:
# 12:00:00.001 BB:22 > CC:11, 10.1.5.10 > 10.1.8.10: ICMP echo request
# 12:00:00.002 CC:11 > BB:22, 10.1.8.10 > 10.1.5.10: ICMP echo reply
步骤三:分析结果
- 源 MAC(BB:22):是路由器 eth2 的 MAC,不是 Server 1 的 MAC
- 源 IP(10.1.5.10):仍然是 Server 1 的 IP,没有变化
TTL 值的变化¶
TTL(Time To Live):数据包的生存时间,每经过一个路由器减 1。
| 场景 | TTL 变化 | 说明 |
|---|---|---|
| 二层交换 | 不变 | 不经过路由器 |
| 三层路由 | 减 1 | 每跳减 1 |
作用:防止数据包在网络中无限循环。当 TTL 减为 0 时,路由器丢弃该数据包。
# 通过 TTL 判断是否经过路由
ping 10.1.8.10
# 同网段:TTL = 64(Linux 默认)
# 跨一跳:TTL = 63
# 跨两跳:TTL = 62
2.7 使用 Containerlab 进行网络实验¶
背景¶
Containerlab 是一个强大的容器化网络实验工具,可以快速搭建包含路由器、交换机、主机的复杂网络拓扑,非常适合学习和验证网络知识。
实验:二层交换 vs 三层路由¶
二层交换拓扑:
name: layer2-lab
topology:
nodes:
bridge:
kind: bridge
server1:
kind: linux
exec:
- ip addr add 10.1.5.10/24 dev net0
server2:
kind: linux
exec:
- ip addr add 10.1.5.11/24 dev net0
links:
- endpoints: ["bridge:eth1", "server1:net0"]
- endpoints: ["bridge:eth2", "server2:net0"]
验证:
# 在 server1 ping server2
ping 10.1.5.11
# TTL = 64,说明没有经过路由器,走的二层交换
三层路由拓扑:
name: layer3-lab
topology:
nodes:
router:
kind: linux
image: vyos/vyos:1.2.8
server1:
kind: linux
exec:
- ip addr add 10.1.5.10/24 dev net0
- ip route add default via 10.1.5.1
server2:
kind: linux
exec:
- ip addr add 10.1.8.10/24 dev net0
- ip route add default via 10.1.8.1
links:
- endpoints: ["router:eth1", "server1:net0"]
- endpoints: ["router:eth2", "server2:net0"]
验证:
# 在 server1 ping server2
ping 10.1.8.10
# TTL = 63,说明经过了一跳路由器
📝 章节小结¶
本章深入讲解了 IP 地址和 MAC 地址的核心知识:
- IP 地址基础:点分十进制、二进制转换、有类地址分类
- VLSM 子网划分:掌握网络位/主机位计算、快速确定地址范围
- MAC 地址结构:48 位硬件地址、OUI 厂商标识
- 二层交换 vs 三层路由:
- 同网段走交换,MAC 不变
- 跨网段走路由,MAC 每跳改变
- 路由转发中的地址变化:
- IP 不变(NAT 除外)
- MAC 每跳改变
[!TIP] 学习建议:
- 熟练掌握 VLSM 计算,这是网络工程师的基本功
- 使用 Containerlab 动手搭建实验环境
- 通过抓包验证 IP/MAC 地址的变化规律
- 理解
/32掩码在 CNI 中的应用场景[!CAUTION] 常见误区:
- ❌ 认为目的 MAC 就是目标主机的 MAC
✅ 目的 MAC 是下一跳设备的 MAC(可能是网关)
❌ 认为 IP 地址在路由过程中会变化
- ✅ 在非 NAT 场景下,IP 地址始终不变
第3章 VETH 虚拟网络设备¶
🎯 学习目标¶
- 理解 VETH Pair 的概念与工作原理
- 掌握 Linux 网络命名空间(Network Namespace)的基本操作
- 学会手工创建和配置 VETH Pair
- 理解 Linux Bridge 与 VETH 的结合使用
- 掌握 VETH 在容器网络(CNI)中的核心应用
3.1 VETH Pair 概念与原理¶
背景¶
在容器网络中,每个容器(Pod)都运行在独立的网络命名空间(Network Namespace)中,与宿主机的网络隔离。那么,容器如何与外界通信呢?
答案是:VETH Pair(虚拟以太网设备对)。
原理¶
VETH Pair 是 Linux 内核提供的一种虚拟网络设备,它总是成对出现,就像一根虚拟的网线,一端插在容器内,一端插在宿主机上。
graph LR
subgraph NS1["容器网络命名空间"]
V1["veth0<br/>10.1.5.10"]
end
subgraph ROOT["宿主机 Root Namespace"]
V2["veth1"]
end
V1 ---|"VETH Pair<br/>虚拟网线"| V2
图解说明:
- VETH Pair 由两个虚拟网卡组成,它们总是成对创建
- 从一端发送的数据包,会立即出现在另一端
- 通过 VETH Pair,可以实现跨网络命名空间的通信
核心特性¶
| 特性 | 说明 |
|---|---|
| 成对出现 | 一个 VETH 设备必定有一个 Peer 设备 |
| 双向通信 | 从一端发送的包会从另一端收到 |
| 跨命名空间 | 两端可以分别位于不同的网络命名空间 |
| 即时传输 | 数据包在内核中直接传递,无需经过物理网络 |
[!NOTE] 形象理解:VETH Pair 就像一根虚拟网线,把容器内部和宿主机连接起来。从网线一端发送的数据,必然从另一端出来。
3.2 VETH 在容器网络中的应用¶
背景¶
在 Kubernetes 中,每个 Pod 都有自己独立的网络命名空间。Pod 内的应用要与外界通信,必须先将流量从 Pod 的网络命名空间"引出"到宿主机的 Root Namespace,然后才能进行后续的路由或转发。
原理¶
graph TD
subgraph Host["宿主机 (Root Namespace)"]
direction TB
ETH0["eth0<br/>物理网卡"]
ROUTING["路由/转发"]
V2["veth-host"]
ETH0 --- ROUTING --- V2
end
subgraph Pod["Pod 网络命名空间"]
V1["eth0<br/>10.244.1.10/32"]
end
V1 ---|"VETH Pair"| V2
ROUTING -->|"南北向流量<br/>SNAT"| INTERNET["外部网络"]
ROUTING -->|"东西向流量<br/>Pod to Pod"| OTHER["其他节点"]
图解说明:
- Pod 网络命名空间:Pod 内的 eth0 是 VETH Pair 的一端
- 宿主机 Root Namespace:VETH Pair 的另一端在宿主机上
- 流量转发:
- 南北向流量(Pod ↔ 外网):从 VETH 到宿主机,经过 SNAT 后发出
- 东西向流量(Pod ↔ Pod):从 VETH 到宿主机,根据路由转发到目标节点
CNI 实现模式¶
不同的 CNI 插件使用 VETH 的方式略有不同:
| CNI | VETH 使用方式 | 说明 |
|---|---|---|
| Flannel | VETH + Bridge | Pod 的 VETH 插入到 cni0 网桥 |
| Calico | VETH + Routing | Pod 的 VETH 直接路由,使用 /32 掩码 |
| Cilium | VETH + eBPF | 使用 eBPF 加速,可绕过部分内核协议栈 |
3.3 手工创建 VETH Pair¶
背景¶
理解 VETH Pair 的最好方式是亲手创建一个。通过手工操作,可以深入理解容器网络的底层实现。
实现步骤¶
步骤一:创建网络命名空间¶
# 创建两个网络命名空间
ip netns add ns1
ip netns add ns2
# 查看已创建的命名空间
ip netns list
代码说明:
ip netns add:创建网络命名空间ip netns list:列出所有网络命名空间
步骤二:创建 VETH Pair¶
# 创建 VETH Pair:veth01 和 veth10
ip link add veth01 type veth peer name veth10
# 查看创建的设备
ip link show type veth
代码说明:
ip link add ... type veth peer name ...:创建一对 VETH 设备- 此时两个设备都在 Root Namespace 中
步骤三:分配到不同命名空间¶
# 将 veth01 移动到 ns1
ip link set veth01 netns ns1
# 将 veth10 移动到 ns2
ip link set veth10 netns ns2
步骤四:配置 IP 地址并启用¶
# 在 ns1 中配置 IP 并启用接口
ip netns exec ns1 ip addr add 10.1.5.10/24 dev veth01
ip netns exec ns1 ip link set veth01 up
ip netns exec ns1 ip link set lo up
# 在 ns2 中配置 IP 并启用接口
ip netns exec ns2 ip addr add 10.1.5.11/24 dev veth10
ip netns exec ns2 ip link set veth10 up
ip netns exec ns2 ip link set lo up
步骤五:测试连通性¶
# 从 ns1 ping ns2
ip netns exec ns1 ping 10.1.5.11
# 应该能够 ping 通,输出类似:
# PING 10.1.5.11 (10.1.5.11) 56(84) bytes of data.
# 64 bytes from 10.1.5.11: icmp_seq=1 ttl=64 time=0.050 ms
步骤六:查看 VETH Pair 关系¶
# 在 ns1 中查看 veth01 的 peer index
ip netns exec ns1 ethtool -S veth01
# 输出示例:
# NIC statistics:
# peer_ifindex: 12 # peer 的接口索引
# 在 ns2 中查看 veth10 的接口索引
ip netns exec ns2 ip link show veth10
# 应该显示接口索引为 12
清理环境¶
# 删除命名空间(会自动删除其中的 VETH 设备)
ip netns del ns1
ip netns del ns2
3.4 Linux Bridge 与 VETH¶
背景¶
在 Flannel 等 CNI 插件中,同一节点上的多个 Pod 需要互相通信。如果每对 Pod 之间都创建一个 VETH Pair,网络结构会非常复杂。
解决方案:引入 Linux Bridge(网桥),让所有 Pod 的 VETH 都"插"在同一个网桥上。
原理¶
graph TD
subgraph Host["宿主机"]
BRIDGE["Linux Bridge (cni0)"]
ETH0["eth0<br/>物理网卡"]
V1H["veth-pod1"]
V2H["veth-pod2"]
V3H["veth-pod3"]
V1H --> BRIDGE
V2H --> BRIDGE
V3H --> BRIDGE
BRIDGE --> ETH0
end
subgraph Pod1["Pod 1"]
V1P["eth0"]
end
subgraph Pod2["Pod 2"]
V2P["eth0"]
end
subgraph Pod3["Pod 3"]
V3P["eth0"]
end
V1P ---|VETH| V1H
V2P ---|VETH| V2H
V3P ---|VETH| V3H
图解说明:
- 每个 Pod 的 VETH 一端在 Pod 内(eth0),另一端插在宿主机的 cni0 网桥上
- 同节点的 Pod 之间通信:通过 cni0 网桥的二层交换完成
- 跨节点的 Pod 通信:从网桥经路由转发到物理网卡
手工实现 Bridge + VETH¶
# 1. 创建两个命名空间
ip netns add ns1
ip netns add ns2
# 2. 创建 Linux Bridge
ip link add br0 type bridge
ip link set br0 up
# 3. 创建两对 VETH
ip link add int0 type veth peer name br-int0
ip link add int1 type veth peer name br-int1
# 4. 将 VETH 一端移入命名空间
ip link set int0 netns ns1
ip link set int1 netns ns2
# 5. 将 VETH 另一端插入 Bridge
ip link set br-int0 master br0
ip link set br-int1 master br0
ip link set br-int0 up
ip link set br-int1 up
# 6. 配置命名空间内的 IP
ip netns exec ns1 ip addr add 10.1.5.10/24 dev int0
ip netns exec ns1 ip link set int0 up
ip netns exec ns1 ip link set lo up
ip netns exec ns2 ip addr add 10.1.5.11/24 dev int1
ip netns exec ns2 ip link set int1 up
ip netns exec ns2 ip link set lo up
# 7. 测试连通性
ip netns exec ns1 ping 10.1.5.11
代码说明:
ip link set ... master br0:将接口插入网桥,相当于把网线插入交换机- 插入网桥后,该接口会显示
master br0标识
查看 Bridge 状态¶
# 查看所有网桥
brctl show
# 输出示例:
# bridge name bridge id STP enabled interfaces
# br0 8000.aabbccdd1122 no br-int0
# br-int1
# 查看网桥的 MAC 地址表
brctl showmacs br0
3.5 VETH 在不同 CNI 中的应用¶
背景¶
不同的 CNI 插件虽然都使用 VETH,但实现方式和网络模型有明显区别。
Flannel 的 Bridge 模式¶
graph TD
subgraph Node["Kubernetes 节点"]
CNI0["cni0 网桥<br/>10.244.1.1/24"]
FLANNEL["flannel.1<br/>VxLAN 设备"]
ETH0["eth0"]
V1["veth-pod1"] --> CNI0
V2["veth-pod2"] --> CNI0
CNI0 --> FLANNEL
FLANNEL --> ETH0
end
subgraph Pod1["Pod 1<br/>10.244.1.10"]
E1["eth0"]
end
subgraph Pod2["Pod 2<br/>10.244.1.11"]
E2["eth0"]
end
E1 ---|VETH| V1
E2 ---|VETH| V2
要点解读:
| 特性 | Flannel Bridge 模式 |
|---|---|
| Pod 掩码 | /24(同节点 Pod 同网段) |
| 同节点通信 | 通过 cni0 网桥二层交换 |
| 跨节点通信 | 通过 flannel.1 VxLAN 封装 |
| 优点 | 配置简单,同节点通信高效 |
| 缺点 | 二层广播域可能导致广播风暴 |
Calico 的路由模式¶
graph TD
subgraph Node["Kubernetes 节点"]
ROUTING["路由表"]
ETH0["eth0"]
V1["cali-xxx1"] --> ROUTING
V2["cali-xxx2"] --> ROUTING
ROUTING --> ETH0
end
subgraph Pod1["Pod 1<br/>10.244.1.10/32"]
E1["eth0"]
end
subgraph Pod2["Pod 2<br/>10.244.1.11/32"]
E2["eth0"]
end
E1 ---|VETH| V1
E2 ---|VETH| V2
要点解读:
| 特性 | Calico 路由模式 |
|---|---|
| Pod 掩码 | /32(每个 Pod 独立网络) |
| 同节点通信 | 通过路由表三层转发 |
| 跨节点通信 | BGP 协议或 IPIP/VxLAN 封装 |
| 优点 | 无广播风暴,支持网络策略 |
| 缺点 | 需要维护路由表,配置较复杂 |
[!IMPORTANT] Calico 使用 /32 掩码的关键意义:
- 使用 /32 掩码意味着没有任何 IP 与该 Pod 同网段
- 因此同节点的 Pod 之间也必须走路由,而非二层交换
- 这使得 Calico 可以在路由层面实施网络策略
3.6 使用 Containerlab 模拟 VETH 网络¶
简单 VETH 拓扑¶
name: veth-demo
topology:
nodes:
server1:
kind: linux
exec:
- ip addr add 10.1.5.10/24 dev net0
server2:
kind: linux
exec:
- ip addr add 10.1.5.11/24 dev net0
links:
- endpoints: ["server1:net0", "server2:net0"]
代码说明:
links定义的连接会自动创建 VETH Pair- server1 的 net0 和 server2 的 net0 通过 VETH 直连
Bridge + VETH 拓扑¶
name: bridge-veth-demo
topology:
nodes:
bridge:
kind: bridge
server1:
kind: linux
exec:
- ip addr add 10.1.5.10/24 dev net0
server2:
kind: linux
exec:
- ip addr add 10.1.5.11/24 dev net0
links:
- endpoints: ["bridge:eth1", "server1:net0"]
- endpoints: ["bridge:eth2", "server2:net0"]
代码说明:
kind: bridge创建一个 Linux Bridge- server1 和 server2 都通过 VETH 连接到这个 Bridge
- 可以使用
brctl show查看 Bridge 上的接口
部署与验证¶
# 部署拓扑
clab deploy -t veth-demo.yaml
# 进入容器测试
docker exec -it clab-veth-demo-server1 ping 10.1.5.11
# 查看 VETH Pair 关系
docker exec -it clab-veth-demo-server1 ethtool -S net0
# 清理环境
clab destroy -t veth-demo.yaml
3.7 常见问题与排障¶
问题1:ping 自己不通¶
现象:在命名空间内 ping 自己的 IP 地址不通
原因:loopback 接口(lo)未启用
解决:
ip netns exec ns1 ip link set lo up
[!TIP] 创建网络命名空间后,记得启用 lo 接口。虽然 ping 自己通常不经过 lo,但某些协议栈处理需要 lo 处于 UP 状态。
问题2:如何判断两个接口是否为 VETH Pair¶
方法:使用 ethtool -S 查看 peer_ifindex
# 查看 veth0 的 peer 接口索引
ethtool -S veth0 | grep peer_ifindex
# 对比另一端的接口索引
ip link show | grep "12:" # 假设 peer_ifindex 是 12
问题3:如何查看接口属于哪个 Bridge¶
方法:查看接口的 master 属性
ip link show veth0
# 输出中如果包含 master br0,说明该接口插在 br0 网桥上
# 例如:5: veth0@eth0: <BROADCAST,MULTICAST,UP> ... master br0 state UP
📝 章节小结¶
本章深入讲解了 VETH 虚拟网络设备:
- VETH Pair 概念:成对出现的虚拟网卡,用于跨网络命名空间通信
- CNI 中的应用:所有主流 CNI 都使用 VETH 将 Pod 流量引入宿主机
- 手工创建 VETH:掌握
ip link add ... type veth peer name ...命令 - Linux Bridge:多个 VETH 可以插入同一个 Bridge,实现二层交换
- CNI 实现差异:
- Flannel:VETH + Bridge,同节点走二层
- Calico:VETH + Routing(/32 掩码),同节点也走三层
[!TIP] 学习建议:
- 动手执行手工创建 VETH 的全部步骤
- 使用 Containerlab 快速搭建各种 VETH 拓扑
- 对比 Flannel 和 Calico 的 VETH 使用方式
- 理解 "VETH 是容器网络的基石" 这一核心概念
第4章 Host-Gateway 网络模式¶
🎯 学习目标¶
- 理解 Host-Gateway(主机网关)模式的核心概念
- 掌握静态路由在容器网络中的应用
- 学会使用 Containerlab 模拟 Host-GW 网络
- 掌握手工配置 Host-GW 模式的完整步骤
- 理解 Host-GW 与 Overlay 网络的优劣对比
4.1 Host-Gateway 概念与原理¶
背景¶
在容器网络中,Pod 需要与其他节点上的 Pod 通信。实现跨节点通信有两种主要方式:
- Overlay 网络:将容器网络封装在底层网络之上(如 VxLAN、IPIP)
- Host-Gateway:利用主机的路由功能直接转发容器流量
Host-Gateway(主机网关)是最简单、性能最高的容器网络模式之一。
原理¶
Host-Gateway 的核心思想:把宿主机当作 Pod 的网关。
graph TD
subgraph Node1["节点1 (192.168.2.71)"]
GW1["cni0 网桥<br/>10.244.1.1"]
P1["Pod1<br/>10.244.1.10"]
P1 -->|"默认网关"| GW1
end
subgraph Node2["节点2 (192.168.2.73)"]
GW2["cni0 网桥<br/>10.244.2.1"]
P2["Pod2<br/>10.244.2.10"]
P2 -->|"默认网关"| GW2
end
GW1 -->|"路由: 10.244.2.0/24 via 192.168.2.73"| GW2
GW2 -->|"路由: 10.244.1.0/24 via 192.168.2.71"| GW1
图解说明:
- Pod1 发送数据包到 Pod2(目的地址 10.244.2.10)
- Pod1 的默认网关是 cni0 网桥(10.244.1.1)
- 节点1 查询路由表:去往 10.244.2.0/24 的下一跳是 192.168.2.73
- 数据包发送到节点2,节点2 将其转发给 Pod2
关键理解:
[!NOTE] Host-Gateway = 把主机当网关
就像你家的无线路由器是你手机的网关一样,Pod 把宿主机当作自己的网关。所有出站流量都先发给宿主机,由宿主机负责路由转发。
4.2 Host-GW 与 Overlay 网络对比¶
原理对比¶
graph LR
subgraph HostGW["Host-Gateway 模式"]
H1["节点1"] -->|"直接路由<br/>原始包"| H2["节点2"]
end
subgraph Overlay["Overlay 模式 (VxLAN)"]
O1["节点1"] -->|"封装<br/>外层包+内层包"| O2["节点2"]
end
图解说明:
| 特性 | Host-Gateway | Overlay (VxLAN/IPIP) |
|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐ 有封装开销 |
| MTU | 无损耗(1500) | 有损耗(1450左右) |
| 配置复杂度 | 简单 | 中等 |
| 对底层网络要求 | 节点必须二层可达 | 节点只需三层可达 |
| Pod IP 暴露 | 暴露在物理网络 | 封装隐藏 |
| 跨子网 | ❌ 不支持 | ✅ 支持 |
适用场景¶
| 场景 | 推荐模式 |
|---|---|
| 节点在同一二层网络 | Host-Gateway(性能最优) |
| 节点跨子网/跨机房 | Overlay(必须封装) |
| 对性能敏感的业务 | Host-Gateway |
| 多云/混合云环境 | Overlay |
[!IMPORTANT] Host-GW 的限制:
节点之间必须二层可达(在同一个广播域)。如果节点跨子网,数据包无法直接路由,必须使用 Overlay 封装。
4.3 使用 Containerlab 实现 Host-GW¶
背景¶
Containerlab 可以快速搭建包含路由器和主机的网络拓扑,非常适合理解 Host-GW 的工作原理。
网络拓扑设计¶
graph TD
subgraph Topology["Host-GW 实验拓扑"]
GW0["GW0 路由器<br/>10.1.5.1"]
GW1["GW1 路由器<br/>10.1.8.1"]
S1["Server1<br/>10.1.5.10"]
S2["Server2<br/>10.1.8.10"]
S1 -->|"网关"| GW0
S2 -->|"网关"| GW1
GW0 <-->|"172.12.1.x"| GW1
end
图解说明:
- Server1 的网关是 GW0(10.1.5.1)
- Server2 的网关是 GW1(10.1.8.1)
- GW0 和 GW1 通过 172.12.1.x 互联
- GW0 需要知道如何到达 10.1.8.0/24,GW1 需要知道如何到达 10.1.5.0/24
Containerlab 配置文件¶
name: host-gw-lab
topology:
nodes:
# 路由器 GW0
gw0:
kind: linux
image: vyos/vyos:1.2.8
startup-config: gw0.cfg
# 路由器 GW1
gw1:
kind: linux
image: vyos/vyos:1.2.8
startup-config: gw1.cfg
# 服务器 Server1
server1:
kind: linux
exec:
- ip addr add 10.1.5.10/24 dev net0
- ip route add default via 10.1.5.1
# 服务器 Server2
server2:
kind: linux
exec:
- ip addr add 10.1.8.10/24 dev net0
- ip route add default via 10.1.8.1
links:
# Server1 连接 GW0
- endpoints: ["gw0:eth1", "server1:net0"]
# Server2 连接 GW1
- endpoints: ["gw1:eth1", "server2:net0"]
# GW0 和 GW1 互联
- endpoints: ["gw0:eth2", "gw1:eth2"]
GW0 路由配置 (gw0.cfg)¶
# 接口配置
set interfaces ethernet eth1 address 10.1.5.1/24
set interfaces ethernet eth2 address 172.12.1.10/24
# 静态路由:去往 10.1.8.0/24 的下一跳是 GW1
set protocols static route 10.1.8.0/24 next-hop 172.12.1.11
GW1 路由配置 (gw1.cfg)¶
# 接口配置
set interfaces ethernet eth1 address 10.1.8.1/24
set interfaces ethernet eth2 address 172.12.1.11/24
# 静态路由:去往 10.1.5.0/24 的下一跳是 GW0
set protocols static route 10.1.5.0/24 next-hop 172.12.1.10
验证连通性¶
# 部署拓扑
clab deploy -t host-gw-lab.yaml
# 从 Server1 ping Server2
docker exec -it clab-host-gw-lab-server1 ping 10.1.8.10
# 观察 TTL 变化
# TTL = 62(经过两个路由器,从 64 减 2)
# 清理
clab destroy -t host-gw-lab.yaml
4.4 手工实现 Host-GW 模式¶
背景¶
理解 Host-GW 最好的方式是手工实现一个完整的配置,模拟 Flannel 的 Host-GW 模式。
实验拓扑¶
graph TD
subgraph BPF71["节点 192.168.2.71"]
BR0_1["br0 网桥<br/>10.1.5.1"]
NS1["ns1 命名空间<br/>10.1.5.10"]
NS1 --> BR0_1
end
subgraph BPF73["节点 192.168.2.73"]
BR0_2["br0 网桥<br/>10.1.8.1"]
NS2["ns2 命名空间<br/>10.1.8.10"]
NS2 --> BR0_2
end
BR0_1 <-->|"路由"| BR0_2
实现步骤(节点1: 192.168.2.71)¶
步骤一:创建网络基础设施¶
# 创建网络命名空间(模拟 Pod)
ip netns add ns1
# 创建 Linux Bridge(模拟 cni0 网桥)
ip link add br0 type bridge
ip link set br0 up
# 创建 VETH Pair
ip link add int0 type veth peer name br-int0
步骤二:配置命名空间内的网络¶
# 将 VETH 一端移入命名空间
ip link set int0 netns ns1
# 配置命名空间内的接口
ip netns exec ns1 ip addr add 10.1.5.10/24 dev int0
ip netns exec ns1 ip link set int0 up
ip netns exec ns1 ip link set lo up
# 配置默认路由(指向网关)
ip netns exec ns1 ip route add default via 10.1.5.1
步骤三:配置宿主机网桥¶
# 将 VETH 另一端插入网桥
ip link set br-int0 master br0
ip link set br-int0 up
# 给网桥配置 IP(作为 Pod 的网关)
ip addr add 10.1.5.1/24 dev br0
步骤四:添加跨节点路由¶
# 添加静态路由:去往 10.1.8.0/24 的下一跳是节点2
ip route add 10.1.8.0/24 via 192.168.2.73 dev ens160
实现步骤(节点2: 192.168.2.73)¶
# 创建网络命名空间
ip netns add ns2
# 创建网桥
ip link add br0 type bridge
ip link set br0 up
# 创建 VETH Pair 并配置
ip link add int0 type veth peer name br-int0
ip link set int0 netns ns2
# 配置命名空间
ip netns exec ns2 ip addr add 10.1.8.10/24 dev int0
ip netns exec ns2 ip link set int0 up
ip netns exec ns2 ip link set lo up
ip netns exec ns2 ip route add default via 10.1.8.1
# 配置网桥
ip link set br-int0 master br0
ip link set br-int0 up
ip addr add 10.1.8.1/24 dev br0
# 添加跨节点路由
ip route add 10.1.5.0/24 via 192.168.2.71 dev ens160
验证测试¶
# 从节点1的 ns1 ping 节点2的 ns2
ip netns exec ns1 ping 10.1.8.10
# 应该能够 ping 通,TTL = 62(经过两跳)
查看路由表¶
# 节点1 路由表
ip route show
# 输出类似:
# 10.1.5.0/24 dev br0 proto kernel scope link src 10.1.5.1
# 10.1.8.0/24 via 192.168.2.73 dev ens160 # Host-GW 关键路由
代码说明:
10.1.8.0/24 via 192.168.2.73:这就是 Host-GW 的核心路由- 去往 Pod 网段(10.1.8.0/24)的流量,下一跳是对端节点(192.168.2.73)
- 这与 Flannel Host-GW 模式自动创建的路由完全一致
4.5 Host-GW 在 Flannel 中的实现¶
背景¶
Flannel 是 Kubernetes 中最常用的 CNI 插件之一。它支持多种后端模式,其中 Host-GW 是性能最高的模式。
Flannel Host-GW 架构¶
graph TD
subgraph Node1["节点1"]
CNI0_1["cni0<br/>10.244.1.1/24"]
FLANNELD1["flanneld"]
P1["Pod<br/>10.244.1.x"]
P1 --> CNI0_1
FLANNELD1 -.->|"监听 etcd<br/>更新路由"| CNI0_1
end
subgraph Node2["节点2"]
CNI0_2["cni0<br/>10.244.2.1/24"]
FLANNELD2["flanneld"]
P2["Pod<br/>10.244.2.x"]
P2 --> CNI0_2
FLANNELD2 -.->|"监听 etcd<br/>更新路由"| CNI0_2
end
FLANNELD1 <-->|"etcd 同步"| FLANNELD2
CNI0_1 <-->|"静态路由"| CNI0_2
图解说明:
- flanneld 进程监听 etcd 中的网络配置
- 当新节点加入或 Pod 网段变化时,flanneld 自动更新路由表
- 每个节点的路由表包含所有其他节点的 Pod 网段路由
Flannel Host-GW 配置¶
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
查看 Flannel 创建的路由¶
# 在 Kubernetes 节点上查看路由表
ip route show | grep 10.244
# 输出示例(假设有三个节点):
# 10.244.0.0/24 via 192.168.1.10 dev eth0 # 节点1 的 Pod 网段
# 10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 # 本节点
# 10.244.2.0/24 via 192.168.1.12 dev eth0 # 节点3 的 Pod 网段
要点解读:
[!IMPORTANT] Flannel Host-GW 的路由特征:
- 本节点的 Pod 网段:
dev cni0(直连)- 其他节点的 Pod 网段:
via <节点IP>(通过节点路由)- flanneld 自动维护这些路由,无需手工配置
4.6 Next Hop 的核心作用¶
背景¶
在 Host-GW 模式中,Next Hop(下一跳)是最关键的概念。理解 Next Hop 对于排障和网络设计至关重要。
原理¶
sequenceDiagram
participant Pod1 as Pod1<br/>10.244.1.10
participant Node1 as 节点1<br/>192.168.2.71
participant Node2 as 节点2<br/>192.168.2.73
participant Pod2 as Pod2<br/>10.244.2.10
Pod1->>Node1: 目的: 10.244.2.10<br/>下一跳: 10.244.1.1 (网关)
Note over Node1: 查路由表:<br/>10.244.2.0/24 via 192.168.2.73
Node1->>Node2: 目的: 10.244.2.10<br/>下一跳: 192.168.2.73
Note over Node2: 查路由表:<br/>10.244.2.0/24 dev cni0
Node2->>Pod2: 目的: 10.244.2.10<br/>直接投递
图解说明:
- Pod1 → Node1:Pod1 把所有出站流量发给默认网关
- Node1 查路由表:发现去往 10.244.2.0/24 的下一跳是 192.168.2.73
- Node1 → Node2:数据包发送到 Node2
- Node2 → Pod2:Node2 发现目的地址在本地 cni0,直接投递
关键点¶
| 概念 | 说明 |
|---|---|
| 默认网关 | Pod 的"下一跳",通常是 cni0 网桥 |
| 静态路由 | 节点路由表中的跨节点路由 |
| Next Hop | 静态路由指定的下一跳地址 |
| 出接口 | 发送数据包的网卡接口 |
# 完整的路由条目格式
ip route add 10.244.2.0/24 via 192.168.2.73 dev eth0
# ↑目的网段 ↑下一跳地址 ↑出接口
[!CAUTION] Next Hop 必须可达:
如果 Next Hop 地址不可达(如跨子网),Host-GW 模式将无法工作。此时必须使用 Overlay 模式。
4.7 常见问题与排障¶
问题1:跨节点 Pod 通信失败¶
检查步骤:
# 1. 检查路由表是否正确
ip route show | grep 10.244
# 2. 检查下一跳是否可达
ping <下一跳IP>
# 3. 检查是否开启 IP 转发
cat /proc/sys/net/ipv4/ip_forward
# 如果是 0,需要开启:
echo 1 > /proc/sys/net/ipv4/ip_forward
问题2:Host-GW 模式下节点跨子网¶
现象:节点在不同子网,Host-GW 无法工作
原因:Next Hop 不可达,ARP 无法解析
解决方案:改用 Overlay 模式(VxLAN 或 IPIP)
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
问题3:路由条目丢失¶
现象:flanneld 重启后路由丢失
检查:
# 检查 flanneld 是否正常运行
kubectl get pods -n kube-flannel
# 查看 flanneld 日志
kubectl logs -n kube-flannel <flannel-pod-name>
📝 章节小结¶
本章深入讲解了 Host-Gateway 网络模式:
- 核心概念:把宿主机当作 Pod 的网关,利用路由转发容器流量
- 工作原理:Pod → 默认网关 → 查路由表 → 下一跳 → 目标节点
- 优势:性能最高、配置简单、无封装开销
- 限制:节点必须二层可达(同一广播域)
- 实现方式:
- Flannel Host-GW 模式
- Calico BGP 模式(类似原理)
- 手工静态路由配置
[!TIP] 学习建议:
- 使用 Containerlab 搭建 Host-GW 实验环境
- 手工配置一次完整的 Host-GW 网络
- 在真实 Kubernetes 集群中查看 Flannel 路由表
- 理解 "Next Hop 必须可达" 这一核心约束
[!IMPORTANT] 选择 Host-GW 还是 Overlay?
- 同一二层网络 → 优先选择 Host-GW(性能更好)
- 跨子网/跨机房 → 必须使用 Overlay(VxLAN/IPIP)
- 混合场景 → 可以使用 Calico 的 ipip: CrossSubnet 模式
第5章 CNI 网络模型¶
🎯 学习目标¶
- 理解 CNI 网络的四大分类:Overlay、Underlay、Routing、高性能
- 掌握 Pod 数据包在宿主机上的转发路径
- 理解网卡复用技术(IPVLAN、MACVLAN)的原理与应用
- 了解高性能网络(SR-IOV、DPDK、VPP)的核心概念
- 掌握 Cilium eBPF 的优化原理
5.1 CNI 网络模型概述¶
背景¶
在 Kubernetes 中,CNI(Container Network Interface)负责为 Pod 配置网络。不同的 CNI 插件采用不同的网络模型,各有优劣。
四大网络模型¶
graph TD
CNI["CNI 网络模型"] --> Overlay["Overlay 网络<br/>叠加网络"]
CNI --> Underlay["Underlay 网络<br/>底层网络"]
CNI --> Routing["Routing 网络<br/>路由网络"]
CNI --> HighPerf["高性能网络<br/>内核 Bypass"]
Overlay --> VxLAN["VxLAN"]
Overlay --> IPIP["IPIP"]
Overlay --> GRE["GRE"]
Underlay --> IPVLAN["IPVLAN"]
Underlay --> MACVLAN["MACVLAN"]
Routing --> HostGW["Host-GW"]
Routing --> BGP["BGP"]
HighPerf --> SRIOV["SR-IOV"]
HighPerf --> DPDK["DPDK"]
HighPerf --> eBPF["eBPF"]
图解说明:
| 类型 | 核心原理 | 代表性 CNI/技术 |
|---|---|---|
| Overlay | 封装隧道,Pod IP 隐藏 | Flannel VxLAN、Calico IPIP |
| Underlay | 网卡复用,Pod 与宿主机同平面 | IPVLAN、MACVLAN |
| Routing | 路由转发,Pod IP 暴露 | Host-GW、Calico BGP |
| 高性能 | 绕过内核协议栈 | SR-IOV、DPDK、Cilium eBPF |
5.2 Pod 数据包转发路径¶
背景¶
理解 Pod 数据包在宿主机上的转发路径,是掌握 CNI 网络模型的关键。
原理¶
graph TD
subgraph Pod["Pod 网络命名空间"]
APP["应用进程"]
STACK1["协议栈处理<br/>TCP/IP"]
ETH0["eth0 (VETH)"]
APP --> STACK1 --> ETH0
end
subgraph Host["宿主机 Root Namespace"]
VETH["veth-xxx"]
BRIDGE["cni0 网桥"]
STACK2["协议栈处理<br/>iptables/路由"]
PHYS["物理网卡 eth0"]
VETH --> BRIDGE --> STACK2 --> PHYS
end
ETH0 ---|"VETH Pair"| VETH
PHYS --> EXT["外部网络"]
图解说明:
- Pod 内部协议栈:应用发送数据包,经过 Pod 自己的 TCP/IP 协议栈
- VETH Pair 传输:数据包通过 VETH Pair 传到宿主机
- 宿主机协议栈:再次经过宿主机的协议栈(iptables、路由查询等)
- 物理网卡发出:最终从物理网卡发送到外部网络
[!IMPORTANT] 关键理解:Pod 发送一个数据包,需要经过两次协议栈处理:
- Pod 自己的协议栈(一次)
- 宿主机的协议栈(一次)
这是容器网络相比传统网络有性能损耗的主要原因。
5.3 Overlay 叠加网络¶
背景¶
Overlay 网络通过隧道封装技术,将 Pod 网络"叠加"在物理网络之上,使 Pod IP 对外部网络不可见。
原理¶
graph LR
subgraph Node1["节点1"]
P1["Pod1<br/>10.244.1.10"]
FLANNEL1["flannel.1<br/>VxLAN 封装"]
end
subgraph Node2["节点2"]
P2["Pod2<br/>10.244.2.10"]
FLANNEL2["flannel.1<br/>VxLAN 解封装"]
end
P1 --> FLANNEL1
FLANNEL1 -->|"外层: Node1 IP → Node2 IP<br/>内层: Pod1 IP → Pod2 IP"| FLANNEL2
FLANNEL2 --> P2
图解说明:
| 特性 | 说明 |
|---|---|
| 封装方式 | VxLAN、IPIP、GRE、Geneve |
| 外层报头 | 宿主机 IP(物理网络可路由) |
| 内层报头 | Pod IP(物理网络不可见) |
| 优点 | 跨子网、跨机房通信 |
| 缺点 | 有封装开销、MTU 损耗 |
常见 Overlay 协议对比¶
| 协议 | 封装开销 | 特点 | 使用场景 |
|---|---|---|---|
| VxLAN | 50 字节 | 基于 UDP,成熟稳定 | Flannel、Calico |
| IPIP | 20 字节 | 封装最小,性能较好 | Calico |
| GRE | 24+ 字节 | 支持多协议 | 较少使用 |
| Geneve | 可变 | 可扩展,未来趋势 | OVN |
[!NOTE] MTU 问题:Overlay 封装会增加报头大小,需要调整 Pod 网络的 MTU。
- VxLAN:MTU = 1450(1500 - 50)
- IPIP:MTU = 1480(1500 - 20)
5.4 Underlay 底层网络¶
背景¶
Underlay 网络通过网卡复用技术,让 Pod 网络与宿主机网络处于同一平面,减少协议栈处理次数。
原理¶
graph TD
subgraph Host["宿主机"]
PHYS["物理网卡 eth0<br/>192.168.1.10"]
subgraph IPVLAN["IPVLAN 子接口"]
IPV1["ipvlan0<br/>192.168.1.11"]
IPV2["ipvlan1<br/>192.168.1.12"]
end
PHYS --> IPV1
PHYS --> IPV2
end
subgraph Pod1["Pod1"]
E1["eth0"]
end
subgraph Pod2["Pod2"]
E2["eth0"]
end
IPV1 --> E1
IPV2 --> E2
图解说明:
- 物理网卡通过 IPVLAN/MACVLAN 技术创建多个子接口
- 每个子接口分配给一个 Pod
- Pod 的 IP 地址与宿主机在同一网段
- 绕过了 Pod 内部的协议栈处理
IPVLAN vs MACVLAN¶
| 特性 | IPVLAN | MACVLAN |
|---|---|---|
| MAC 地址 | 子接口与父接口 MAC 相同 | 子接口有独立 MAC |
| IP 地址 | 子接口有独立 IP | 子接口有独立 IP |
| 二层通信 | 不支持(需要三层) | 支持 |
| 适用场景 | IaaS 虚拟化环境 | 裸机 K8s 环境 |
| 内核版本 | 4.2+ 稳定 | 3.x 即可 |
适用场景¶
| 场景 | 推荐技术 |
|---|---|
| OpenStack + K8s 环境 | IPVLAN |
| 裸机 K8s 环境 | MACVLAN |
| 高吞吐量需求 | IPVLAN/MACVLAN(多网卡) |
| 传统 Service 场景 | 不推荐(使用 Overlay/Routing) |
[!TIP] 多网卡架构:在实际生产中,Pod 通常有多张网卡:
- 默认 CNI 网卡:用于 Service 发现、K8s 网络
- IPVLAN/MACVLAN 网卡:用于高速数据通信
5.5 Routing 路由网络¶
背景¶
Routing 网络模式通过路由协议直接转发 Pod 流量,Pod IP 完全暴露在物理网络中。
原理¶
graph TD
subgraph Node1["节点1<br/>192.168.1.10"]
P1["Pod<br/>10.244.1.10"]
RT1["路由表"]
end
subgraph Node2["节点2<br/>192.168.1.11"]
P2["Pod<br/>10.244.2.10"]
RT2["路由表"]
end
subgraph Switch["物理交换机/路由器"]
RT3["路由表"]
end
P1 --> RT1
RT1 -->|"10.244.2.0/24 via 192.168.1.11"| Switch
Switch -->|"10.244.2.0/24 via 192.168.1.11"| RT2
RT2 --> P2
图解说明:
| 模式 | 路由来源 | 适用场景 |
|---|---|---|
| Host-GW | 静态路由(flanneld 维护) | 同二层网络 |
| BGP | 动态路由(BGP 协议) | 跨子网、大规模集群 |
Host-GW vs BGP¶
| 特性 | Host-GW | BGP |
|---|---|---|
| 路由协议 | 静态路由 | BGP 动态路由 |
| 跨子网 | ❌ 不支持 | ✅ 支持 |
| 外部集成 | 不需要 | 需要与 ToR 交换机对接 |
| 复杂度 | 简单 | 较复杂 |
| 代表 CNI | Flannel Host-GW | Calico BGP |
[!IMPORTANT] BGP 的核心价值:
- 可以将 Pod IP 发布到物理网络
- 外部设备可以直接访问 Pod IP(无需 NodePort/LoadBalancer)
- 支持 Service ClusterIP 的外部发布(Calico 特性)
5.6 高性能网络¶
背景¶
对于延迟敏感、高吞吐量的应用(如电信、视频流、金融交易),传统的内核协议栈处理已成为瓶颈。高性能网络技术通过绕过内核协议栈来实现极致性能。
技术分类¶
graph TD
HP["高性能网络技术"] --> SRIOV["SR-IOV<br/>网卡直通"]
HP --> DPDK["DPDK<br/>用户态协议栈"]
HP --> eBPF["eBPF<br/>内核优化"]
SRIOV --> VF["VF 虚拟网卡<br/>直接分配给 Pod"]
DPDK --> VPP["VPP 用户态转发"]
eBPF --> CILIUM["Cilium<br/>bpf_redirect"]
SR-IOV(网卡直通)¶
graph TD
subgraph Host["宿主机"]
PF["物理网卡 PF<br/>e.g. Intel X710"]
VF0["VF0"]
VF1["VF1"]
VF2["VF2"]
PF --> VF0
PF --> VF1
PF --> VF2
end
VF0 -->|"直通"| Pod1["Pod1"]
VF1 -->|"直通"| Pod2["Pod2"]
要点解读:
| 概念 | 说明 |
|---|---|
| PF | Physical Function,物理网卡 |
| VF | Virtual Function,虚拟子网卡 |
| 直通 | VF 直接分配给 Pod,绕过宿主机协议栈 |
| 硬件要求 | 需要支持 SR-IOV 的网卡(Intel、Mellanox) |
DPDK + VPP(用户态协议栈)¶
graph LR
subgraph Traditional["传统模式"]
A1["应用"] --> K1["内核协议栈"] --> N1["网卡驱动"] --> HW1["网卡"]
end
subgraph DPDK["DPDK 模式"]
A2["应用"] --> VPP["VPP 用户态协议栈"] --> PMD["PMD 轮询驱动"] --> HW2["网卡"]
end
要点解读:
| 技术 | 说明 |
|---|---|
| DPDK | Data Plane Development Kit,Intel 开源的高速数据包处理框架 |
| VPP | Vector Packet Processing,Cisco 开源的用户态协议栈 |
| PMD | Poll Mode Driver,轮询模式驱动,避免中断开销 |
| 性能 | 可达千万级 PPS,远超内核协议栈 |
Cilium eBPF 优化¶
graph TD
subgraph Traditional["传统模式"]
P1_T["Pod1 协议栈"] --> V1_T["veth1"] --> HOST_T["宿主机协议栈<br/>iptables/路由"] --> V2_T["veth2"] --> P2_T["Pod2 协议栈"]
end
subgraph eBPF["Cilium eBPF Host Routing"]
P1_E["Pod1 协议栈"] --> V1_E["veth1<br/>TC hook"]
V1_E -->|"bpf_redirect_peer"| V2_E["veth2"]
V2_E --> P2_E["Pod2 协议栈"]
end
要点解读:
| 函数 | 作用 | 适用场景 |
|---|---|---|
| bpf_redirect_peer | 同节点 Pod 间数据包重定向 | Pod ↔ Pod(同节点) |
| bpf_redirect_neigh | 跨节点数据包直接发往物理网卡 | Pod → 外部网络 |
[!TIP] Cilium eBPF Host Routing 的效果:
- 同节点 Pod 通信:绕过宿主机协议栈和 iptables
- 抓包现象:在 veth 上只能看到单向流量(因为数据包被 redirect 了)
5.7 CNI 模式对比与选型¶
综合对比¶
| 特性 | Overlay | Underlay | Routing | 高性能 |
|---|---|---|---|---|
| 性能 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 跨子网 | ✅ | ❌ | 部分支持 | 取决于具体技术 |
| 配置复杂度 | 简单 | 中等 | 中等 | 复杂 |
| Pod IP 可见性 | 隐藏 | 暴露 | 暴露 | 暴露 |
| 硬件要求 | 无 | 无 | 无 | SR-IOV 需要特定网卡 |
选型建议¶
graph TD
START["CNI 选型"] --> Q1{"节点跨子网?"}
Q1 -->|"是"| Q2{"需要高性能?"}
Q1 -->|"否"| Q3{"需要 NetworkPolicy?"}
Q2 -->|"是"| BGP["Calico BGP + IPIP CrossSubnet"]
Q2 -->|"否"| OVERLAY["Flannel VxLAN / Calico IPIP"]
Q3 -->|"是"| Q4{"需要最高性能?"}
Q3 -->|"否"| HOSTGW["Flannel Host-GW"]
Q4 -->|"是"| CILIUM["Cilium eBPF"]
Q4 -->|"否"| CALICO["Calico BGP"]
| 场景 | 推荐 CNI |
|---|---|
| 小型集群、测试环境 | Flannel Host-GW |
| 需要 NetworkPolicy | Calico 或 Cilium |
| 跨子网、大规模集群 | Calico BGP |
| 追求极致性能 | Cilium eBPF Host Routing |
| 电信/视频流/高频交易 | SR-IOV + DPDK |
📝 章节小结¶
本章系统讲解了 CNI 网络模型:
- 四大网络类型:
- Overlay:封装隧道(VxLAN、IPIP),跨子网通信
- Underlay:网卡复用(IPVLAN、MACVLAN),同平面通信
- Routing:路由转发(Host-GW、BGP),Pod IP 暴露
-
高性能:绕过内核(SR-IOV、DPDK、eBPF)
-
数据包路径:Pod 发包需经过两次协议栈处理(Pod + 宿主机)
-
关键技术:
- VxLAN/IPIP 的封装原理
- IPVLAN/MACVLAN 的网卡复用
- SR-IOV 的网卡直通
-
Cilium eBPF 的 bpf_redirect 优化
-
选型关键点:
- 跨子网 → Overlay 或 BGP
- 同二层 → Host-GW(最简单高效)
- 需要 NetworkPolicy → Calico 或 Cilium
- 极致性能 → SR-IOV + DPDK 或 Cilium eBPF
[!TIP] 学习建议:
- 理解"两次协议栈处理"是容器网络性能损耗的根源
- 根据业务场景选择合适的网络模型
- 关注 eBPF 技术的发展趋势
- 高性能场景需要了解 SR-IOV、DPDK 的基本原理
第6章 CNI 工作原理¶
🎯 学习目标¶
- 理解 CNI 的插件分类:Main、IPAM、Meta
- 掌握 CNI 的两大核心目录结构
- 理解 CNI 配置文件的格式与字段含义
- 掌握 IPAM(IP 地址管理)的工作机制
- 了解多网卡方案(Multus、Network Attachment Definition)
6.1 CNI 概述¶
背景¶
CNI(Container Network Interface)是 Kubernetes 网络的标准接口规范。它定义了容器运行时如何调用网络插件来配置 Pod 的网络。
原理¶
graph TD
KUBELET["kubelet"] -->|"创建 Pod"| CRI["容器运行时<br/>containerd/CRI-O"]
CRI -->|"调用 CNI 插件"| CNI["CNI 插件"]
CNI -->|"读取配置"| CONF["/etc/cni/net.d/"]
CNI -->|"执行二进制"| BIN["/opt/cni/bin/"]
CNI -->|"配置网络"| POD["Pod 网络"]
图解说明:
- kubelet 调用容器运行时创建 Pod
- 容器运行时调用 CNI 插件配置网络
- CNI 插件读取
/etc/cni/net.d/下的配置文件 - CNI 插件执行
/opt/cni/bin/下的二进制程序 - 完成 Pod 网络配置(创建 VETH、分配 IP、配置路由等)
[!NOTE] CNI 的本质:CNI 只是一个接口规范,定义了输入输出格式。具体网络实现由各 CNI 插件负责(如 Flannel、Calico、Cilium)。
6.2 CNI 插件分类¶
原理¶
CNI 插件分为三大类:
graph TD
CNI["CNI 插件分类"] --> Main["Main 插件<br/>负责网络连通性"]
CNI --> IPAM["IPAM 插件<br/>负责 IP 地址管理"]
CNI --> Meta["Meta 插件<br/>辅助功能"]
Main --> Bridge["bridge"]
Main --> VETH["veth/ptp"]
Main --> IPVLAN["ipvlan"]
Main --> MACVLAN["macvlan"]
Main --> SRIOV["sriov"]
IPAM --> HostLocal["host-local"]
IPAM --> DHCP["dhcp"]
IPAM --> Static["static"]
IPAM --> Whereabouts["whereabouts"]
Meta --> PortMap["portmap"]
Meta --> Bandwidth["bandwidth"]
Meta --> Tuning["tuning"]
Meta --> SBR["sbr"]
图解说明:
| 类型 | 职责 | 代表插件 |
|---|---|---|
| Main | 创建网络接口、配置网络连通性 | bridge、veth、ipvlan、macvlan、sriov |
| IPAM | 分配和回收 IP 地址 | host-local、dhcp、static、whereabouts |
| Meta | 辅助功能(端口映射、带宽限制等) | portmap、bandwidth、tuning、sbr |
Main 插件详解¶
| 插件 | 功能 | 使用场景 |
|---|---|---|
| bridge | 创建 Linux Bridge,VETH 插入 Bridge | Flannel、默认方案 |
| ptp | 点对点 VETH Pair | Calico |
| ipvlan | IPVLAN 子接口 | 高性能虚拟化环境 |
| macvlan | MACVLAN 子接口 | 裸机高性能 |
| sriov | SR-IOV VF 直通 | 电信/金融高性能 |
IPAM 插件详解¶
| 插件 | 作用域 | 特点 |
|---|---|---|
| host-local | 单节点 | 最常用,每个节点独立管理 IP 池 |
| dhcp | 跨节点 | 使用 DHCP 服务器分配 IP |
| static | 固定 IP | 手工指定 IP,不自动分配 |
| whereabouts | 集群级 | 跨节点 IP 管理,多网卡常用 |
[!IMPORTANT] IPAM 插件的选择:
- 普通场景:host-local(Flannel、Calico 默认)
- 多网卡场景:whereabouts(集群级 IP 管理)
- 固定 IP 需求:static
6.3 CNI 核心目录结构¶
背景¶
理解 CNI 的目录结构,是排障和自定义网络配置的基础。
两大核心目录¶
graph LR
subgraph CNI["CNI 核心目录"]
CONF["/etc/cni/net.d/<br/>配置文件目录"]
BIN["/opt/cni/bin/<br/>二进制程序目录"]
end
CONF -->|"定义:怎么配置网络"| DESC["网络配置描述"]
BIN -->|"执行:实际配置网络"| EXEC["网络配置执行"]
/etc/cni/net.d/ - 配置文件目录¶
# 查看 CNI 配置文件
ls -la /etc/cni/net.d/
# 典型输出
# 10-flannel.conflist # Flannel 配置
# 10-calico.conflist # Calico 配置
# 10-cilium.conflist # Cilium 配置
配置文件命名规则:
- 按数字前缀排序,数字小的优先加载
.conf格式:单个插件配置.conflist格式:多个插件链式调用
/opt/cni/bin/ - 二进制程序目录¶
# 查看 CNI 二进制程序
ls -la /opt/cni/bin/
# 典型输出
# bandwidth # 带宽限制插件
# bridge # Linux Bridge 插件
# dhcp # DHCP IPAM 插件
# flannel # Flannel 专用插件
# host-local # host-local IPAM 插件
# ipvlan # IPVLAN 插件
# macvlan # MACVLAN 插件
# portmap # 端口映射插件
# ptp # 点对点 VETH 插件
# sbr # Source Based Routing 插件
# sriov # SR-IOV 插件
# tuning # 网卡参数调优插件
# vlan # VLAN 插件
代码说明:
- 这些二进制文件由 CNI 插件项目提供
- 容器运行时会调用这些二进制来配置网络
- 安装不同 CNI 时会添加对应的二进制(如 Calico 的
calico、calico-ipam)
6.4 CNI 配置文件详解¶
背景¶
CNI 配置文件定义了网络如何配置。理解配置文件结构,有助于自定义网络和排障。
配置文件示例(Calico)¶
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico",
"datastore_type": "kubernetes",
"mtu": 0,
"nodename_file_optional": false,
"log_level": "Info",
"log_file_path": "/var/log/calico/cni/cni.log",
"ipam": {
"type": "calico-ipam",
"assign_ipv4": "true",
"assign_ipv6": "false"
},
"container_settings": {
"allow_ip_forwarding": false
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"k8s_api_root": "https://10.96.0.1:443",
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type": "portmap",
"snat": true,
"capabilities": {
"portMappings": true
}
},
{
"type": "bandwidth",
"capabilities": {
"bandwidth": true
}
}
]
}
配置字段解析:
| 字段 | 说明 |
|---|---|
| name | 网络名称 |
| cniVersion | CNI 规范版本 |
| plugins | 插件列表(链式调用) |
| type | 插件类型(对应 /opt/cni/bin/ 下的二进制) |
| ipam | IP 地址管理配置 |
| datastore_type | 数据存储类型(kubernetes/etcdv3) |
| policy | NetworkPolicy 实现方式 |
链式调用示意图¶
graph LR
Pod["Pod 创建"] --> P1["calico 插件<br/>创建 VETH、配置路由"]
P1 --> P2["portmap 插件<br/>配置端口映射"]
P2 --> P3["bandwidth 插件<br/>配置带宽限制"]
P3 --> Done["网络配置完成"]
图解说明:
- CNI 插件按
plugins数组顺序依次执行 - 每个插件完成特定功能后,传递给下一个插件
- 类似 Linux 管道的链式处理
6.5 IPAM 工作机制¶
背景¶
IPAM(IP Address Management)负责 Pod 的 IP 地址分配与回收。不同的 IPAM 插件有不同的管理范围和特点。
原理¶
graph TD
subgraph HostLocal["host-local IPAM(节点级)"]
N1["节点1<br/>10.244.1.0/24"]
N2["节点2<br/>10.244.2.0/24"]
N3["节点3<br/>10.244.3.0/24"]
end
subgraph Whereabouts["whereabouts IPAM(集群级)"]
POOL["IP 池<br/>10.244.0.0/16"]
POOL --> PA["Pod A: 10.244.1.10"]
POOL --> PB["Pod B: 10.244.2.20"]
POOL --> PC["Pod C: 10.244.3.30"]
end
图解说明:
| IPAM | 管理范围 | IP 池分配 | 适用场景 |
|---|---|---|---|
| host-local | 单节点 | 每个节点独立 IP 池 | 默认 CNI(Flannel/Calico) |
| whereabouts | 集群级 | 统一 IP 池,跨节点分配 | 多网卡、需要 IP 跨节点迁移 |
host-local 存储位置¶
# host-local 的 IP 分配记录存储位置
ls /var/lib/cni/networks/<network-name>/
# 每个文件名是已分配的 IP 地址
# 文件内容是 Pod 的 Container ID
whereabouts 存储方式¶
# whereabouts 使用 Kubernetes CRD 存储 IP 分配信息
kubectl get ippools.whereabouts.cni.cncf.io -A
kubectl get overlappingrangeipreservations.whereabouts.cni.cncf.io -A
6.6 多网卡方案(Multus)¶
背景¶
在高性能场景中,Pod 可能需要多张网卡:默认网卡用于 Service 通信,额外网卡用于高速数据传输。
原理¶
graph TD
subgraph Pod["Pod"]
ETH0["eth0<br/>默认 CNI 网卡<br/>10.244.1.10"]
NET1["net1<br/>MACVLAN 网卡<br/>192.168.1.100"]
ETH0 --> SVC["Service 通信"]
NET1 --> DATA["高速数据通信"]
end
MULTUS["Multus CNI<br/>多网卡编排"] --> ETH0
MULTUS --> NET1
图解说明:
- Multus CNI 是一个 CNI "元插件",可以调用其他 CNI 为 Pod 配置多张网卡
- 默认网卡(eth0):由集群默认 CNI(如 Calico)配置
- 额外网卡(net1, net2...):通过 Network Attachment Definition 配置
Network Attachment Definition (NAD)¶
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-net
namespace: default
spec:
config: '{
"cniVersion": "0.3.1",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "whereabouts",
"range": "192.168.1.0/24",
"gateway": "192.168.1.1"
}
}'
配置说明:
| 字段 | 说明 |
|---|---|
| type | 网卡类型(macvlan/ipvlan/sriov) |
| master | 父接口(物理网卡) |
| mode | MACVLAN 模式(bridge/vepa/private) |
| ipam.type | IP 管理方式(whereabouts 用于多网卡) |
| ipam.range | IP 地址范围 |
Pod 使用多网卡¶
apiVersion: v1
kind: Pod
metadata:
name: multi-nic-pod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-net
spec:
containers:
- name: app
image: nginx
代码说明:
- 通过
k8s.v1.cni.cncf.io/networks注解指定额外网卡 - 可以指定多个,用逗号分隔:
macvlan-net, sriov-net
6.7 Source Based Routing (SBR)¶
背景¶
当 Pod 有多张网卡时,需要确保从某张网卡进来的流量,响应也从同一张网卡出去。这需要基于源地址的路由(SBR)。
原理¶
graph LR
subgraph Pod["Pod 多网卡"]
ETH0["eth0<br/>10.244.1.10"]
NET1["net1<br/>192.168.1.100"]
end
CLIENT["客户端<br/>192.168.1.50"]
CLIENT -->|"请求到 net1"| NET1
NET1 -->|"SBR: 响应从 net1 返回"| CLIENT
配置示例:
# 添加路由规则:从 192.168.1.0/24 出来的流量,查 table 100
ip rule add from 192.168.1.0/24 table 100
# 在 table 100 中添加默认路由
ip route add default via 192.168.1.1 dev net1 table 100
要点解读:
- 基于源地址路由 优先级高于基于目的地址的路由
- 多网卡场景必须配置 SBR,否则响应流量可能走错网卡
- CNI 的
sbr插件可以自动配置
6.8 Pod 网络配置流程¶
完整流程¶
sequenceDiagram
participant Kubelet as kubelet
participant CRI as 容器运行时
participant CNI as CNI 插件
participant Config as /etc/cni/net.d/
participant Bin as /opt/cni/bin/
Kubelet->>CRI: 创建 Pod
CRI->>Config: 读取 CNI 配置文件
Config-->>CRI: 返回配置(plugin 列表)
CRI->>Bin: 调用 Main 插件(bridge/calico)
Bin-->>CRI: 创建 VETH、配置路由
CRI->>Bin: 调用 IPAM 插件(host-local)
Bin-->>CRI: 分配 IP 地址
CRI->>Bin: 调用 Meta 插件(portmap/bandwidth)
Bin-->>CRI: 配置端口映射/带宽
CRI-->>Kubelet: Pod 网络就绪
流程说明:
- kubelet 调用容器运行时创建 Pod
- 容器运行时读取
/etc/cni/net.d/下的配置文件(按名称排序,优先加载) - 按配置文件中的
plugins列表顺序: - 调用 Main 插件:创建网络接口
- 调用 IPAM 插件:分配 IP 地址
- 调用 Meta 插件:附加功能
- 所有插件执行完成,Pod 网络就绪
6.9 常见问题与排障¶
问题1:Pod 网络配置失败¶
检查步骤:
# 1. 检查 CNI 配置文件是否存在
ls -la /etc/cni/net.d/
# 2. 检查 CNI 二进制是否存在
ls -la /opt/cni/bin/
# 3. 查看 kubelet 日志
journalctl -u kubelet | grep -i cni
# 4. 查看容器运行时日志
crictl logs <container-id>
问题2:IP 地址分配失败¶
# 检查 host-local IPAM 的 IP 分配记录
ls /var/lib/cni/networks/
# 如果 IP 池耗尽,可以清理无效记录
# (谨慎操作,仅在确认 IP 未被使用时)
问题3:多网卡 IP 冲突¶
# 检查 whereabouts 的 IP 分配
kubectl get ippools.whereabouts.cni.cncf.io -A -o wide
# 查看 IP 预留情况
kubectl get overlappingrangeipreservations -A
📝 章节小结¶
本章深入讲解了 CNI 工作原理:
- CNI 插件分类:
- Main 插件:创建网络接口(bridge、veth、ipvlan、macvlan、sriov)
- IPAM 插件:分配 IP 地址(host-local、dhcp、static、whereabouts)
-
Meta 插件:辅助功能(portmap、bandwidth、sbr)
-
两大核心目录:
/etc/cni/net.d/:配置文件,定义"怎么配置网络"-
/opt/cni/bin/:二进制程序,"实际执行配置" -
配置文件结构:链式调用多个插件
-
IPAM 机制:
- host-local:节点级,每节点独立 IP 池
-
whereabouts:集群级,跨节点 IP 管理
-
多网卡方案:
- Multus CNI 编排多网卡
- Network Attachment Definition 定义额外网卡
- SBR 保证多网卡响应路径正确
[!TIP] 学习建议:
- 在实际集群中查看
/etc/cni/net.d/和/opt/cni/bin/目录- 理解 CNI 配置文件中的 plugins 链式调用
- 尝试使用 Multus 配置多网卡 Pod
- 出现网络问题时,首先检查 CNI 配置文件和二进制
[!IMPORTANT] CNI 排障核心思路:
- 配置文件存在吗?(
/etc/cni/net.d/)- 二进制存在吗?(
/opt/cni/bin/)- 配置文件内容正确吗?(type、ipam 等字段)
- IP 池是否耗尽?(检查 IPAM 存储)
第二部分:Cilium-CNI¶
第7章 eBPF 介绍与网络应用¶
🎯 学习目标¶
- 理解 eBPF 技术的背景与核心概念
- 掌握 eBPF 程序的加载流程
- 理解 eBPF 在网络中的 Hook 点(XDP、TC)
- 掌握数据包收发流程与 eBPF 介入点
- 了解 eBPF Map 的作用
7.1 eBPF 技术背景¶
背景¶
eBPF(extended Berkeley Packet Filter)是 Linux 内核中的一项革命性技术。它允许在不修改内核源码的情况下,动态向内核注入特定逻辑。
从 cBPF 到 eBPF¶
graph LR
cBPF["cBPF<br/>Classical BPF<br/>仅用于包过滤"] -->|"扩展增强"| eBPF["eBPF<br/>Extended BPF<br/>通用内核扩展"]
eBPF --> NET["网络<br/>XDP/TC"]
eBPF --> TRACE["追踪<br/>kprobe/tracepoint"]
eBPF --> SEC["安全<br/>seccomp"]
eBPF --> PERF["性能<br/>perf events"]
图解说明:
| 特性 | cBPF | eBPF |
|---|---|---|
| 诞生时间 | 1992 年(伯克利大学) | 2014 年(Linux 3.18) |
| 功能范围 | 仅包过滤 | 网络、追踪、安全、性能等 |
| 寄存器 | 2 个 32 位 | 10 个 64 位 |
| 指令集 | 简单 | 丰富(跳转、调用等) |
eBPF 的核心价值¶
[!IMPORTANT] eBPF = 内核可编程化
- 传统方式:修改内核源码 → 重新编译 → 重启
- eBPF 方式:编写 eBPF 程序 → 动态加载 → 无需重启
这就像给内核"打补丁",但不需要重新编译内核。
7.2 eBPF 程序加载流程¶
原理¶
graph TD
SRC["1. 编写源码<br/>C/Python/Go"] --> COMPILE["2. 编译<br/>LLVM/Clang → BPF 字节码"]
COMPILE --> VERIFY["3. 验证 Verify<br/>安全性检查"]
VERIFY -->|"通过"| JIT["4. JIT 编译<br/>字节码 → 机器码"]
VERIFY -->|"失败"| REJECT["拒绝加载"]
JIT --> INJECT["5. 注入 Hook 点<br/>XDP/TC/kprobe 等"]
INJECT --> RUN["6. 运行<br/>事件触发执行"]
流程说明:
| 步骤 | 说明 |
|---|---|
| 编写源码 | 使用 C、Python、Go 等高级语言编写 |
| 编译 | 通过 LLVM/Clang 编译为 eBPF 字节码 |
| 验证 Verify | 内核验证器检查:无死循环、无越界访问、执行时间有限 |
| JIT 编译 | 将字节码编译为本机机器指令,提高执行效率 |
| 注入 Hook | 将程序注入到内核的特定位置(Hook 点) |
| 运行 | 当事件(如数据包到达)触发时执行 |
[!NOTE] Verify 验证器的作用:确保 eBPF 程序不会导致内核崩溃。检查项包括:
- 无无限循环
- 无越界内存访问
- 执行指令数有上限
- 仅能调用白名单内的内核函数
7.3 eBPF 网络 Hook 点¶
背景¶
eBPF 可以在网络数据包处理路径的多个位置注入程序,实现流量过滤、转发、修改等功能。
核心 Hook 点¶
graph TD
subgraph DataPath["数据包处理路径"]
NIC["网卡驱动<br/>NIC Driver"] --> XDP["XDP Hook<br/>最早介入点"]
XDP --> SKB["sk_buff 分配"]
SKB --> TC_IN["TC ingress Hook"]
TC_IN --> NETFILTER["Netfilter/iptables"]
NETFILTER --> PROTO["协议栈处理<br/>IP/TCP/UDP"]
PROTO --> APP["应用程序"]
end
style XDP fill:#90EE90
style TC_IN fill:#87CEEB
图解说明:
| Hook 点 | 位置 | 特点 | 适用场景 |
|---|---|---|---|
| XDP | 网卡驱动层 | 最早、最快 | DDoS 防护、简单丢弃/放行 |
| TC | sk_buff 分配后 | 功能丰富 | 流量整形、复杂策略 |
| Netfilter | 协议栈内部 | 传统方式 | iptables 规则 |
| Socket | 应用层接口 | 最靠近应用 | Service Mesh |
XDP vs TC 对比¶
graph LR
subgraph XDP层["XDP(网卡驱动层)"]
XDP_ACT["动作:PASS/DROP/TX/REDIRECT/ABORTED"]
end
subgraph TC层["TC(Traffic Control 层)"]
TC_ACT["动作:OK/SHOT/REDIRECT + 复杂处理"]
end
NIC["网卡"] --> XDP层 --> TC层 --> PROTO["协议栈"]
| 特性 | XDP | TC |
|---|---|---|
| 位置 | 网卡驱动(最前) | sk_buff 分配后 |
| 速度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 功能复杂度 | 简单(无完整协议信息) | 复杂(可访问完整 sk_buff) |
| Cilium 使用 | 部分功能(DDoS) | 主要使用 |
| 可实现功能 | DROP/PASS/TX/REDIRECT | SNAT/DNAT/策略/重定向 |
[!IMPORTANT] Cilium 主要使用 TC Hook:
- XDP 过于靠前,无法获取完整的协议信息
- TC 可以实现地址转换(SNAT/DNAT)、策略路由等复杂功能
- TC 在 Cilium 中替代了 iptables 的功能
7.4 数据包收发流程¶
背景¶
理解数据包在 Linux 内核中的收发流程,是理解 eBPF 网络优化的基础。
收包流程(RX)¶
graph TD
WIRE["网络线缆"] --> NIC["网卡接收"]
NIC --> DMA["DMA 拷贝到内存"]
DMA --> IRQ["硬中断通知 CPU"]
IRQ --> SOFTIRQ["软中断处理<br/>ksoftirqd"]
SOFTIRQ --> SKB["分配 sk_buff"]
SKB --> XDP_HOOK["XDP Hook"]
XDP_HOOK --> TC_HOOK["TC ingress Hook"]
TC_HOOK --> NETFILTER["Netfilter"]
NETFILTER --> L3["IP 层处理"]
L3 --> L4["TCP/UDP 处理"]
L4 --> SOCKET["Socket 缓冲区"]
SOCKET --> APP["应用程序读取"]
流程说明:
- 网卡接收:数据包到达物理网卡
- DMA 拷贝:网卡通过 DMA 将数据拷贝到内存(Ring Buffer)
- 硬中断:通知 CPU 有数据到达
- 软中断:ksoftirqd 处理数据包
- 分配 sk_buff:为数据包分配内核数据结构
- XDP/TC Hook:eBPF 程序介入点
- 协议栈处理:Netfilter → IP → TCP/UDP
- 应用读取:数据到达 Socket 缓冲区,应用程序读取
发包流程(TX)¶
graph TD
APP["应用程序写入"] --> SOCKET["Socket 缓冲区"]
SOCKET --> L4["TCP/UDP 封装"]
L4 --> L3["IP 封装"]
L3 --> NETFILTER["Netfilter"]
NETFILTER --> TC_HOOK["TC egress Hook"]
TC_HOOK --> QDISC["队列调度 Qdisc"]
QDISC --> NIC["网卡发送"]
NIC --> WIRE["网络线缆"]
[!TIP] eBPF 优化的关键:在数据包进入完整协议栈之前,在 XDP 或 TC 层面就完成处理,避免不必要的协议栈开销。
7.5 TC ingress/egress 方向¶
背景¶
理解 TC 的 ingress 和 egress 方向,对于理解 Cilium 的流量处理至关重要。
原理¶
graph TD
subgraph Node["Linux 节点"]
NIC["物理网卡"]
subgraph TC["TC 层"]
INGRESS["ingress<br/>入方向:网卡 → 协议栈"]
EGRESS["egress<br/>出方向:协议栈 → 网卡"]
end
PROTO["协议栈"]
NIC -->|"收包"| INGRESS
INGRESS --> PROTO
PROTO --> EGRESS
EGRESS -->|"发包"| NIC
end
图解说明:
| 方向 | 定义 | 数据流向 |
|---|---|---|
| ingress | 入方向 | 网卡 → 协议栈 |
| egress | 出方向 | 协议栈 → 网卡 |
在 VETH 上的 ingress/egress¶
graph LR
subgraph Pod["Pod 网络命名空间"]
ETH0["eth0"]
end
subgraph Host["宿主机 Root 命名空间"]
VETH["veth-xxx<br/>TC ingress/egress"]
end
ETH0 <-->|"VETH Pair"| VETH
VETH <--> PROTO["协议栈"]
Cilium 的处理方式:
- 在 VETH 宿主机端的 TC ingress 挂载 eBPF 程序
- 当数据包从 Pod 发出时,到达 VETH 宿主机端的 ingress 方向
- eBPF 程序可以直接重定向(redirect),绕过宿主机协议栈
7.6 eBPF Map¶
背景¶
eBPF 程序运行在内核空间,如何与用户空间的程序通信?答案是 eBPF Map。
原理¶
graph TD
subgraph Kernel["内核空间"]
BPF_PROG["eBPF 程序<br/>运行在 XDP/TC"]
BPF_MAP["eBPF Map<br/>Key-Value 存储"]
BPF_PROG <-->|"读写"| BPF_MAP
end
subgraph User["用户空间"]
USER_PROG["用户程序<br/>cilium-agent"]
USER_PROG <-->|"读写"| BPF_MAP
end
图解说明:
| 特性 | 说明 |
|---|---|
| 数据结构 | Key-Value 存储(类似 Python 字典) |
| 作用 | 内核空间 ↔ 用户空间通信桥梁 |
| 类型 | Hash、Array、LRU、Ring Buffer 等 |
| 持久化 | 可通过文件系统(/sys/fs/bpf/)持久化 |
在 Cilium 中的应用¶
# 查看 Cilium 的 eBPF Map
cilium bpf ct list global # 连接跟踪表
cilium bpf lb list # 负载均衡表
cilium bpf policy get --all # 策略表
cilium bpf endpoint list # Endpoint 表
代码说明:
- Cilium Agent 将 Service、Endpoint、Policy 等信息写入 eBPF Map
- 内核中的 eBPF 程序读取 Map 进行决策
- 无需经过用户空间,实现高性能数据平面
7.7 抓包与 eBPF Hook 点¶
背景¶
使用 tcpdump 抓包时,可能会遇到"抓不到包"的情况。理解 eBPF Hook 点与 tcpdump 的位置关系,有助于排障。
原理¶
graph TD
NIC["网卡"] --> XDP["XDP Hook"]
XDP --> SKB["sk_buff"]
SKB --> TCPDUMP["tcpdump 抓包点<br/>AF_PACKET"]
TCPDUMP --> TC["TC ingress Hook"]
TC -->|"可能 redirect"| OTHER["其他接口/丢弃"]
TC --> PROTO["协议栈"]
图解说明:
| 情况 | 能否抓到包 | 原因 |
|---|---|---|
| 入方向,TC 未 redirect | ✅ 能 | tcpdump 在 TC 之前 |
| 入方向,TC redirect | ✅ 能 | 数据包先经过 tcpdump |
| 出方向,TC redirect | ❌ 不能 | 数据包被 redirect,不经过 tcpdump |
[!WARNING] Cilium eBPF Host Routing 抓包现象:
- 在 VETH 上只能看到单向流量
- 因为响应流量被
bpf_redirect_peer直接重定向了- 排障时需要在 Pod 内部抓包,或使用 Cilium 的
cilium monitor命令
7.8 eBPF 在 Cilium 中的应用¶
Cilium 的 eBPF 程序分布¶
graph TD
subgraph Pod["Pod"]
APP["应用"]
POD_ETH["eth0"]
end
subgraph Host["宿主机"]
VETH["veth<br/>TC ingress: eBPF"]
BRIDGE["cilium_vxlan/cilium_host"]
PHYS["物理网卡<br/>TC/XDP: eBPF"]
end
POD_ETH <--> VETH
VETH <--> BRIDGE
BRIDGE <--> PHYS
Cilium 使用 eBPF 实现的功能:
| 功能 | 传统方式 | Cilium eBPF 方式 |
|---|---|---|
| Service 负载均衡 | kube-proxy + iptables | eBPF Map + TC |
| NetworkPolicy | iptables 规则 | eBPF Map + TC |
| SNAT/Masquerade | iptables MASQUERADE | eBPF TC |
| 连接跟踪 | nf_conntrack | eBPF CT Map |
| Host Routing | 协议栈路由 | bpf_redirect_peer |
📝 章节小结¶
本章介绍了 eBPF 技术及其在网络中的应用:
- eBPF 概念:
- 从 cBPF 演进而来的内核扩展技术
- 无需重编译内核,动态加载程序
-
"给内核打补丁"的能力
-
程序加载流程:
-
编写 → 编译 → Verify → JIT → 注入 Hook → 运行
-
网络 Hook 点:
- XDP:网卡驱动层,最快但功能简单
- TC:Traffic Control 层,Cilium 主要使用
-
ingress/egress 方向理解
-
eBPF Map:
- 内核与用户空间通信的桥梁
-
Key-Value 存储结构
-
在 Cilium 中的应用:
- 替代 iptables 实现 Service、NetworkPolicy
- 使用 bpf_redirect 实现 Host Routing
[!TIP] 学习建议:
- 记住 XDP 和 TC 的位置关系
- 理解 ingress/egress 方向的定义
- 了解 tcpdump 与 eBPF Hook 的位置关系
- 使用
cilium bpf命令查看 eBPF Map[!IMPORTANT] 核心记忆点:
- XDP 最快但功能简单,TC 功能丰富是 Cilium 主力
- 数据包从网卡 → XDP → tcpdump → TC → 协议栈
- Cilium 用 eBPF 替代 iptables,实现高性能数据平面
第8章 Cilium 及安装模式介绍¶
🎯 学习目标¶
- 了解 Cilium 的核心架构与组件
- 掌握 Cilium 的三种安装模式及其区别
- 理解 native routing 与 host routing 的概念差异
- 学会使用 Helm 安装和配置 Cilium
- 掌握 Cilium 常用命令
8.1 Cilium 概述¶
背景¶
Cilium 是一个基于 eBPF 的高性能 CNI 插件,专为 Kubernetes 设计。它不仅提供网络连通性,还集成了安全策略、负载均衡、可观测性等功能。
核心架构¶
graph TD
subgraph ControlPlane["控制平面"]
OPERATOR["cilium-operator<br/>集群级管理"]
end
subgraph DataPlane["数据平面(每节点)"]
AGENT["cilium-agent<br/>节点级管理"]
EBPF["eBPF 程序<br/>TC/XDP"]
AGENT --> EBPF
end
subgraph Storage["存储"]
ETCD["Kubernetes etcd<br/>或 Cilium etcd"]
end
OPERATOR --> Storage
AGENT --> Storage
组件说明:
| 组件 | 位置 | 职责 |
|---|---|---|
| cilium-operator | Deployment | 集群级资源管理(IPAM、BGP 等) |
| cilium-agent | DaemonSet | 节点级网络配置、eBPF 程序管理 |
| cilium-envoy | 嵌入 Agent | L7 策略、可观测性 |
| hubble | 可选组件 | 网络流量可视化 |
Cilium 核心功能¶
graph LR
CILIUM["Cilium"] --> CNI["CNI 网络<br/>Pod 连通性"]
CILIUM --> LB["负载均衡<br/>替代 kube-proxy"]
CILIUM --> POLICY["NetworkPolicy<br/>L3/L4/L7 安全"]
CILIUM --> BGP["BGP<br/>路由通告"]
CILIUM --> MESH["Cluster Mesh<br/>多集群"]
CILIUM --> HUBBLE["Hubble<br/>可观测性"]
8.2 三种安装模式¶
背景¶
Cilium 提供三种安装模式,对应不同的功能层次和内核要求。理解这三种模式是使用 Cilium 的基础。
模式概览¶
graph TD
subgraph Mode1["模式1: with kube-proxy"]
M1_DESC["传统模式<br/>兼容性最好"]
M1_IPTABLES["Service: iptables"]
M1_ROUTING["Routing: 内核协议栈"]
end
subgraph Mode2["模式2: kube-proxy replacement"]
M2_DESC["进阶模式<br/>替代 kube-proxy"]
M2_BPF["Service: eBPF"]
M2_ROUTING["Routing: 内核协议栈"]
end
subgraph Mode3["模式3: eBPF Host Routing"]
M3_DESC["高级模式<br/>性能最优"]
M3_BPF["Service: eBPF"]
M3_HOSTROUTING["Routing: eBPF redirect"]
end
Mode1 -->|"进阶"| Mode2
Mode2 -->|"进阶"| Mode3
模式对比:
| 特性 | with kube-proxy | kube-proxy replacement | eBPF Host Routing |
|---|---|---|---|
| Service 实现 | iptables | eBPF | eBPF |
| Host 路由 | 内核协议栈 | 内核协议栈 | eBPF redirect |
| 性能 | 一般 | 较好 | 最优 |
| 内核要求 | 4.9+ | 4.19+ | 5.10+ |
| 兼容性 | 最好 | 较好 | 部分功能不支持 |
[!IMPORTANT] 概念澄清:
- native routing = direct routing = Host-GW → 路由转发方式
- host routing → eBPF 绕过宿主机协议栈的能力
这两个概念完全不同,不要混淆!
8.3 模式1: with kube-proxy¶
背景¶
这是最基础的模式,Cilium 只负责 CNI 网络连通性,Service 依然由 kube-proxy + iptables 实现。
原理¶
graph LR
subgraph Pod["Pod"]
APP["应用"]
end
subgraph Host["宿主机"]
VETH["veth<br/>TC: eBPF"]
IPTABLES["iptables<br/>kube-proxy 规则"]
PROTO["协议栈"]
end
APP --> VETH
VETH --> PROTO
PROTO --> IPTABLES
IPTABLES --> PHYS["物理网卡"]
图解说明:
- Cilium 在 TC 层实现 Pod 网络
- Service 访问仍经过 iptables(kube-proxy 维护)
- 适合内核版本较低或需要最大兼容性的场景
Helm 安装参数¶
helm install cilium cilium/cilium --version 1.13.0 \
--namespace kube-system \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true \
--set ipv4NativeRoutingCIDR="10.244.0.0/16"
参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
tunnel |
disabled | 禁用 VxLAN 隧道,使用 direct routing |
autoDirectNodeRoutes |
true | 自动配置节点间路由 |
ipv4NativeRoutingCIDR |
Pod CIDR | 不做 SNAT 的地址范围 |
8.4 模式2: kube-proxy replacement¶
背景¶
此模式下 Cilium 完全替代 kube-proxy,使用 eBPF 实现 Service 负载均衡,不再需要 iptables。
原理¶
graph LR
subgraph Pod["Pod"]
APP["应用"]
end
subgraph Host["宿主机"]
VETH["veth<br/>TC: eBPF"]
BPF_LB["eBPF LB<br/>Service 处理"]
PROTO["协议栈"]
end
APP --> VETH
VETH --> BPF_LB
BPF_LB --> PROTO
PROTO --> PHYS["物理网卡"]
图解说明:
- Service ClusterIP、NodePort 由 eBPF 直接处理
- 不再需要 kube-proxy 和 iptables 规则
- 性能提升明显(减少 iptables 规则匹配)
Helm 安装参数¶
helm install cilium cilium/cilium --version 1.13.0 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=6443 \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true \
--set ipv4NativeRoutingCIDR="10.244.0.0/16"
关键参数:
| 参数 | 值 | 说明 |
|---|---|---|
kubeProxyReplacement |
strict | 严格模式,完全替代 kube-proxy |
k8sServiceHost |
API Server IP | 必须提供,因为没有 kube-proxy |
k8sServicePort |
6443 | API Server 端口 |
[!NOTE] kubeProxyReplacement 取值:
disabled:不替代 kube-proxypartial:部分替代strict:严格替代,完全使用 eBPF
8.5 模式3: eBPF Host Routing¶
背景¶
这是性能最优的模式。除了替代 kube-proxy,还使用 eBPF 绕过宿主机协议栈的路由处理。
原理¶
graph LR
subgraph Pod1["Pod1"]
APP1["应用"]
end
subgraph Host["宿主机"]
VETH1["veth1<br/>TC ingress"]
BPF_REDIRECT["bpf_redirect_peer<br/>bpf_redirect_neigh"]
VETH2["veth2"]
end
subgraph Pod2["Pod2"]
APP2["应用"]
end
APP1 --> VETH1
VETH1 -->|"eBPF 直接重定向<br/>绕过协议栈"| BPF_REDIRECT
BPF_REDIRECT --> VETH2
VETH2 --> APP2
图解说明:
- 使用
bpf_redirect_peer和bpf_redirect_neigh函数 - 绕过宿主机的协议栈处理
- 同节点 Pod 通信只经过一次协议栈(Pod 内)
Host Routing 的核心价值¶
graph TD
subgraph Traditional["传统模式(两次协议栈)"]
T_POD1["Pod1 协议栈"] --> T_HOST["宿主机协议栈"] --> T_POD2["Pod2 协议栈"]
end
subgraph HostRouting["eBPF Host Routing(一次协议栈)"]
H_POD1["Pod1 协议栈"] -->|"bpf_redirect"| H_POD2["Pod2 协议栈"]
end
Helm 安装参数¶
helm install cilium cilium/cilium --version 1.13.0 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=6443 \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true \
--set ipv4NativeRoutingCIDR="10.244.0.0/16" \
--set bpf.masquerade=true
新增关键参数:
| 参数 | 值 | 说明 |
|---|---|---|
bpf.masquerade |
true | 使用 eBPF 实现 SNAT(而非 iptables) |
[!WARNING] eBPF Host Routing 的限制:
- 不支持 IPsec 加密
- 内核要求 5.10+
- 部分高级功能可能受限
8.6 安装参数详解¶
Helm Values 查找方法¶
# 1. 添加 Cilium Helm 仓库
helm repo add cilium https://helm.cilium.io/
# 2. 下载 chart 查看 values.yaml
helm pull cilium/cilium --version 1.13.0 --untar
cat cilium/values.yaml
核心配置分类:
| 配置类别 | 典型参数 |
|---|---|
| 网络模式 | tunnel, routingMode, autoDirectNodeRoutes |
| kube-proxy | kubeProxyReplacement, k8sServiceHost |
| eBPF 功能 | bpf.masquerade, hostRouting |
| IPAM | ipam.mode, ipam.operator.clusterPoolIPv4PodCIDR |
| 调试 | debug.enabled, debug.verbose |
常用安装参数对照表¶
| 需求 | 参数设置 |
|---|---|
| 使用 Direct Routing | tunnel=disabled |
| 使用 VxLAN | tunnel=vxlan(默认) |
| 替代 kube-proxy | kubeProxyReplacement=strict |
| 启用 eBPF masquerade | bpf.masquerade=true |
| 启用 BGP | bgpControlPlane.enabled=true |
| 启用 Hubble | hubble.enabled=true |
8.7 Cilium 常用命令¶
cilium status¶
# 查看 Cilium 状态
cilium status
# 查看详细状态
cilium status --verbose
输出解读:
| 字段 | 说明 |
|---|---|
KubeProxyReplacement |
strict/partial/disabled |
Host Routing |
Legacy(传统)或 BPF(eBPF) |
Masquerading |
IPTables 或 BPF |
Tunnel Mode |
Disabled/VXLAN/Geneve |
cilium bpf 命令¶
# 查看 eBPF Service 表(替代 iptables -L)
cilium bpf lb list
# 查看连接跟踪表
cilium bpf ct list global
# 查看 Endpoint 信息
cilium bpf endpoint list
# 查看策略表
cilium bpf policy get --all
cilium monitor¶
# 实时监控数据包流向
cilium monitor
# 详细模式
cilium monitor -v
# 查看 datapath 事件
cilium monitor --type trace
8.8 VxLAN vs Direct Routing¶
背景¶
Cilium 支持两种基础网络模式:VxLAN(隧道)和 Direct Routing(路由)。
对比¶
graph TD
subgraph VxLAN["VxLAN 模式(默认)"]
V_POD1["Pod1<br/>10.244.1.10"] --> V_ENCAP["VxLAN 封装"]
V_ENCAP --> V_DECAP["VxLAN 解封装"]
V_DECAP --> V_POD2["Pod2<br/>10.244.2.10"]
end
subgraph DirectRouting["Direct Routing 模式"]
D_POD1["Pod1<br/>10.244.1.10"] --> D_ROUTE["路由转发"]
D_ROUTE --> D_POD2["Pod2<br/>10.244.2.10"]
end
| 特性 | VxLAN | Direct Routing |
|---|---|---|
| 跨子网 | ✅ 支持 | ❌ 需同二层 |
| 性能 | 有封装开销 | 更高 |
| MTU | 需调整(-50) | 无需调整 |
| 配置 | 默认 | tunnel=disabled |
📝 章节小结¶
本章介绍了 Cilium 的架构与安装模式:
- Cilium 架构:
- cilium-operator:集群级控制平面
- cilium-agent:节点级数据平面
-
eBPF 程序:实际的数据包处理
-
三种安装模式:
| 模式 | Service | 路由 | 性能 |
|---|---|---|---|
| with kube-proxy | iptables | 协议栈 | 一般 |
| kube-proxy replacement | eBPF | 协议栈 | 较好 |
| eBPF Host Routing | eBPF | eBPF redirect | 最优 |
- 关键概念澄清:
- native/direct routing = Host-GW 路由方式
- host routing = eBPF 绕过协议栈
-
两者是完全不同的概念!
-
Helm 安装:
kubeProxyReplacement=strict:替代 kube-proxybpf.masquerade=true:启用 eBPF Host Routing
[!TIP] 学习建议:
- 准备三套安装脚本作为基础模板
- 使用
cilium status --verbose验证安装结果- 通过
cilium bpf lb list验证 kube-proxy replacement- 参考官方 values.yaml 了解所有配置项
[!IMPORTANT] 模式选择指南:
- 内核 < 4.19 → with kube-proxy
- 内核 ≥ 4.19,追求稳定 → kube-proxy replacement
- 内核 ≥ 5.10,追求极致性能 → eBPF Host Routing
第9章 Native-Routing-with-kubeProxy 模式¶
🎯 学习目标¶
- 理解 Native Routing 与 Host-GW 的等价关系
- 掌握同节点 Pod 通信的数据包流程
- 掌握跨节点 Pod 通信的数据包流程
- 理解 32 位掩码的作用与 L3 路由转发
- 理解 Cilium eBPF ARP 劫持机制
9.1 Native Routing 概述¶
背景¶
Native Routing 是 Cilium 中与 VxLAN 隧道模式相对的另一种网络模式。它不使用封装,而是依赖节点间的路由进行 Pod 网络通信。
核心概念¶
graph LR
NATIVE["Native Routing<br/>Cilium 术语"] === DIRECT["Direct Routing<br/>路由模式"]
DIRECT === HOSTGW["Host-GW<br/>传统术语"]
[!IMPORTANT] 概念等价关系:
Native Routing = Direct Routing = Host-GW
三者描述的是同一种网络模式,只是不同场景下的叫法不同。
启用 Native Routing¶
# 关键参数
--set tunnel=disabled # 禁用隧道
--set autoDirectNodeRoutes=true # 自动配置节点路由
--set ipv4NativeRoutingCIDR="10.244.0.0/16" # 原生路由网段
9.2 Pod 的 32 位掩码¶
背景¶
在 Cilium 中,Pod 的 IP 地址使用 32 位掩码(/32),这与传统 CNI 有所不同。
原理¶
# Pod 内查看 IP
$ ip addr show eth0
inet 10.0.2.253/32 scope global eth0
32 位掩码意味着什么?
| 掩码 | 含义 | 结果 |
|---|---|---|
| /24 | 同网段有 254 个主机 | 同网段走二层 |
| /32 | "网段"只有自己 | 与任何地址都不同网段 |
graph TD
POD["Pod<br/>10.0.2.253/32"]
POD --> CHECK{"与目的 IP 在同一网段?"}
CHECK -->|"永远 No"| L3["走三层路由"]
[!NOTE] 32 位掩码的设计目的:
- 强制所有流量走 L3 路由(而非 L2 交换)
- 流量必须经过网关,便于 eBPF 程序介入处理
- 这是 Cilium 能够劫持和重定向流量的基础
9.3 同节点 Pod 通信¶
背景¶
理解同节点 Pod 间的通信流程,是理解 Cilium 数据平面的第一步。
场景描述¶
Pod A (10.0.2.253) → Pod B (10.0.2.36)
两个 Pod 位于同一节点
网络拓扑¶
graph TD
subgraph Pod_A["Pod A (10.0.2.253)"]
APP_A["应用"]
ETH_A["eth0<br/>10.0.2.253/32"]
end
subgraph Host["宿主机"]
LXC_A["lxc-xxxx<br/>(对应 Pod A)"]
LXC_B["lxc-yyyy<br/>(对应 Pod B)"]
CILIUM_HOST["cilium_host<br/>10.0.2.131"]
end
subgraph Pod_B["Pod B (10.0.2.36)"]
ETH_B["eth0<br/>10.0.2.36/32"]
APP_B["应用"]
end
ETH_A <-->|"veth pair"| LXC_A
ETH_B <-->|"veth pair"| LXC_B
路由表分析¶
# Pod A 内部路由表
$ ip route
default via 10.0.2.131 dev eth0 # 默认路由,网关是 cilium_host
10.0.2.131 dev eth0 scope link # 网关地址的直连路由
关键点解读:
| 路由条目 | 说明 |
|---|---|
default via 10.0.2.131 |
所有流量发往 cilium_host |
10.0.2.131 dev eth0 |
告诉系统如何到达网关 |
数据包流程¶
sequenceDiagram
participant PodA as Pod A<br/>10.0.2.253
participant LXC_A as lxc-A<br/>(宿主机)
participant LXC_B as lxc-B<br/>(宿主机)
participant PodB as Pod B<br/>10.0.2.36
Note over PodA: 查路由: 目的 IP 不在同网段<br/>需走网关 10.0.2.131
PodA->>PodA: ARP: Who has 10.0.2.131?
Note over PodA: eBPF 劫持 ARP 请求<br/>返回 lxc-A 的 MAC(非网关 MAC)
PodA->>LXC_A: 发送数据包<br/>dst-MAC = lxc-A 的 MAC
Note over LXC_A: eBPF 拦截<br/>查询 Pod B 对应的 lxc-B
LXC_A->>LXC_B: eBPF redirect<br/>直接转发到 lxc-B
LXC_B->>PodB: 通过 veth pair 送入 Pod B
ARP 劫持机制¶
传统行为 vs Cilium 行为:
| 步骤 | 传统 CNI | Cilium |
|---|---|---|
| ARP 请求 | 请求网关 MAC | 请求网关 MAC |
| ARP 响应 | 返回网关 MAC | 返回 lxc 网卡 MAC |
| 数据包 dst-MAC | 填写网关 MAC | 填写 lxc 网卡 MAC |
# 验证 ARP 响应
$ arping -I eth0 10.0.2.131
# 返回的 MAC 是 lxc 网卡的 MAC,而非 cilium_host 的 MAC
[!WARNING] Cilium 的 ARP 劫持:
Pod 认为它在和网关通信,但实际上 eBPF 程序劫持了 ARP 响应,返回的是 veth pair 对端(lxc 网卡)的 MAC 地址。这使得数据包直接发往 lxc 网卡,而非经过完整的协议栈路由。
关键发现¶
虽然路由表显示出接口是 cilium_host,但抓包发现:
# 在 cilium_host 上抓包
$ tcpdump -pne -i cilium_host icmp
# 没有任何包!
# 在 lxc-B 上抓包
$ tcpdump -pne -i lxc-yyyy icmp
# 能看到 ICMP 请求和响应!
[!IMPORTANT] 同节点通信的精髓:
- 路由表说走 cilium_host,但实际 eBPF 将包 redirect 到 lxc 网卡
- 数据包直接从 lxc-A → lxc-B,不经过协议栈路由
- 这就是 Cilium 的 "跳跃式转发" 特性
9.4 跨节点 Pod 通信¶
背景¶
跨节点通信需要经过物理网络(或底层网络),流程比同节点通信多了节点间路由的步骤。
场景描述¶
Pod A (10.0.2.253, Node 1) → Pod C (10.0.1.173, Node 2)
两个 Pod 位于不同节点
网络拓扑¶
graph TD
subgraph Node1["Node 1 (172.18.0.2)"]
POD_A["Pod A<br/>10.0.2.253"]
LXC_A["lxc-A"]
ETH0_1["eth0<br/>172.18.0.2"]
end
subgraph Network["物理网络"]
SWITCH["交换机/路由器"]
end
subgraph Node2["Node 2 (172.18.0.4)"]
ETH0_2["eth0<br/>172.18.0.4"]
LXC_C["lxc-C"]
POD_C["Pod C<br/>10.0.1.173"]
end
POD_A --> LXC_A --> ETH0_1
ETH0_1 --> SWITCH
SWITCH --> ETH0_2
ETH0_2 --> LXC_C --> POD_C
宿主机路由表¶
# Node 1 路由表
$ ip route
default via 172.18.0.1 dev eth0
10.0.1.0/24 via 172.18.0.4 dev eth0 # 去 10.0.1.x 走 Node 2
10.0.2.0/24 via 10.0.2.131 dev cilium_host # 本节点 Pod 网段
10.0.3.0/24 via 172.18.0.3 dev eth0 # 去 10.0.3.x 走 Node 3
路由表解读:
| 目的网段 | 下一跳 | 说明 |
|---|---|---|
| 10.0.1.0/24 | 172.18.0.4 | Pod C 在 Node 2,走 Node 2 |
| 10.0.2.0/24 | cilium_host | 本节点 Pod,走内部网关 |
| 默认 | 172.18.0.1 | 其他流量走默认网关 |
数据包流程¶
sequenceDiagram
participant PodA as Pod A<br/>10.0.2.253
participant LXC_A as lxc-A
participant ETH1 as Node1 eth0<br/>172.18.0.2
participant ETH2 as Node2 eth0<br/>172.18.0.4
participant LXC_C as lxc-C
participant PodC as Pod C<br/>10.0.1.173
Note over PodA: 目的: 10.0.1.173<br/>走默认路由
PodA->>LXC_A: 第1跳: veth pair
Note over LXC_A: eBPF 查路由<br/>目的匹配 10.0.1.0/24
LXC_A->>ETH1: 第2跳: 路由转发
Note over ETH1: src-MAC: 172.18.0.2 的 MAC<br/>dst-MAC: 172.18.0.4 的 MAC
ETH1->>ETH2: 第3跳: 物理网络
Note over ETH2: 收包后查路由<br/>目的 10.0.1.173 是本地 Pod
ETH2->>LXC_C: 第4跳: 转发到 lxc
LXC_C->>PodC: 第5跳: veth pair
MAC 地址变化¶
graph LR
subgraph Hop1["第1跳"]
SRC_MAC1["src: Pod A eth0 MAC"]
DST_MAC1["dst: lxc-A MAC"]
end
subgraph Hop2["第2跳"]
SRC_MAC2["src: Node1 eth0 MAC<br/>(172.18.0.2)"]
DST_MAC2["dst: Node2 eth0 MAC<br/>(172.18.0.4)"]
end
subgraph Hop3["第3跳"]
SRC_MAC3["src: Node2 eth0 MAC"]
DST_MAC3["dst: lxc-C MAC"]
end
Hop1 --> Hop2 --> Hop3
抓包验证(在 Node1 eth0 上):
$ tcpdump -pne -i eth0 icmp
# src-MAC: 12:00:00:02 (Node1 eth0)
# dst-MAC: 12:00:00:04 (Node2 eth0)
# src-IP: 10.0.2.253 (不变)
# dst-IP: 10.0.1.173 (不变)
[!TIP] 跨节点通信的核心规律:
- IP 地址不变:src-IP 和 dst-IP 全程保持
- MAC 地址逐跳变化:每经过一个路由点,MAC 都会更新
- 这就是 L3 路由转发 的本质
9.5 Cilium 与传统 CNI 的差异¶
架构对比¶
graph TD
subgraph Traditional["传统 CNI (如 Flannel)"]
T_POD["Pod"] --> T_VETH["veth"]
T_VETH --> T_BRIDGE["Linux Bridge"]
T_BRIDGE --> T_ROUTE["协议栈路由"]
T_ROUTE --> T_ETH["物理网卡"]
end
subgraph Cilium["Cilium CNI"]
C_POD["Pod"] --> C_VETH["veth"]
C_VETH --> C_LXC["lxc 网卡"]
C_LXC -->|"eBPF redirect"| C_LXC2["lxc 网卡"]
C_LXC2 --> C_POD2["目的 Pod"]
end
关键差异¶
| 特性 | 传统 CNI | Cilium |
|---|---|---|
| 路由执行者 | Linux 协议栈 | eBPF 程序 |
| ARP 响应 | 真实网关 MAC | lxc 网卡 MAC(劫持) |
| 同节点转发 | 经过 Bridge | eBPF redirect |
| 抓包位置 | 网卡上可抓 | 部分位置抓不到 |
| 转发风格 | 一跳一跳 | "跳跃式"转发 |
📝 章节小结¶
本章介绍了 Cilium Native-Routing 模式下的数据包转发流程:
- Native Routing 概念:
- 等价于 Host-GW 模式
- 使用
tunnel=disabled启用 -
依赖节点间路由(非隧道封装)
-
32 位掩码的作用:
- 强制所有流量走 L3 路由
-
便于 eBPF 程序介入处理
-
同节点 Pod 通信:
- eBPF 劫持 ARP,返回 lxc 网卡 MAC
- 数据包通过 lxc 网卡直接 redirect,不走协议栈路由
-
路由表显示走 cilium_host,实际走 lxc 网卡
-
跨节点 Pod 通信:
- 遵循传统 L3 路由规则
- IP 不变,MAC 逐跳变化
-
依赖宿主机路由表(autoDirectNodeRoutes)
-
与传统 CNI 的差异:
- ARP 劫持机制
- eBPF redirect 替代协议栈路由
- "跳跃式"转发
[!TIP] 学习建议:
- 使用
tcpdump在不同接口抓包,验证数据流向- 使用
arping验证 ARP 响应的 MAC 地址- 对比路由表和实际抓包结果,理解 eBPF redirect
[!IMPORTANT] Native Routing with kube-proxy 模式核心特点:
- Pod 间流量:eBPF redirect(bypass 协议栈)
- Service 流量:iptables(kube-proxy 维护)
- 这是一个"混合"模式
第10章 Native-Routing-with-eBPF-HostRouting 模式¶
🎯 学习目标¶
- 掌握 eBPF Host Routing 的核心函数
- 理解
bpf_redirect_peer和bpf_redirect_neigh的作用 - 掌握同节点/跨节点 Pod 通信的数据流
- 理解 TC Hook 的位置与方向
- 了解 Socket LB 机制
10.1 eBPF Host Routing 概述¶
背景¶
eBPF Host Routing 是 Cilium 的最高级模式,它在 kube-proxy replacement 基础上,进一步使用 eBPF 绕过宿主机协议栈的路由处理,实现最高性能。
核心优势¶
graph LR
subgraph Traditional["传统模式"]
T1["Pod"] --> T2["lxc 网卡"]
T2 --> T3["协议栈<br/>(iptables 处理)"]
T3 --> T4["物理网卡"]
end
subgraph eBPF["eBPF Host Routing"]
E1["Pod"] --> E2["lxc 网卡"]
E2 -->|"bpf_redirect"| E4["物理网卡"]
end
[!IMPORTANT] 核心优势:跳过宿主机协议栈处理,减少:
- 上下文切换
- 数据包拷贝
- 中断处理
- iptables 规则匹配
10.2 核心 eBPF 函数¶
两个关键函数¶
graph TD
subgraph Functions["eBPF 核心函数"]
PEER["bpf_redirect_peer<br/>(内核 ≥ 5.10)"]
NEIGH["bpf_redirect_neigh<br/>(内核 ≥ 5.10)"]
end
PEER -->|"用于"| SAME["同节点 Pod 通信"]
NEIGH -->|"用于"| CROSS["跨节点通信"]
函数对比:
| 函数 | 作用 | 跳转目标 |
|---|---|---|
bpf_redirect_peer |
同节点 Pod 间 redirect | 直接到目的 Pod 的 eth0 |
bpf_redirect_neigh |
跨节点通信 redirect | 从 lxc 网卡到物理网卡 |
内核版本影响¶
| 内核版本 | 可用函数 | 跳转能力 |
|---|---|---|
| < 5.10 | bpf_redirect |
只能跳到 lxc 网卡 |
| ≥ 5.10 | bpf_redirect_peer |
直接跳到 Pod 的 eth0 |
[!NOTE] 为什么需要 5.10+ 内核:
bpf_redirect_peer在 Linux 5.10 引入- 它允许直接跳转到 veth pair 的对端(即 Pod 的 eth0)
- 这使得数据包完全绕过宿主机协议栈
10.3 同节点 Pod 通信¶
场景描述¶
Pod A (10.0.2.253) → Pod B (10.0.2.141)
两个 Pod 位于同一节点,使用 eBPF Host Routing
数据包流程¶
sequenceDiagram
participant PodA as Pod A<br/>eth0
participant LXC_A as lxc-A<br/>TC Hook
participant LXC_B as lxc-B
participant PodB as Pod B<br/>eth0
Note over PodA: 发送 ICMP Request
PodA->>LXC_A: 数据包到达 lxc-A
Note over LXC_A: TC Hook 调用<br/>bpf_redirect_peer
LXC_A->>PodB: 直接 redirect 到 Pod B 的 eth0
Note over PodB: 收到 ICMP Request<br/>(绕过 lxc-B!)
Note over PodB: 发送 ICMP Reply
PodB->>LXC_B: 数据包到达 lxc-B
Note over LXC_B: TC Hook 调用<br/>bpf_redirect_peer
LXC_B->>PodA: 直接 redirect 到 Pod A 的 eth0
Note over PodA: 收到 ICMP Reply<br/>(绕过 lxc-A!)
抓包验证¶
# 在 lxc-A(Pod A 对应的网卡)抓包
$ tcpdump -pne -i lxc-xxxx icmp
# 结果:只能看到 ICMP Request,没有 Reply!
# 原因:Reply 通过 bpf_redirect_peer 直接送到 Pod A 的 eth0
# 在 Pod A 内部抓包
$ tcpdump -pne -i eth0 icmp
# 结果:ICMP Request 和 Reply 都有!
[!WARNING] 抓包"诡异"现象:
- lxc 网卡上只能看到"去的包"(Request)
- "回的包"(Reply)直接 redirect 到 Pod 的 eth0
- 这是 eBPF Host Routing 的正常行为!
10.4 跨节点 Pod 通信¶
场景描述¶
Pod A (10.0.2.253, Node 1) → Pod C (10.0.1.193, Node 2)
两个 Pod 位于不同节点
数据包流程¶
sequenceDiagram
participant PodA as Pod A<br/>eth0
participant LXC_A as lxc-A<br/>TC Hook
participant ETH1 as Node1 eth0
participant ETH2 as Node2 eth0<br/>TC Hook
participant PodC as Pod C<br/>eth0
Note over PodA: 发送 ICMP Request
PodA->>LXC_A: 数据包到达 lxc-A
Note over LXC_A: TC Hook 调用<br/>bpf_redirect_neigh
LXC_A->>ETH1: redirect 到物理网卡<br/>(绕过协议栈路由!)
ETH1->>ETH2: 物理网络传输
Note over ETH2: TC Hook 调用<br/>bpf_redirect_peer
ETH2->>PodC: redirect 到 Pod C 的 eth0
Note over PodC: 收到 ICMP Request
抓包验证¶
# 在 Node1 的 lxc-A 抓包
$ tcpdump -pne -i lxc-xxxx icmp
# 只有 ICMP Request,没有 Reply
# 在 Node1 的 eth0 抓包
$ tcpdump -pne -i eth0 icmp
# Request 和 Reply 都有!
# 说明:
# - 发出的包经过 lxc 网卡
# - 返回的包直接 redirect 到 Pod 的 eth0,不经过 lxc
10.5 TC Hook 位置与方向¶
背景¶
理解 TC Hook 的位置和方向,是理解 Cilium 数据平面的关键。
四个关键 Hook¶
graph TD
subgraph Pod["Pod 网络空间"]
ETH0["eth0"]
end
subgraph Host["宿主机网络空间"]
LXC["lxc 网卡"]
CILIUM_HOST["cilium_host"]
ETH["物理网卡 eth0"]
end
ETH0 <-->|"veth pair"| LXC
LXC -->|"from_container<br/>(TC ingress)"| PROCESS["eBPF 处理"]
ETH -->|"from_netdev<br/>(TC ingress)"| PROCESS
PROCESS -->|"to_netdev<br/>(TC egress)"| ETH
PROCESS -->|"to_container"| ETH0
Hook 说明:
| Hook 名称 | 位置 | 方向 | 作用 |
|---|---|---|---|
from_container |
lxc 网卡 | ingress | 处理 Pod 发出的包 |
to_container |
调用函数 | - | 发往 Pod 的包 |
to_netdev |
物理网卡 | egress | 发往外部的包 |
from_netdev |
物理网卡 | ingress | 收到外部的包 |
方向判断口诀¶
Ingress(进入):数据包进入某个网卡
Egress(发出):数据包从某个网卡发出
从 Pod 出来 → lxc ingress → from_container
发往外部网络 → eth0 egress → to_netdev
从外部收包 → eth0 ingress → from_netdev
10.6 Socket LB 机制¶
背景¶
在 eBPF Host Routing 模式下,Service 访问也通过 eBPF 实现(而非 iptables)。
原理¶
sequenceDiagram
participant App as 应用
participant Socket as Socket 层<br/>eBPF Hook
participant Pod as 目的 Pod
Note over App: curl ClusterIP:Port
App->>Socket: 发起连接
Note over Socket: eBPF 拦截<br/>查询 Service → Pod 映射
Socket->>Socket: 替换目的 IP:Port<br/>(Socket LB)
Socket->>Pod: 直接连接 Pod IP
与 iptables 的对比¶
| 特性 | iptables (kube-proxy) | Socket LB (eBPF) |
|---|---|---|
| DNAT 位置 | 宿主机协议栈 | Pod 的 Socket 层 |
| 第一跳目的 IP | 仍是 ClusterIP | 已是 Pod IP |
| SNAT | 需要(externalTrafficPolicy: Cluster) | 可不需要 |
| 抓包看到的 IP | ClusterIP → Pod IP 转换 | 直接是 Pod IP |
抓包验证¶
# 访问 Service
$ curl 172.18.0.2:32000
# 在 Pod 内抓包
$ tcpdump -pne -i eth0 port 80
# 结果:目的 IP 直接是 Pod IP,不是 NodePort IP!
[!TIP] Socket LB 的意义:
- 在 Pod 发包前就完成 Service → Pod 的解析
- 数据包从发出的第一跳就是 Pod IP
- 不需要经过宿主机做 DNAT
10.7 eBPF Host Routing 的限制¶
不支持的场景¶
| 功能 | 是否支持 | 原因 |
|---|---|---|
| IPsec 加密 | ❌ | 需要经过协议栈处理 |
| WireGuard 加密 | ❌ | 需要经过协议栈处理 |
| 某些 NetworkPolicy | 部分 | 需要协议栈介入 |
[!CAUTION] 如果需要加密:
使用 IPsec 或 WireGuard 时,不能启用 eBPF Host Routing。 流量需要进入协议栈进行加解密处理。
10.8 验证 eBPF Host Routing 状态¶
# 查看 Cilium 状态
$ cilium status --verbose
# 关键字段
KubeProxyReplacement: Strict
Host Routing: BPF # ← 这里显示 BPF 表示已启用
Masquerading: BPF
| Host Routing 值 | 含义 |
|---|---|
Legacy |
传统模式,使用协议栈路由 |
BPF |
eBPF 模式,绕过协议栈 |
📝 章节小结¶
本章介绍了 Cilium 最高级模式 —— eBPF Host Routing:
- 核心函数:
bpf_redirect_peer:同节点 Pod 间直接跳转-
bpf_redirect_neigh:跨节点通信,lxc → 物理网卡 -
同节点通信:
- lxc 网卡只能看到"去的包"
-
"回的包"直接 redirect 到 Pod 的 eth0
-
跨节点通信:
- 发出的包:lxc → bpf_redirect_neigh → 物理网卡
-
收到的包:物理网卡 → bpf_redirect_peer → Pod eth0
-
TC Hook:
from_container:lxc ingress,处理 Pod 发出的包from_netdev:物理网卡 ingress,处理收到的包-
to_netdev:物理网卡 egress,处理发出的包 -
Socket LB:
- Service 解析在 Pod 的 Socket 层完成
-
第一跳目的 IP 就是 Pod IP
-
限制:
- 不支持 IPsec/WireGuard 加密
- 需要内核 ≥ 5.10
[!TIP] 学习要点:
- 理解两个 eBPF 函数的作用
- 在不同接口抓包,验证 redirect 行为
- 用
cilium status确认 Host Routing: BPF[!IMPORTANT] 模式对比总结:
模式 Service Pod 路由 性能 with kube-proxy iptables eBPF redirect 一般 kube-proxy replacement eBPF eBPF redirect 较好 eBPF Host Routing Socket LB bpf_redirect_peer/neigh 最优
第11章 Cilium-VxLAN 模式¶
🎯 学习目标¶
- 理解 VxLAN 诞生的背景与解决的问题
- 掌握 VxLAN 报文结构
- 理解 VTEP、VNI 等核心概念
- 掌握 Linux 下创建 VxLAN 设备的方法
- 理解 Overlay vs Underlay 网络
11.1 VxLAN 诞生背景¶
背景¶
VxLAN(Virtual eXtensible Local Area Network)是由 VMware、Cisco 等厂商提出的一种 Overlay 网络技术。
解决的问题¶
graph TD
subgraph Problem["传统问题"]
P1["数据中心虚机迁移"]
P2["迁移后 IP/MAC 需保持不变"]
P3["跨三层网络无法实现"]
end
subgraph Solution["VxLAN 方案"]
S1["将三层网络抽象为 '大二层'"]
S2["IP/MAC 不变迁移"]
end
Problem --> Solution
核心场景:
| 场景 | 问题 | VxLAN 方案 |
|---|---|---|
| 虚机迁移 | 跨机房后 IP/MAC 变化 | 封装后传输,IP/MAC 保持 |
| 多租户隔离 | VLAN ID 只有 4094 个 | VNI 支持 1600 万+ |
| 跨三层通信 | 二层网络无法跨越路由器 | Overlay 隧道穿越 |
大二层概念¶
graph LR
subgraph DC1["数据中心 A"]
VM1["虚机<br/>10.1.1.2"]
VTEP1["VTEP"]
end
subgraph Transport["传输网络<br/>(复杂的路由网络)"]
R1["路由器"] --- R2["路由器"]
R2 --- R3["路由器"]
end
subgraph DC2["数据中心 B"]
VTEP2["VTEP"]
VM2["虚机<br/>10.1.1.2<br/>迁移后"]
end
VTEP1 --> Transport
Transport --> VTEP2
classDef vtep fill:#f9f,stroke:#333
class VTEP1,VTEP2 vtep
[!NOTE] 大二层的本质:
将中间复杂的三层路由网络抽象成一个"大交换机"。 无论底层网络多复杂,对上层应用来说就像在同一个二层局域网内。
11.2 VxLAN 报文结构¶
封装原理¶
graph TD
subgraph Original["原始数据包"]
O_ETH["原始 MAC 层"]
O_IP["原始 IP 层"]
O_TCP["原始 TCP/UDP"]
O_DATA["应用数据"]
end
subgraph VxLAN["VxLAN 封装后"]
V_ETH["外层 MAC"]
V_IP["外层 IP"]
V_UDP["UDP 8472"]
V_HEADER["VxLAN Header<br/>(含 VNI)"]
V_INNER["原始完整数据包"]
end
Original --> V_INNER
报文各层说明¶
| 层级 | 内容 | 说明 |
|---|---|---|
| 外层 MAC | VTEP 的 MAC 地址 | 源/目的都是 VTEP 设备 |
| 外层 IP | VTEP 的 IP 地址 | 如节点的物理网卡 IP |
| UDP | 端口 8472 | Cilium/Flannel 使用 8472 |
| VxLAN Header | 8 字节,含 VNI | 标识隧道/租户 |
| 内层包 | 原始完整以太网帧 | Pod 间通信的真实数据 |
+----------------+----------------+---------------+
| 外层 MAC | 外层 IP | UDP 8472 |
+----------------+----------------+---------------+
| VxLAN Header (VNI) | 原始数据包 |
+----------------------+-------------------------+
[!IMPORTANT] VxLAN 的核心思想:
把原始数据包作为外层 UDP 的 Payload。 相当于把一个信封(原始包)塞进另一个信封(VxLAN 包)发送。
11.3 核心概念¶
VTEP¶
VTEP(VxLAN Tunnel End Point)是 VxLAN 隧道的端点设备。
graph LR
subgraph Node1["Node 1"]
POD1["Pod A"]
VTEP1["VTEP<br/>cilium_vxlan"]
ETH1["eth0<br/>172.18.0.2"]
end
subgraph Node2["Node 2"]
ETH2["eth0<br/>172.18.0.3"]
VTEP2["VTEP<br/>cilium_vxlan"]
POD2["Pod B"]
end
POD1 --> VTEP1
VTEP1 --> ETH1
ETH1 <-->|"VxLAN 隧道"| ETH2
ETH2 --> VTEP2
VTEP2 --> POD2
VTEP 的职责:
| 方向 | 操作 | 说明 |
|---|---|---|
| 发送 | 封装 | 添加外层 MAC/IP/UDP/VxLAN Header |
| 接收 | 解封装 | 剥离外层,露出原始包 |
VNI¶
VNI(VxLAN Network Identifier)是 VxLAN 的网络标识。
| 对比 | VLAN ID | VNI |
|---|---|---|
| 位数 | 12 位 | 24 位 |
| 可用数量 | 4094 个 | 约 1600 万个 |
| 用途 | 传统交换机隔离 | 大规模多租户隔离 |
[!TIP] 在 Cilium 中:
VNI 的使用比较灵活,不像传统网络那样要求两端 VNI 严格匹配。 Cilium 可能会动态分配 VNI,这是虚拟化网络与传统网络的区别之一。
11.4 Overlay vs Underlay¶
概念对比¶
graph TD
subgraph Overlay["Overlay 网络"]
O1["Pod 网络"]
O2["VxLAN 隧道"]
O3["逻辑上的 '大二层'"]
end
subgraph Underlay["Underlay 网络"]
U1["物理网络"]
U2["节点间 IP 路由"]
U3["交换机/路由器"]
end
Overlay -.->|"运行在其上"| Underlay
| 特性 | Overlay | Underlay |
|---|---|---|
| 定义 | 构建在现有网络之上的逻辑网络 | 承载 Overlay 的物理网络 |
| 代表 | VxLAN、GRE、GENEVE | 数据中心交换机、路由器 |
| 优势 | 灵活、跨三层、多租户 | 性能高、延迟低 |
| 劣势 | 封装开销、MTU 减少 | 配置复杂、VLAN 数量有限 |
11.5 Linux 创建 VxLAN 设备¶
命令格式¶
ip link add <name> type vxlan \
id <VNI> \
dev <物理网卡> \
local <本端 VTEP IP> \
remote <对端 VTEP IP> \
dstport <端口>
示例¶
# 在 Node1 创建 VxLAN 设备
ip link add vxlan0 type vxlan \
id 100 \
dev eth0 \
local 192.168.1.10 \
remote 192.168.1.20 \
dstport 8472
# 启用设备
ip link set vxlan0 up
# 配置 IP
ip addr add 10.0.0.1/24 dev vxlan0
在对端 Node2 创建对称配置:
ip link add vxlan0 type vxlan \
id 100 \
dev eth0 \
local 192.168.1.20 \
remote 192.168.1.10 \
dstport 8472
ip link set vxlan0 up
ip addr add 10.0.0.2/24 dev vxlan0
验证¶
# 查看 VxLAN 设备
ip -d link show vxlan0
# 测试连通性
ping 10.0.0.2
# 抓包验证
tcpdump -pne -i eth0 udp port 8472
11.6 Cilium 的 VxLAN 实现¶
启用方式¶
# 默认就是 VxLAN 模式,或显式指定
helm install cilium cilium/cilium \
--set tunnel=vxlan # 或 geneve
Cilium 创建的设备¶
# 查看 Cilium 创建的 VxLAN 设备
$ ip -d link show cilium_vxlan
# 输出示例
cilium_vxlan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 ...
vxlan id 1 ... dstport 8472 ...
与 Native Routing 的对比¶
| 特性 | VxLAN 模式 | Native Routing 模式 |
|---|---|---|
| 跨节点通信 | VxLAN 封装 | 路由转发 |
| 底层网络要求 | 仅需 IP 可达 | 需要配置路由 |
| MTU | 减少(封装开销) | 保持原始 |
| 性能 | 略低(封装/解封装) | 较高 |
| 配置复杂度 | 低(默认即可) | 较高(需路由配置) |
📝 章节小结¶
本章介绍了 VxLAN 技术及其在 Cilium 中的应用:
- VxLAN 背景:
- 解决数据中心虚机迁移问题
- 支持大规模多租户(VNI 1600 万+)
-
将三层网络抽象为"大二层"
-
报文结构:
- 外层 MAC + IP + UDP(8472) + VxLAN Header
- 内层是原始完整以太网帧
-
原始包成为外层的 Payload
-
核心概念:
- VTEP:隧道端点,负责封装/解封装
-
VNI:网络标识,24 位,约 1600 万个
-
Overlay vs Underlay:
- Overlay 运行在 Underlay 之上
-
VxLAN 是典型的 Overlay 技术
-
Linux 创建 VxLAN:
- 使用
ip link add type vxlan命令 -
需要指定 VNI、local、remote、dstport
-
Cilium 实现:
- 默认使用 VxLAN 模式
- 创建
cilium_vxlan设备
[!TIP] 选择 VxLAN 的场景:
- 节点跨三层网络(不在同一二层)
- 底层网络配置受限
- 快速部署,不想配置路由
- 多租户隔离需求
[!IMPORTANT] VxLAN vs Native Routing 选择:
- 同二层网络 → Native Routing(性能更好)
- 跨三层网络 / 配置简单 → VxLAN
第12章 Containerlab 实现通用 VxLAN 环境¶
🎯 学习目标¶
- 掌握 Containerlab 搭建 VxLAN 实验环境
- 深入理解 VxLAN 数据包转发的三步走
- 理解 VxLAN 封装过程中 MAC 地址的变化
- 掌握 VxLAN 配置的关键参数
- 能够独立分析 VxLAN 抓包结果
12.1 实验拓扑¶
背景¶
Containerlab 是一个用于创建网络实验环境的工具,非常适合用来学习和验证网络概念。
拓扑图¶
graph LR
subgraph Node1["GW0 (网关0)"]
VXLAN0["vxlan0<br/>1.1.1.1/24"]
ETH2_0["eth2<br/>172.12.1.10"]
end
subgraph Node2["GW1 (网关1)"]
ETH2_1["eth2<br/>172.12.1.11"]
VXLAN1["vxlan0<br/>1.1.1.2/24"]
end
S0["Server0<br/>10.1.5.10"] --> Node1
ETH2_0 <-->|"物理连接"| ETH2_1
Node2 --> S1["Server1<br/>10.1.8.10"]
拓扑说明:
| 设备 | 接口 | IP 地址 | 说明 |
|---|---|---|---|
| Server0 | eth0 | 10.1.5.10 | 源端 Pod |
| GW0 | eth1 | 10.1.5.1 | Server0 的网关 |
| GW0 | eth2 | 172.12.1.10 | 物理互联接口 |
| GW0 | vxlan0 | 1.1.1.1 | VxLAN 隧道端点 |
| GW1 | vxlan0 | 1.1.1.2 | VxLAN 隧道端点 |
| GW1 | eth2 | 172.12.1.11 | 物理互联接口 |
| GW1 | eth1 | 10.1.8.1 | Server1 的网关 |
| Server1 | eth0 | 10.1.8.10 | 目的端 Pod |
12.2 关键配置¶
VxLAN 接口配置¶
# 在 GW0 上配置
# 1. 创建 vxlan0 接口
ip link add vxlan0 type vxlan \
id 10 \
remote 172.12.1.11 \
dstport 8472
# 2. 配置 IP 地址
ip addr add 1.1.1.1/24 dev vxlan0
# 3. 启用接口
ip link set vxlan0 up
# 4. 添加路由
ip route add 10.1.8.0/24 via 1.1.1.2 dev vxlan0
配置参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
id |
10 | VNI ID,两端必须一致 |
remote |
172.12.1.11 | 对端 VTEP 的物理 IP |
local |
(默认) | 本端物理 IP,默认使用同网段接口 |
dstport |
8472 | VxLAN 目的端口 |
为什么需要两套地址?¶
graph TD
subgraph Virtual["虚拟地址(VxLAN 接口)"]
V1["1.1.1.1 (vxlan0)"]
V2["1.1.1.2 (vxlan0)"]
end
subgraph Physical["物理地址(eth2 接口)"]
P1["172.12.1.10"]
P2["172.12.1.11"]
end
V1 -.->|"借助"| P1
V2 -.->|"借助"| P2
P1 <-->|"物理连接"| P2
[!IMPORTANT] 为什么需要物理地址:
VxLAN 接口是虚拟接口,没有物理网线连接。 数据包实际上需要通过物理接口(172.12.1.x)发送。 虚拟接口"借助"物理接口完成数据传输。
12.3 数据包转发三步走¶
核心概念¶
VxLAN 数据包转发可以分解为三个步骤:
sequenceDiagram
participant Server as Server0<br/>10.1.5.10
participant GW as GW0
participant VXLAN as vxlan0 接口
participant ETH as eth2 接口
participant Remote as 远端
rect rgb(200, 230, 200)
Note over Server,GW: 第一步:引导到网关
Server->>GW: 默认路由 via 10.1.5.1
end
rect rgb(200, 200, 230)
Note over GW,VXLAN: 第二步:路由引入 VxLAN 接口
GW->>VXLAN: 路由 10.1.8.0/24 via 1.1.1.2 dev vxlan0
end
rect rgb(230, 200, 200)
Note over VXLAN,Remote: 第三步:VxLAN 封装
VXLAN->>ETH: 封装 VxLAN 头部
ETH->>Remote: 发送到 remote 172.12.1.11
end
三步详解¶
| 步骤 | 发生位置 | 关键操作 | 路由条目 |
|---|---|---|---|
| 第一步 | Server0 | 默认路由引导到网关 | default via 10.1.5.1 |
| 第二步 | GW0 | 路由引入 VxLAN 接口 | 10.1.8.0/24 via 1.1.1.2 dev vxlan0 |
| 第三步 | vxlan0 | VxLAN 封装 + 发送 | 查询 remote/local 配置 |
[!TIP] 通用套路:
这三步是所有 Overlay 网络的通用模式:
- 引导数据包到 VTEP 设备
- 通过路由让数据包"经过" VxLAN 接口
- VxLAN 接口进行封装并发送
无论是 VxLAN、IPIP、GRE,都是这个套路!
12.4 抓包分析¶
抓包命令¶
# 在 GW0 的 eth2 接口抓包
tcpdump -pne -i eth2 udp port 8472 -w vxlan.pcap
抓包结果分析¶
外层 MAC: aa:ce:ab:xx:xx:xx -> 9a:83:2a:xx:xx:xx
外层 IP: 172.12.1.10 -> 172.12.1.11
UDP: 随机端口 -> 8472
VxLAN: VNI = 10
内层 MAC: 76:29:63:xx:xx:xx -> 16:26:36:xx:xx:xx
内层 IP: 10.1.5.10 -> 10.1.8.10
MAC 地址来源¶
graph TD
subgraph Outer["外层 MAC"]
O_SRC["源 MAC: eth2 的 MAC"]
O_DST["目的 MAC: 对端 eth2 的 MAC"]
end
subgraph Inner["内层 MAC"]
I_SRC["源 MAC: vxlan0 的 MAC<br/>(不是 Server0 的!)"]
I_DST["目的 MAC: 对端 vxlan0 的 MAC"]
end
[!WARNING] 内层 MAC 不是 Server 的:
内层源 MAC 是 vxlan0 接口的 MAC 地址,而不是 Server0 的 MAC。 这是因为数据包"经过"了 vxlan0 接口,MAC 地址会被替换。
12.5 为什么下一跳是 1.1.1.2?¶
问题¶
为什么路由是 10.1.8.0/24 via 1.1.1.2 而不是 via 172.12.1.11?
答案¶
graph TD
A["包发往 172.12.1.11"] --> B{"172.12.1.11 是什么接口?"}
B -->|"普通网卡 eth2"| C["不会做 VxLAN 封装<br/>包直接丢弃!"]
D["包发往 1.1.1.2"] --> E{"1.1.1.2 是什么接口?"}
E -->|"VxLAN 接口 vxlan0"| F["触发 VxLAN 封装<br/>正确转发!"]
[!CAUTION] 关键点:
- 下一跳必须指向 VxLAN 接口的 IP(1.1.1.2)
- 只有经过 VxLAN 接口,才会触发封装
- 如果直接指向物理 IP(172.12.1.11),包不会被封装,无法到达目的地
12.6 VxLAN 接口的 IP 地址作用¶
问题¶
vxlan0 接口的 IP 地址(1.1.1.1/1.1.1.2)在通信过程中真的用到了吗?
分析¶
| 观察点 | 结果 |
|---|---|
| 外层 IP | 172.12.1.10 → 172.12.1.11(物理接口 IP) |
| 内层 IP | 10.1.5.10 → 10.1.8.10(Server IP) |
| vxlan0 IP | 没有出现在数据包中 |
那为什么还需要配置?¶
- 路由匹配:下一跳 1.1.1.2 需要有对应的接口
- ARP 学习:需要知道 1.1.1.2 对应的 MAC 地址
- 接口可达性:用于判断 VxLAN 隧道是否可用
[!NOTE] vxlan0 的 IP 地址主要用于路由决策和 ARP 学习, 而不是直接用于数据包封装。
12.7 实践练习¶
练习 1:使用 ip 命令搭建 VxLAN¶
在两台 Linux 机器上搭建 VxLAN 隧道:
# 机器 A (192.168.1.10)
ip link add vxlan0 type vxlan id 100 remote 192.168.1.20 dstport 8472
ip addr add 10.0.0.1/24 dev vxlan0
ip link set vxlan0 up
# 机器 B (192.168.1.20)
ip link add vxlan0 type vxlan id 100 remote 192.168.1.10 dstport 8472
ip addr add 10.0.0.2/24 dev vxlan0
ip link set vxlan0 up
# 测试
ping 10.0.0.2
练习 2:观察 ARP 过程¶
# 清除 ARP 缓存
ip neigh del 10.0.0.2 dev vxlan0
# 抓包观察 ARP
tcpdump -pne -i eth0 udp port 8472
# 触发通信
ping 10.0.0.2
📝 章节小结¶
本章通过 Containerlab 实验环境,深入理解了 VxLAN 的工作原理:
- 实验拓扑:
- Server → GW (VTEP) → 物理网络 → GW (VTEP) → Server
-
需要两套地址:虚拟地址 + 物理地址
-
VxLAN 配置:
- VNI ID 两端一致
- remote 指向对端物理 IP
-
dstport 通常为 8472
-
三步转发:
- 第一步:引导到网关(默认路由)
- 第二步:路由引入 VxLAN 接口
-
第三步:VxLAN 封装并发送
-
MAC 地址变化:
- 内层 MAC:vxlan0 接口的 MAC
-
外层 MAC:物理接口的 MAC
-
下一跳设置:
- 必须指向 VxLAN 接口的 IP
- 不能直接指向物理 IP
[!IMPORTANT] Overlay 网络通用模式:
无论是 VxLAN、IPIP 还是 GRE,都遵循相同的三步走:
- 引导数据包到隧道设备
- 路由让包"经过"隧道接口
- 隧道接口进行封装
掌握这个模式,所有 Overlay 网络都能理解!
第13章 Cilium-VxLAN-DataPath¶
🎯 学习目标¶
- 理解 Cilium VxLAN 与传统 VxLAN 的差异
- 掌握 Cilium eBPF Map 存储隧道信息的方式
- 理解 to_overlay / from_overlay eBPF Hook
- 掌握 Cilium Identity 概念
- 学会使用 cilium monitor 调试数据路径
13.1 Cilium VxLAN 概述¶
背景¶
Cilium 的 VxLAN 模式是其默认的 Overlay 方案,但实现方式与传统 VxLAN 有显著差异。
与传统 VxLAN 的对比¶
graph LR
subgraph Traditional["传统 VxLAN"]
T1["vxlan0 接口<br/>有 IP 地址"]
T2["路由引入流量"]
T3["VNI 固定"]
end
subgraph Cilium["Cilium VxLAN"]
C1["cilium_vxlan<br/>无 IP 地址"]
C2["eBPF redirect 引入"]
C3["VNI = Identity ID"]
end
| 特性 | 传统 VxLAN | Cilium VxLAN |
|---|---|---|
| 接口 IP | 有(用于 ARP 学习) | 无 |
| 流量引入方式 | 路由(via VxLAN 接口) | eBPF redirect |
| VNI 来源 | 静态配置 | Cilium Identity ID |
| 隧道信息存储 | 内核配置 | eBPF Map |
| MAC 地址 | 替换为 vxlan0 的 MAC | 保留原始 Pod 的 MAC |
[!IMPORTANT] Cilium VxLAN 的核心差异:
- 不依赖路由,使用 eBPF redirect 引入流量
- 不需要在 vxlan 接口配置 IP 地址
- VNI ID 使用 Cilium Identity,而非静态配置
13.2 cilium_vxlan 设备¶
查看设备¶
# 查看 VxLAN 类型的设备
$ ip -d link show type vxlan
cilium_vxlan: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
vxlan id 1 ... dstport 8472 nolearning ...
奇怪之处¶
# 传统 VxLAN 会显示 remote、local 等信息
# Cilium 的 cilium_vxlan 几乎没有这些信息!
$ ip -d link show cilium_vxlan
# 没有 remote
# 没有 local
# 只有基本的 vxlan 配置
[!NOTE] 为什么没有隧道信息?
因为 Cilium 的隧道信息存储在 eBPF Map 中,而不是内核的 VxLAN 模块配置。
13.3 eBPF Map 存储隧道信息¶
查看隧道信息¶
# 查看 Cilium 的隧道 Map
$ cilium bpf tunnel list
TUNNEL VALUE
10.0.2.0/24 172.18.0.3
10.0.0.0/24 172.18.0.2
输出解释:
| 字段 | 含义 |
|---|---|
TUNNEL |
目标 Pod CIDR |
VALUE |
对端 VTEP 的 IP(节点 IP) |
隧道 Map 结构¶
graph TD
subgraph Map["eBPF Tunnel Map"]
E1["10.0.2.0/24 → 172.18.0.3"]
E2["10.0.0.0/24 → 172.18.0.2"]
end
POD["Pod 发包<br/>目标: 10.0.2.x"]
POD --> E1
E1 --> VTEP["封装目标: 172.18.0.3"]
[!TIP] eBPF Map 的优势:
- 查询速度快(O(1) 复杂度)
- 可动态更新,无需重启
- 与 eBPF 程序紧密集成
13.4 Cilium Identity¶
概念¶
Cilium 为每个 Pod 分配一个 Identity ID,用于:
- 安全策略
- VxLAN 封装的 VNI
# 查看 Pod 的 Identity
$ cilium endpoint list
ENDPOINT IDENTITY LABELS STATUS
238 68863 k8s:app=web ready
145 68863 k8s:app=web ready
Identity vs Endpoint¶
graph TD
subgraph Identity["Identity 68863"]
E1["Endpoint 238<br/>Pod A"]
E2["Endpoint 145<br/>Pod B"]
end
LABEL["共同标签:<br/>k8s:app=web"] --> Identity
| 概念 | 粒度 | 用途 |
|---|---|---|
| Identity | 一类 Pod(相同标签) | 安全策略、VNI |
| Endpoint | 单个 Pod | 具体路由、状态 |
[!IMPORTANT] VNI = Identity ID
VxLAN 封装时,VNI 字段使用的是目标 Pod 的 Identity ID。 这使得同一类 Pod 共享相同的 VNI,便于策略管理。
13.5 数据路径分析¶
从 Pod 到 VxLAN 封装¶
sequenceDiagram
participant Pod as Pod eth0
participant LXC as lxc 网卡
participant eBPF as eBPF Hook<br/>from_container
participant VXLAN as cilium_vxlan
participant Overlay as to_overlay Hook
participant Stack as 内核协议栈
participant ETH as eth0
Pod->>LXC: 数据包
LXC->>eBPF: TC Hook 处理
Note over eBPF: 查询 Tunnel Map<br/>获取 remote IP
eBPF->>VXLAN: bpf_redirect
Note over VXLAN: to_overlay Hook<br/>VxLAN 封装
VXLAN->>Stack: 封装后的 UDP 包
Note over Stack: 第二次协议栈处理
Stack->>ETH: 发送
关键点¶
- 不走路由:流量通过
bpf_redirect直接到 cilium_vxlan - 保留原始 MAC:因为没有经过路由替换 MAC
- 两次协议栈处理:
- 第一次:Pod 内部协议栈
- 第二次:VxLAN 封装后,作为普通 UDP 包处理
13.6 eBPF Hook 说明¶
to_overlay¶
graph LR
LXC["lxc 网卡<br/>from_container"] -->|"bpf_redirect"| VXLAN["cilium_vxlan"]
VXLAN -->|"to_overlay"| ENC["VxLAN 封装"]
ENC --> STACK["内核协议栈"]
to_overlay 职责:
- 接收从 lxc 网卡 redirect 过来的包
- 调用内核 VxLAN 模块进行封装
- 但不替换 MAC 地址(与传统方式不同)
from_overlay¶
graph LR
ETH["eth0<br/>from_netdev"] --> VXLAN["cilium_vxlan"]
VXLAN -->|"from_overlay"| DEC["VxLAN 解封装"]
DEC -->|"bpf_redirect_peer"| POD["目标 Pod eth0"]
from_overlay 职责:
- 处理接收到的 VxLAN 包
- 解封装
- 直接 redirect 到目标 Pod(跳过 lxc 网卡)
13.7 抓包分析¶
VNI 的变化¶
# 抓包观察 VNI
tcpdump -pne -i eth0 udp port 8472 -w cilium_vxlan.pcap
抓包结果:
# 去程(Request)
VNI: 68863
# 回程(Reply)
VNI: 18705
[!WARNING] VNI 去回不一致:
- 去程 VNI = 目标 Pod 的 Identity
- 回程 VNI = 源 Pod 的 Identity
- 这与传统 VxLAN "两端 VNI 必须一致" 的规则不同!
MAC 地址分析¶
# 内层 MAC
源 MAC: Pod A 的 eth0 MAC(保留原始)
目的 MAC: 目标 Pod 的 MAC
# 外层 MAC
源 MAC: eth0 的 MAC
目的 MAC: 对端 eth0 的 MAC
13.8 使用 cilium monitor 调试¶
命令¶
# 进入 Cilium Agent Pod
kubectl exec -it -n kube-system cilium-xxxx -- bash
# 监控所有流量
cilium monitor -v
# 只监控特定 Pod
cilium monitor --related-to <endpoint-id>
输出分析¶
# 第一次协议栈处理(Pod 内部)
-> endpoint 238 flow 0x1234 identity 68863
ICMP 10.0.1.112 -> 10.0.2.237
# VxLAN 封装后
<- host flow 0x1234
UDP 172.18.0.2:random -> 172.18.0.3:8472
# 收到回复
-> endpoint 238 flow 0x1234
ICMP 10.0.2.237 -> 10.0.1.112 (reply)
关键信息:
identity:Pod 的 Identity IDendpoint:Pod 的 Endpoint ID- 两次处理:先是 ICMP,后是 UDP(封装后)
13.9 Cilium VxLAN vs 传统 VxLAN 总结¶
| 方面 | 传统 VxLAN | Cilium VxLAN |
|---|---|---|
| 流量引入 | 路由(下一跳是 VxLAN 接口 IP) | eBPF redirect |
| VxLAN 接口 IP | 必须配置 | 不需要 |
| VNI | 静态配置,两端一致 | 动态(Identity),去回可不同 |
| 隧道信息存储 | 内核 VxLAN 模块 | eBPF Map |
| 内层 MAC | vxlan0 接口的 MAC | 原始 Pod 的 MAC |
| 抓包 | 在 vxlan0 能看到完整流量 | 蹦蹦跳跳,需多点抓包 |
📝 章节小结¶
本章深入分析了 Cilium VxLAN 的数据路径:
- 与传统 VxLAN 的差异:
- 不依赖路由,使用 eBPF redirect
- cilium_vxlan 接口没有 IP 地址
-
VNI 使用 Identity ID
-
eBPF Map 存储隧道信息:
cilium bpf tunnel list查看-
Pod CIDR → 对端节点 IP
-
Cilium Identity:
- 一类 Pod 共享一个 Identity
-
用于安全策略和 VNI
-
数据路径:
- lxc → bpf_redirect → cilium_vxlan
- to_overlay Hook 封装
-
两次协议栈处理
-
调试方法:
cilium monitor监控流量- 多点抓包分析
[!TIP] 学习建议:
- 先掌握传统 VxLAN 的"朴实"玩法
- 再理解 Cilium 的 eBPF 增强方式
- 使用 cilium monitor + 抓包 验证理解
[!IMPORTANT] Cilium VxLAN 数据路径特点:
- 流量"蹦蹦跳跳",不是线性经过每个设备
- 回程包可能跳过某些设备(如 lxc 网卡)
- 需要多点抓包 + cilium monitor 才能完整理解
第14章 IPSec 手工实现¶
🎯 学习目标¶
- 理解 IPSec 的基本概念和作用
- 掌握 IPSec 的两种模式:隧道模式和传输模式
- 理解 SA(安全联盟)和 SPI(安全参数索引)
- 学会使用 ip xfrm 命令手工配置 IPSec
- 掌握 Wireshark 解密 ESP 包的方法
14.1 IPSec 基础¶
背景¶
IPSec(Internet Protocol Security)是一个在 IP 层提供安全性的协议族,主要用于:
- 加密数据报文
- 保护数据机密性
- 防止中间人攻击
IPSec 的特性¶
graph TD
subgraph Features["IPSec 特性"]
F1["机密性<br/>数据加密"]
F2["完整性<br/>数据未被篡改"]
F3["抗重放<br/>防止重复攻击"]
F4["来源认证<br/>验证发送者身份"]
end
| 特性 | 说明 |
|---|---|
| 机密性 | 数据加密,抓包看不到明文内容 |
| 完整性 | 校验数据是否被篡改 |
| 抗重放 | 防止攻击者重复发送旧数据包 |
| 来源认证 | 验证数据确实来自声称的发送者 |
[!NOTE] 与 TLS 的区别:
- TLS 工作在传输层(如 HTTPS)
- IPSec 工作在网络层(IP 层)
- IPSec 对上层应用透明
14.2 IPSec 模式¶
两种模式对比¶
graph LR
subgraph Transport["传输模式"]
T1["IP Header"] --> T2["ESP Header"]
T2 --> T3["Payload"]
end
subgraph Tunnel["隧道模式"]
N1["New IP Header"] --> N2["ESP Header"]
N2 --> N3["Original IP Header"]
N3 --> N4["Payload"]
end
| 模式 | 适用场景 | IP 头数量 | 典型应用 |
|---|---|---|---|
| 传输模式 | 点对点直接通信 | 1 个 | 两台主机直接加密 |
| 隧道模式 | 网关间通信 | 2 个(内外) | CNI Pod 间通信、VPN |
[!IMPORTANT] Cilium 使用隧道模式:
因为 Pod 间通信需要经过节点(网关),所以 Cilium IPSec 使用隧道模式。 数据包结构:
[新 IP][ESP][原始 IP][Payload]
14.3 ESP 封装结构¶
ESP 头部¶
+-------------------+
| IP Header | ← 新的外层 IP(隧道模式)
+-------------------+
| ESP Header | ← SPI + Sequence Number
+-------------------+
| Original IP | ← 原始 IP 头(加密)
+-------------------+
| Payload | ← 原始数据(加密)
+-------------------+
| ESP Trailer | ← Padding + Next Header
+-------------------+
| ESP Auth | ← 认证数据(可选)
+-------------------+
ESP(Encapsulating Security Payload):封装安全载荷
| 字段 | 说明 |
|---|---|
| SPI | Security Parameter Index,安全参数索引 |
| Sequence Number | 序列号,用于抗重放 |
| Payload | 加密后的原始数据 |
| Auth Data | 认证数据(可选) |
14.4 核心概念¶
SA(Security Association)¶
安全联盟:定义了 IPSec 通信的参数
# 查看 SA
$ ip xfrm state
src 192.168.2.71 dst 192.168.2.73
proto esp spi 0x00000978 ...
enc cbc(aes) 0x2668a9a6...
SA 包含的信息:
| 参数 | 说明 |
|---|---|
src/dst |
源和目的 IP |
proto |
协议(ESP) |
spi |
安全参数索引 |
mode |
模式(tunnel/transport) |
enc |
加密算法和密钥 |
SPI(Security Parameter Index)¶
安全参数索引:32 位数值,用于标识 SA
# 抓包中查看 SPI
# ESP Header 中包含 SPI 字段
SPI: 0x00000978
[!TIP] 解密 ESP 包需要:
- SPI(标识使用哪个 SA)
- 加密算法
- 密钥(Key)
14.5 ip xfrm 命令¶
命令框架¶
# xfrm = transform(转换)
# 用于配置 IPSec
# 查看 State(安全联盟)
ip xfrm state
# 查看 Policy(安全策略)
ip xfrm policy
# 简写
ip x s # state
ip x p # policy
# 清空配置
ip xfrm state flush
ip xfrm policy flush
State vs Policy¶
| 对象 | 作用 | 包含内容 |
|---|---|---|
| State | 定义如何加密 | SPI、算法、密钥 |
| Policy | 定义哪些流量需要加密 | 源/目的、方向 |
14.6 手工配置 IPSec¶
实验拓扑¶
graph LR
subgraph Node1["节点 1 (192.168.2.71)"]
NS1["ns1<br/>1.1.1.2"]
VETH1["veth"]
end
subgraph Node2["节点 2 (192.168.2.73)"]
VETH2["veth"]
NS2["ns1<br/>1.1.2.2"]
end
NS1 --> VETH1
VETH1 <-->|"IPSec 隧道"| VETH2
VETH2 --> NS2
步骤 1:创建网络命名空间¶
# 节点 1 (192.168.2.71)
ip netns add ns1
ip link add veth type veth peer name ceth0
ip link set ceth0 netns ns1
# 配置 IP
ip netns exec ns1 ip addr add 1.1.1.2/24 dev ceth0
ip netns exec ns1 ip link set ceth0 up
ip netns exec ns1 ip link set lo up
# 添加路由
ip netns exec ns1 ip route add default via 1.1.1.1 dev ceth0
步骤 2:添加路由¶
# 节点 1:去往 1.1.2.0/24 走 192.168.2.73
ip route add 1.1.2.0/24 via 192.168.2.73 src 192.168.2.71
# 节点 2:去往 1.1.1.0/24 走 192.168.2.71
ip route add 1.1.1.0/24 via 192.168.2.71 src 192.168.2.73
步骤 3:配置 IPSec State¶
# 节点 1:添加 State
# 方向:71 -> 73(出)
ip xfrm state add \
src 192.168.2.71 dst 192.168.2.73 \
proto esp spi 0x00000978 \
mode tunnel \
enc "cbc(aes)" 0x2668a9a6b3c4d5e6f7a8b9c0d1e2f3a4
# 方向:73 -> 71(入)
ip xfrm state add \
src 192.168.2.73 dst 192.168.2.71 \
proto esp spi 0x00000978 \
mode tunnel \
enc "cbc(aes)" 0x2668a9a6b3c4d5e6f7a8b9c0d1e2f3a4
步骤 4:配置 IPSec Policy¶
# 出方向 Policy
ip xfrm policy add \
src 1.1.1.0/24 dst 1.1.2.0/24 \
dir out \
tmpl src 192.168.2.71 dst 192.168.2.73 \
proto esp mode tunnel
# 入方向 Policy
ip xfrm policy add \
src 1.1.2.0/24 dst 1.1.1.0/24 \
dir in \
tmpl src 192.168.2.73 dst 192.168.2.71 \
proto esp mode tunnel
# 转发 Policy
ip xfrm policy add \
src 1.1.2.0/24 dst 1.1.1.0/24 \
dir fwd \
tmpl src 192.168.2.73 dst 192.168.2.71 \
proto esp mode tunnel
14.7 验证配置¶
查看 State¶
$ ip xfrm state
src 192.168.2.71 dst 192.168.2.73
proto esp spi 0x00000978 reqid 16385 mode tunnel
replay-window 0
enc cbc(aes) 0x2668a9a6b3c4d5e6f7a8b9c0d1e2f3a4
src 192.168.2.73 dst 192.168.2.71
proto esp spi 0x00000978 reqid 16385 mode tunnel
replay-window 0
enc cbc(aes) 0x2668a9a6b3c4d5e6f7a8b9c0d1e2f3a4
查看 Policy¶
$ ip xfrm policy
src 1.1.1.0/24 dst 1.1.2.0/24
dir out priority 0
tmpl src 192.168.2.71 dst 192.168.2.73
proto esp mode tunnel
抓包验证¶
# 抓 ESP 包
tcpdump -pne -i eth0 esp
# 测试连通性
ip netns exec ns1 ping 1.1.2.2
14.8 Wireshark 解密 ESP¶
配置步骤¶
- 打开 Preferences:Edit → Preferences
- 找到 ESP 协议:Protocols → ESP
- 添加 SA:ESP SAs → Edit
填写信息¶
| 字段 | 值 |
|---|---|
| Protocol | IPv4 |
| Src IP | * 或具体 IP |
| Dest IP | * 或具体 IP |
| SPI | 从抓包中复制(如 0x00000978) |
| Encryption | AES-CBC |
| Encryption Key | 密钥(16 进制) |
解密结果¶
# 解密前
ESP (Encapsulating Security Payload)
SPI: 0x00000978
[Encrypted Data]
# 解密后
ESP (Encapsulating Security Payload)
SPI: 0x00000978
Inner IP: 1.1.1.2 -> 1.1.2.2
ICMP: Echo Request
14.9 Cilium IPSec 的特殊之处¶
与通用 IPSec 的差异¶
# 查看 Cilium 的 IPSec State
$ ip xfrm state
src 10.0.0.165 dst 10.0.0.187
proto esp spi 0x00000003 ...
mark 0x3cb6 ... # ← Cilium 特有
| 特点 | 通用 IPSec | Cilium IPSec |
|---|---|---|
| mark 字段 | 无 | 有(用于流量匹配) |
| 外层 IP | 节点 IP(如 172.18.0.x) | Pod CIDR IP(如 10.0.0.x) |
| 密钥管理 | 手工或 IKE | Cilium 自动管理 |
[!WARNING] Cilium IPSec 的外层 IP 不是节点 IP:
与 VxLAN 不同,Cilium IPSec 的外层 IP 使用 Pod CIDR 的 IP。 这需要通过源地址路由(SBR)来实现。
14.10 实践练习¶
练习 1:手工配置 IPSec¶
按照本章步骤,在两个 Linux 节点间配置 IPSec 隧道:
- 创建网络命名空间
- 添加路由
- 配置 State 和 Policy
- 验证 ping 连通性
- 抓包查看 ESP 封装
练习 2:使用 Wireshark 解密¶
- 抓取 ESP 包
- 配置 ESP SA 解密
- 查看解密后的原始内容
📝 章节小结¶
本章介绍了 IPSec 的基础知识和手工配置方法:
- IPSec 基础:
- 机密性、完整性、抗重放、来源认证
-
工作在 IP 层
-
两种模式:
- 传输模式:点对点
-
隧道模式:网关间(Cilium 使用)
-
核心概念:
- SA:安全联盟,定义加密参数
-
SPI:安全参数索引,标识 SA
-
ip xfrm 命令:
ip xfrm state:查看/配置 SA-
ip xfrm policy:查看/配置策略 -
手工配置步骤:
- 创建网络环境
- 添加路由
- 配置 State(加密参数)
- 配置 Policy(流量匹配)
[!TIP] 学习建议:
- 先在空白环境练习手工配置
- 使用 Wireshark 解密验证理解
- 再对比 Cilium IPSec 的特殊实现
[!IMPORTANT] IPSec 配置要点:
- State 需要双向配置(出和入)
- Policy 需要 out/in/fwd 三个方向
- 两端的 SPI、算法、密钥必须匹配
第15章 Cilium-IPSec-DataPath¶
🎯 学习目标¶
- 理解 Cilium IPSec 的部署和配置方式
- 掌握 mark 字段在 IPSec 中的作用
- 理解 SBR(源地址路由)的工作原理
- 分析 Cilium IPSec 的数据路径
- 理解外层 IP 为何使用 cilium_host 地址
15.1 Cilium IPSec 部署¶
背景¶
Cilium 支持 IPSec 加密模式,可以对 Pod 间的流量进行加密。部署时需要:
- 生成加密密钥
- 配置 Cilium 启用 IPSec
部署配置¶
# 创建加密密钥 Secret
apiVersion: v1
kind: Secret
metadata:
name: cilium-ipsec-keys
namespace: kube-system
type: Opaque
stringData:
keys: "3 rfc4106(gcm(aes)) 2668a9a6b3c4d5e6f7a8b9c0d1e2f3a4 128"
密钥格式说明:
| 字段 | 说明 |
|---|---|
3 |
SPI(安全参数索引) |
rfc4106(gcm(aes)) |
加密算法 |
2668a9a6... |
密钥(128 位) |
128 |
密钥长度 |
Helm 安装选项¶
helm install cilium cilium/cilium \
--set encryption.enabled=true \
--set encryption.type=ipsec
[!WARNING] 注意:Cilium IPSec 模式通常需要使用传统 kube-proxy,不支持完全替换 kube-proxy 的模式。 这是因为 IPSec 使用 xfrm 框架,与完全 eBPF 替换存在兼容性问题。
15.2 Cilium IPSec 的 mark 字段¶
与通用 IPSec 的差异¶
# 通用 IPSec(无 mark)
$ ip xfrm state
src 192.168.2.71 dst 192.168.2.73
proto esp spi 0x00000978 ...
enc cbc(aes) 0x2668a9a6...
# Cilium IPSec(有 mark)
$ ip xfrm state
src 10.0.0.159 dst 10.0.0.213
proto esp spi 0x00000003 ...
mark 0xd00/0xf00 output mark 0xe00/0xf00
enc cbc(aes) 0x2668a9a6...
mark 字段的作用:
| 字段 | 说明 |
|---|---|
mark 0xd00/0xf00 |
入向流量匹配标记 |
output mark 0xe00/0xf00 |
出向流量标记 |
[!IMPORTANT] mark 的核心作用:
mark 用于将流量与特定的 IPSec SA(安全联盟)关联。 Cilium 通过 mark 实现流量分类和路由选择。
15.3 SBR(源地址路由)机制¶
原理¶
SBR(Source Based Routing,源地址路由)是 Cilium IPSec 的关键机制。
graph TD
subgraph Traditional["传统路由(基于目的地址)"]
T1["查看目的 IP"]
T2["匹配路由表"]
T3["转发到下一跳"]
T1 --> T2 --> T3
end
subgraph SBR["源地址路由"]
S1["查看源 IP"]
S2["匹配 ip rule"]
S3["选择特定路由表"]
S4["转发"]
S1 --> S2 --> S3 --> S4
end
| 路由方式 | 匹配依据 | 典型应用 |
|---|---|---|
| DBR (Destination Based) | 目的 IP | 默认路由 |
| SBR (Source Based) | 源 IP | IPSec、多网卡、策略路由 |
为什么需要 SBR¶
在 Cilium IPSec 中:
- IPSec 端点使用
cilium_host的 IP(如 10.0.0.159) - 而非节点的物理网卡 IP(如 172.18.0.2)
- 需要将流量引导到
cilium_host进行 IPSec 处理
15.4 ip rule 配置分析¶
查看 ip rule¶
$ ip rule list
0: from all lookup local
100: from all fwmark 0xd00/0xf00 lookup 200
101: from all fwmark 0xe00/0xf00 lookup 200
32766: from all lookup main
32767: from all lookup default
规则解读:
| 优先级 | 规则 | 说明 |
|---|---|---|
| 0 | lookup local | 本地路由 |
| 100 | fwmark 0xd00 → table 200 | IPSec 入向流量 |
| 101 | fwmark 0xe00 → table 200 | IPSec 出向流量 |
| 32766 | lookup main | 主路由表 |
查看路由表 200¶
$ ip route show table 200
10.0.0.0/24 dev cilium_host scope link
10.0.1.0/24 via 10.0.0.1 dev cilium_host
10.0.2.0/24 via 10.0.0.1 dev cilium_host
[!TIP] 关键理解:
被标记为
0xd00或0xe00的流量会进入路由表 200, 然后通过cilium_host设备进行转发和 IPSec 处理。
15.5 数据路径分析¶
发送流程¶
graph LR
subgraph Pod["源 Pod"]
P1["应用发包"]
end
subgraph LXC["lxc 网卡"]
L1["eBPF 处理"]
end
subgraph Host["节点"]
H1["cilium_host<br/>10.0.0.159"]
H2["xfrm 加密"]
H3["eth0<br/>172.18.0.2"]
end
P1 --> L1
L1 -->|"SBR"| H1
H1 --> H2
H2 -->|"路由"| H3
流程说明:
| 步骤 | 位置 | 操作 |
|---|---|---|
| 1 | Pod | 发送原始数据包 |
| 2 | lxc 网卡 | eBPF 处理,打标记 |
| 3 | SBR 路由 | 根据标记匹配 table 200 |
| 4 | cilium_host | 进入 xfrm 框架 |
| 5 | xfrm | IPSec 加密封装 |
| 6 | eth0 | 通过物理网卡发出 |
外层 IP 的来源¶
# 抓包查看
$ tcpdump -pne -i eth0 esp
# 外层 IP
src: 10.0.0.159 (cilium_host)
dst: 10.0.0.213 (对端 cilium_host)
# 内层 IP
src: 10.0.0.233 (Pod)
dst: 10.0.0.192 (目标 Pod)
[!IMPORTANT] 为什么外层 IP 是 cilium_host?
因为通过 SBR,流量被引导到
cilium_host设备。 xfrm 使用cilium_host的 IP 作为封装的源地址。 这与ip xfrm state中配置的 src/dst 一致。
15.6 mark 与 xfrm state 的关联¶
关联过程¶
graph TD
subgraph Packet["数据包"]
P1["原始包<br/>Pod → Pod"]
end
subgraph Mark["标记"]
M1["eBPF 打标记<br/>mark = 0xe00"]
end
subgraph Rule["ip rule"]
R1["fwmark 0xe00<br/>→ table 200"]
end
subgraph Table["table 200"]
T1["via cilium_host"]
end
subgraph XFRM["xfrm state"]
X1["mark 0xe00<br/>→ SA 匹配"]
end
P1 --> M1
M1 --> R1
R1 --> T1
T1 --> X1
查看关联¶
# ip xfrm state 中的 mark
$ ip xfrm state
src 10.0.0.159 dst 10.0.0.213
mark 0xd00/0xf00 output mark 0xe00/0xf00
...
# ip rule 中的 fwmark
$ ip rule list
100: from all fwmark 0xd00/0xf00 lookup 200
101: from all fwmark 0xe00/0xf00 lookup 200
# table 200 中的路由
$ ip route show table 200
10.0.2.0/24 dev cilium_host
15.7 抓包分析¶
抓包位置¶
在节点上使用 tcpdump -i any esp 可能抓到多个包:
| 接口 | 抓到的包 | 说明 |
|---|---|---|
cilium_host |
ESP 包 | IPSec 处理点 |
eth0 |
ESP 包 | 物理网卡出口 |
lxc_xxx |
原始包 | 未加密的 Pod 包 |
Wireshark 解密¶
- 获取密钥:
$ ip xfrm state
src 10.0.0.159 dst 10.0.0.213
proto esp spi 0x00000003
enc cbc(aes) 0x2c...
- 配置 Wireshark:
| 字段 | 值 |
|---|---|
| SPI | 0x00000003 |
| Algorithm | AES-CBC [RFC3602] |
| Key | 从 ip xfrm state 复制 |
15.8 与 Flannel/Calico IPSec 的对比¶
密钥管理差异¶
| 特性 | Cilium | Flannel/Calico |
|---|---|---|
| SPI | 固定(如 0x3) | 可能不同 |
| Key 方向 | 来回可能相同 | 来回可能不同 |
| mark 字段 | 有 | 无 |
| 外层 IP | cilium_host IP | 节点 IP |
[!NOTE] Flannel IPSec 的差异:
Flannel 的来去方向可能使用不同的 Key。 在 Wireshark 解密时需要配置两条 SA。
15.9 密钥更新¶
动态更新¶
Cilium 支持密钥轮换:
# 更新 Secret
kubectl patch secret cilium-ipsec-keys -n kube-system \
-p '{"stringData":{"keys":"4 rfc4106(gcm(aes)) new_key_here 128"}}'
# Cilium 会自动检测并更新
[!TIP] 安全建议:
- 定期轮换密钥(如每小时)
- 即使密钥泄露,更新后攻击者无法解密新流量
- Cilium 自动处理密钥分发
15.10 实践练习¶
练习 1:分析 SBR 配置¶
# 1. 查看 ip rule
ip rule list
# 2. 查看特定路由表
ip route show table 200
# 3. 分析 mark 与 table 的关系
练习 2:抓包分析¶
# 1. 在 cilium_host 抓包
tcpdump -pne -i cilium_host esp
# 2. 在 eth0 抓包
tcpdump -pne -i eth0 esp
# 3. 对比两个接口的包
📝 章节小结¶
本章介绍了 Cilium IPSec 的数据路径:
- 部署方式:
- 创建 Secret 存储密钥
-
Helm 启用 IPSec
-
mark 字段:
- Cilium 特有
-
用于流量分类和 SA 匹配
-
SBR 机制:
- Source Based Routing
-
将流量引导到 cilium_host
-
数据路径:
- Pod → lxc → SBR → cilium_host → xfrm → eth0
-
外层 IP 使用 cilium_host 地址
-
关联关系:
- mark ↔ ip rule ↔ table 200 ↔ cilium_host ↔ xfrm state
[!TIP] 理解要点:
Cilium IPSec 的核心是通过 mark + SBR 将流量引导到
cilium_host, 然后由 xfrm 框架完成 IPSec 加密。[!IMPORTANT] 调试方法:
ip rule list- 查看规则ip route show table 200- 查看 SBR 路由ip xfrm state- 查看 SA 信息tcpdump -i eth0 esp- 抓包验证
第16章 WireGuard 手工实现¶
🎯 学习目标¶
- 理解 WireGuard 的基本概念和特点
- 掌握 WireGuard 与 IPSec/OpenVPN 的区别
- 学会手工配置 WireGuard 隧道
- 理解 WireGuard 的数据包结构
- 了解 WireGuard 在 VPN 场景的应用
16.1 WireGuard 简介¶
背景¶
WireGuard 是一种现代、高效的 VPN 协议,于 Linux 5.6 内核正式合入。
核心特点¶
graph TD
subgraph Features["WireGuard 特点"]
F1["简洁<br/>约 4000 行代码"]
F2["高性能<br/>内核态实现"]
F3["现代加密<br/>Curve25519"]
F4["易配置<br/>类似 SSH"]
end
| 特点 | 说明 |
|---|---|
| 简洁 | 内核代码仅约 4000 行(OpenVPN 约 10 万行) |
| 高性能 | 完全内核态实现,无用户态拷贝 |
| 现代加密 | 使用 Curve25519、ChaCha20 等 |
| 易配置 | 像配置 SSH 一样简单 |
[!NOTE] Linus Torvalds 评价:
"与 IPSec 和 OpenVPN 相比,WireGuard 更像一件艺术品" (Can I just once again state my love for it and hope it gets merged soon? Compared to the horrors that are OpenVPN and IPSec, it is a work of art.)
16.2 与其他 VPN 技术对比¶
技术对比¶
| 特性 | WireGuard | IPSec | OpenVPN |
|---|---|---|---|
| 代码量 | ~4000 行 | ~10万行 | ~10万行 |
| 运行位置 | 内核态 | 半内核半用户态 | 用户态 |
| 配置复杂度 | 简单 | 复杂 | 中等 |
| 协议 | UDP | ESP | UDP/TCP |
| 密钥管理 | 公钥/私钥 | IKE/手工 | 证书/密钥 |
| 内核版本 | ≥5.6 | 原生支持 | 无需 |
性能对比¶
graph LR
subgraph Performance["性能排名"]
W["WireGuard<br/>最快"]
I["IPSec<br/>中等"]
O["OpenVPN<br/>较慢"]
end
W --> I --> O
[!IMPORTANT] WireGuard 的优势:
- 完全内核态 → 无上下文切换
- 代码简洁 → bug 少、审计容易
- 现代加密 → 更强安全性
16.3 内核支持检查¶
检查内核版本¶
# 查看内核版本
$ uname -r
5.15.0-generic
# WireGuard 需要 >= 5.6
# 如果版本低于 5.6,需要先升级内核
安装 WireGuard 工具¶
# Ubuntu/Debian
apt install wireguard-tools
# CentOS/RHEL
yum install wireguard-tools
验证安装¶
# 查看 wg 命令
$ which wg
/usr/bin/wg
# 检查模块
$ lsmod | grep wireguard
wireguard 81920 0
16.4 手工配置 WireGuard¶
实验拓扑¶
graph LR
subgraph Node1["节点 1 (192.168.2.71)"]
W1["wg0<br/>20.0.0.1/24"]
end
subgraph Node2["节点 2 (192.168.2.73)"]
W2["wg0<br/>20.0.0.2/24"]
end
W1 <-->|"WireGuard 隧道<br/>UDP 51820"| W2
步骤 1:生成密钥¶
# 节点 1:生成私钥
wg genkey > /etc/wireguard/private
# 从私钥提取公钥
cat /etc/wireguard/private | wg pubkey
# 保护私钥权限
chmod 600 /etc/wireguard/private
步骤 2:创建 WireGuard 接口¶
# 创建 wg0 接口
ip link add wg0 type wireguard
# 配置 IP 地址
ip addr add 20.0.0.1/24 dev wg0
# 应用私钥
wg set wg0 private-key /etc/wireguard/private
# 设置监听端口
wg set wg0 listen-port 51820
# 启动接口
ip link set wg0 up
步骤 3:添加 Peer¶
# 在节点 1 添加 peer(节点 2)
# PEER_PUBLIC_KEY = 节点 2 的公钥
wg set wg0 peer <PEER_PUBLIC_KEY> \
allowed-ips 20.0.0.2/32 \
endpoint 192.168.2.73:51820
# 同样在节点 2 添加 peer(节点 1)
16.5 查看配置¶
wg 命令¶
$ wg
interface: wg0
public key: aWE4xxx...
private key: (hidden)
listening port: 51820
peer: bXY5xxx...
endpoint: 192.168.2.73:51820
allowed ips: 20.0.0.2/32
latest handshake: 4 seconds ago
transfer: 1.2 KiB received, 984 B sent
关键字段:
| 字段 | 说明 |
|---|---|
public key |
本端公钥 |
listening port |
监听端口 |
peer |
对端公钥 |
endpoint |
对端地址:端口 |
allowed ips |
允许的 IP 范围 |
latest handshake |
最近握手时间 |
wg show 子命令¶
# 显示公钥
wg show wg0 public-key
# 显示私钥
wg show wg0 private-key
# 显示监听端口
wg show wg0 listen-port
# 显示所有 peer
wg show wg0 peers
16.6 验证连通性¶
ping 测试¶
# 在节点 1 ping 节点 2
$ ping 20.0.0.2 -c 3
PING 20.0.0.2 (20.0.0.2) 56(84) bytes of data.
64 bytes from 20.0.0.2: icmp_seq=1 ttl=64 time=0.432 ms
查看握手状态¶
$ wg
# 成功握手会显示:
peer: bXY5xxx...
latest handshake: 4 seconds ago
# 未握手显示:
peer: bXY5xxx...
(no handshake yet)
16.7 数据包结构¶
WireGuard 封装格式¶
+-------------------+
| IP Header | ← 外层 IP(节点 IP)
+-------------------+
| UDP Header | ← 端口 51820
+-------------------+
| WireGuard | ← WireGuard 协议头
+-------------------+
| Encrypted | ← 加密的原始数据包
| Payload |
+-------------------+
[!TIP] 与 VxLAN 类似:
- VxLAN: IP + UDP + VNI + 原始帧
- WireGuard: IP + UDP + WG Header + 加密数据
两者都使用 UDP 封装。
链路类型¶
# 抓包查看
$ tcpdump -pne -i wg0
listening on wg0, link-type RAW (Raw IP)...
RAW IP:
- 没有以太网头(无 MAC 地址)
- 类似于 tun 设备
- 只有三层 IP 信息
16.8 抓包分析¶
在 wg0 接口抓包¶
# wg0 上抓到的是明文(已解密)
$ tcpdump -pne -i wg0 icmp
# 注意:没有 MAC 地址,只有 IP
20.0.0.1 > 20.0.0.2: ICMP echo request
20.0.0.2 > 20.0.0.1: ICMP echo reply
在物理接口抓包¶
# eth0 上抓到的是加密数据
$ tcpdump -pne -i eth0 udp port 51820
# 只能看到 WireGuard 协议,内容加密
192.168.2.71.51820 > 192.168.2.73.51820: UDP, length 128
Wireshark 过滤¶
# 过滤 WireGuard 协议
wireguard
[!WARNING] WireGuard 解密困难:
与 IPSec 不同,WireGuard 的解密需要从内核内存提取密钥, 目前没有简单的方法在 Wireshark 中解密 WireGuard 流量。
16.9 路由与流量引导¶
路由规则¶
# 当添加 peer 时,系统自动添加路由
$ ip route show
20.0.0.0/24 dev wg0 proto kernel scope link src 20.0.0.1
流量引导原理:
- 目的 IP 为 20.0.0.2
- 匹配路由表,出接口为 wg0
- wg0 查找 peer 配置
- 使用 peer 的 endpoint 封装发送
[!IMPORTANT] 与 IPSec 的区别:
- IPSec 使用 SBR(源地址路由)+ mark
- WireGuard 使用 DBR(目的地址路由)
- WireGuard 配置更简单
16.10 UDP 封装的优势¶
为什么使用 UDP¶
graph TD
subgraph Question["为什么不用 TCP?"]
Q1["TCP over TCP<br/>双重确认"]
Q2["性能极差<br/>重传风暴"]
end
subgraph Answer["UDP 的优势"]
A1["上层应用保证<br/>可靠传输"]
A2["避免双重<br/>确认机制"]
A3["高效传输<br/>低延迟"]
end
原因:
- 原始数据如果是 TCP,已有可靠性保证
- UDP 封装 + TCP 上层 = 单层可靠传输
- TCP 封装 + TCP 上层 = 双层可靠传输(性能灾难)
[!NOTE] WireGuard 官方声明:
"我们不会支持 TCP 封装" 因为 TCP-over-TCP 会导致严重的性能问题。
16.11 VPN 应用场景¶
典型场景:远程访问家庭网络¶
graph LR
subgraph Remote["远程设备"]
R["笔记本<br/>10.0.0.2"]
end
subgraph Internet["公网"]
I["路由器<br/>公网 IP"]
end
subgraph Home["家庭网络"]
H["WireGuard 服务器<br/>192.168.1.100"]
K["K8s 集群<br/>192.168.1.x"]
end
R <-->|"WireGuard"| I
I <-->|"端口映射"| H
H <--> K
配置示例¶
服务端(家庭):
[Interface]
Address = 10.0.0.1/24
PrivateKey = <SERVER_PRIVATE_KEY>
ListenPort = 51820
# 允许转发到内网
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
客户端(远程):
[Interface]
Address = 10.0.0.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <公网IP>:51820
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
16.12 实践练习¶
练习 1:手工配置 WireGuard¶
按照本章步骤,在两个 Linux 节点间配置 WireGuard 隧道:
- 生成密钥对
- 创建 wg0 接口
- 添加 peer
- 测试连通性
练习 2:抓包对比¶
# 1. 在 wg0 抓包(明文)
tcpdump -pne -i wg0 icmp
# 2. 在 eth0 抓包(密文)
tcpdump -pne -i eth0 udp port 51820
# 3. 对比两个接口的数据
📝 章节小结¶
本章介绍了 WireGuard 的基础知识和手工配置方法:
- WireGuard 特点:
- 简洁(~4000 行代码)
- 高性能(内核态)
-
现代加密
-
与其他 VPN 对比:
- 比 IPSec 配置简单
-
比 OpenVPN 性能更好
-
手工配置步骤:
-
生成密钥 → 创建接口 → 添加 peer
-
数据包结构:
- IP + UDP + WireGuard Header + 加密数据
-
类似 VxLAN 封装方式
-
路由机制:
- 使用 DBR(目的地址路由)
- 比 IPSec 的 SBR 更简单
[!TIP] 学习建议:
- 先手工配置两节点隧道
- 对比 wg0 和 eth0 的抓包数据
- 理解 UDP 封装的优势
[!IMPORTANT] WireGuard 核心命令:
# 生成密钥 wg genkey > private cat private | wg pubkey > public # 创建接口 ip link add wg0 type wireguard # 查看配置 wg show wg0
第17章 Cilium-WireGuard-DataPath¶
🎯 学习目标¶
- 理解 Cilium WireGuard 的部署方式
- 掌握 SBR(源地址路由)在 WireGuard 中的应用
- 理解 fwmark 标记机制
- 分析 Cilium WireGuard 的数据路径
- 对比手工配置与 Cilium 的差异
17.1 Cilium WireGuard 部署¶
部署配置¶
# 创建 WireGuard 密钥 Secret
apiVersion: v1
kind: Secret
metadata:
name: cilium-wireguard-keys
namespace: kube-system
type: Opaque
stringData:
# 私钥(base64 编码)
key: "..."
Helm 安装¶
helm install cilium cilium/cilium \
--set encryption.enabled=true \
--set encryption.type=wireguard \
--set routingMode=native \
--set kubeProxyReplacement=false
[!NOTE] 注意:Cilium WireGuard 使用 Native Routing 模式(Direct Routing)。
17.2 Cilium WireGuard 接口¶
查看接口¶
$ ip link show type wireguard
10: cilium_wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 ...
link/none
接口特点:
| 属性 | 值 |
|---|---|
| 接口名 | cilium_wg0 |
| 类型 | wireguard |
| MTU | 1420 |
| 标志 | POINTOPOINT, NOARP |
查看 Peer¶
$ wg show cilium_wg0
interface: cilium_wg0
public key: aWE4xxx...
listening port: 51820
peer: bXY5xxx...
endpoint: 10.0.0.2:51820
allowed ips: 10.0.0.2/32
peer: cZA6xxx...
endpoint: 10.0.0.3:51820
allowed ips: 10.0.0.3/32
[!TIP] Peer 数量:
3 节点集群,每个节点有 2 个 peer(其他 2 个节点)。 allowed ips 使用 /32 掩码,对应每个 Pod IP。
17.3 SBR vs DBR¶
手工配置 vs Cilium¶
| 对比项 | 手工配置 | Cilium |
|---|---|---|
| 路由方式 | DBR(目的地址) | SBR(源地址) |
| 路由表 | main | table 201 |
| 流量引导 | ip route | ip rule + fwmark |
为什么 Cilium 使用 SBR¶
graph TD
subgraph DBR["DBR(手工配置)"]
D1["查看目的 IP"]
D2["匹配 ip route"]
D3["出接口 = wg0"]
D1 --> D2 --> D3
end
subgraph SBR["SBR(Cilium)"]
S1["eBPF 打 fwmark"]
S2["匹配 ip rule"]
S3["查 table 201"]
S4["出接口 = cilium_wg0"]
S1 --> S2 --> S3 --> S4
end
[!IMPORTANT] SBR 的优势:
- 不污染默认路由表
- 可以精确控制哪些流量需要加密
- 与 eBPF 配合更灵活
17.4 fwmark 标记机制¶
标记过程¶
graph LR
subgraph Pod["Pod"]
P1["发送数据包"]
end
subgraph BPF["eBPF"]
B1["在 skb 上<br/>mark = MARK_MAGIC_ENCRYPT"]
end
subgraph Rule["ip rule"]
R1["fwmark 匹配<br/>→ table 201"]
end
subgraph WG["cilium_wg0"]
W1["WireGuard 加密"]
end
P1 --> B1 --> R1 --> W1
查看 ip rule¶
$ ip rule list
0: from all lookup local
100: from all fwmark 0x1/0xf0 lookup 201
32766: from all lookup main
32767: from all lookup default
规则解读:
| 优先级 | 条件 | 动作 |
|---|---|---|
| 100 | fwmark = 0x1 (ENCRYPT) | 查 table 201 |
查看 table 201¶
$ ip route show table 201
default dev cilium_wg0 scope link
[!TIP] 核心理解:
被 eBPF 标记为
MARK_MAGIC_ENCRYPT的流量, 会匹配 ip rule,进入 table 201, 然后通过cilium_wg0发出。
17.5 数据路径分析¶
发送流程¶
graph LR
subgraph Pod["源 Pod"]
P1["应用发包"]
end
subgraph LXC["lxc 网卡"]
L1["eBPF 处理"]
L2["mark = ENCRYPT"]
end
subgraph SBR["SBR"]
S1["ip rule 匹配"]
S2["table 201"]
end
subgraph WG["cilium_wg0"]
W1["WireGuard 加密<br/>UDP 51820"]
end
subgraph ETH["eth0"]
E1["物理网卡发出"]
end
P1 --> L1 --> L2
L2 --> S1 --> S2 --> W1 --> E1
步骤说明:
| 步骤 | 位置 | 操作 |
|---|---|---|
| 1 | Pod | 发送原始数据包 |
| 2 | lxc | eBPF 处理,打 ENCRYPT 标记 |
| 3 | ip rule | fwmark 匹配 table 201 |
| 4 | table 201 | 默认路由到 cilium_wg0 |
| 5 | cilium_wg0 | WireGuard 加密封装 |
| 6 | eth0 | 通过物理网卡发出 |
接收流程¶
graph LR
subgraph ETH["eth0"]
E1["收到 UDP 51820"]
end
subgraph Kernel["内核"]
K1["检查端口"]
K2["51820 → WireGuard"]
end
subgraph WG["cilium_wg0"]
W1["WireGuard 解密"]
end
subgraph Route["路由"]
R1["送到目标 Pod"]
end
E1 --> K1 --> K2 --> W1 --> R1
[!NOTE] 接收判断:
内核通过 UDP 端口 51820 判断是否为 WireGuard 流量。 监听在内核态(显示为
-),不是用户态进程。
17.6 抓包分析¶
抓包位置¶
# 在 eth0 抓包(加密数据)
tcpdump -pne -i eth0 udp port 51820 -w cilium_wg.pcap
# 在 cilium_wg0 抓包(解密后的明文)
tcpdump -pne -i cilium_wg0
Wireshark 分析¶
# 过滤 WireGuard 协议
wireguard
抓包结果:
| 接口 | 内容 |
|---|---|
eth0 |
UDP 51820 + 加密数据 |
cilium_wg0 |
Raw IP(无 MAC) |
[!WARNING] 解密困难:
WireGuard 的密钥需要从内核内存提取, 目前无法像 IPSec 那样在 Wireshark 中直接解密。
17.7 与手工配置的差异¶
主要区别¶
| 对比项 | 手工配置 | Cilium |
|---|---|---|
| 接口名 | wg0 | cilium_wg0 |
| 路由方式 | DBR(ip route) | SBR(ip rule + table 201) |
| 流量标记 | 无 | fwmark |
| Peer 管理 | 手工添加 | 自动管理 |
| allowed-ips | 手工指定 | 自动添加 Pod IP |
[!IMPORTANT] 为什么 Cilium 不用 DBR:
手工配置时,目的地址路由很简单:
20.0.0.0/24 dev wg0但 Cilium 环境中:
- Pod IP 不在同一网段
- 需要精确控制哪些流量加密
- SBR + fwmark 更灵活
17.8 性能对比¶
WireGuard vs IPSec¶
| 场景 | WireGuard | IPSec |
|---|---|---|
| MTU 1500 | 更高吞吐量 | 较低 |
| MTU 9000 | 更高吞吐量 | 较低 |
| CPU 占用 | 较低 | 较高 |
[!NOTE] Benchmark 说明:
Cilium 官方测试显示 WireGuard 性能优于 IPSec。 但实际性能取决于:网络环境、MTU、负载类型等。
graph TD
subgraph Compare["性能对比(相对)"]
W["WireGuard<br/>★★★★★"]
I["IPSec<br/>★★★☆☆"]
end
17.9 实践练习¶
练习 1:查看 Cilium WireGuard 配置¶
# 1. 查看 WireGuard 接口
ip link show type wireguard
# 2. 查看 Peer
wg show cilium_wg0
# 3. 查看 ip rule
ip rule list
# 4. 查看 table 201
ip route show table 201
练习 2:验证数据路径¶
# 1. 在节点间 ping Pod
kubectl exec -it pod1 -- ping <pod2-ip>
# 2. 抓包验证
tcpdump -pne -i eth0 udp port 51820
📝 章节小结¶
本章介绍了 Cilium WireGuard 的数据路径:
- 部署方式:
encryption.type=wireguard-
自动创建
cilium_wg0接口 -
SBR 机制:
- eBPF 打 fwmark 标记
- ip rule 匹配 table 201
-
路由到 cilium_wg0
-
与手工配置的区别:
- 手工:DBR(目的地址路由)
-
Cilium:SBR(源地址路由)
-
数据路径:
- 发送:Pod → eBPF(mark) → SBR → cilium_wg0 → eth0
-
接收:eth0 → 51820端口 → cilium_wg0 → Pod
-
性能:
- WireGuard 通常优于 IPSec
[!TIP] 核心理解:
Cilium 通过 fwmark + SBR 将流量引导到
cilium_wg0, 然后由 WireGuard 完成加密封装。[!IMPORTANT] 调试命令:
# 查看规则 ip rule list # 查看 SBR 路由 ip route show table 201 # 查看 WireGuard 状态 wg show cilium_wg0 # 抓包验证 tcpdump -i eth0 udp port 51820
第十八章 Cilium-SocketLB¶
本章介绍 Cilium 独有的 Socket-based Load Balancing(Socket LB)特性,这是一种基于 eBPF 在 Socket 层面实现服务负载均衡的技术,能够显著优化集群内部的流量路径。
18.1 背景与问题¶
18.1.1 传统 Service 访问模式的问题¶
在传统的 Kubernetes 网络实现中(如 Flannel、Calico 等),当 Pod 访问 Service 时,数据包需要经历以下过程:
sequenceDiagram
participant Pod as Pod (Client)
participant NS as Root Namespace
participant IPT as iptables/IPVS
participant Backend as Backend Pod
Pod->>NS: 发送数据包<br/>dst=Service IP
NS->>IPT: 进入宿主机网络栈
IPT->>IPT: DNAT 转换<br/>Service IP → Pod IP
IPT->>Backend: 转发到后端 Pod
Backend-->>IPT: 响应
IPT-->>NS: SNAT (如跨节点)
NS-->>Pod: 返回响应
问题分析:
| 问题 | 描述 |
|---|---|
| 多层处理 | 数据包需先到 Root Namespace,再经 iptables/IPVS 处理 |
| 规则匹配 | iptables 链式匹配,规则多时性能下降 |
| 额外跳转 | 跨节点时还需要 SNAT,增加处理开销 |
| 延迟增加 | 整个处理流程增加了访问延迟 |
18.1.2 理想的访问模式¶
用户期望的访问模式应该更加直接:
graph LR
subgraph "理想模式"
Pod["Pod (Client)"] -->|"直接访问"| Backend1["Backend Pod 1"]
Pod -->|"直接访问"| Backend2["Backend Pod 2"]
end
[!NOTE] 理想状态:Pod 发出的数据包直接以后端 Pod 的 IP 作为目的地址,无需经过中间的 NAT 转换。
18.2 Socket LB 原理¶
18.2.1 核心思想¶
Socket LB 的核心思想是:在 Socket 层面提前完成负载均衡决策,在数据包离开 Pod 之前就将 Service IP 替换为真实的后端 Pod IP。
flowchart LR
subgraph Pod["Pod Namespace"]
App["应用程序<br/>connect(Service IP)"]
eBPF["eBPF Hook<br/>(cgroup/connect4)"]
Socket["Socket<br/>dst=Backend Pod IP"]
end
subgraph Host["Root Namespace"]
Stack["网络栈"]
end
subgraph Remote["远端节点"]
Backend["Backend Pod"]
end
App -->|"原始目的: Service IP"| eBPF
eBPF -->|"Socket LB 替换"| Socket
Socket -->|"实际目的: Pod IP"| Stack
Stack -->|"普通路由"| Backend
18.2.2 工作机制¶
| 阶段 | 传统模式 | Socket LB 模式 |
|---|---|---|
| 发包位置 | Pod 内发包,dst = Service IP | Pod 内发包,dst = Backend Pod IP |
| NAT 位置 | Root Namespace (iptables) | Pod Namespace (eBPF) |
| 后续路由 | 需要 iptables 规则匹配 | 简单的跨节点/同节点路由 |
| 返回处理 | 可能需要 SNAT 反向转换 | 直接返回,无需额外处理 |
[!IMPORTANT] 关键优势:DNAT 提前在 Pod 命名空间内完成,后续流量变成简单的 Pod-to-Pod 通信,无需经过 iptables 规则链。
18.2.3 eBPF 实现位置¶
Socket LB 通过 eBPF 程序挂载在 cgroup 的 socket 操作钩子上实现:
graph TB
subgraph "eBPF 挂载点"
connect["cgroup/connect4<br/>connect() 系统调用"]
sendmsg["cgroup/sendmsg4<br/>sendmsg() 系统调用"]
recvmsg["cgroup/recvmsg4<br/>recvmsg() 系统调用"]
end
subgraph "Service Map"
svc["Service → Endpoints 映射"]
end
connect -->|"查询"| svc
sendmsg -->|"查询"| svc
svc -->|"返回 Backend Pod IP"| connect
svc -->|"返回 Backend Pod IP"| sendmsg
18.3 传统模式抓包分析¶
18.3.1 环境准备(以 Flannel 为例)¶
# 创建测试服务
kubectl apply -f demo-deployment.yaml
# 查看 Service
kubectl get svc demo-svc
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# demo-svc NodePort 172.18.0.2 <none> 80:32000/TCP 1m
# 查看 iptables 规则链
iptables -t nat -L | grep 32000
# 可以看到 KUBE-SVC-xxx 链
18.3.2 抓包验证¶
# 进入 Pod 内抓包
kubectl exec -it frontend-pod -- tcpdump -i eth0 -n
# 在另一个终端,从 Pod 内访问 Service
kubectl exec -it frontend-pod -- curl 172.18.0.2:32000
抓包结果分析:
# TCP 三次握手
10.244.2.2.42962 > 172.18.0.2.32000: Flags [S] # SYN: dst=Service IP
172.18.0.2.32000 > 10.244.2.2.42962: Flags [S.] # SYN-ACK
10.244.2.2.42962 > 172.18.0.2.32000: Flags [.] # ACK
sequenceDiagram
participant Client as Client Pod<br/>10.244.2.2
participant Service as Service IP<br/>172.18.0.2:32000
Note over Client,Service: 传统模式:目的地址是 Service IP
Client->>Service: SYN (src=10.244.2.2:42962, dst=172.18.0.2:32000)
Service-->>Client: SYN-ACK
Client->>Service: ACK
Note over Client,Service: iptables 在 Root NS 做 DNAT
[!NOTE] 观察要点:
- 目的 IP 是 Service IP(172.18.0.2),而非后端 Pod IP
- 数据包需要到达 Root Namespace 后,由 iptables 进行 DNAT 转换
- 这是传统的、符合直觉的实现方式
18.4 Socket LB 模式抓包分析¶
18.4.1 启用 Socket LB¶
通过 Helm 部署 Cilium 并启用 Socket LB:
helm install cilium cilium/cilium --namespace kube-system \
--set kubeProxyReplacement=strict \
--set socketLB.enabled=true
18.4.2 验证配置¶
# 查看 Cilium 配置
cilium config view | grep -i socket
# socket-lb: enabled
# 或通过 cilium status
cilium status --verbose | grep -i socket
# KubeProxyReplacement Info: Socket LB: enabled
18.4.3 抓包验证¶
# 进入 Pod 内抓包
kubectl exec -it frontend-pod -- tcpdump -i eth0 -n
# 从 Pod 内访问 Service
kubectl exec -it frontend-pod -- curl 172.18.0.2:32000
抓包结果分析:
# TCP 三次握手
10.0.0.221.58504 > 10.0.2.22.80: Flags [S] # SYN: dst=Backend Pod IP !!!
10.0.2.22.80 > 10.0.0.221.58504: Flags [S.] # SYN-ACK
10.0.0.221.58504 > 10.0.2.22.80: Flags [.] # ACK
sequenceDiagram
participant Client as Client Pod<br/>10.0.0.221
participant Backend as Backend Pod<br/>10.0.2.22:80
Note over Client,Backend: Socket LB:目的地址直接是 Backend Pod IP
Client->>Backend: SYN (src=10.0.0.221:58504, dst=10.0.2.22:80)
Backend-->>Client: SYN-ACK
Client->>Backend: ACK
Note over Client,Backend: eBPF 在 Pod NS 内提前完成 DNAT
[!IMPORTANT] 关键发现:
- 虽然应用访问的是 Service IP(172.18.0.2:32000)
- 但抓包看到的目的 IP 直接是 Backend Pod IP(10.0.2.22:80)
- 这说明 Socket LB 在数据包发出前就完成了地址替换
18.5 对比总结¶
18.5.1 数据路径对比¶
flowchart TB
subgraph Traditional["传统模式 (Flannel/Calico)"]
direction LR
T_Pod["Pod"] -->|"dst=Service IP"| T_veth["veth pair"]
T_veth --> T_Root["Root NS"]
T_Root --> T_IPT["iptables DNAT"]
T_IPT -->|"dst=Pod IP"| T_Backend["Backend"]
end
subgraph SocketLB["Socket LB 模式 (Cilium)"]
direction LR
S_Pod["Pod"] -->|"eBPF 替换"| S_Socket["Socket"]
S_Socket -->|"dst=Pod IP"| S_Root["Root NS"]
S_Root -->|"普通路由"| S_Backend["Backend"]
end
18.5.2 核心差异表¶
| 对比项 | 传统模式 | Socket LB 模式 |
|---|---|---|
| DNAT 位置 | Root Namespace | Pod Namespace |
| DNAT 时机 | 数据包到达宿主机后 | 数据包离开 Pod 前 |
| 实现技术 | iptables / IPVS | eBPF (cgroup hooks) |
| 抓包看到的目的 IP | Service IP | Backend Pod IP |
| 后续处理 | 需要规则匹配 | 简单路由 |
| 性能开销 | 较高(规则链匹配) | 较低(Map 查询) |
18.5.3 性能优势¶
graph LR
subgraph "性能优势"
A["提前 DNAT"] --> B["跳过 iptables"]
B --> C["减少规则匹配"]
C --> D["降低延迟"]
D --> E["提升吞吐量"]
end
| 优势 | 说明 |
|---|---|
| 减少跳数 | 不需要经过 Root NS 的 iptables 处理 |
| 规避规则膨胀 | Service 多时,iptables 规则链很长 |
| 路径简化 | 变成简单的 Pod-to-Pod 通信 |
| 性能提升 | 减少 CPU 开销和延迟 |
18.6 配置选项¶
18.6.1 Helm 配置参数¶
# values.yaml 关键配置
kubeProxyReplacement: strict # 或 partial
socketLB:
enabled: true
hostNamespaceOnly: false # 是否仅对 host namespace 生效
18.6.2 模式说明¶
| 模式 | kubeProxyReplacement | 说明 |
|---|---|---|
| strict | strict | 完全替换 kube-proxy,启用所有 eBPF 功能 |
| partial | partial | 部分替换,与 kube-proxy 共存 |
18.6.3 验证命令¶
# 查看 Socket LB 状态
cilium status --verbose
# 查看详细配置
cilium config view
# 查看 BPF 程序
bpftool prog list | grep cgroup
18.7 历史演进¶
| 版本阶段 | 功能名称 | 说明 |
|---|---|---|
| 早期版本 | Host Reachable Services | 最初的功能名称 |
| 当前版本 | Socket LB | 统一命名为 Socket-based Load Balancing |
[!TIP] 如果在旧版本 Cilium 文档中看到 "Host Reachable Services",它与 Socket LB 描述的是同一功能。
18.8 适用场景¶
graph TB
subgraph "适用场景"
A["集群内 Pod 访问 Service"]
B["东西向流量优化"]
C["大规模 Service 环境"]
D["延迟敏感型应用"]
end
subgraph "不适用场景"
E["外部流量进入集群"]
F["NodePort 外部访问"]
end
| 场景 | Socket LB 作用 |
|---|---|
| Pod → ClusterIP | ✅ 直接替换为后端 Pod IP |
| Pod → NodePort(集群内) | ✅ 同样可以优化 |
| 外部 → NodePort | ❌ 需要其他机制(如 DSR) |
18.9 章节小结¶
mindmap
root((Socket LB))
背景
传统模式需要 iptables DNAT
规则匹配开销大
路径复杂
原理
eBPF 挂载 cgroup hooks
Socket 层面完成 LB
提前替换目的 IP
验证
传统模式:抓包看到 Service IP
Socket LB:抓包看到 Backend Pod IP
优势
减少跳数
降低延迟
提升性能
[!IMPORTANT] 核心要点总结:
什么是 Socket LB:在 Socket 层面通过 eBPF 实现的负载均衡,提前完成 Service IP 到 Backend Pod IP 的转换
与传统模式区别:
- 传统:Pod → Root NS → iptables DNAT → Backend
Socket LB:Pod (eBPF DNAT) → Root NS → 直接路由 → Backend
抓包验证:
- 传统模式抓包看到 dst = Service IP
Socket LB 模式抓包看到 dst = Backend Pod IP
配置方法:
kubeProxyReplacement=strict或单独设置socketLB.enabled=true核心优势:减少 iptables 规则匹配,降低延迟,提升大规模集群的 Service 访问性能
第十九章 Cilium-DSR 模式¶
本章介绍 Cilium 的 DSR(Direct Server Return)模式,这是一种优化南北向流量的技术,通过让后端 Pod 直接响应客户端,减少返回路径的跳数,降低入口节点的负载。
19.1 背景与问题¶
19.1.1 传统 SNAT 模式的数据路径¶
在传统的 NodePort/LoadBalancer 访问模式中,外部客户端访问集群内服务时,数据包需要经过 SNAT 处理:
sequenceDiagram
participant Client as 外部客户端<br/>1.1.1.10
participant NodeA as Node A<br/>1.1.1.1:32000
participant NodeB as Node B<br/>1.1.1.2
participant Pod as Backend Pod<br/>10.0.0.1
Note over Client,Pod: SNAT 模式:返回路径经过入口节点
Client->>NodeA: ① SYN (src=1.1.1.10, dst=1.1.1.1:32000)
NodeA->>NodeA: SNAT: src=1.1.1.10 → src=1.1.1.1
NodeA->>Pod: ② (src=1.1.1.1, dst=10.0.0.1:80)
Pod->>NodeA: ③ SYN-ACK (src=10.0.0.1, dst=1.1.1.1)
NodeA->>NodeA: Reverse SNAT
NodeA->>Client: ④ (src=1.1.1.1, dst=1.1.1.10)
SNAT 模式的问题:
| 问题 | 描述 |
|---|---|
| 入口节点瓶颈 | 所有进出流量都经过入口节点,容易成为瓶颈 |
| 多一跳 | 返回响应需要先回到入口节点,再转发给客户端 |
| 源 IP 丢失 | Pod 看到的源 IP 是入口节点 IP,而非真实客户端 IP |
| 负载不均 | 入口节点承担额外的转发负担 |
19.1.2 DSR 的核心思想¶
DSR(Direct Server Return)的核心思想是:让后端 Pod 直接将响应发送给客户端,跳过入口节点。
sequenceDiagram
participant Client as 外部客户端<br/>1.1.1.10
participant NodeA as Node A<br/>1.1.1.1:32000
participant NodeB as Node B<br/>1.1.1.2
participant Pod as Backend Pod<br/>10.0.0.1
Note over Client,Pod: DSR 模式:响应直接返回客户端
Client->>NodeA: ① SYN (src=1.1.1.10, dst=1.1.1.1:32000)
NodeA->>Pod: ② SYN (携带原始目的 IP 信息)
Pod->>Client: ③ SYN-ACK (src=1.1.1.1, dst=1.1.1.10)
Client->>NodeA: ④ ACK
NodeA->>Pod: ⑤ ACK
[!IMPORTANT] DSR 的关键:后端 Pod 在发送响应时,必须使用客户端原始访问的目的 IP(入口节点 IP)作为源 IP,否则客户端会丢弃这个"意外"的响应包。
19.2 DSR vs SNAT 对比¶
19.2.1 数据路径对比¶
flowchart TB
subgraph SNAT["SNAT 模式"]
direction LR
S_Client["Client"] -->|"①"| S_NodeA["Node A"]
S_NodeA -->|"② SNAT"| S_Pod["Pod"]
S_Pod -->|"③"| S_NodeA
S_NodeA -->|"④ Reverse"| S_Client
end
subgraph DSR["DSR 模式"]
direction LR
D_Client["Client"] -->|"①"| D_NodeA["Node A"]
D_NodeA -->|"② + IP Info"| D_Pod["Pod"]
D_Pod -->|"③ 直接返回"| D_Client
end
19.2.2 核心差异表¶
| 对比项 | SNAT 模式 | DSR 模式 |
|---|---|---|
| 返回路径 | Client ← Node A ← Pod | Client ← Pod (直接) |
| 跳数 | 请求 2 跳 + 响应 2 跳 = 4 跳 | 请求 2 跳 + 响应 1 跳 = 3 跳 |
| 入口节点负载 | 高(处理双向流量) | 低(只处理入向流量) |
| 源 IP 可见性 | Pod 看到 Node A IP | Pod 看到真实 Client IP |
| 响应包源 IP | Node A IP | Node A IP (伪装) |
19.3 核心挑战:IP 传递问题¶
19.3.1 问题描述¶
DSR 模式的核心挑战是:如何将客户端原始访问的目的 IP(入口节点 IP)传递给后端 Pod?
graph TB
subgraph "问题"
A["客户端访问 1.1.1.1:32000"]
B["Pod 在 Node B (1.1.1.2)"]
C["Pod 响应时必须使用 src=1.1.1.1"]
D["但 Pod 不知道 1.1.1.1 这个地址"]
end
A --> B --> C --> D
subgraph "解决方案"
E["在请求转发时携带原始目的 IP"]
end
D --> E
19.3.2 IP 响应的要求¶
客户端发送请求后,期望收到来自原始目的 IP 的响应:
| 场景 | 客户端行为 |
|---|---|
| 响应源 IP = 原始目的 IP | ✅ 接受响应,通信正常 |
| 响应源 IP ≠ 原始目的 IP | ❌ 丢弃响应,认为是无效包 |
[!NOTE] 类比理解:就像你 ping 1.1.1.1,但收到 1.1.1.2 的 reply,这个 reply 会被丢弃,因为"你问的是 A,但 B 来回答了"。
19.4 Cilium DSR 实现机制¶
19.4.1 IP Options 方案¶
Cilium 原生使用 IP Options 字段 来传递原始目的 IP 信息:
graph LR
subgraph "第一跳:Client → Node A"
A1["IP Header"]
A2["src=1.1.1.10"]
A3["dst=1.1.1.1"]
end
subgraph "第二跳:Node A → Pod (携带 Options)"
B1["IP Header"]
B2["src=1.1.1.10"]
B3["dst=10.0.0.1"]
B4["<b>Options: orig_dst=1.1.1.1:32000</b>"]
end
A1 --> B1
19.4.2 IP Options 字段解析¶
IP Header Options 字段内容(十六进制):
+------+------+------+------+------+------+------+------+
| 0x9a | ... | 0xAC | 0x12 | 0x00 | 0x04 | 0x7D | 0x00 |
+------+------+------+------+------+------+------+------+
↓ ↓ ↓ ↓ ↓ ↓
172 18 0 4 32000端口高位
解析结果:原始目的 IP = 172.18.0.4,端口 = 32000
| 十六进制 | 十进制 | 含义 |
|---|---|---|
| 0xAC | 172 | IP 第一段 |
| 0x12 | 18 | IP 第二段 |
| 0x00 | 0 | IP 第三段 |
| 0x04 | 4 | IP 第四段 |
| 0x7D00 | 32000 | 端口号 |
19.4.3 抓包验证¶
第一跳抓包(Client → Node A):
# 正常的 SYN 包,没有 Options 字段
172.18.0.1.38645 > 172.18.0.4.32000: Flags [S]
IP Header: 无 Options
第二跳抓包(Node A → Pod):
# 携带 Options 字段的 SYN 包
172.18.0.1.38645 > 10.0.2.148.80: Flags [S]
IP Header Options: unknown (0x9a) [包含原始目的 IP]
sequenceDiagram
participant C as Client<br/>172.18.0.1
participant A as Node A<br/>172.18.0.4
participant B as Node B<br/>172.18.0.2
participant P as Pod<br/>10.0.2.148
C->>A: ① SYN (无 Options)
A->>P: ② SYN + Options (orig_dst=172.18.0.4:32000)
Note over P: 解析 Options 获取原始目的 IP
P->>C: ③ SYN-ACK (src=172.18.0.4)
Note over A: Node A 上只能看到 SYN 和 ACK
C->>A: ④ ACK
A->>P: ⑤ ACK
19.5 TCP 三次握手详解¶
19.5.1 握手过程分析¶
在 DSR 模式下,三次握手的包分布在不同路径:
| 包名 | 路径 | Node A 可见 | Node B/Pod 可见 |
|---|---|---|---|
| SYN | Client → A → Pod | ✅ | ✅ |
| SYN-ACK | Pod → Client | ❌ (跳过) | ✅ |
| ACK | Client → A → Pod | ✅ | ✅ |
[!TIP] 抓包技巧:在 Node A 上抓包只能看到 SYN 和 ACK,看不到 SYN-ACK;要看 SYN-ACK 需要在 Pod 所在节点抓包。
19.5.2 TCP 三次握手简单理解¶
sequenceDiagram
participant A as Client (张三)
participant B as Server (李四)
A->>B: SYN: "你好,我是张三"
B->>A: SYN-ACK: "张三你好,我是李四"
A->>B: ACK: "好的,我知道你是李四了"
Note over A,B: 握手完成,可以开始通信
- 第一次握手 (SYN):证明自己的存在
- 第二次握手 (SYN-ACK):证明对方的存在 + 确认收到
- 第三次握手 (ACK):确认彼此都存在
19.6 配置方法¶
19.6.1 Helm 部署¶
helm install cilium cilium/cilium --namespace kube-system \
--set kubeProxyReplacement=strict \
--set loadBalancer.mode=dsr
19.6.2 验证配置¶
# 查看 Cilium 状态
cilium status --verbose | grep -i "load"
# LoadBalancer Mode: DSR
# 或通过 config view
cilium config view | grep -i loadbalancer
# loadbalancer-mode: dsr
19.7 进阶方案:IPIP 隧道¶
19.7.1 IP Options 的局限性¶
| 问题 | 描述 |
|---|---|
| TCP Fast Open 不兼容 | 使用 Options 字段会影响 TCP Fast Open |
| 交换机处理慢 | 交换机需要解析 Options,增加处理开销 |
| Slow Path | 触发非快速路径处理 |
19.7.2 IPIP 隧道方案¶
字节跳动工程师提出了使用 IPIP 隧道 来传递原始 IP 信息的方案:
graph TB
subgraph "IPIP 封装"
direction TB
Outer["外层 IP Header<br/>src=Node A IP, dst=Pod IP"]
Inner["内层 IP Header<br/>src=Client IP, dst=Pod IP<br/>+ Options: orig_dst"]
Data["TCP/Payload"]
Outer --> Inner --> Data
end
19.7.3 IPIP vs IP Options 对比¶
| 对比项 | IP Options | IPIP 隧道 |
|---|---|---|
| 交换机可见 | ✅ 可见,需解析 | ❌ 不可见,快速转发 |
| 处理路径 | Slow Path | Fast Path |
| 封装开销 | 低 | 略高(多一层 IP) |
| 实现状态 | Cilium 原生支持 | 需要二次开发 |
[!NOTE] IPIP 隧道方案将 Options 信息"隐藏"在内层 IP 中,外层 IP 是标准包,交换机可以快速转发。
19.8 应用场景¶
graph TB
subgraph "适用 DSR 的场景"
A["外部流量访问 NodePort"]
B["LoadBalancer 类型 Service"]
C["南北向流量优化"]
D["入口节点负载分散"]
end
subgraph "不适用 DSR 的场景"
E["集群内 Pod-to-Pod"]
F["东西向流量 (用 Socket LB)"]
end
| 场景 | 推荐模式 |
|---|---|
| 外部 → ClusterIP/NodePort | DSR |
| 外部 → LoadBalancer | DSR |
| Pod → Service(集群内) | Socket LB |
| 需要源 IP 保留 | DSR (externalTrafficPolicy=Local) |
19.9 与 LVS DSR 的关系¶
Cilium DSR 的思想源自 Linux LVS 的 DR 模式:
| LVS 术语 | Cilium 对应 |
|---|---|
| Director | 入口节点 (Node A) |
| Real Server | 后端 Pod |
| VIP | Service IP / NodePort |
| DIP | Pod IP |
[!TIP] 学习 LVS 的 DR/NAT/TUN 模式可以帮助理解 Cilium DSR 的设计思想。
19.10 章节小结¶
mindmap
root((DSR 模式))
背景
SNAT 模式入口节点瓶颈
返回流量多一跳
原理
后端直接响应客户端
跳过入口节点
挑战
如何传递原始目的 IP
Pod 响应需伪装源 IP
实现
IP Options 字段
IPIP 隧道(进阶)
验证
Node A 看不到 SYN-ACK
Pod 节点可见完整握手
配置
loadBalancer.mode=dsr
[!IMPORTANT] 核心要点总结:
什么是 DSR:Direct Server Return,后端 Pod 直接响应客户端,跳过入口节点
解决的问题:
- 减少返回路径跳数(4 跳 → 3 跳)
- 分散入口节点负载
保留真实客户端源 IP
核心挑战:如何将原始目的 IP 传递给后端 Pod
Cilium 实现:通过 IP Options 字段携带
orig_dst信息抓包验证:SYN-ACK 从 Pod 直接发往 Client,Node A 看不到
配置方法:
loadBalancer.mode=dsr与 Socket LB 的区别:
- Socket LB:优化东西向(集群内)流量
- DSR:优化南北向(外部访问)流量
第二十章 Cilium 双栈模式¶
本章介绍 Cilium 的 IPv4/IPv6 双栈(Dual Stack)支持,这是一种让 Pod 和 Service 同时拥有 IPv4 和 IPv6 地址的网络配置方式,是从 IPv4 向 IPv6 过渡的重要技术。
20.1 背景与概念¶
20.1.1 为什么需要双栈¶
随着 IPv4 地址资源枯竭和 IPv6 的推广,越来越多的企业开始进行 IPv6 改造:
graph LR
subgraph "演进路径"
A["纯 IPv4"] --> B["双栈过渡"]
B --> C["纯 IPv6"]
end
| 方案 | 描述 |
|---|---|
| 纯 IPv4 | 传统方案,地址资源紧张 |
| 假双栈 | 两个网卡分别承载 IPv4/IPv6 |
| 真双栈 | 同一网卡同时拥有 IPv4 和 IPv6 地址 |
| 纯 IPv6 | 未来目标,彻底解决地址问题 |
20.1.2 双栈的定义¶
真正的双栈(Dual Stack):一个网络接口上同时配置 IPv4 和 IPv6 地址,两种协议栈独立运行。
graph TB
subgraph "双栈网卡"
NIC["eth0"]
IPv4["IPv4: 10.0.0.1/24"]
IPv6["IPv6: fd00::1/64"]
NIC --> IPv4
NIC --> IPv6
end
[!NOTE] 假双栈 vs 真双栈:
- 假双栈:两个接口分别提供 IPv4 和 IPv6 服务,客户端根据协议访问不同接口
- 真双栈:一个接口同时提供两种协议,客户端可以使用任意协议访问
20.2 Cilium 双栈配置¶
20.2.1 Helm 部署参数¶
helm install cilium cilium/cilium --namespace kube-system \
--set ipv6.enabled=true \
--set ipam.mode=kubernetes
| 参数 | 说明 |
|---|---|
ipv6.enabled=true |
启用 IPv6 支持 |
ipv4.enabled=true |
默认启用,无需显式设置 |
kubeProxyReplacement |
双栈暂不支持 strict 模式 |
[!WARNING] 注意事项:Cilium 双栈模式目前不支持
kubeProxyReplacement=strict,需要保留 kube-proxy。
20.2.2 Kind 集群配置¶
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
ipFamily: dual # 关键配置:启用双栈
podSubnet: "10.244.0.0/16,fd00:10:244::/56"
serviceSubnet: "10.96.0.0/12,fd00:10:96::/112"
20.3 Pod 双栈验证¶
20.3.1 查看 Pod IP¶
# 简单查看(只显示一个 IP)
kubectl get pod -o wide
# 详细查看(显示所有 IP)
kubectl get pod -o yaml | grep -A 10 "podIPs"
输出示例:
podIPs:
- ip: "10.244.1.57" # IPv4 地址
- ip: "fd00::982a:..." # IPv6 地址
20.3.2 验证网卡配置¶
# 进入 Pod 查看 IP 地址
kubectl exec -it <pod-name> -- ip addr show eth0
输出示例:
2: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 10.244.1.57/32 scope global eth0
inet6 fd00::982a:.../128 scope global
inet6 fe80::xxxx/64 scope link
graph TB
subgraph "Pod 网卡 eth0"
A["IPv4: 10.244.1.57/32"]
B["IPv6: fd00::982a.../128<br/>(Global)"]
C["IPv6: fe80::xxxx/64<br/>(Link-local)"]
end
[!NOTE] 三种地址:
- IPv4 地址:Pod 的 IPv4 通信地址
- IPv6 Global 地址:Pod 的 IPv6 全局通信地址
- IPv6 Link-local 地址:以
fe80::开头,仅用于本地链路通信
20.4 Service 双栈支持¶
20.4.1 Service 配置¶
apiVersion: v1
kind: Service
metadata:
name: demo-svc
spec:
ipFamilyPolicy: PreferDualStack # 优先双栈
ipFamilies:
- IPv4
- IPv6
selector:
app: demo
ports:
- port: 80
| 字段 | 值 | 说明 |
|---|---|---|
ipFamilyPolicy |
PreferDualStack |
优先使用双栈 |
ipFamilyPolicy |
RequireDualStack |
强制要求双栈 |
ipFamilies |
[IPv4, IPv6] |
指定支持的 IP 类型 |
20.4.2 验证 Service IP¶
kubectl get svc demo-svc -o yaml | grep -A 5 "clusterIPs"
输出示例:
clusterIPs:
- "10.96.44.152" # IPv4 ClusterIP
- "fd00:10:96::xxx" # IPv6 ClusterIP
20.5 DNS 解析¶
20.5.1 A 记录与 AAAA 记录¶
graph LR
subgraph "DNS 记录类型"
A["A 记录"] -->|"返回"| IPv4["IPv4 地址"]
AAAA["AAAA 记录"] -->|"返回"| IPv6["IPv6 地址"]
end
20.5.2 解析示例¶
# 解析 IPv4 地址(A 记录)
nslookup -type=A demo-svc.default.svc.cluster.local
# 返回:10.96.44.152
# 解析 IPv6 地址(AAAA 记录)
nslookup -type=AAAA demo-svc.default.svc.cluster.local
# 返回:fd00:10:96::xxx
| 记录类型 | 返回内容 | 用途 |
|---|---|---|
| A | IPv4 地址 | IPv4 客户端访问 |
| AAAA | IPv6 地址 | IPv6 客户端访问 |
20.6 双栈路由¶
20.6.1 IPv4 路由表¶
# Pod 内查看 IPv4 路由
ip route
default via 10.244.1.5 dev eth0
10.244.1.5 dev eth0 scope link
20.6.2 IPv6 路由表¶
# Pod 内查看 IPv6 路由
ip -6 route
default via fe80::xxxx dev eth0
fd00::/64 dev eth0
graph TB
subgraph "双栈路由"
Pod["Pod"]
subgraph "IPv4 路径"
GW4["网关: 10.244.1.5"]
end
subgraph "IPv6 路径"
GW6["网关: fe80::xxxx"]
end
Pod -->|"IPv4 流量"| GW4
Pod -->|"IPv6 流量"| GW6
end
20.7 IPv6 邻居发现¶
20.7.1 与 ARP 的区别¶
| 特性 | IPv4 ARP | IPv6 NDP |
|---|---|---|
| 协议名称 | ARP | NDP (Neighbor Discovery Protocol) |
| 请求消息 | ARP Request | NS (Neighbor Solicitation) |
| 响应消息 | ARP Reply | NA (Neighbor Advertisement) |
| 复杂度 | 简单 | 复杂(有状态机) |
| 查看命令 | arp -n |
ip -6 neigh |
20.7.2 IPv6 地址类型¶
graph TB
subgraph "IPv6 地址类型"
Global["全局地址<br/>fd00::/8 或 2000::/3"]
LinkLocal["链路本地地址<br/>fe80::/10"]
Multicast["组播地址<br/>ff00::/8"]
end
| 地址类型 | 前缀 | 用途 |
|---|---|---|
| Global | 2000::/3 或 fd00::/8 |
全局通信 |
| Link-local | fe80::/10 |
本地链路通信 |
| Multicast | ff00::/8 |
组播通信 |
[!NOTE] Link-local 地址:每个启用 IPv6 的网卡都会自动生成一个
fe80::开头的链路本地地址,用于邻居发现等本地通信。
20.7.3 邻居发现状态机¶
IPv6 邻居发现有多种状态:
stateDiagram-v2
[*] --> INCOMPLETE
INCOMPLETE --> REACHABLE: 收到 NA
REACHABLE --> STALE: 超时
STALE --> DELAY: 有数据发送
DELAY --> PROBE: 超时
PROBE --> REACHABLE: 收到 NA
PROBE --> [*]: 失败
| 状态 | 说明 |
|---|---|
| INCOMPLETE | 正在解析,等待 NA 响应 |
| REACHABLE | 可达,最近确认过 |
| STALE | 过期,需要重新验证 |
| DELAY | 延迟探测 |
| PROBE | 主动探测中 |
20.8 跨节点通信验证¶
20.8.1 IPv6 Ping 测试¶
# Pod 内 ping IPv6 地址
ping6 fd00::9d86:...
20.8.2 抓包分析¶
# 在节点上抓包
tcpdump -i lxc-xxx icmp6
抓包输出:
# NS/NA 消息(邻居发现)
fe80::xxxx > ff02::1:ffxx:xxxx: ICMP6, neighbor solicitation
fe80::xxxx > fe80::yyyy: ICMP6, neighbor advertisement
# Echo Request/Reply
fd00::982a > fd00::9d86: ICMP6, echo request
fd00::9d86 > fd00::982a: ICMP6, echo reply
20.9 MAC 地址学习¶
20.9.1 eBPF 的作用¶
Cilium 使用 eBPF 来处理 IPv6 邻居发现:
graph LR
subgraph "MAC 地址学习"
NS["NS 请求"] --> eBPF["eBPF Hook"]
eBPF --> NA["NA 响应"]
NA --> MAC["获取 MAC 地址"]
end
[!TIP] Cilium 的 eBPF 程序会劫持邻居发现请求,返回对应的 lxc 网卡 MAC 地址,类似于 Calico 中的
169.254.1.1代理 ARP 机制。
20.9.2 验证 MAC 地址¶
# 抓包查看目的 MAC
tcpdump -i lxc-xxx -e icmp6
# 输出示例
# src MAC: 7c:f9:26:... (Pod 网卡)
# dst MAC: b6:44:1b:... (lxc 网卡)
20.10 配置总结¶
20.10.1 完整配置流程¶
flowchart TB
A["1. Kind 集群配置<br/>ipFamily: dual"] --> B["2. Helm 安装 Cilium<br/>ipv6.enabled=true"]
B --> C["3. 部署 Pod<br/>自动获取双 IP"]
C --> D["4. 创建 Service<br/>ipFamilyPolicy: PreferDualStack"]
D --> E["5. 验证通信<br/>ping/ping6 测试"]
20.10.2 关键配置对照表¶
| 层级 | 配置项 | 值 |
|---|---|---|
| Cluster | ipFamily |
dual |
| Cilium | ipv6.enabled |
true |
| Service | ipFamilyPolicy |
PreferDualStack |
| Service | ipFamilies |
[IPv4, IPv6] |
20.11 章节小结¶
mindmap
root((双栈模式))
概念
同一网卡双 IP
IPv4 + IPv6 共存
配置
ipv6.enabled=true
ipFamilyPolicy: PreferDualStack
Pod 验证
kubectl get pod -o yaml
ip addr show eth0
Service 验证
A 记录返回 IPv4
AAAA 记录返回 IPv6
IPv6 特性
NDP 替代 ARP
Link-local 地址
状态机复杂
[!IMPORTANT] 核心要点总结:
什么是双栈:同一网卡同时拥有 IPv4 和 IPv6 地址
配置要点:
- Cilium:
ipv6.enabled=trueService:
ipFamilyPolicy: PreferDualStack验证方法:
- Pod:
ip addr show eth0看到两个地址Service:
kubectl get svc -o yaml看到两个 ClusterIPDNS 解析:
- A 记录 → IPv4 地址
AAAA 记录 → IPv6 地址
IPv6 特性:
- 使用 NDP 替代 ARP
- 自动生成 Link-local 地址 (
fe80::)邻居状态机比 ARP 复杂
当前限制:双栈不支持
kubeProxyReplacement=strict
第二十一章 Cilium-LB-IPAM¶
本章介绍 Cilium 的 LB IPAM(LoadBalancer IP Address Management)功能,这是一个专门为 LoadBalancer 类型 Service 分配 IP 地址的工具,常与 BGP Control Plane 结合使用实现外部访问。
21.1 背景与问题¶
21.1.1 LoadBalancer Service 的困境¶
在裸金属(Bare Metal)Kubernetes 集群中,创建 LoadBalancer 类型的 Service 时,常遇到以下问题:
graph LR
subgraph "云环境"
A["创建 LB Service"] --> B["云厂商自动分配 IP"]
B --> C["External IP 就绪"]
end
subgraph "裸金属环境"
D["创建 LB Service"] --> E["无 IP 分配器"]
E --> F["External IP: Pending"]
end
| 环境 | LoadBalancer 支持 |
|---|---|
| 云环境(AWS/GCP/Azure) | 云厂商自动分配公网 IP |
| 裸金属集群 | 默认无 IP 分配,状态为 Pending |
[!NOTE] 很多初学者在部署 Ingress Controller 时遇到
EXTERNAL-IP一直显示<pending>的问题,原因就是没有 LoadBalancer IP 分配器。
21.1.2 常见解决方案¶
graph TB
subgraph "LoadBalancer IP 解决方案"
A["MetalLB"]
B["Cilium LB IPAM"]
C["kube-vip"]
D["OpenELB"]
end
| 方案 | 特点 |
|---|---|
| MetalLB | 独立项目,支持 L2 和 BGP 模式 |
| Cilium LB IPAM | Cilium 内置,需配合 BGP 使用 |
| kube-vip | 轻量级,支持 VIP 和 LB |
21.2 核心概念¶
21.2.1 LB IPAM 的职责¶
LB IPAM 只负责分配 IP 地址,不负责流量转发!
graph LR
subgraph "LB IPAM 职责"
A["IP Pool 管理"]
B["IP 分配"]
C["Service 标签匹配"]
end
subgraph "不负责"
D["流量路由"]
E["负载均衡"]
F["外部可达性"]
end
A --> B --> C
[!IMPORTANT] 关键理解:LB IPAM 分配的 IP 地址默认不可路由,需要配合 BGP Control Plane 将地址宣告出去,才能实现外部访问。
21.2.2 与 BGP 的关系¶
sequenceDiagram
participant Svc as Service
participant IPAM as LB IPAM
participant BGP as BGP Control Plane
participant Router as 外部路由器
Svc->>IPAM: 请求 LoadBalancer IP
IPAM->>Svc: 分配 IP(如 20.0.10.1)
Note over Svc: EXTERNAL-IP 就绪
BGP->>Router: 宣告 20.0.10.1
Router->>Router: 更新路由表
Note over Router: 地址变得可路由
21.3 Cilium vs Calico 设计对比¶
21.3.1 宣告的 IP 类型¶
graph TB
subgraph "Cilium 方案"
A["宣告 LoadBalancer IP"]
B["符合 K8s 设计理念"]
C["LB IP 本应外部可达"]
end
subgraph "Calico 方案"
D["宣告 ClusterIP"]
E["ClusterIP 变得外部可达"]
F["可能违背设计初衷"]
end
| 对比项 | Cilium | Calico |
|---|---|---|
| 宣告的 IP | LoadBalancer IP | ClusterIP |
| 设计契合度 | 符合 K8s 设计 | 略有争议 |
| ClusterIP 暴露 | ❌ 保持内部 | ✅ 可外部访问 |
[!TIP] Kubernetes 设计中 ClusterIP 是集群内部地址,Cilium 选择宣告 LB IP 更符合这一理念。
21.4 配置与使用¶
21.4.1 CRD 资源¶
Cilium 安装后会自动创建 CiliumLoadBalancerIPPool CRD:
# 查看 CRD
kubectl api-resources | grep cilium | grep pool
# 输出
ciliumloadbalancerippools ippools,lbippool cilium.io/v2alpha1 false CiliumLoadBalancerIPPool
21.4.2 创建 IP Pool¶
apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
name: blue-pool
spec:
blocks:
- cidr: "20.0.10.0/24" # IP 地址池范围
serviceSelector:
matchLabels:
color: blue # 匹配的 Service 标签
---
apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
name: red-pool
spec:
blocks:
- cidr: "30.0.10.0/24"
serviceSelector:
matchLabels:
color: red
21.4.3 IP Pool 结构说明¶
graph TB
subgraph "IP Pool 结构"
Pool["CiliumLoadBalancerIPPool"]
Blocks["blocks<br/>(IP 地址段列表)"]
CIDR["cidr: 20.0.10.0/24"]
Selector["serviceSelector<br/>(Service 选择器)"]
Labels["matchLabels<br/>color: blue"]
Pool --> Blocks --> CIDR
Pool --> Selector --> Labels
end
| 字段 | 说明 |
|---|---|
blocks |
IP 地址段列表,支持多个 CIDR |
serviceSelector |
Service 标签选择器 |
matchLabels |
精确匹配标签 |
matchExpressions |
表达式匹配(可选) |
21.5 Service 配置¶
21.5.1 创建使用 IP Pool 的 Service¶
apiVersion: v1
kind: Service
metadata:
name: demo-svc
labels:
color: red # 匹配 red-pool
spec:
type: LoadBalancer # 必须是 LoadBalancer 类型
selector:
app: demo
ports:
- port: 80
21.5.2 验证 IP 分配¶
# 查看 Service
kubectl get svc demo-svc
# 输出示例
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
demo-svc LoadBalancer 10.96.1.100 30.0.10.208 80:31234/TCP
graph LR
subgraph "IP 分配流程"
A["Service<br/>labels: color=red"] --> B["匹配 red-pool"]
B --> C["从 30.0.10.0/24 分配"]
C --> D["EXTERNAL-IP: 30.0.10.208"]
end
21.6 工作原理¶
21.6.1 标签匹配机制¶
flowchart TB
subgraph "匹配流程"
S["Service 创建<br/>type: LoadBalancer"]
L["检查 Service labels"]
P1["blue-pool<br/>matchLabels: color=blue"]
P2["red-pool<br/>matchLabels: color=red"]
IP1["分配 20.0.10.x"]
IP2["分配 30.0.10.x"]
S --> L
L -->|"color=blue"| P1 --> IP1
L -->|"color=red"| P2 --> IP2
end
21.6.2 多 Pool 匹配规则¶
| 场景 | 行为 |
|---|---|
| Service 标签匹配单个 Pool | 从该 Pool 分配 IP |
| Service 标签匹配多个 Pool | 从第一个匹配的 Pool 分配 |
| Service 无匹配标签 | IP 不分配,状态 Pending |
21.7 与 MetalLB 对比¶
21.7.1 功能对比¶
| 特性 | Cilium LB IPAM | MetalLB |
|---|---|---|
| IP 分配 | ✅ | ✅ |
| L2 模式 | ❌ | ✅ |
| BGP 模式 | ✅(需 BGP CP) | ✅ |
| 独立使用 | ❌(需配合 BGP) | ✅ |
| CNI 集成 | ✅ 原生 | ❌ 独立部署 |
21.7.2 选择建议¶
graph TB
subgraph "选择指南"
Q1["使用 Cilium CNI?"]
Q2["有 BGP 基础设施?"]
A1["Cilium LB IPAM<br/>+ BGP Control Plane"]
A2["MetalLB L2 模式"]
A3["MetalLB"]
Q1 -->|"是"| Q2
Q1 -->|"否"| A3
Q2 -->|"是"| A1
Q2 -->|"否"| A2
end
21.8 重要限制¶
[!WARNING] LB IPAM 单独使用时的限制:
- IP 不可路由:分配的 IP 只是写入 Service,外部无法访问
- 需要 BGP:必须配合 BGP Control Plane 宣告路由
- 版本要求:Cilium 1.13+ 支持 BGP 宣告 LB IP
21.8.1 单独使用 LB IPAM 的效果¶
# Service 获得 IP,但无法访问
kubectl get svc
NAME TYPE EXTERNAL-IP ...
demo-svc LoadBalancer 30.0.10.208 ...
# 从集群外部访问
curl 30.0.10.208
# 超时 - IP 不可路由
21.9 完整工作流程¶
21.9.1 LB IPAM + BGP Control Plane¶
sequenceDiagram
participant User as 用户
participant Svc as Service
participant IPAM as LB IPAM
participant BGP as BGP CP
participant TOR as ToR 交换机
participant Client as 外部客户端
User->>Svc: 创建 LB Service
IPAM->>Svc: 分配 IP 30.0.10.208
BGP->>TOR: BGP 宣告 30.0.10.208
TOR->>TOR: 更新路由表
Client->>TOR: 访问 30.0.10.208
TOR->>Svc: 路由到集群节点
Svc->>Client: 响应
21.9.2 ECMP 负载均衡¶
配合 BGP 可实现 ECMP(等价多路径)负载均衡:
# 路由表示例
30.0.10.208 via 10.1.5.10 # Node 1
30.0.10.208 via 10.1.5.11 # Node 2
# = 等价路由,流量分担
21.10 章节小结¶
mindmap
root((LB IPAM))
概念
LoadBalancer IP 管理器
只分配IP不负责路由
配置
CiliumLoadBalancerIPPool
blocks + serviceSelector
匹配
Service labels 匹配 Pool
分配对应网段 IP
限制
单独使用IP不可路由
需配合BGP使用
对比
Cilium宣告LB IP
Calico宣告ClusterIP
[!IMPORTANT] 核心要点总结:
什么是 LB IPAM:Cilium 内置的 LoadBalancer IP 地址分配器
核心职责:只负责分配 IP,不负责路由和负载均衡
配置方式:
- 创建
CiliumLoadBalancerIPPool定义 IP 池Service 通过 labels 匹配 Pool
重要限制:
- 单独使用时 IP 不可路由
需要配合 BGP Control Plane 宣告路由
与 Calico 对比:
- Cilium:宣告 LB IP(符合 K8s 设计)
Calico:宣告 ClusterIP(有争议)
版本要求:Cilium 1.13+ 才支持 BGP 宣告 LB IP
第二十二章 Cilium 带宽管理¶
本章介绍 Cilium 的带宽管理(Bandwidth Manager)功能,这是一种通过 EDT(Earliest Departure Time)时间戳机制在物理网卡上实现 Pod 流量限速的技术。
22.1 背景与问题¶
22.1.1 传统限速思路的局限¶
最直觉的限速方式是在 Pod 的网卡上做限制:
graph LR
subgraph "传统思路"
Pod["Pod eth0"] --> LXC["lxc 网卡"]
LXC --> PHY["物理网卡"]
Pod -.->|"❌ 在这限速"| Pod
LXC -.->|"❌ 在这限速"| LXC
end
为什么不能在 veth 网卡上限速?
| 问题 | 说明 |
|---|---|
| Buffer Bloat | veth 队列缓冲区容易被填满 |
| TCP TSQ 处理差 | 影响 TCP 小队列优化 |
| 驱动限制 | veth pair 驱动不支持高级队列调度 |
[!WARNING] Docker/Kubernetes 的 veth pair 虚拟网卡在带宽管理上存在天然限制,不适合做精确限速。
22.1.2 Cilium 的解决方案¶
Cilium 选择在物理网卡上做限速,通过 EDT 时间戳告诉物理网卡何时发送数据包:
graph LR
subgraph "Cilium 方案"
Pod["Pod eth0"] --> LXC["lxc 网卡"]
LXC --> |"EDT 时间戳"| PHY["物理网卡"]
PHY -.->|"✅ 在这限速"| PHY
end
22.2 核心原理¶
22.2.1 EDT (Earliest Departure Time)¶
EDT 是一个内核功能,数据包在发送时会携带一个时间戳,告诉网卡设备这个包最早什么时候可以发出:
sequenceDiagram
participant Pod as Pod
participant eBPF as eBPF Datapath
participant NIC as 物理网卡
Pod->>eBPF: 发送数据包
eBPF->>eBPF: 计算 EDT 时间戳
eBPF->>NIC: 数据包 + EDT
Note over NIC: 根据 EDT 调度发送
NIC->>NIC: 时间到才发送
[!NOTE] EDT 时间戳是 Linux 内核 5.x 版本引入的功能,Cilium 利用这一特性实现精确的带宽控制。
22.2.2 MQ + FQ 队列协作¶
物理网卡使用两种队列机制配合实现限速:
graph TB
subgraph "队列调度架构"
Packets["数据包流"] --> MQ["MQ (Multi-Queue)<br/>多队列分发"]
MQ --> Q1["队列 1"]
MQ --> Q2["队列 2"]
MQ --> Q3["队列 N"]
Q1 --> FQ["FQ (Fair Queue)<br/>公平队列"]
Q2 --> FQ
Q3 --> FQ
FQ --> |"根据 EDT 调度"| NIC["网卡发送"]
end
| 组件 | 全称 | 作用 |
|---|---|---|
| MQ | Multi-Queue | 多队列,将流量分散到多个 CPU/队列 |
| FQ | Fair Queue | 公平队列,基于时间轮按 EDT 调度发包 |
22.2.3 工作流程¶
flowchart TB
A["1. Cilium Agent 监控 Pod 注解"]
B["2. 读取带宽限制配置"]
C["3. 将限制写入 eBPF Datapath"]
D["4. 数据包发送时计算 EDT"]
E["5. 物理网卡 FQ 根据 EDT 调度"]
F["6. 实现带宽限速"]
A --> B --> C --> D --> E --> F
22.3 配置方法¶
22.3.1 启用带宽管理¶
安装 Cilium 时需要开启 bandwidthManager 功能:
helm install cilium cilium/cilium --namespace kube-system \
--set bandwidthManager.enabled=true \
--set bandwidthManager.bbr=true \
--set bpf.masquerade=true \
--set kubeProxyReplacement=true
| 参数 | 说明 |
|---|---|
bandwidthManager.enabled=true |
启用带宽管理功能 |
bandwidthManager.bbr=true |
启用 BBR 拥塞控制算法 |
22.3.2 验证功能启用¶
# 查看 Cilium 状态
cilium status
# 输出中确认
BandwidthManager: EDT with BPF [BBR]
22.3.3 Pod 注解配置¶
通过注解(Annotation)为 Pod 设置带宽限制:
apiVersion: v1
kind: Pod
metadata:
name: bandwidth-limited-pod
annotations:
kubernetes.io/egress-bandwidth: "50M" # 限制出站带宽 50Mbps
# kubernetes.io/ingress-bandwidth: "100M" # 入站带宽(可选)
spec:
containers:
- name: app
image: nginx
| 注解 | 说明 |
|---|---|
kubernetes.io/egress-bandwidth |
限制出站(发送)带宽 |
kubernetes.io/ingress-bandwidth |
限制入站(接收)带宽 |
[!TIP] 常用单位:
K(Kbps)、M(Mbps)、G(Gbps),如10M表示 10 Mbps。
22.4 实现原理详解¶
22.4.1 为什么在物理网卡限速¶
graph TB
subgraph "veth 限速问题"
V1["veth 队列满"]
V2["Buffer Bloat"]
V3["TCP 性能下降"]
V1 --> V2 --> V3
end
subgraph "物理网卡优势"
P1["硬件队列支持"]
P2["MQ+FQ 调度"]
P3["EDT 精确控制"]
P1 --> P2 --> P3
end
| 对比项 | veth 网卡 | 物理网卡 |
|---|---|---|
| 队列支持 | 简单队列 | 多队列 + 硬件队列 |
| 调度能力 | 有限 | FQ 公平调度 |
| 性能影响 | 大(buffer bloat) | 小 |
| 限速精度 | 低 | 高(EDT 时间戳) |
22.4.2 EDT 时间戳原理¶
sequenceDiagram
participant App as 应用
participant Kernel as 内核
participant FQ as FQ 调度器
participant NIC as 网卡
App->>Kernel: 发送数据包
Kernel->>Kernel: 计算 EDT = 当前时间 + 延迟
Kernel->>FQ: 数据包 + EDT
Note over FQ: 时间轮调度
loop 每个时间片
FQ->>FQ: 检查 EDT <= 当前时间?
alt EDT 到达
FQ->>NIC: 发送数据包
else EDT 未到
FQ->>FQ: 继续等待
end
end
限速计算示例:
原始带宽:1 Gbps = 每秒发送 1000M 数据
限制带宽:50 Mbps
计算:1000 / 50 = 20 倍
实现:每 20 个时间片才发送 1 个包
结果:带宽降低到 50 Mbps
22.5 测试验证¶
22.5.1 部署测试 Pod¶
# 10M 带宽限制
apiVersion: v1
kind: Pod
metadata:
name: netperf-10m
annotations:
kubernetes.io/egress-bandwidth: "10M"
spec:
containers:
- name: netperf
image: networkstatic/netperf
---
# 100M 带宽限制
apiVersion: v1
kind: Pod
metadata:
name: netperf-100m
annotations:
kubernetes.io/egress-bandwidth: "100M"
spec:
containers:
- name: netperf
image: networkstatic/netperf
22.5.2 使用 netperf 测试¶
# 获取服务端 IP
SERVER_IP=$(kubectl get pod netperf-server -o jsonpath='{.status.podIP}')
# 从限速 Pod 测试带宽
kubectl exec -it netperf-10m -- netperf -H $SERVER_IP -t TCP_STREAM
# 预期结果:~9.5 Mbps(接近 10M 限制)
22.5.3 测试结果对照¶
| 配置 | 预期带宽 | 实测带宽 |
|---|---|---|
egress-bandwidth: 10M |
10 Mbps | ~9.5 Mbps |
egress-bandwidth: 100M |
100 Mbps | ~93 Mbps |
| 无限制 | 网卡最大 | ~500+ Mbps |
22.6 与传统方案对比¶
22.6.1 与 CNI bandwidth 插件对比¶
graph TB
subgraph "传统方案 - CNI bandwidth"
A1["使用 tc 规则"]
A2["在 veth 上限速"]
A3["tbf/htb 队列"]
end
subgraph "Cilium 方案"
B1["使用 eBPF + EDT"]
B2["在物理网卡限速"]
B3["MQ + FQ 调度"]
end
| 特性 | CNI bandwidth 插件 | Cilium 带宽管理 |
|---|---|---|
| 限速位置 | veth 网卡 | 物理网卡 |
| 实现方式 | tc (tbf/htb) | eBPF + EDT |
| 性能影响 | 较大 | 较小 |
| 精度 | 一般 | 高 |
| TCP 优化 | 无 | TSQ 支持 |
22.7 重要限制¶
[!CAUTION] 使用限制:
- 不支持 Kind 环境:Kind 使用 veth pair,无法正确限速
- 需要物理/虚拟机环境:必须有真正的物理网卡驱动
- 内核版本要求:需要支持 EDT 的内核(5.x+)
- 主要限制 egress:入站限速场景较少
22.8 注意事项¶
22.8.1 环境要求¶
graph LR
subgraph "支持的环境"
A["物理服务器"]
B["VMware/KVM 虚拟机"]
C["云服务器"]
end
subgraph "不支持的环境"
D["Kind 集群"]
E["Docker Desktop"]
end
22.8.2 驱动检查¶
# 查看网卡驱动
ethtool -i eth0 | grep driver
# 物理网卡驱动示例
driver: vmxnet3 # VMware
driver: virtio_net # KVM
driver: ixgbe # Intel 万兆
22.9 章节小结¶
mindmap
root((带宽管理))
原理
EDT时间戳
物理网卡限速
MQ+FQ队列
配置
bandwidthManager.enabled
Pod注解方式
限速注解
egress-bandwidth
ingress-bandwidth
限制
不支持Kind
需要真实网卡
内核5.x+
[!IMPORTANT] 核心要点总结:
为什么不在 veth 限速:Buffer Bloat、队列受限、精度差
Cilium 方案:在物理网卡使用 EDT 时间戳限速
启用方式:
- Helm:
bandwidthManager.enabled=truePod:
kubernetes.io/egress-bandwidth: "50M"工作原理:
- eBPF 为数据包打上 EDT 时间戳
FQ 调度器根据 EDT 控制发包时机
MQ + FQ 协作:
- MQ(多队列)分散流量
FQ(公平队列)按时间调度
环境要求:
- 需要真实物理/虚拟网卡
- 不支持 Kind(veth pair)
- 内核版本 5.x+
第二十三章 Cilium Ingress Controller¶
本章介绍 Cilium 原生的 Ingress Controller 功能,无需部署额外的 Ingress 控制器(如 Nginx Ingress),Cilium 可以直接提供七层负载均衡能力。
23.1 背景与概念¶
23.1.1 Ingress 回顾¶
Ingress 是 Kubernetes 中用于管理七层(HTTP/HTTPS)流量入口的资源:
graph LR
Client["外部客户端"] --> LB["LoadBalancer<br/>L4 负载均衡"]
LB --> IC["Ingress Controller<br/>L7 负载均衡"]
IC --> |"基于 URI 路由"| Svc1["Service A"]
IC --> |"基于 URI 路由"| Svc2["Service B"]
Svc1 --> Pod1["Pod A"]
Svc2 --> Pod2["Pod B"]
四层 vs 七层负载均衡:
| 特性 | 四层(L4) | 七层(L7) |
|---|---|---|
| 工作层 | 传输层(TCP/UDP) | 应用层(HTTP/HTTPS) |
| 路由依据 | IP + 端口 | URL 路径、Host 头 |
| SSL 卸载 | ❌ | ✅ |
| 典型场景 | Service LoadBalancer | Ingress |
23.1.2 传统 Ingress 架构¶
graph TB
subgraph "传统方案"
Nginx["Nginx Ingress Controller"]
Rules["Ingress Rules"]
Backend["后端 Service/Pod"]
Nginx --> |"解析 Rules"| Rules
Rules --> |"路由到"| Backend
end
需要额外部署 Nginx Ingress、Traefik 等控制器。
23.1.3 Cilium Ingress Controller¶
Cilium 内置 Ingress Controller,无需额外组件:
graph TB
subgraph "Cilium 方案"
Cilium["Cilium Agent<br/>内置 Ingress Controller"]
Envoy["Envoy L7 Proxy"]
Rules["Ingress Rules"]
Backend["后端 Service/Pod"]
Cilium --> Envoy
Envoy --> |"解析 Rules"| Rules
Rules --> |"路由到"| Backend
end
[!TIP] Cilium 使用 Envoy 作为七层代理,提供 Ingress Controller 能力,同时为 Service Mesh 功能奠定基础。
23.2 前置要求¶
23.2.1 配置要求¶
| 要求 | 说明 |
|---|---|
| kubeProxyReplacement | 必须为 strict 或 true |
| L7 Proxy | 默认启用 |
| Kubernetes 版本 | ≥ 1.19 |
| Cilium 版本 | ≥ 1.13 |
23.2.2 启用 Ingress Controller¶
helm install cilium cilium/cilium --namespace kube-system \
--set kubeProxyReplacement=true \
--set ingressController.enabled=true \
--set ingressController.loadbalancerMode=dedicated
| 参数 | 说明 |
|---|---|
ingressController.enabled=true |
启用 Ingress Controller |
ingressController.loadbalancerMode |
dedicated(专用)或 shared(共享) |
23.3 HTTP 模式¶
23.3.1 工作流程¶
sequenceDiagram
participant Client as 客户端
participant LB as LoadBalancer
participant Envoy as Cilium Envoy
participant Svc as Service
participant Pod as Pod
Client->>LB: HTTP 请求 (pass: /details)
LB->>Envoy: 转发请求
Envoy->>Envoy: 解析 Ingress Rules
Envoy->>Svc: 匹配后端服务
Svc->>Pod: 路由到 Pod
Pod->>Client: 返回响应
23.3.2 创建后端服务¶
# 部署后端应用
apiVersion: apps/v1
kind: Deployment
metadata:
name: productpage
spec:
replicas: 1
selector:
matchLabels:
app: productpage
template:
metadata:
labels:
app: productpage
spec:
containers:
- name: productpage
image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2
ports:
- containerPort: 9080
---
apiVersion: v1
kind: Service
metadata:
name: productpage
spec:
selector:
app: productpage
ports:
- port: 9080
23.3.3 创建 Ingress 资源¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: basic-ingress
spec:
ingressClassName: cilium # 使用 Cilium Ingress Controller
rules:
- http:
paths:
- path: /details
pathType: Prefix
backend:
service:
name: details
port:
number: 9080
- path: /
pathType: Prefix
backend:
service:
name: productpage
port:
number: 9080
23.3.4 验证配置¶
# 查看 Ingress
kubectl get ingress
# 输出示例
NAME CLASS HOSTS ADDRESS PORTS
basic-ingress cilium * 172.18.0.200 80
# 测试访问
curl http://172.18.0.200/details | jq
23.4 HTTPS 模式¶
23.4.1 TLS 卸载原理¶
sequenceDiagram
participant Client as 客户端
participant Envoy as Cilium Envoy
participant Pod as 后端 Pod
Client->>Envoy: HTTPS (TLS 加密)
Note over Envoy: TLS 卸载(解密)
Envoy->>Pod: HTTP (明文)
Pod->>Envoy: HTTP 响应
Note over Envoy: TLS 加密
Envoy->>Client: HTTPS 响应
[!NOTE] 信任域划分:
- 非信任域:客户端 ↔ Ingress Controller(HTTPS)
- 信任域:Ingress Controller ↔ 后端 Pod(HTTP)
23.4.2 创建 TLS 证书¶
方式一:使用 minica
# 安装 minica
go install github.com/jsha/minica@latest
# 生成证书
minica --domains demo.cilium.rocks
# 创建 Secret
kubectl create secret tls demo-cert \
--cert=demo.cilium.rocks/cert.pem \
--key=demo.cilium.rocks/key.pem
方式二:使用 OpenSSL
# 生成自签名证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=demo.cilium.rocks"
# 创建 Secret
kubectl create secret tls demo-cert \
--cert=tls.crt --key=tls.key
方式三:使用 cert-manager
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: demo-cert
spec:
secretName: demo-cert
dnsNames:
- demo.cilium.rocks
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
23.4.3 创建 HTTPS Ingress¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: cilium
tls:
- hosts:
- demo.cilium.rocks
secretName: demo-cert # 引用 TLS Secret
rules:
- host: demo.cilium.rocks # 指定 Host
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: productpage
port:
number: 9080
23.4.4 验证 HTTPS¶
# 添加 hosts 解析
echo "172.18.0.200 demo.cilium.rocks" >> /etc/hosts
# 测试 HTTPS 访问
curl -k -v https://demo.cilium.rocks/
# 查看 TLS 握手信息
curl -k -v https://demo.cilium.rocks/ 2>&1 | grep -A5 "SSL connection"
23.5 与 LoadBalancer 配合¶
23.5.1 MetalLB 集成¶
Ingress Controller 需要 LoadBalancer 类型的 Service 暴露外部访问:
graph LR
Client["外部客户端"] --> MetalLB["MetalLB<br/>L2/BGP 模式"]
MetalLB --> |"External IP"| Ingress["Cilium Ingress<br/>Controller"]
Ingress --> Pod["后端 Pod"]
23.5.2 MetalLB 配置¶
# MetalLB L2 模式配置
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- 172.18.0.200-172.18.0.254
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-system
23.6 Ingress 资源详解¶
23.6.1 资源结构¶
graph TB
subgraph "Ingress 结构"
Ingress["Ingress"]
Class["ingressClassName: cilium"]
TLS["tls(可选)"]
Rules["rules"]
Host["host"]
Paths["paths"]
Backend["backend"]
Ingress --> Class
Ingress --> TLS
Ingress --> Rules
Rules --> Host
Rules --> Paths
Paths --> Backend
end
23.6.2 关键字段¶
| 字段 | 说明 |
|---|---|
ingressClassName |
指定使用的 Ingress Controller |
tls.hosts |
TLS 证书适用的域名 |
tls.secretName |
包含证书的 Secret 名称 |
rules.host |
匹配的 Host 头(可选) |
rules.http.paths |
URL 路径匹配规则 |
backend.service |
后端 Service 配置 |
23.7 与传统方案对比¶
23.7.1 功能对比¶
| 特性 | Nginx Ingress | Cilium Ingress |
|---|---|---|
| 部署方式 | 独立 Deployment | CNI 内置 |
| L7 代理 | Nginx | Envoy |
| 配置方式 | 注解 + ConfigMap | 标准 Ingress |
| 与 CNI 集成 | 独立 | 原生集成 |
| Service Mesh | 需额外组件 | 原生支持 |
23.7.2 选择建议¶
graph TB
Q1["使用 Cilium CNI?"]
Q2["需要 Service Mesh?"]
A1["Cilium Ingress<br/>原生集成"]
A2["Nginx/Traefik<br/>成熟稳定"]
Q1 -->|"是"| Q2
Q1 -->|"否"| A2
Q2 -->|"是"| A1
Q2 -->|"否"| A1
23.8 注意事项¶
[!WARNING] 使用注意:
- HTTP 模式异常:部分版本可能出现 503 错误,建议创建资源后等待 2-3 分钟
- Host 必填(HTTPS):TLS 模式必须指定 host 字段
- LoadBalancer 依赖:需要 MetalLB 或云厂商 LB 提供 External IP
- 证书有效期:自签证书需注意过期问题,生产环境建议使用 cert-manager
23.9 章节小结¶
mindmap
root((Cilium Ingress))
概念
Cilium内置L7代理
基于Envoy
启用
ingressController.enabled
kubeProxyReplacement
HTTP模式
ingressClassName: cilium
path路由
HTTPS模式
TLS证书
SSL卸载
依赖
LoadBalancer
MetalLB/云LB
[!IMPORTANT] 核心要点总结:
什么是 Cilium Ingress:Cilium 内置的七层 Ingress Controller,基于 Envoy
前置要求:
kubeProxyReplacement=true
ingressController.enabled=trueHTTP 模式:
ingressClassName: cilium定义 path 和后端 Service
HTTPS 模式:
- 添加
tls字段配置证书必须指定
host字段TLS 证书创建:
- minica / OpenSSL / cert-manager
创建为 Kubernetes Secret
依赖组件:
- 需要 LoadBalancer(MetalLB/云 LB)提供外部 IP
第二十四章 Cilium Gateway API¶
本章介绍 Cilium 对 Kubernetes Gateway API 的支持。Gateway API 是 Ingress 的下一代替代方案,提供更强大、更标准化的七层流量管理能力。
24.1 背景与概念¶
24.1.1 什么是 Gateway API¶
Gateway API 是 Kubernetes SIG-Network 设计的新一代流量管理 API,目标是替代 Ingress:
graph TB
subgraph "演进历程"
Ingress["Ingress<br/>(传统方式)"] --> GatewayAPI["Gateway API<br/>(新一代)"]
end
subgraph "Gateway API 特点"
A["更强表达能力"]
B["角色分离"]
C["跨命名空间"]
D["可扩展性"]
end
[!NOTE] Gateway API 是 Kubernetes 官方推荐的 Ingress 替代方案,Cilium 通过 Envoy 原生支持 Gateway API。
24.1.2 与 Ingress 对比¶
| 特性 | Ingress | Gateway API |
|---|---|---|
| API 成熟度 | 稳定(GA) | 逐步稳定(部分 Beta) |
| 表达能力 | 有限 | 更丰富 |
| 角色分离 | 无 | 支持(Infra/Cluster/App) |
| TLS 管理 | 简单 | 更灵活 |
| 跨命名空间 | 复杂 | 原生支持 |
| 扩展性 | 注解(非标准) | 标准化扩展 |
24.1.3 核心资源对象¶
graph TB
subgraph "Gateway API 资源"
GC["GatewayClass<br/>定义控制器类型"]
GW["Gateway<br/>定义入口网关"]
HR["HTTPRoute<br/>定义路由规则"]
GC --> GW
GW --> HR
HR --> Svc["后端 Service"]
end
| 资源 | 说明 |
|---|---|
| GatewayClass | 定义使用的 Gateway 控制器(如 Cilium) |
| Gateway | 定义入口网关(监听端口、TLS 配置) |
| HTTPRoute | 定义 HTTP 路由规则(类似 Ingress Rules) |
24.2 前置要求¶
24.2.1 配置要求¶
| 要求 | 说明 |
|---|---|
| kubeProxyReplacement | 必须为 strict 或 true |
| L7 Proxy | 默认启用 |
| Gateway API CRDs | 必须预先安装 |
24.2.2 安装 Gateway API CRDs¶
必须预先安装四个 CRD:
# 安装 Gateway API CRDs
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.0.0/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.0.0/config/crd/standard/gateway.networking.k8s.io_gateways.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.0.0/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.0.0/config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml
[!CAUTION] 必须先安装 CRDs!如果不预先安装 Gateway API CRDs,Cilium 将无法启用 Gateway API 功能。
24.2.3 启用 Gateway API¶
helm install cilium cilium/cilium --namespace kube-system \
--set kubeProxyReplacement=true \
--set gatewayAPI.enabled=true
| 参数 | 说明 |
|---|---|
gatewayAPI.enabled=true |
启用 Gateway API 支持 |
24.3 HTTP 模式¶
24.3.1 工作流程¶
sequenceDiagram
participant Client as 客户端
participant GW as Gateway
participant Route as HTTPRoute
participant Svc as Service
participant Pod as Pod
Client->>GW: HTTP 请求
GW->>Route: 匹配路由规则
Route->>Svc: 转发到后端
Svc->>Pod: 路由到 Pod
Pod->>Client: 返回响应
24.3.2 创建 Gateway¶
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cilium-gateway
spec:
gatewayClassName: cilium # 使用 Cilium 作为控制器
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
24.3.3 创建 HTTPRoute¶
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-route
spec:
parentRefs:
- name: cilium-gateway # 关联 Gateway
rules:
- matches:
- path:
type: PathPrefix
value: /details
backendRefs:
- name: details
port: 9080
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: productpage
port: 9080
24.3.4 验证配置¶
# 查看 Gateway
kubectl get gateway
# 输出示例
NAME CLASS ADDRESS READY
cilium-gateway cilium 172.18.0.200 True
# 查看 HTTPRoute
kubectl get httproute
# 测试访问
LB_IP=$(kubectl get gateway cilium-gateway -o jsonpath='{.status.addresses[0].value}')
curl http://$LB_IP/details | jq
24.4 HTTPS 模式¶
24.4.1 TLS 配置架构¶
graph LR
Client["客户端"] -->|"HTTPS"| GW["Gateway<br/>TLS 终结"]
GW -->|"HTTP"| Route["HTTPRoute"]
Route --> Pod["后端 Pod"]
24.4.2 创建 TLS 证书¶
# 使用 minica 生成证书
minica --domains bookinfo.cilium.rocks
# 创建 Secret
kubectl create secret tls bookinfo-cert \
--cert=bookinfo.cilium.rocks/cert.pem \
--key=bookinfo.cilium.rocks/key.pem
24.4.3 创建 HTTPS Gateway¶
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: tls-gateway
spec:
gatewayClassName: cilium
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: bookinfo.cilium.rocks
tls:
mode: Terminate
certificateRefs:
- name: bookinfo-cert # 引用 TLS Secret
allowedRoutes:
namespaces:
from: Same
24.4.4 创建 HTTPS HTTPRoute¶
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: https-route
spec:
parentRefs:
- name: tls-gateway
hostnames:
- bookinfo.cilium.rocks # 必须匹配 Gateway 的 hostname
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: productpage
port: 9080
24.4.5 验证 HTTPS¶
# 添加 hosts 解析
LB_IP=$(kubectl get gateway tls-gateway -o jsonpath='{.status.addresses[0].value}')
echo "$LB_IP bookinfo.cilium.rocks" >> /etc/hosts
# 测试 HTTPS 访问
curl -k -v https://bookinfo.cilium.rocks/
24.5 资源关系详解¶
24.5.1 资源层级¶
graph TB
subgraph "Gateway API 层级"
GC["GatewayClass<br/>⬇ 定义控制器"]
GW["Gateway<br/>⬇ 定义入口"]
HR["HTTPRoute<br/>⬇ 定义路由"]
Svc["Service<br/>⬇ 后端"]
Pod["Pod"]
GC --> GW
GW --> HR
HR --> Svc
Svc --> Pod
end
24.5.2 与 Ingress 映射¶
| Ingress | Gateway API |
|---|---|
ingressClassName |
GatewayClass + Gateway.gatewayClassName |
| Ingress 资源 | Gateway + HTTPRoute |
rules.host |
Gateway.listeners.hostname |
rules.http.paths |
HTTPRoute.rules.matches |
tls |
Gateway.listeners.tls |
24.6 与 Ingress 对比¶
24.6.1 配置对比¶
graph TB
subgraph "Ingress 方式"
I1["Ingress<br/>(单一资源)"]
end
subgraph "Gateway API 方式"
G1["GatewayClass"]
G2["Gateway"]
G3["HTTPRoute"]
G1 --> G2 --> G3
end
24.6.2 功能对比¶
| 场景 | Ingress | Gateway API |
|---|---|---|
| 基础 HTTP 路由 | ✅ | ✅ |
| TLS 终结 | ✅ | ✅ |
| Header 匹配 | 注解 | 原生支持 |
| 请求重写 | 注解 | 原生支持 |
| 流量拆分 | 注解 | 原生支持 |
| 跨命名空间 | 困难 | 简单 |
24.7 实际应用场景¶
24.7.1 场景一:多团队共享网关¶
graph TB
GW["Gateway<br/>(Infra Team 管理)"]
subgraph "Team A 命名空间"
HR1["HTTPRoute A"]
Svc1["Service A"]
end
subgraph "Team B 命名空间"
HR2["HTTPRoute B"]
Svc2["Service B"]
end
GW --> HR1
GW --> HR2
HR1 --> Svc1
HR2 --> Svc2
24.7.2 场景二:Service Mesh 集成¶
graph LR
GW["Gateway API"] --> Envoy["Cilium Envoy"]
Envoy --> SM["Service Mesh<br/>流量管理"]
[!TIP] Gateway API 是 Service Mesh 的基础组件,Cilium 的 Gateway API 实现与其 Service Mesh 功能无缝集成。
24.8 注意事项¶
[!WARNING] 使用注意:
- CRDs 必须预装:Gateway API CRDs 必须在安装 Cilium 之前安装
- 版本兼容性:确保 CRDs 版本与 Cilium 版本兼容
- LoadBalancer 依赖:Gateway 会创建 LoadBalancer 类型的 Service
- hostname 匹配:HTTPS 模式下,HTTPRoute 的 hostnames 必须匹配 Gateway 的 hostname
24.9 章节小结¶
mindmap
root((Gateway API))
概念
Ingress替代
更强表达能力
核心资源
GatewayClass
Gateway
HTTPRoute
前置要求
CRDs预装
gatewayAPI.enabled
HTTP模式
Gateway定义入口
HTTPRoute定义路由
HTTPS模式
TLS配置
hostname匹配
[!IMPORTANT] 核心要点总结:
什么是 Gateway API:Ingress 的下一代替代方案,更强大的七层流量管理
核心资源:
GatewayClass:定义控制器Gateway:定义入口网关
HTTPRoute:定义路由规则前置要求:
- 必须预装 Gateway API CRDs
gatewayAPI.enabled=trueHTTP 模式:
- 创建 Gateway + HTTPRoute
gatewayClassName: ciliumHTTPS 模式:
- Gateway 配置 TLS 证书
HTTPRoute 配置 hostnames
与 Ingress 区别:
- 更丰富的表达能力
- 原生支持流量拆分、Header 匹配等
- 更好的跨命名空间支持
第二十五章 BGP 基础知识¶
本章介绍 BGP(Border Gateway Protocol)协议的基础知识。BGP 是 Cilium 实现跨网络路由通告的核心技术,理解 BGP 原理对于配置 Cilium BGP 功能至关重要。
25.1 背景与概念¶
25.1.1 为什么需要 BGP¶
在 Kubernetes 环境中,每个 Pod 都有独立的 IP 地址,导致路由条目数量急剧增加:
graph LR
subgraph "传统虚拟机"
VM1["VM1: 1 IP"]
VM2["VM2: 1 IP"]
end
subgraph "Kubernetes Pod"
Pod1["Pod1: 独立 IP"]
Pod2["Pod2: 独立 IP"]
Pod3["Pod3: 独立 IP"]
PodN["Pod N: 独立 IP"]
end
路由条目膨胀:
| 场景 | 节点数 | 路由条目数 |
|---|---|---|
| 传统虚拟机 | 200 | ~200 |
| Kubernetes(100 Pod/节点) | 200 | ~20,000 |
[!NOTE] 当路由条目达到数万甚至数十万时,传统 IGP 协议(如 OSPF)难以高效管理,需要使用 BGP 协议。
25.1.2 路由协议分类¶
graph TB
subgraph "路由协议"
Static["静态路由<br/>手动配置"]
IGP["IGP 内部网关协议<br/>OSPF / IS-IS"]
BGP["BGP 边界网关协议<br/>iBGP / eBGP"]
end
Static --> |"简单场景"| IGP
IGP --> |"大规模场景"| BGP
| 协议类型 | 代表 | 适用规模 | 特点 |
|---|---|---|---|
| 静态路由 | - | 小型 | 手动配置,简单直观 |
| IGP | OSPF、IS-IS | 中型园区网 | 自动学习,成百上千条 |
| BGP | iBGP、eBGP | 大型互联网 | 成千上万条,更灵活 |
25.1.3 BGP 定义¶
BGP(Border Gateway Protocol):边界网关协议
graph TB
AS1["自治系统 AS 100<br/>(如:电信)"]
AS2["自治系统 AS 200<br/>(如:联通)"]
AS1 <-->|"BGP 邻居"| AS2
- 边界:用于不同自治系统(AS)之间的路由交换
- 网关:运行 BGP 的路由器称为 BGP 网关
- 协议:基于 TCP 179 端口通信
25.2 核心概念¶
25.2.1 自治系统(AS)¶
AS(Autonomous System):自治系统号,用于标识一个独立管理的网络域。
graph TB
subgraph AS123["AS 123"]
R1["Router 1"]
R2["Router 2"]
R3["Router 3"]
end
subgraph AS456["AS 456"]
R4["Router 4"]
R5["Router 5"]
end
R3 <-->|"eBGP"| R4
| 概念 | 说明 |
|---|---|
| AS 号 | 全局唯一标识符(如 AS 100、AS 65000) |
| 私有 AS | 64512 - 65534(类似私有 IP) |
| 公有 AS | 需向 IANA 申请 |
25.2.2 iBGP 与 eBGP¶
graph TB
subgraph AS123["AS 123"]
R1["R1"] <-->|"iBGP"| R2["R2"]
R2 <-->|"iBGP"| R3["R3"]
end
subgraph AS456["AS 456"]
R4["R4"]
end
R3 <-->|"eBGP"| R4
| 类型 | 全称 | 说明 |
|---|---|---|
| iBGP | Internal BGP | 同一 AS 内部的 BGP 邻居 |
| eBGP | External BGP | 不同 AS 之间的 BGP 邻居 |
关键区别:
| 特性 | iBGP | eBGP |
|---|---|---|
| AS 号 | 相同 | 不同 |
| 直连要求 | 不需要直连 | 通常需要直连 |
| TTL | 大于 1(可跨路由器) | 默认 1(直连) |
| Next-Hop | 不修改 | 修改为自己 |
25.2.3 BGP 邻居建立¶
BGP 使用 TCP 179 端口建立邻居关系:
sequenceDiagram
participant R1 as Router 1
participant R2 as Router 2
R1->>R2: TCP 三次握手 (port 179)
R1->>R2: Open 消息
R2->>R1: Open 消息
R1-->>R2: Keepalive
R2-->>R1: Keepalive
Note over R1,R2: 邻居关系建立 (Established)
[!TIP] BGP 邻居不要求物理直连,只要 TCP 179 端口可达即可。这是 BGP 的重要特性。
25.3 水平分割原则¶
25.3.1 问题背景¶
水平分割(Split Horizon):从 iBGP 对等体学习到的路由,不再通告给其他 iBGP 对等体。
graph LR
R1["R1"] <-->|"iBGP"| R2["R2"]
R2 <-->|"iBGP"| R3["R3"]
R2 -.->|"❌ 不传递"| R1
目的:防止路由环路
问题:R1 无法学习到 R3 的路由
25.3.2 解决方案¶
为了让所有路由器学习到完整路由,需要建立全互联(Full Mesh):
graph TB
R1["R1"] <--> R2["R2"]
R2 <--> R3["R3"]
R1 <--> R3
问题:邻居数量 = N × (N-1) / 2,规模增大时开销巨大
25.4 路由反射器¶
25.4.1 概念¶
RR(Route Reflector):路由反射器,用于解决 iBGP 全互联问题。
graph TB
RR["RR 路由反射器"]
subgraph "RR Clients"
C1["Client 1"]
C2["Client 2"]
C3["Client 3"]
end
NonC["Non-Client"]
RR <--> C1
RR <--> C2
RR <--> C3
RR <--> NonC
| 角色 | 说明 |
|---|---|
| RR(Route Reflector) | 路由反射器 |
| Client | RR 的客户端 |
| Non-Client | 非客户端的普通 iBGP 邻居 |
25.4.2 反射规则¶
graph TB
subgraph "路由反射规则"
Rule1["规则 1:Non-Client → 反射给所有 Client"]
Rule2["规则 2:Client → 反射给 Non-Client + 其他 Client"]
Rule3["规则 3:eBGP → 反射给所有 Client + Non-Client"]
end
| 路由来源 | 反射目标 |
|---|---|
| Non-Client | 所有 Client(不包括其他 Non-Client) |
| Client | Non-Client + 其他 Client |
| eBGP Peer | 所有 Client + Non-Client |
25.4.3 反射示例¶
graph TB
subgraph "AS 100"
R2["R2<br/>RR"]
R3["R3<br/>Client"]
R4["R4<br/>Client"]
R5["R5<br/>Non-Client"]
end
subgraph "AS 200"
R1["R1"]
end
R1 <-->|"eBGP"| R2
R2 <-->|"iBGP"| R3
R2 <-->|"iBGP"| R4
R2 <-->|"iBGP"| R5
路由流向:
| 场景 | 路由来源 | 反射目标 |
|---|---|---|
| R5 → R2 | Non-Client | R3、R4(Client) |
| R3 → R2 | Client | R4(Client)+ R5(Non-Client) |
| R1 → R2 | eBGP | R3、R4、R5(所有) |
25.4.4 理解技巧¶
[!TIP] 记忆技巧:
- Client = RR 的一部分(对内分彼此,对外是整体)
- Non-Client = 普通邻居(遵守水平分割)
- eBGP = 外部来源(无水平分割限制)
25.5 BGP 在 Kubernetes 中的应用¶
25.5.1 典型拓扑¶
graph TB
subgraph "数据中心"
ToR1["ToR Switch 1<br/>AS 65001"]
ToR2["ToR Switch 2<br/>AS 65002"]
subgraph "Kubernetes Cluster"
Node1["Node 1<br/>AS 65010"]
Node2["Node 2<br/>AS 65010"]
Node3["Node 3<br/>AS 65010"]
end
end
ToR1 <-->|"eBGP"| Node1
ToR1 <-->|"eBGP"| Node2
ToR2 <-->|"eBGP"| Node3
25.5.2 Cilium BGP 实现¶
Cilium 支持多种 BGP 后端:
| 后端 | 说明 |
|---|---|
| GoBGP | 当前推荐,原生 Go 实现 |
| BIRD | 早期版本使用 |
| MetalLB BGP | 与 MetalLB 集成 |
25.6 章节小结¶
mindmap
root((BGP 基础))
概念
边界网关协议
TCP 179 端口
自治系统
AS 号
私有/公有
邻居类型
iBGP 内部
eBGP 外部
水平分割
防止环路
全互联问题
路由反射器
RR
Client
Non-Client
[!IMPORTANT] 核心要点总结:
- 什么是 BGP:
- 边界网关协议,用于大规模路由管理
基于 TCP 179 端口
AS 自治系统:
- 独立管理的网络域
私有 AS:64512 - 65534
iBGP vs eBGP:
- iBGP:同一 AS 内部
eBGP:不同 AS 之间
水平分割:
- 防止路由环路
导致全互联问题
路由反射器:
- 解决全互联问题
- Client 和 Non-Client 角色区分
三条反射规则
Kubernetes 应用:
- 用于 Pod/Service IP 通告
- Cilium 使用 GoBGP 实现
第二十六章 Cilium BGP Control Plane¶
本章介绍 Cilium BGP Control Plane 的配置与实践。通过 Kind + ContainerLab 构建完整的 BGP 测试环境,实现 Kubernetes Pod 网络与数据中心网络的互通。
26.1 背景与架构¶
26.1.1 Spine-Leaf 网络架构¶
现代数据中心普遍采用 Spine-Leaf 架构:
graph TB
subgraph "Spine 层"
Spine0["Spine 0<br/>AS 500"]
Spine1["Spine 1<br/>AS 800"]
end
subgraph "Leaf 层"
Leaf0["Leaf 0<br/>AS 65005"]
Leaf1["Leaf 1<br/>AS 65008"]
end
subgraph "Kubernetes Nodes"
Node0["Node 0<br/>10.1.5.10"]
Node1["Node 1<br/>10.1.5.11"]
Node2["Node 2<br/>10.1.8.10"]
Node3["Node 3<br/>10.1.8.11"]
end
Spine0 <-->|"eBGP"| Leaf0
Spine0 <-->|"eBGP"| Leaf1
Spine1 <-->|"eBGP"| Leaf0
Spine1 <-->|"eBGP"| Leaf1
Leaf0 <-->|"iBGP"| Node0
Leaf0 <-->|"iBGP"| Node1
Leaf1 <-->|"iBGP"| Node2
Leaf1 <-->|"iBGP"| Node3
| 层级 | 角色 | 说明 |
|---|---|---|
| Spine | 核心交换机 | 负责跨 Leaf 流量转发 |
| Leaf | 接入交换机 | 连接服务器、充当 BGP RR |
| Node | K8s 节点 | 运行 Cilium BGP |
26.1.2 BGP 邻居关系¶
graph LR
subgraph "AS 65005"
Leaf0["Leaf 0<br/>RR"]
Node0["Node 0"]
Node1["Node 1"]
end
subgraph "AS 500"
Spine0["Spine 0"]
end
Leaf0 <-->|"iBGP"| Node0
Leaf0 <-->|"iBGP"| Node1
Leaf0 <-->|"eBGP"| Spine0
| 邻居类型 | 场景 | AS 关系 |
|---|---|---|
| iBGP | Leaf ↔ Node | 同一 AS |
| eBGP | Spine ↔ Leaf | 不同 AS |
26.2 环境搭建¶
26.2.1 整体架构¶
graph TB
subgraph "ContainerLab"
Spine["Spine 交换机"]
Leaf["Leaf 交换机"]
end
subgraph "Kind Cluster"
CP["Control Plane"]
Worker["Worker Nodes"]
end
subgraph "网络桥接"
BR["Linux Bridge"]
end
Leaf <--> BR
BR <--> CP
BR <--> Worker
26.2.2 网络复用原理¶
Kind 创建的容器通过网络复用方式与 ContainerLab 连接:
sequenceDiagram
participant Kind as Kind 容器
participant Server as ContainerLab Server
participant Leaf as Leaf 交换机
Note over Kind: eth0: 172.18.0.x<br/>(管理网络)
Server->>Kind: 共享网络命名空间
Server->>Kind: 添加 net0 网卡
Note over Kind: net0: 10.1.5.x<br/>(业务网络)
Kind->>Leaf: 通过 net0 通信
关键配置:
# ContainerLab Server 配置
network-mode: container:clab-bgp-control-plane
exec:
- ip route replace default via 10.1.5.1 # 替换默认路由
[!NOTE] 核心原理:通过
container网络模式复用 Kind 容器的网络命名空间,再添加新网卡并替换默认路由,实现流量从 Kind 到 ContainerLab 的引导。
26.3 Cilium BGP 配置¶
26.3.1 Helm 安装参数¶
helm install cilium cilium/cilium --namespace kube-system \
--set bgpControlPlane.enabled=true \
--set ipam.mode=kubernetes \
--set ipv4NativeRoutingCIDR=10.0.0.0/8 \
--set tunnel=disabled
| 参数 | 说明 |
|---|---|
bgpControlPlane.enabled=true |
启用 BGP Control Plane |
ipam.mode=kubernetes |
使用 Kubernetes IPAM |
ipv4NativeRoutingCIDR |
直接路由的 CIDR |
tunnel=disabled |
禁用隧道模式 |
26.3.2 CiliumBGPPeeringPolicy¶
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeeringPolicy
metadata:
name: bgp-peering-policy
spec:
nodeSelector:
matchLabels:
rack: rack0 # 选择节点
virtualRouters:
- localASN: 65005 # 本地 AS 号
exportPodCIDR: true # 宣告 Pod CIDR
neighbors:
- peerAddress: 10.1.5.1/32 # 邻居地址
peerASN: 65005 # 邻居 AS 号
关键字段说明:
| 字段 | 说明 |
|---|---|
nodeSelector |
选择应用策略的节点 |
localASN |
本节点的 AS 号 |
exportPodCIDR |
是否宣告 Pod CIDR |
peerAddress |
BGP 邻居地址 |
peerASN |
BGP 邻居的 AS 号 |
26.4 交换机配置¶
26.4.1 Leaf 交换机(路由反射器)¶
# Leaf 0 配置示例
router bgp 65005
router-id 10.1.5.1
# iBGP 邻居 - K8s 节点(作为 RR Client)
neighbor 10.1.5.10 remote-as 65005
neighbor 10.1.5.10 route-reflector-client
neighbor 10.1.5.11 remote-as 65005
neighbor 10.1.5.11 route-reflector-client
# eBGP 邻居 - Spine 交换机
neighbor 10.1.10.2 remote-as 500
neighbor 10.1.12.2 remote-as 800
26.4.2 Spine 交换机¶
# Spine 0 配置示例
router bgp 500
router-id 10.1.10.1
# eBGP 邻居 - Leaf 交换机
neighbor 10.1.10.1 remote-as 65005
neighbor 10.1.11.1 remote-as 65008
26.5 ECMP 负载分担¶
26.5.1 工作原理¶
graph TB
subgraph "Leaf 0"
L0["Leaf 0"]
end
subgraph "Spine 层"
S0["Spine 0"]
S1["Spine 1"]
end
subgraph "Leaf 1"
L1["Leaf 1"]
end
L0 -->|"路径 1"| S0
L0 -->|"路径 2"| S1
S0 --> L1
S1 --> L1
ECMP(Equal-Cost Multi-Path):当有多条等价路径时,进行负载分担。
26.5.2 配置启用¶
# 在 Leaf 交换机上启用
router bgp 65005
maximum-paths 2
bestpath as-path multipath-relax
26.5.3 验证 ECMP¶
# 查看路由表
ip route show
# 输出示例 - 多个 nexthop
10.98.1.0/24 proto bgp
nexthop via 10.1.10.2 dev eth1 weight 1
nexthop via 10.1.12.2 dev eth2 weight 1
26.6 验证与测试¶
26.6.1 查看 BGP 邻居状态¶
# 在 Leaf 交换机上
show ip bgp summary
# 或在 K8s 节点上
cilium bgp peers
26.6.2 查看路由表¶
# 查看 BGP 路由
show ip bgp
# 验证 Pod 网络路由
ip route | grep 10.98
26.6.3 跨节点 Pod 通信测试¶
# 从一个节点的 Pod ping 另一个节点的 Pod
kubectl exec -it test-pod -- ping <target-pod-ip>
26.7 生产环境考量¶
26.7.1 网络规划¶
graph TB
subgraph "网络分离"
Mgmt["管理网络<br/>eth0: 172.x.x.x"]
Data["业务网络<br/>net0: 10.x.x.x"]
end
| 网络类型 | 用途 | 示例 |
|---|---|---|
| 管理网络 | SSH、监控 | 172.18.0.0/16 |
| 业务网络 | Pod 流量 | 10.1.0.0/16 |
26.7.2 高可用设计¶
graph TB
Node["K8s Node"]
L0["Leaf 0"]
L1["Leaf 1"]
S0["Spine 0"]
S1["Spine 1"]
Node -->|"主路径"| L0
Node -->|"备路径"| L1
L0 --> S0
L0 --> S1
L1 --> S0
L1 --> S1
26.8 章节小结¶
mindmap
root((BGP Control Plane))
架构
Spine-Leaf
iBGP/eBGP
环境
Kind + ContainerLab
网络复用
配置
CiliumBGPPeeringPolicy
localASN/peerASN
交换机
RR 路由反射器
ECMP 多路径
验证
BGP 邻居状态
路由表检查
[!IMPORTANT] 核心要点总结:
- Spine-Leaf 架构:
- Spine 负责跨 Leaf 转发
Leaf 作为 BGP RR
环境搭建:
- Kind + ContainerLab 联合
网络复用实现流量引导
Cilium 配置:
bgpControlPlane.enabled=true
CiliumBGPPeeringPolicy定义邻居关键参数:
localASN:本地 AS 号peerASN:邻居 AS 号
exportPodCIDR:宣告 Pod 网络交换机配置:
- Leaf 作为 RR,配置
route-reflector-client启用
maximum-paths实现 ECMP生产考量:
- 管理网络与业务网络分离
- 多路径高可用设计
第二十七章 Cilium BGP with LB-IPAM¶
本章介绍如何将 Cilium BGP Control Plane 与 LoadBalancer IPAM 结合,实现 Service 的 External IP 可路由,从而让外部网络能够直接访问 Kubernetes 集群内的服务。
27.1 背景与问题¶
27.1.1 问题场景¶
在生产环境中,外部客户端需要访问 Kubernetes 集群内的服务:
graph LR
Client["外部客户端"] --> |"访问 LB IP"| Router["网络设备"]
Router --> |"?????"| Service["K8s Service"]
Service --> Pod["Pod"]
核心问题:外部网络设备如何知道 Service 的 LB IP 在哪里?
27.1.2 传统方案对比¶
| 方案 | 工作层级 | 说明 |
|---|---|---|
| MetalLB L2 | 二层 | ARP 响应,仅限同一子网 |
| MetalLB BGP | 三层 | 需要额外部署 |
| Cilium BGP | 三层 | CNI 原生集成 |
27.2 Service Announcement 原理¶
27.2.1 工作流程¶
sequenceDiagram
participant Svc as LoadBalancer Service
participant Cilium as Cilium Agent
participant BGP as BGP 邻居
participant Router as 外部路由器
Svc->>Svc: 分配 External IP
Cilium->>Cilium: 检测 Service 变化
Cilium->>BGP: 宣告 LB IP 路由
BGP->>Router: 路由传播
Note over Router: 现在知道<br/>LB IP 怎么走了!
27.2.2 核心概念¶
| 概念 | 说明 |
|---|---|
| Service Announcement | 将 Service 的 LB IP 通过 BGP 宣告 |
| serviceSelector | 选择哪些 Service 进行宣告 |
| exportPodCIDR | 宣告 Pod 网段(已在 26 章介绍) |
27.3 CiliumBGPPeeringPolicy 配置¶
27.3.1 完整配置示例¶
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeeringPolicy
metadata:
name: bgp-peering-policy
spec:
nodeSelector:
matchLabels:
rack: rack0
virtualRouters:
- localASN: 65005
exportPodCIDR: true
# 服务宣告配置(1.13+ 新增)
serviceSelector:
matchExpressions:
- key: somekey
operator: NotIn
values:
- never-used-value
neighbors:
- peerAddress: 10.1.5.1/32
peerASN: 65005
27.3.2 serviceSelector 详解¶
宣告所有 LoadBalancer Service:
serviceSelector:
matchExpressions:
- key: somekey
operator: NotIn
values:
- never-used-value
[!NOTE] 原理解释:使用一个不存在的 key-value 组合,
NotIn表示"不在列表中",由于没有 Service 有这个标签,所以所有 Service 都满足条件。
只宣告特定 Service:
serviceSelector:
matchLabels:
expose-bgp: "true" # 只宣告带此标签的 Service
27.4 MetalLB 集成方案¶
27.4.1 架构图¶
graph TB
subgraph "IP 分配"
MetalLB["MetalLB<br/>分配 LB IP"]
end
subgraph "路由宣告"
Cilium["Cilium BGP<br/>宣告路由"]
end
subgraph "外部网络"
Router["路由器<br/>学习路由"]
end
MetalLB -->|"分配 172.18.0.200"| Service["LoadBalancer<br/>Service"]
Service -->|"通知"| Cilium
Cilium -->|"BGP Update"| Router
27.4.2 配置步骤¶
1. 安装 MetalLB:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/main/config/manifests/metallb-native.yaml
2. 配置 IP 池:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- 172.18.0.200-172.18.0.254
3. 创建 LoadBalancer Service:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
4. 应用 BGP Peering Policy(包含 serviceSelector)
27.4.3 验证路由宣告¶
# 在路由器上查看路由表
show ip route
# 应能看到类似输出
B 172.18.0.200/32 via 10.1.5.10 ...
27.5 Cilium LB-IPAM 方案¶
27.5.1 与 MetalLB 的区别¶
graph LR
subgraph "MetalLB 方案"
ML["MetalLB"] -->|"分配 IP"| Svc1["Service"]
ML -->|"ARP/BGP"| Net1["网络"]
end
subgraph "Cilium LB-IPAM 方案"
IPAM["Cilium LB-IPAM"] -->|"分配 IP"| Svc2["Service"]
BGP["Cilium BGP"] -->|"宣告路由"| Net2["网络"]
end
| 特性 | MetalLB | Cilium LB-IPAM |
|---|---|---|
| IP 分配 | MetalLB 管理 | Cilium 管理 |
| 路由宣告 | MetalLB BGP 或 Cilium | Cilium BGP |
| 组件数量 | 需要额外部署 | CNI 原生 |
27.5.2 配置 Cilium LB-IPAM¶
1. 创建 IP Pool:
apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
name: blue-pool
spec:
blocks:
- cidr: 30.0.10.0/24
serviceSelector:
matchLabels:
color: blue
2. 创建 Service 使用指定池:
apiVersion: v1
kind: Service
metadata:
name: test-service
labels:
color: blue # 匹配 IP Pool 的 serviceSelector
spec:
type: LoadBalancer
selector:
app: test
ports:
- port: 80
3. BGP 自动宣告:
# 在路由器上验证
show ip route | grep 30.0
# 输出示例
B 30.0.10.22/32 via 10.1.5.10 ...
27.6 完整流程演示¶
27.6.1 从创建到访问¶
sequenceDiagram
participant Admin as 管理员
participant K8s as Kubernetes
participant Cilium as Cilium
participant Router as 路由器
participant Client as 客户端
Admin->>K8s: 创建 LoadBalancer Service
K8s->>Cilium: 通知 Service 创建
Note over Cilium: LB-IPAM 分配 IP<br/>30.0.10.22
Cilium->>Router: BGP Update<br/>宣告 30.0.10.22/32
Router->>Router: 更新路由表
Client->>Router: 访问 30.0.10.22:80
Router->>Cilium: 根据路由转发
Cilium->>K8s: 到达 Service
K8s->>Client: 返回响应
27.6.2 验证命令¶
# 1. 查看 Service External IP
kubectl get svc
# 输出示例
NAME TYPE EXTERNAL-IP PORT(S)
my-service LoadBalancer 30.0.10.22 80:31234/TCP
# 2. 在路由器上验证路由
show ip bgp
# 3. 从外部访问测试
curl http://30.0.10.22/
27.7 生产环境考量¶
27.7.1 宣告策略选择¶
graph TB
Q1["需要对外暴露?"]
Q2["需要精细控制?"]
A1["不配置 serviceSelector"]
A2["配置 matchLabels"]
A3["配置 matchExpressions<br/>宣告所有 LB"]
Q1 -->|"否"| A1
Q1 -->|"是"| Q2
Q2 -->|"是"| A2
Q2 -->|"否"| A3
27.7.2 安全建议¶
| 建议 | 说明 |
|---|---|
| 精准宣告 | 只宣告需要对外的 Service |
| 网络隔离 | LB IP 与内部 IP 分开规划 |
| 访问控制 | 配合防火墙/NetworkPolicy |
27.8 Cluster IP vs LB IP 宣告对比¶
27.8.1 两种方式对比¶
| 项目 | Calico 方式 | Cilium 方式 |
|---|---|---|
| 宣告内容 | Cluster IP | LoadBalancer IP |
| 适用场景 | 内部互通 | 对外服务 |
| 安全性 | 较低 | 较高 |
| 推荐程度 | ⭐⭐ | ⭐⭐⭐⭐ |
[!WARNING] Cluster IP 本身是集群内部 IP,将其通过 BGP 宣告到外部网络存在安全隐患,不推荐在生产环境使用。
27.9 章节小结¶
mindmap
root((BGP + LB-IPAM))
问题
LB IP 不可路由
外部无法访问
方案
Service Announcement
BGP 宣告
配置
serviceSelector
matchExpressions
集成
MetalLB
Cilium LB-IPAM
验证
路由表检查
外部访问测试
[!IMPORTANT] 核心要点总结:
- 问题本质:
- LoadBalancer IP 默认只在集群内有效
外部网络设备不知道如何路由
解决方案:
- 使用 BGP 将 LB IP 宣告到网络
Cilium 1.13+ 支持 Service Announcement
配置要点:
serviceSelector控制宣告哪些 Service使用
NotIn+ 不存在的值宣告所有两种集成:
- MetalLB(分配 IP)+ Cilium BGP(宣告)
Cilium LB-IPAM(分配)+ Cilium BGP(宣告)
验证方法:
- 路由器查看
show ip route外部
curl测试访问最佳实践:
- 宣告 LB IP 而非 Cluster IP
- 配合标签精细控制宣告范围
第二十八章 Cilium ClusterMesh¶
本章介绍 Cilium ClusterMesh 多集群网络互联功能。通过 ClusterMesh,多个 Kubernetes 集群可以形成逻辑上的统一集群,实现跨集群的服务发现、负载均衡和故障切换。
28.1 背景与概念¶
28.1.1 什么是 ClusterMesh¶
ClusterMesh 是 Cilium 提供的多集群连接方案:
graph LR
subgraph "Cluster 1"
Pod1A["Pod A"]
Pod1B["Pod B"]
Svc1["Service"]
end
subgraph "Cluster 2"
Pod2A["Pod A"]
Pod2B["Pod B"]
Svc2["Service"]
end
Svc1 <-->|"ClusterMesh"| Svc2
| 特性 | 说明 |
|---|---|
| 多集群连接 | 多个物理集群形成逻辑统一 |
| 跨集群服务发现 | Service 可发现其他集群的 Pod |
| 统一负载均衡 | 请求可负载到多集群的 Pod |
28.1.2 应用场景¶
graph TB
subgraph "场景"
HA["高可用容灾"]
GEO["地理分布"]
SCALE["水平扩展"]
end
| 场景 | 说明 |
|---|---|
| 高可用容灾 | 一个集群故障,自动切换到另一个 |
| 地理分布 | 多地域部署,就近访问 |
| 水平扩展 | 突破单集群规模限制 |
28.2 架构原理¶
28.2.1 核心组件¶
graph TB
subgraph "Cluster 1"
API1["ClusterMesh API Server"]
ETCD1["etcd"]
Agent1["Cilium Agent"]
Operator1["Cilium Operator"]
end
subgraph "Cluster 2"
API2["ClusterMesh API Server"]
ETCD2["etcd"]
Agent2["Cilium Agent"]
Operator2["Cilium Operator"]
end
Agent1 <-->|"NodePort/LB"| API2
Agent2 <-->|"NodePort/LB"| API1
| 组件 | 作用 |
|---|---|
| ClusterMesh API Server | 提供跨集群状态同步接口 |
| etcd | 存储集群状态信息 |
| Cilium Agent | 同步信息到本地 |
28.2.2 信息同步原理¶
sequenceDiagram
participant Agent1 as Cluster1 Agent
participant API1 as Cluster1 API
participant API2 as Cluster2 API
participant Agent2 as Cluster2 Agent
Note over Agent2: 创建 Pod/Service
Agent2->>API2: 上报状态
API2->>API1: 同步信息
API1->>Agent1: 推送更新
Note over Agent1: 更新本地 Endpoint 列表
核心要点:
- 每个集群的 Agent 将本地 Pod/Service 信息上报到 ClusterMesh API
- API Server 之间相互同步
- Service 因此能"知道"其他集群的后端 Pod
28.3 Global Service¶
28.3.1 普通 Service vs Global Service¶
graph LR
subgraph "普通 Service"
LocalSvc["Service"]
LocalPod1["Pod 1"]
LocalPod2["Pod 2"]
LocalSvc --> LocalPod1
LocalSvc --> LocalPod2
end
subgraph "Global Service"
GlobalSvc["Service"]
GlobalPod1["Cluster1 Pod"]
GlobalPod2["Cluster2 Pod"]
GlobalSvc --> GlobalPod1
GlobalSvc --> GlobalPod2
end
28.3.2 配置方式¶
只需在 Service 上添加注解:
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
# 标记为 Global Service
io.cilium/global-service: "true"
spec:
selector:
app: my-app
ports:
- port: 80
[!NOTE] 只有添加了
io.cilium/global-service: "true"注解的 Service 才会享有跨集群能力。普通 Service 仍然只在本集群内有效。
28.4 Service Affinity(服务亲和性)¶
28.4.1 三种模式¶
graph TB
subgraph "Affinity 模式"
Local["local<br/>本地优先"]
Remote["remote<br/>远端优先"]
None["无标注<br/>随机负载"]
end
| 模式 | 注解值 | 行为 |
|---|---|---|
| Local | local |
优先访问本集群 Pod |
| Remote | remote |
优先访问远端集群 Pod |
| 无标注 | 不设置 | 所有 Pod 随机负载 |
28.4.2 配置示例¶
apiVersion: v1
kind: Service
metadata:
name: echo-local
annotations:
io.cilium/global-service: "true"
io.cilium/service-affinity: "local" # 本地优先
spec:
selector:
app: echo
ports:
- port: 80
---
apiVersion: v1
kind: Service
metadata:
name: echo-remote
annotations:
io.cilium/global-service: "true"
io.cilium/service-affinity: "remote" # 远端优先
spec:
selector:
app: echo
ports:
- port: 80
28.4.3 应用场景¶
graph TB
Q1["需要降低延迟?"]
Q2["需要测试远端?"]
A1["使用 local"]
A2["使用 remote"]
A3["不设置(随机)"]
Q1 -->|"是"| A1
Q1 -->|"否"| Q2
Q2 -->|"是"| A2
Q2 -->|"否"| A3
28.5 故障切换(Failover)¶
28.5.1 工作原理¶
sequenceDiagram
participant Client as 客户端
participant Svc as Global Service
participant C1 as Cluster1 Pods
participant C2 as Cluster2 Pods
Note over C1, C2: 正常状态:两集群都有 Pod
Client->>Svc: 请求
Svc->>C1: 负载到 Cluster1
Svc->>C2: 负载到 Cluster2
Note over C1: Cluster1 故障!
Client->>Svc: 请求
Svc->>C2: 全部负载到 Cluster2
28.5.2 测试验证¶
# 1. 正常状态 - 两个集群都响应
for i in {1..10}; do curl http://service; done
# 输出: Cluster1, Cluster2, Cluster1, Cluster2...
# 2. 模拟 Cluster2 故障(缩容到 0)
kubectl --context=cluster2 scale deploy backend --replicas=0
# 3. 再次测试 - 只有 Cluster1 响应
for i in {1..10}; do curl http://service; done
# 输出: Cluster1, Cluster1, Cluster1...
28.6 环境搭建¶
28.6.1 前置条件¶
| 要求 | 说明 |
|---|---|
| Pod CIDR | 各集群不能重叠 |
| 网络互通 | 集群间需能相互访问 |
| CA 证书 | 需共享或继承 |
28.6.2 安装步骤¶
1. 安装 Cluster 1:
cilium install --context kind-cluster1 --cluster-name cluster1 --cluster-id 1
2. 安装 Cluster 2(继承 CA):
cilium install --context kind-cluster2 --cluster-name cluster2 --cluster-id 2 \
--inherit-ca kind-cluster1
[!NOTE]
--inherit-ca参数让 Cluster2 继承 Cluster1 的 CA 证书,这是最简单的方式。如果使用 Helm 安装则需要手动配置证书。
3. 启用 ClusterMesh:
# 在两个集群上启用
cilium clustermesh enable --context kind-cluster1 --service-type NodePort
cilium clustermesh enable --context kind-cluster2 --service-type NodePort
4. 连接集群:
cilium clustermesh connect --context kind-cluster1 --destination-context kind-cluster2
5. 验证状态:
cilium clustermesh status --context kind-cluster1
28.6.3 连接方式¶
| 方式 | 适用场景 |
|---|---|
| NodePort | 开发测试环境 |
| LoadBalancer | 生产环境(需要 MetalLB 等) |
28.7 网络通信模式¶
28.7.1 两种模式¶
graph LR
subgraph "隧道模式"
T1["Pod A"] -->|"VXLAN 封装"| T2["Pod B"]
end
subgraph "直接路由"
R1["Pod A"] -->|"BGP/静态路由"| R2["Pod B"]
end
| 模式 | 说明 | 适用 |
|---|---|---|
| VXLAN | Overlay 封装 | 网络设备无法配置 |
| 直接路由 | 需要底层路由支持 | 可控制网络设备 |
28.7.2 跨集群通信¶
从 ClusterMesh 角度看,两个集群相当于一个逻辑集群,Pod 间通信与单集群内跨节点通信类似。
# 查看 tunnel 信息
cilium bpf tunnel list
# 会显示所有节点(包括其他集群的节点)
28.8 验证与调试¶
28.8.1 查看 ClusterMesh 状态¶
# 查看连接状态
cilium clustermesh status
# 查看节点列表(包含所有集群)
cilium node list
28.8.2 查看 Service 后端¶
# 登录 Cilium Pod
kubectl exec -it cilium-xxx -- bash
# 查看 Service 后端列表
cilium service list
# 查看 Endpoint 详情(会显示 preferred 状态)
cilium bpf lb list
28.8.3 验证输出示例¶
# preferred = 本集群的 Pod(affinity=local 时优先)
Backend State
10.1.1.1:80 active, preferred
10.1.1.2:80 active, preferred
10.2.1.1:80 active # 其他集群的 Pod
10.2.1.2:80 active
28.9 限制与注意事项¶
| 限制 | 说明 |
|---|---|
| 集群数量 | 最多 255 个集群 |
| Pod CIDR | 不能重叠 |
| Cluster ID | 每个集群需唯一 |
| CNI 要求 | 必须使用 Cilium |
28.10 章节小结¶
mindmap
root((ClusterMesh))
概念
多集群融合
逻辑统一
配置
Global Service 注解
Service Affinity
功能
跨集群发现
故障切换
部署
inherit-ca
clustermesh enable/connect
模式
VXLAN
直接路由
[!IMPORTANT] 核心要点总结:
- ClusterMesh 本质:
- 多个物理集群 → 一个逻辑集群
Service 能发现所有集群的 Pod
Global Service:
- 注解:
io.cilium/global-service: "true"必须添加才有跨集群能力
Service Affinity:
local:本地优先,降低延迟remote:远端优先,测试用途无标注:随机负载
故障切换:
- 一个集群 Pod 消失
自动切换到其他集群
部署关键:
- Pod CIDR 不能重叠
- 使用
--inherit-ca共享证书
clustermesh enable+connectCilium 独有:
- ClusterMesh 是 Cilium CNI 特有功能
- 其他 CNI 不具备此能力
Cilium CNI 部分总结¶
至此,Cilium CNI 的全部章节已完成。以下是 Cilium 各功能模块的概览:
mindmap
root((Cilium CNI))
基础
eBPF 原理
安装配置
kube-proxy 替代
网络模式
VXLAN 隧道
直接路由
Native Routing
Service
ClusterIP
NodePort
LoadBalancer
ExternalIPs
高级功能
Network Policy
Hubble 可观测性
带宽管理
XDP 加速
七层功能
Ingress Controller
Gateway API
BGP
BGP 基础
Control Plane
LB-IPAM 集成
多集群
ClusterMesh
第三部分:Calico-CNI¶
第二十九章 Calico 基础介绍¶
本章介绍 Calico CNI 的基础知识,包括其支持的多种网络模式、核心组件和架构设计。Calico 是目前使用最广泛的 Kubernetes CNI 之一,在生产环境中积累了大量经验。
29.1 Calico 概述¶
29.1.1 什么是 Calico¶
Calico 是一个开源的网络和安全解决方案,提供:
graph TB
subgraph "Calico 功能"
Net["网络功能"]
Policy["网络策略"]
IPAM["IP 地址管理"]
end
Net --> IPIP["IPIP 隧道"]
Net --> VXLAN["VXLAN 隧道"]
Net --> BGP["BGP 路由"]
Policy --> NP["Network Policy"]
| 特性 | 说明 |
|---|---|
| 多种网络模式 | IPIP、VXLAN、BGP |
| 网络策略 | 支持 Kubernetes NetworkPolicy |
| 自有 IPAM | calico-ipam,支持 IP 池管理 |
| 成熟稳定 | 在 OpenStack 时代即已存在 |
29.1.2 官方资源¶
| 资源 | 地址 |
|---|---|
| 官网 | https://www.tigera.io/project-calico/ |
| 文档 | https://docs.tigera.io/ |
| 版本 | 社区版 3.25+,企业版另有 |
29.2 网络模式对比¶
29.2.1 支持的网络模式¶
graph LR
subgraph "Overlay 模式"
IPIP["IPIP<br/>IP-in-IP"]
VXLAN["VXLAN<br/>MAC-in-UDP"]
end
subgraph "路由模式"
BGP["BGP<br/>Full Mesh / RR"]
end
subgraph "高性能"
eBPF["eBPF<br/>加速"]
VPP["VPP<br/>用户态协议栈"]
end
29.2.2 模式对比¶
| 模式 | 封装方式 | 额外开销 | 适用场景 |
|---|---|---|---|
| IPIP | IP 包封装在 IP 中 | 20 字节 | 跨子网通信 |
| VXLAN | MAC 包封装在 UDP 中 | 50 字节 | 大二层网络 |
| BGP | 无封装,纯路由 | 0 | 同子网或可控网络 |
29.2.3 Cross Subnet 模式¶
Calico 支持智能选择封装方式:
graph TB
Q["节点间通信"]
Same["同子网?"]
Route["直接路由<br/>无封装"]
Encap["Overlay 封装<br/>IPIP/VXLAN"]
Q --> Same
Same -->|"是"| Route
Same -->|"否"| Encap
[!NOTE] Cross Subnet 模式:节点在同一子网时使用直接路由(节省开销),跨子网时使用 Overlay 封装(保证连通)。这是生产环境常见的配置方式。
29.3 核心组件¶
29.3.1 组件架构¶
graph TB
subgraph "Calico 组件"
Felix["Felix<br/>数据平面代理"]
Bird["BIRD<br/>BGP 守护进程"]
Confd["Confd<br/>配置管理"]
IPAM["calico-ipam<br/>IP 地址管理"]
Typha["Typha<br/>数据缓存(可选)"]
end
subgraph "数据存储"
Etcd["etcd"]
K8sAPI["Kubernetes API"]
end
Felix --> Bird
Felix --> Confd
Confd --> K8sAPI
Felix --> K8sAPI
| 组件 | 作用 |
|---|---|
| Felix | 核心代理,管理路由、ACL、接口 |
| BIRD | BGP 守护进程,路由交换 |
| Confd | 监听配置变化,生成 BIRD 配置 |
| Typha | 大规模集群数据缓存 |
29.3.2 BIRD 进程¶
graph LR
subgraph "BGP 实现"
Calico["Calico"]
Bird["BIRD"]
GoBGP["GoBGP"]
end
Calico -->|"原生支持"| Bird
Cilium -->|"使用"| GoBGP
| 特性 | BIRD(Calico) | GoBGP(Cilium) |
|---|---|---|
| 语言 | C | Go |
| 成熟度 | 非常成熟 | 较新 |
| 资源占用 | 较低 | 适中 |
29.4 BGP 网络架构¶
29.4.1 AS per Rack 模式¶
graph TB
subgraph "Spine 层"
Spine0["Spine 0"]
Spine1["Spine 1"]
end
subgraph "Rack 0 (AS 65001)"
ToR0["ToR Switch 0"]
Node0["Node 0"]
Node1["Node 1"]
end
subgraph "Rack 1 (AS 65002)"
ToR1["ToR Switch 1"]
Node2["Node 2"]
Node3["Node 3"]
end
Spine0 <-->|"eBGP"| ToR0
Spine0 <-->|"eBGP"| ToR1
Spine1 <-->|"eBGP"| ToR0
Spine1 <-->|"eBGP"| ToR1
ToR0 <-->|"iBGP"| Node0
ToR0 <-->|"iBGP"| Node1
ToR1 <-->|"iBGP"| Node2
ToR1 <-->|"iBGP"| Node3
特点:
- 同一机架的节点共享同一 AS 号
- ToR 交换机作为 BGP 路由反射器(RR)
- Spine 与 ToR 之间建立 eBGP
29.4.2 AS per Node 模式¶
graph TB
subgraph "ToR Switch"
ToR["ToR"]
end
subgraph "Nodes"
N1["Node 1<br/>AS 65001"]
N2["Node 2<br/>AS 65002"]
N3["Node 3<br/>AS 65003"]
end
ToR <-->|"eBGP"| N1
ToR <-->|"eBGP"| N2
ToR <-->|"eBGP"| N3
特点:
- 每个节点都是独立的 AS
- 节点间都是 eBGP 关系
- 配置更简单,适合小规模
29.4.3 Downward Default 模式¶
graph TB
Spine["Spine<br/>掌握全网路由"]
ToR["ToR"]
Node["Node"]
Node -->|"通告: 默认路由"| ToR
ToR -->|"转发"| Spine
Note["节点只通告默认路由<br/>减轻负载"]
特点:
- 节点只向上通告默认路由
- 降低端侧设备压力
- 全网路由集中在 Spine 层
29.5 VPP 高性能方案¶
29.5.1 VPP 简介¶
graph TB
subgraph "传统路径"
App1["应用"]
Kernel["内核协议栈"]
NIC1["网卡"]
end
subgraph "VPP 路径"
App2["应用"]
VPP["VPP 用户态协议栈"]
DPDK["DPDK"]
NIC2["网卡"]
end
App1 --> Kernel --> NIC1
App2 --> VPP --> DPDK --> NIC2
| 概念 | 说明 |
|---|---|
| VPP | 用户态协议栈,等价于内核协议栈 |
| DPDK | 绕过内核直接访问网卡 |
| 用途 | 高带宽场景(10G/25G/40G+) |
29.5.2 VPP 与 Calico 集成¶
# VPP 支持的功能
- 路由 (Routing)
- 负载均衡 (Load Balancing)
- 策略 (Policy)
- VXLAN / IPIP / IPsec / MPLS
[!WARNING] VPP 方案门槛较高,需要深入理解用户态协议栈和 DPDK。目前主要用于电信/媒体处理等高带宽行业。
29.6 calicoctl 工具¶
29.6.1 基本用法¶
# 安装
curl -O -L https://github.com/projectcalico/calico/releases/download/v3.25.0/calicoctl-linux-amd64
mv calicoctl-linux-amd64 /usr/local/bin/calicoctl
chmod +x /usr/local/bin/calicoctl
# 常用命令
calicoctl get nodes
calicoctl get ippool
calicoctl get bgpconfig
calicoctl get bgppeer
29.6.2 功能对比¶
| 工具 | 说明 |
|---|---|
| calicoctl | Calico 资源管理 |
| cilium | Cilium 资源管理 |
| kubectl | 通用 K8s 资源 |
29.7 IP 池管理¶
29.7.1 IP Pool 配置¶
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-pool
spec:
cidr: 10.244.0.0/16
ipipMode: CrossSubnet
vxlanMode: Never
nodeSelector: all()
| 字段 | 说明 |
|---|---|
cidr |
Pod 使用的 IP 段 |
ipipMode |
IPIP 模式:Always/CrossSubnet/Never |
vxlanMode |
VXLAN 模式:Always/CrossSubnet/Never |
nodeSelector |
哪些节点使用此池 |
29.7.2 指定 Pod IP¶
apiVersion: v1
kind: Pod
metadata:
name: test-pod
annotations:
# 指定 Pod 使用特定 IP
cni.projectcalico.org/ipAddrs: '["172.16.0.2"]'
spec:
containers:
- name: nginx
image: nginx
29.8 IPIP vs VXLAN 的区别¶
29.8.1 与 Flannel 迁移¶
graph LR
Flannel["Flannel"] -->|"迁移"| Calico["Calico"]
subgraph "Calico IPIP"
IPIP["IPIP + BIRD"]
end
subgraph "Calico VXLAN"
VXLAN["VXLAN (无 BIRD)"]
end
| 模式 | BIRD 进程 | 说明 |
|---|---|---|
| IPIP | 需要 | 使用 BGP 分发路由 |
| VXLAN | 不需要 | 兼容 Flannel 迁移 |
[!NOTE] VXLAN 模式不使用 BIRD 进程,是为了支持从 Flannel 迁移。因为 Flannel 的 VXLAN 模式也不使用 BGP。
29.9 与 Cilium 对比¶
| 特性 | Calico | Cilium |
|---|---|---|
| BGP 实现 | BIRD | GoBGP |
| eBPF 支持 | 有(可选) | 原生 |
| 高性能方案 | VPP/DPDK | XDP |
| 多集群 | Federation | ClusterMesh |
| 成熟度 | 非常成熟 | 较新但活跃 |
29.10 章节小结¶
mindmap
root((Calico 基础))
网络模式
IPIP
VXLAN
BGP
组件
Felix
BIRD
Confd
架构
AS per Rack
AS per Node
Downward Default
高级
VPP/DPDK
eBPF
工具
calicoctl
[!IMPORTANT] 核心要点总结:
- 网络模式:
- IPIP:IP 封装,20 字节开销
- VXLAN:MAC 封装,50 字节开销
BGP:纯路由,无开销
Cross Subnet:
- 同子网走路由
- 跨子网走 Overlay
智能选择,兼顾性能
BGP 架构:
- AS per Rack:机架共享 AS
- AS per Node:节点独立 AS
Downward Default:减轻端侧压力
BIRD vs 无 BIRD:
- IPIP 模式需要 BIRD
VXLAN 模式不需要 BIRD
高性能方案:
- VPP:用户态协议栈
- DPDK:绕过内核
适用于高带宽场景
工具:
- calicoctl 管理 Calico 资源
- 类似 Cilium CLI
第三十章 Calico 环境准备¶
本章介绍 Calico CNI 的环境准备工作,包括集群创建、Calico 安装、管理工具配置和环境验证。这是后续学习 Calico 各种网络模式的基础。
30.1 背景与目标¶
30.1.1 学习环境需求¶
graph TB
subgraph "环境组成"
K8s["Kubernetes 集群"]
Calico["Calico CNI"]
CLI["calicoctl 工具"]
Lab["ContainerLab (可选)"]
end
K8s --> Calico
CLI --> Calico
Lab --> K8s
| 组件 | 版本 | 说明 |
|---|---|---|
| Kubernetes | 1.25+ | 使用 kind 创建 |
| Calico | 3.23.x | 默认 IPIP 模式 |
| calicoctl | 与 Calico 版本匹配 | 管理 Calico 资源 |
30.1.2 环境目标¶
| 目标 | 说明 |
|---|---|
| Pod 互通 | Pod 之间可以相互 ping 通 |
| Service 可达 | Service 可正常访问 |
| 跨节点通信 | 不同节点的 Pod 可互通 |
30.2 集群创建¶
30.2.1 使用 kind 创建集群¶
# calico-cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
disableDefaultCNI: true # 禁用默认 CNI
podSubnet: "10.244.0.0/16" # Pod CIDR
nodes:
- role: control-plane
- role: worker
- role: worker
# 创建集群
kind create cluster --name calico-demo --config calico-cluster.yaml
# 去除 master 节点污点(可选)
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
30.2.2 创建流程¶
sequenceDiagram
participant User as 用户
participant Kind as kind
participant K8s as Kubernetes
participant CNI as CNI 插件
User->>Kind: kind create cluster
Kind->>K8s: 创建集群
Note over K8s: 默认 CNI 禁用
Note over K8s: Pod CIDR: 10.244.0.0/16
User->>K8s: 安装 Calico
K8s->>CNI: Calico 就绪
Note over K8s: Pod 网络可用
30.3 安装 Calico¶
30.3.1 安装方式对比¶
| 方式 | 适用场景 | 特点 |
|---|---|---|
| Quick Start | 快速体验 | 使用 YAML Manifest |
| Helm | 生产环境 | 更灵活的配置 |
| Operator | 企业环境 | 声明式管理 |
30.3.2 Quick Start 安装¶
# 安装 Calico Operator 和 CRD
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.23.2/manifests/tigera-operator.yaml
# 安装 Calico 自定义资源
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.23.2/manifests/custom-resources.yaml
# 等待 Pod 就绪
kubectl wait --for=condition=Ready pods --all -n calico-system --timeout=300s
kubectl wait --for=condition=Ready pods --all -n calico-apiserver --timeout=300s
30.3.3 查看安装结果¶
# 查看 Calico Pod
kubectl get pods -n calico-system
kubectl get pods -n calico-apiserver
# 输出示例
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-xxx 1/1 Running 0 5m
calico-node-xxx 1/1 Running 0 5m
calico-typha-xxx 1/1 Running 0 5m
30.4 calicoctl 工具¶
30.4.1 安装 calicoctl¶
# 下载 calicoctl(需与 Calico 版本匹配)
curl -L https://github.com/projectcalico/calico/releases/download/v3.23.2/calicoctl-linux-amd64 -o calicoctl
# 添加执行权限
chmod +x calicoctl
mv calicoctl /usr/local/bin/
# 验证版本
calicoctl version
30.4.2 版本匹配重要性¶
graph LR
Client["calicoctl v3.23.5"]
Cluster["Cluster v3.23.2"]
Client -->|"版本不匹配"| Warning["WARNING: mismatched versions"]
Client2["calicoctl v3.23.2"]
Cluster2["Cluster v3.23.2"]
Client2 -->|"版本匹配"| OK["正常工作"]
[!WARNING] 版本匹配非常重要! calicoctl 版本必须与集群中 Calico 版本一致,否则会出现
mismatched versions警告。虽然可以使用--allow-version-mismatch强制运行,但不推荐在生产环境中这样做。
30.4.3 常用命令¶
# 查看节点
calicoctl get nodes
# 查看 IP 池
calicoctl get ippool -o yaml
# 查看 BGP 配置
calicoctl get bgpconfig
# 查看 BGP Peer
calicoctl get bgppeer
30.5 IPPool 配置详解¶
30.5.1 默认 IPPool¶
# 查看 IPPool
calicoctl get ippool -o yaml
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
cidr: 10.244.0.0/16 # Pod CIDR
ipipMode: Always # IPIP 模式
vxlanMode: Never # 不使用 VXLAN
natOutgoing: true # 出站 NAT
nodeSelector: all() # 所有节点
disabled: false # 启用状态
30.5.2 关键字段说明¶
graph TB
subgraph "IPPool 字段"
CIDR["cidr<br/>Pod 网段"]
IPIP["ipipMode<br/>IPIP 封装模式"]
VXLAN["vxlanMode<br/>VXLAN 封装模式"]
NAT["natOutgoing<br/>出站 NAT"]
Selector["nodeSelector<br/>节点选择器"]
Disabled["disabled<br/>是否禁用"]
end
| 字段 | 可选值 | 说明 |
|---|---|---|
ipipMode |
Always/CrossSubnet/Never | IPIP 封装策略 |
vxlanMode |
Always/CrossSubnet/Never | VXLAN 封装策略 |
natOutgoing |
true/false | Pod 出站是否 NAT |
nodeSelector |
all()/标签表达式 | 哪些节点使用此池 |
disabled |
true/false | 是否禁用此池 |
30.5.3 封装模式对比¶
graph TB
subgraph "ipipMode/vxlanMode 值"
Always["Always<br/>始终封装"]
Cross["CrossSubnet<br/>跨子网封装"]
Never["Never<br/>不封装"]
end
Always --> A1["所有跨节点通信都封装"]
Cross --> C1["同子网路由,跨子网封装"]
Never --> N1["纯路由,不封装"]
[!NOTE] IPIP 和 VXLAN 互斥:不能同时设置
ipipMode: Always和vxlanMode: Always。通常只启用其中一种,另一种设为Never。
30.6 环境验证¶
30.6.1 验证流程¶
graph LR
Deploy["部署测试 Pod"]
PodPing["Pod 间 ping 测试"]
SvcTest["Service 访问测试"]
Result["验证通过"]
Deploy --> PodPing --> SvcTest --> Result
30.6.2 部署测试资源¶
# test-pods.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod1
labels:
app: test
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: pod2
labels:
app: test
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "3600"]
---
apiVersion: v1
kind: Service
metadata:
name: test-svc
spec:
selector:
app: test
ports:
- port: 80
targetPort: 80
# 创建测试资源
kubectl apply -f test-pods.yaml
# 等待 Pod 就绪
kubectl wait --for=condition=Ready pods --all --timeout=120s
30.6.3 执行验证¶
# 获取 Pod IP
POD1_IP=$(kubectl get pod pod1 -o jsonpath='{.status.podIP}')
POD2_IP=$(kubectl get pod pod2 -o jsonpath='{.status.podIP}')
# Pod 间 ping 测试
kubectl exec pod1 -- ping -c 3 $POD2_IP
# Service 访问测试
SVC_IP=$(kubectl get svc test-svc -o jsonpath='{.spec.clusterIP}')
kubectl exec pod1 -- wget -qO- http://$SVC_IP --timeout=5
# 验证成功标志
echo "Pod 互通: OK"
echo "Service 可达: OK"
30.7 ContainerLab 拓扑规划¶
30.7.1 IPIP 学习拓扑¶
为后续学习 IPIP 模式,建议搭建以下拓扑:
graph TB
subgraph "Router"
GW["网关路由器<br/>192.168.1.1"]
end
subgraph "子网 1: 192.168.1.0/24"
Node1["Node 1<br/>192.168.1.10"]
Node2["Node 2<br/>192.168.1.11"]
end
subgraph "子网 2: 192.168.2.0/24"
Node3["Node 3<br/>192.168.2.10"]
end
Node1 --- Node2
Node1 <--> GW
Node2 <--> GW
GW <--> Node3
30.7.2 拓扑说明¶
| 节点 | 子网 | 说明 |
|---|---|---|
| Node 1 | 192.168.1.0/24 | 与 Node 2 同子网 |
| Node 2 | 192.168.1.0/24 | 与 Node 1 同子网 |
| Node 3 | 192.168.2.0/24 | 跨子网,需 IPIP |
通信路径:
- Node 1 ↔ Node 2:同子网,可直接路由
- Node 1 ↔ Node 3:跨子网,经过网关,需要 IPIP 封装
30.8 IPAM 问题排查¶
30.8.1 常见问题¶
| 问题 | 原因 | 解决方案 |
|---|---|---|
| IP 重复 | IPAM 数据库不一致 | 使用 calicoctl ipam check |
| 无法分配 IP | IPPool 禁用或耗尽 | 检查 IPPool 状态 |
| Pod 无 IP | CNI 配置错误 | 检查 /etc/cni/net.d/ |
30.8.2 IPAM 命令¶
# 检查 IPAM 状态
calicoctl ipam check
# 释放未使用的 IP
calicoctl ipam release --ip=10.244.x.x
# 显示 IPAM 使用情况
calicoctl ipam show
# 显示某个 IP 的分配信息
calicoctl ipam show --ip=10.244.x.x
[!TIP] IBM 文档中有详细的 Calico IPAM 故障排查指南,搜索 "IBM Calico IPAM" 可以找到更多实用信息。
30.9 章节小结¶
mindmap
root((环境准备))
集群创建
kind
禁用默认 CNI
安装 Calico
Quick Start
Helm
Operator
calicoctl
版本匹配
常用命令
IPPool
ipipMode
vxlanMode
nodeSelector
验证
Pod 互通
Service 可达
拓扑规划
同子网节点
跨子网节点
[!IMPORTANT] 核心要点总结:
- 集群创建:
- 使用 kind 创建集群
- 禁用默认 CNI:
disableDefaultCNI: true指定 Pod CIDR
Calico 安装:
- Quick Start 适合快速体验
- 安装 Operator + CustomResources
等待所有 Pod 就绪
calicoctl 工具:
- 版本必须与 Calico 匹配
- 避免使用
--allow-version-mismatch可用于查看/管理 Calico 资源
IPPool 配置:
ipipMode:Always/CrossSubnet/NevervxlanMode:与 ipipMode 互斥
nodeSelector:all() 表示全部节点环境验证:
- Pod 间 ping 测试
- Service 访问测试
两项都通过 = CNI 工作正常
后续学习:
- 搭建 ContainerLab 拓扑
- 准备同子网/跨子网场景
- 实践 IPIP/CrossSubnet 模式
第三十一章 Calico 同节点通信¶
本章深入分析 Calico 同节点 Pod 之间的通信原理,这是所有 Calico 网络模式(IPIP/VXLAN/BGP)的共同基础。同节点通信采用 L3 路由转发方式,核心技术是 Proxy ARP。
31.1 背景与概述¶
31.1.1 同节点通信的重要性¶
| 特点 | 说明 |
|---|---|
| 模式通用 | IPIP、VXLAN、BGP 同节点通信方式相同 |
| L3 转发 | 采用三层路由方式,非二层交换 |
| Proxy ARP | 核心技术,打通 Pod 与 Root Namespace |
31.1.2 与其他 CNI 对比¶
graph LR
subgraph "Calico 方式"
P1["Pod 1"] --> Router["Host 路由器"]
Router --> P2["Pod 2"]
end
subgraph "Flannel 方式"
F1["Pod 1"] --> Bridge["Linux Bridge"]
Bridge --> F2["Pod 2"]
end
| CNI | 转发层级 | 设备 | 特点 |
|---|---|---|---|
| Calico | L3 路由 | Host 作为路由器 | 无广播,效率高 |
| Flannel | L2 交换 | Linux Bridge | 有广播,传统方式 |
31.2 网络拓扑结构¶
31.2.1 veth pair 连接¶
graph TB
subgraph "Pod 1 (10.244.x.66/32)"
eth0_1["eth0"]
end
subgraph "Host (Root Namespace)"
cali1["cali-xxx1"]
cali2["cali-xxx2"]
Routing["路由表"]
end
subgraph "Pod 2 (10.244.x.65/32)"
eth0_2["eth0"]
end
eth0_1 ---|"veth pair"| cali1
eth0_2 ---|"veth pair"| cali2
cali1 --> Routing
cali2 --> Routing
关键点:
- 每个 Pod 的 eth0 通过 veth pair 连接到 Host
- Host 端的网卡名为
cali-xxx格式 - Pod IP 使用
/32掩码(与任何地址都不同网段)
31.2.2 网卡特征¶
# 在 Pod 内查看网卡
ip link show eth0
# eth0@if5: ...
# 在 Host 上查看对应的 cali 网卡
ip link show | grep "^5:"
# 5: cali-xxx@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
| 网卡 | 位置 | MAC 地址 | 说明 |
|---|---|---|---|
eth0 |
Pod 内 | 随机 MAC | Pod 的主网卡 |
cali-xxx |
Host | ee:ee:ee:ee:ee:ee |
全 1 固定 MAC |
31.3 路由转发原理¶
31.3.1 Pod 内路由表¶
# 在 Pod 内查看路由
ip route
# 输出示例
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
路由表解读:
- 默认路由:所有流量发往
169.254.1.1(网关) - 出接口:通过
eth0发出 - scope link:本地链路范围
31.3.2 为什么使用 /32 掩码¶
graph TB
subgraph "Pod IP: 10.244.66.66/32"
Any["任何其他 IP"]
end
Any -->|"不同网段"| L3["必须走 L3 路由"]
L3 -->|"查询路由表"| Gateway["发往网关 169.254.1.1"]
| 掩码 | 含义 | 结果 |
|---|---|---|
/32 |
只有自己在网段内 | 任何目的 IP 都走 L3 |
/24 |
256 个地址同网段 | 同网段走 L2 交换 |
[!NOTE] 使用
/32掩码强制所有流量走三层路由,这是 Calico L3 网络模型的核心设计。
31.3.3 Host 路由表¶
# 在 Host 上查看路由
ip route | grep 10.244
# 输出示例
10.244.66.65 dev cali-xxx1 scope link
10.244.66.66 dev cali-xxx2 scope link
解读:到达每个 Pod IP 的路由,出接口就是连接该 Pod 的 cali 网卡。
31.4 数据包转发流程¶
31.4.1 同节点通信流程¶
sequenceDiagram
participant Pod1 as Pod 1 (10.244.66.66)
participant Cali1 as cali-xxx1
participant Host as Host 路由表
participant Cali2 as cali-xxx2
participant Pod2 as Pod 2 (10.244.66.65)
Pod1->>Pod1: 1. 查路由表,找到网关 169.254.1.1
Pod1->>Pod1: 2. ARP 请求网关 MAC
Note over Pod1: Proxy ARP 返回全 1 MAC
Pod1->>Cali1: 3. 封包发送 (dst MAC: ee:ee:ee:ee:ee:ee)
Cali1->>Host: 4. 进入 Host 路由表查询
Host->>Host: 5. 查到 10.244.66.65 出口是 cali-xxx2
Host->>Cali2: 6. 从 cali-xxx2 发出
Cali2->>Pod2: 7. 到达 Pod 2
31.4.2 数据包封装详解¶
第一跳(Pod → Host):
| 字段 | 值 | 说明 |
|---|---|---|
| Src IP | 10.244.66.66 | 源 Pod IP |
| Dst IP | 10.244.66.65 | 目的 Pod IP |
| Src MAC | Pod 1 的 MAC | eth0 的 MAC |
| Dst MAC | ee:ee:ee:ee:ee:ee |
网关(Proxy ARP 返回) |
第二跳(Host → Pod 2):
| 字段 | 值 | 说明 |
|---|---|---|
| Src IP | 10.244.66.66 | 保持不变 |
| Dst IP | 10.244.66.65 | 保持不变 |
| Src MAC | ee:ee:ee:ee:ee:ee |
cali-xxx2 的 MAC |
| Dst MAC | Pod 2 的 MAC | 目的 Pod 的 MAC |
31.5 Proxy ARP 机制¶
31.5.1 什么是 Proxy ARP¶
graph LR
subgraph "Pod"
ARP["ARP Request<br/>Who has 169.254.1.1?"]
end
subgraph "Host cali 网卡"
Proxy["Proxy ARP<br/>启用 proxy_arp"]
end
ARP -->|"请求"| Proxy
Proxy -->|"用自己的 MAC 回复"| ARP
Proxy ARP 原理:
- Pod 发送 ARP 请求查询
169.254.1.1的 MAC - cali 网卡开启了
proxy_arp功能 - cali 网卡用自己的 MAC 地址(全 1)回复
- Pod 得到 MAC 后即可封装数据包
31.5.2 169.254.1.1 的意义¶
# 这个地址在 Host 上找不到
ip addr | grep 169.254
# 无输出
# 但 ARP 可以获得响应
arping -I eth0 169.254.1.1
# ARPING 169.254.1.1
# 60 bytes from ee:ee:ee:ee:ee:ee: index=0 ...
| 问题 | 答案 |
|---|---|
| 为什么找不到 169.254.1.1? | 这个 IP 从未分配给任何接口 |
| 为什么 ARP 能成功? | Proxy ARP 代为响应 |
| 为什么用链路本地地址? | 避免与其他 IP 冲突 |
[!IMPORTANT] 169.254.0.0/16 是链路本地地址(Link-Local),RFC 3927 规定这个网段用于无 DHCP 时的自动配置。Calico 使用它作为虚拟网关,因为:
- 不会与真实 IP 冲突
- 不需要实际分配给接口
- 通过 Proxy ARP 即可工作
31.5.3 查看 Proxy ARP 配置¶
# 查看 cali 网卡的 proxy_arp 设置
cat /proc/sys/net/ipv4/conf/cali*/proxy_arp
# 1 (启用状态)
# 或使用 sysctl
sysctl net.ipv4.conf.cali*.proxy_arp
31.6 全 1 MAC 地址的原因¶
31.6.1 为什么使用 ee:ee:ee:ee:ee:ee¶
graph TB
subgraph "设计考虑"
Stable["稳定性<br/>内核无法生成持久 MAC"]
Unique["唯一性<br/>不与厂商 OUI 冲突"]
Simple["简单性<br/>点对点链路不需唯一"]
end
| 原因 | 说明 |
|---|---|
| 内核限制 | 内核不能为 veth 生成持久稳定的 MAC |
| 不冲突 | 全 1 不会与任何厂商 OUI 冲突 |
| 点对点 | veth pair 是点对点,MAC 只需本地有效 |
31.6.2 MAC 地址作用域¶
graph TB
subgraph "Pod 1 广播域"
P1["Pod 1 eth0"]
C1["cali-xxx1"]
P1 <--> C1
end
subgraph "Pod 2 广播域"
P2["Pod 2 eth0"]
C2["cali-xxx2"]
P2 <--> C2
end
subgraph "Host"
Router["路由转发 (L3)"]
end
C1 --> Router
Router --> C2
[!TIP] MAC 地址只需在冲突域/广播域内唯一。每个 Pod 与其 cali 网卡组成独立的点对点链路,所以全部使用相同的 MAC 地址不会冲突。
31.6.3 Calico 官方 FAQ¶
Calico 官网 FAQ 解答了这些常见疑问:
- Why do my containers have a route to 169.254.1.1?
-
这是虚拟网关,通过 Proxy ARP 工作
-
Why can't I see 169.254.1.1 on my host?
-
不需要实际配置,Proxy ARP 代为响应
-
Why do all cali* interfaces have the same MAC?
- 点对点链路,无需唯一 MAC
31.7 与 Flannel 对比¶
31.7.1 架构对比¶
graph TB
subgraph "Calico L3 模式"
CP1["Pod 1"] --> CR["Host (Router)"]
CR --> CP2["Pod 2"]
Note1["无广播<br/>路由转发"]
end
subgraph "Flannel Bridge 模式"
FP1["Pod 1"] --> FB["Linux Bridge"]
FB --> FP2["Pod 2"]
Note2["有广播<br/>交换转发"]
end
31.7.2 特性对比¶
| 特性 | Calico | Flannel |
|---|---|---|
| 转发层级 | L3(路由) | L2(交换) |
| 广播域 | 隔离(每 Pod 独立) | 共享(同节点共享) |
| ARP 广播 | 无(Proxy ARP) | 有 |
| 效率 | 高(无广播开销) | 较低 |
| 隔离性 | 强 | 弱 |
31.8 抓包验证¶
31.8.1 在 Pod 内抓包¶
# 进入 Pod
kubectl exec -it pod1 -- sh
# 安装 tcpdump(如果没有)
apk add tcpdump
# 抓取 ICMP 包
tcpdump -i eth0 icmp -n
31.8.2 在 Host 抓包¶
# 找到对应的 cali 网卡
ip link | grep cali
# 抓包
tcpdump -i cali-xxx -n icmp
31.8.3 验证 MAC 地址¶
# Ping 测试后查看 ARP 缓存
ip neigh
# 示例输出
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee REACHABLE
31.9 章节小结¶
mindmap
root((同节点通信))
网络拓扑
veth pair
cali 网卡
/32 掩码
路由转发
Pod 路由表
Host 路由表
L3 处理
Proxy ARP
169.254.1.1
全 1 MAC
代理响应
设计优势
无广播
效率高
隔离性强
[!IMPORTANT] 核心要点总结:
- 网络拓扑:
- Pod eth0 ↔ Host cali 网卡(veth pair)
- Pod IP 使用 /32 掩码
强制所有流量走 L3
路由转发:
- Pod 默认路由指向 169.254.1.1
- Host 路由表有到每个 Pod 的明细路由
出接口就是对应的 cali 网卡
Proxy ARP:
- cali 网卡开启 proxy_arp
- 代替 169.254.1.1 响应 ARP
返回自己的 MAC(全 1)
全 1 MAC:
- 所有 cali 网卡使用相同 MAC
- 点对点链路,本地有效
不与厂商 OUI 冲突
与 Flannel 对比:
- Calico: L3 路由,无广播
Flannel: L2 交换,有广播
排查技巧:
- 不懂就查路由表
ip route- 确认 ARP 缓存
ip neigh- 抓包验证
tcpdump
第三十二章 Calico-Proxy-ARP 实践¶
本章通过手工实现 Proxy ARP 来深入理解 Calico 同节点通信的核心机制。通过实践,你将掌握 veth pair、路由配置、Proxy ARP 开关等关键技术点。
32.1 背景与目标¶
32.1.1 实验目标¶
graph LR
subgraph "Namespace (ns1)"
Pod["1.1.1.2/24"]
end
subgraph "Root Namespace"
Host["Host"]
end
subgraph "External"
Internet["外网"]
end
Pod -->|"1. Proxy ARP"| Host
Host -->|"2. 路由转发"| Internet
| 目标 | 说明 |
|---|---|
| 理解 Proxy ARP | 手工实现虚拟网关 169.254.1.1 |
| 掌握路由配置 | 先配网关路由,再配默认路由 |
| 解决回程问题 | 添加回程路由确保双向通信 |
| 外网访问 | 通过 SNAT 实现出公网 |
32.1.2 实验拓扑¶
graph TB
subgraph "Namespace: ns1"
NS1["c-eth0<br/>1.1.1.2/24"]
end
subgraph "Root Namespace"
Veth["veth"]
Route["路由表"]
SNAT["SNAT"]
end
subgraph "External"
GW["网关"]
Ext["外网 114.114.114.114"]
end
NS1 ---|"veth pair"| Veth
Veth --> Route
Route --> SNAT
SNAT --> GW
GW --> Ext
32.2 查看 Calico Proxy ARP 配置¶
32.2.1 Proxy ARP 开关位置¶
# 查看 cali 网卡的 proxy_arp 状态
cat /proc/sys/net/ipv4/conf/cali*/proxy_arp
# 输出: 1 (已启用)
# 查看 eth0 的 proxy_arp 状态
cat /proc/sys/net/ipv4/conf/eth0/proxy_arp
# 输出: 0 (未启用)
[!NOTE] Proxy ARP 是针对单个网卡的配置:
cali*网卡开启了 proxy_arp(值为 1)eth0等其他网卡默认关闭
32.2.2 开关含义¶
| 值 | 含义 |
|---|---|
0 |
禁用 Proxy ARP |
1 |
启用 Proxy ARP |
32.3 手工实现 Proxy ARP¶
32.3.1 第一步:创建网络命名空间¶
# 创建命名空间 ns1
ip netns add ns1
# 验证
ip netns list
32.3.2 第二步:创建 veth pair¶
# 创建 veth pair
# 一端: veth (留在 root namespace)
# 另一端: c-eth0 (放入 ns1)
ip link add veth type veth peer name c-eth0
# 启用两端网卡
ip link set c-eth0 up
ip link set veth up
# 将 c-eth0 移入 ns1
ip link set c-eth0 netns ns1
# 在 ns1 中启用并配置 IP
ip netns exec ns1 ip link set c-eth0 up
ip netns exec ns1 ip addr add 1.1.1.2/24 dev c-eth0
sequenceDiagram
participant Root as Root Namespace
participant NS1 as ns1 Namespace
Root->>Root: 创建 veth pair (veth + c-eth0)
Root->>NS1: 移动 c-eth0 到 ns1
Root->>Root: 启用 veth
NS1->>NS1: 启用 c-eth0
NS1->>NS1: 配置 IP 1.1.1.2/24
32.3.3 第三步:配置路由(关键!)¶
[!IMPORTANT] 路由配置顺序非常重要:必须先配置到网关的路由,再配置默认路由!
# 进入 ns1
ip netns exec ns1 bash
# 第一步:配置到网关 169.254.1.1 的路由
ip route add 169.254.1.1 dev c-eth0 scope link
# 第二步:配置默认路由,指向网关
ip route add default via 169.254.1.1 dev c-eth0
# 查看路由表
ip route
# default via 169.254.1.1 dev c-eth0
# 169.254.1.1 dev c-eth0 scope link
为什么必须先配置网关路由?
graph TB
subgraph "正确顺序"
A1["1. 配置 169.254.1.1 路由"] --> B1["系统知道如何到达网关"]
B1 --> C1["2. 配置 default via 169.254.1.1"]
C1 --> D1["成功!"]
end
subgraph "错误顺序"
A2["1. 直接配置 default via 169.254.1.1"] --> B2["系统不知道如何到达网关"]
B2 --> C2["报错!"]
end
| 错误示例 | 原因 |
|---|---|
ip route add default via 169.254.1.1 |
系统不知道 169.254.1.1 怎么走 |
32.3.4 第四步:启用 Proxy ARP¶
# 在 root namespace 中对 veth 启用 proxy_arp
echo 1 > /proc/sys/net/ipv4/conf/veth/proxy_arp
# 验证
cat /proc/sys/net/ipv4/conf/veth/proxy_arp
# 输出: 1
32.3.5 第五步:验证 ARP 响应¶
# 在 ns1 中测试 ARP
ip netns exec ns1 arping -I c-eth0 169.254.1.1
# 输出示例
# ARPING 169.254.1.1 from 1.1.1.2 c-eth0
# Unicast reply from 169.254.1.1 [9B:12:0C:xx:xx:xx] ...
至此,第一跳(Pod → Host)已经打通!
32.4 解决回程路由问题¶
32.4.1 问题现象¶
# 在 ns1 中 ping Host 地址
ip netns exec ns1 ping -I 1.1.1.2 192.168.2.66
# 结果:不通!
32.4.2 抓包分析¶
# 在 veth 上抓包
tcpdump -i veth icmp -n
# 输出
# ICMP echo request 1.1.1.2 -> 192.168.2.66
# (无 reply)
# 在 eth0 上抓包
tcpdump -i eth0 icmp -n
# 输出
# ICMP echo request 1.1.1.2 -> 192.168.2.66
# ICMP echo reply 192.168.2.66 -> 1.1.1.2 (发往默认网关!)
32.4.3 问题原因¶
graph LR
NS1["ns1<br/>1.1.1.2"] -->|"request"| Host["Host<br/>192.168.2.66"]
Host -->|"reply"| GW["默认网关"]
GW -->|"???"| Lost["包丢失"]
原因:Host 收到包后,要回复给 1.1.1.2,但 Host 路由表没有到 1.1.1.2 的路由,匹配默认路由发给了外部网关。
32.4.4 解决方案:添加回程路由¶
# 在 root namespace 添加到 1.1.1.2 的路由
ip route add 1.1.1.2 dev veth scope link
# 或添加整个网段
ip route add 1.1.1.0/24 dev veth scope link
# 验证路由表
ip route | grep 1.1.1
# 1.1.1.2 dev veth scope link
# 再次测试
ip netns exec ns1 ping -I 1.1.1.2 192.168.2.66
# 结果:通了!
32.5 实现外网访问¶
32.5.1 问题:无法访问外网¶
ip netns exec ns1 ping 114.114.114.114
# 不通
原因:缺少 SNAT,外部网络不知道如何回复私有 IP 1.1.1.2
32.5.2 解决方案:配置 SNAT¶
# 启用 IP 转发
echo 1 > /proc/sys/net/ipv4/ip_forward
# 添加 SNAT 规则
iptables -t nat -A POSTROUTING -s 1.1.1.0/24 -j MASQUERADE
# 或指定出接口
iptables -t nat -A POSTROUTING -s 1.1.1.0/24 -o eth0 -j MASQUERADE
# 测试外网访问
ip netns exec ns1 ping 114.114.114.114
# 通了!
32.6 完整实现脚本¶
#!/bin/bash
# proxy-arp-demo.sh - 手工实现 Proxy ARP
# 1. 创建命名空间
ip netns add ns1
# 2. 创建 veth pair
ip link add veth type veth peer name c-eth0
ip link set veth up
ip link set c-eth0 netns ns1
ip netns exec ns1 ip link set c-eth0 up
ip netns exec ns1 ip addr add 1.1.1.2/24 dev c-eth0
# 3. 配置路由(顺序重要!)
ip netns exec ns1 ip route add 169.254.1.1 dev c-eth0 scope link
ip netns exec ns1 ip route add default via 169.254.1.1 dev c-eth0
# 4. 启用 Proxy ARP
echo 1 > /proc/sys/net/ipv4/conf/veth/proxy_arp
# 5. 添加回程路由
ip route add 1.1.1.2 dev veth scope link
# 6. 启用 IP 转发和 SNAT(如需外网访问)
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 1.1.1.0/24 -j MASQUERADE
echo "Proxy ARP 配置完成!"
echo "测试:ip netns exec ns1 ping 114.114.114.114"
32.7 与 Calico 实现对比¶
graph TB
subgraph "手工实现"
M1["创建 veth pair"] --> M2["配置路由"]
M2 --> M3["启用 proxy_arp"]
M3 --> M4["添加回程路由"]
M4 --> M5["配置 SNAT"]
end
subgraph "Calico 自动实现"
C1["CNI 创建 cali* 网卡"] --> C2["注入 Pod 路由"]
C2 --> C3["Felix 启用 proxy_arp"]
C3 --> C4["自动维护路由表"]
C4 --> C5["iptables 规则"]
end
| 步骤 | 手工实现 | Calico 实现 |
|---|---|---|
| 创建 veth | ip link add |
CNI 插件自动创建 |
| 配置路由 | ip route add |
Felix 自动注入 |
| Proxy ARP | 写 /proc/sys |
Felix 自动配置 |
| 回程路由 | 手动添加 | Felix 监听并维护 |
| SNAT | iptables 规则 | Felix 管理 iptables |
32.8 扩展:东西向流量¶
32.8.1 实验思路¶
要实现两个节点上的命名空间互通(东西向流量):
graph LR
subgraph "Node 1"
NS1["ns1<br/>1.1.1.2/24"]
end
subgraph "Node 2"
NS2["ns1<br/>1.1.2.2/24"]
end
NS1 <-->|"跨节点通信"| NS2
需要添加的配置:
- Node 1:添加到
1.1.2.0/24的路由,下一跳为 Node 2 - Node 2:添加到
1.1.1.0/24的路由,下一跳为 Node 1 - 两边都配置 Proxy ARP
[!TIP] 这就是 Calico Host-GW 模式的原理!通过路由表实现跨节点通信。
32.9 关键技术总结¶
| 技术 | 作用 | 命令 |
|---|---|---|
| veth pair | 连接命名空间 | ip link add type veth peer |
| 网关路由 | 定义如何到达网关 | ip route add 169.254.1.1 dev xxx scope link |
| 默认路由 | 定义默认出口 | ip route add default via 169.254.1.1 |
| Proxy ARP | 代理 ARP 响应 | echo 1 > /proc/sys/.../proxy_arp |
| 回程路由 | 解决回包问题 | ip route add x.x.x.x dev xxx |
| SNAT | 外网访问 | iptables -t nat -A POSTROUTING -j MASQUERADE |
32.10 章节小结¶
mindmap
root((Proxy ARP 实践))
基础配置
创建 netns
创建 veth pair
配置 IP
路由配置
先配网关路由
再配默认路由
添加回程路由
Proxy ARP
/proc/sys 开关
针对单个网卡
返回自己 MAC
外网访问
IP 转发
SNAT/MASQUERADE
扩展
东西向流量
Host-GW 原理
[!IMPORTANT] 核心要点总结:
- Proxy ARP 开关:
- 位置:
/proc/sys/net/ipv4/conf/<iface>/proxy_arp- 值 1 启用,值 0 禁用
针对单个网卡配置
路由配置顺序:
- 必须先配置网关路由:
ip route add 169.254.1.1 dev xxx scope link- 再配置默认路由:
ip route add default via 169.254.1.1顺序错误会报错!
回程路由:
- Host 需要知道如何回到 Pod
- 否则包会走默认路由发到外部网关
解决:添加到 Pod IP 的明细路由
外网访问四要素:
- veth pair 连接
- Proxy ARP 响应
- 路由表配置
SNAT 地址转换
与 Calico 对比:
- 手工实现帮助理解原理
- Calico 通过 Felix 自动化这些配置
- 核心技术完全相同
第三十三章 Calico-IPIP 跨节点通信¶
本章详细讲解 Calico 在 IPIP 模式下如何实现跨节点 Pod 通信。IPIP 是一种 IP-in-IP 的 Overlay 封装技术,通过在原始 IP 包外再封装一层 IP 头来实现隧道传输。
33.1 背景与概述¶
33.1.1 为什么需要 IPIP¶
graph LR
subgraph "Node 1 (172.18.0.4)"
Pod1["Pod A<br/>10.24.197.x"]
end
subgraph "Node 2 (172.18.0.2)"
Pod2["Pod B<br/>10.24.79.x"]
end
Pod1 -->|"跨节点通信"| Pod2
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 同节点 | L3 路由 + Proxy ARP | 直接转发 |
| 跨节点 | 节点间网络不认识 Pod IP | IPIP 隧道封装 |
33.1.2 IPIP 模式特点¶
| 特性 | 说明 |
|---|---|
| 封装方式 | IP-in-IP(协议号 4) |
| 额外开销 | 20 字节(外层 IP 头) |
| 二层信息 | 无(只有 IP 头) |
| 隧道设备 | tunl0 |
33.2 跨节点通信流程¶
33.2.1 整体数据流¶
sequenceDiagram
participant PodA as Pod A (10.24.197.x)
participant Node1 as Node 1 (172.18.0.4)
participant tunl0_1 as tunl0
participant Network as 物理网络
participant tunl0_2 as tunl0
participant Node2 as Node 2 (172.18.0.2)
participant PodB as Pod B (10.24.79.x)
PodA->>Node1: 1. 原始包发送
Note over PodA,Node1: SRC: 10.24.197.x<br/>DST: 10.24.79.x
Node1->>tunl0_1: 2. 查路由表匹配 tunl0
tunl0_1->>Network: 3. IPIP 封装
Note over tunl0_1,Network: 外层: 172.18.0.4 -> 172.18.0.2<br/>内层: 10.24.197.x -> 10.24.79.x
Network->>tunl0_2: 4. 物理网络传输
tunl0_2->>Node2: 5. IPIP 解封装
Node2->>PodB: 6. 原始包送达
33.2.2 详细步骤¶
| 步骤 | 位置 | 动作 |
|---|---|---|
| 1 | Pod A | 发送 ICMP 包,DST 为 Pod B IP |
| 2 | Node 1 路由表 | 匹配到 Pod B 网段,下一跳 172.18.0.2,出接口 tunl0 |
| 3 | tunl0 设备 | IPIP 封装,外层 IP: 172.18.0.4 → 172.18.0.2 |
| 4 | eth0 | 二层封装 MAC,发送到物理网络 |
| 5 | Node 2 eth0 | 接收 IPIP 包 |
| 6 | Node 2 tunl0 | 解封装,露出原始包 |
| 7 | Node 2 路由表 | 匹配 Pod B 的 /32 路由,转发到 cali 网卡 |
| 8 | Pod B | 收到原始包 |
33.3 路由表分析¶
33.3.1 Node 1 路由表¶
# 查看 Node 1 路由表
ip route
# 输出示例
10.24.79.0/26 via 172.18.0.2 dev tunl0 proto bird onlink
10.24.197.0/26 dev cali-xxx scope link
| 路由条目 | 含义 |
|---|---|
10.24.79.0/26 via 172.18.0.2 dev tunl0 |
到 Node 2 Pod 网段,下一跳 172.18.0.2,出接口 tunl0 |
proto bird |
由 BIRD 协议注入 |
onlink |
下一跳在链路上(不需 ARP) |
33.3.2 路由匹配流程¶
graph TB
Start["Pod A 发包<br/>DST: 10.24.79.1"] --> Route["查路由表"]
Route --> Match["匹配 10.24.79.0/26"]
Match --> Gateway["下一跳: 172.18.0.2"]
Gateway --> Dev["出接口: tunl0"]
Dev --> IPIP["IPIP 封装"]
33.4 tunl0 设备详解¶
33.4.1 查看 tunl0 设备¶
ip link show tunl0
# 输出示例
tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN
link/ipip 0.0.0.0 brd 0.0.0.0
| 属性 | 说明 |
|---|---|
NOARP |
不处理 ARP(三层设备) |
link/ipip |
IPIP 类型隧道 |
mtu 1480 |
MTU = 1500 - 20(IPIP 头) |
33.4.2 tunl0 配置¶
ip -d link show tunl0
# 输出示例
tunl0@NONE: ...
ipip remote any local any ttl inherit nopmtudisc
| 参数 | 说明 |
|---|---|
remote any |
远端 IP 不固定(动态) |
local any |
本端 IP 不固定(使用默认路由出接口 IP) |
ttl inherit |
TTL 继承自原始包 |
[!NOTE] 当
local和remote为any时,实际使用的 IP 由路由表决定:
- local: 默认路由对应的接口 IP(如 eth0 的 IP)
- remote: 路由条目中指定的下一跳 IP
33.5 IPIP 封装原理¶
33.5.1 IP-in-IP 结构¶
graph LR
subgraph "IPIP 封装后"
subgraph "外层 IP 头"
OuterIP["SRC: 172.18.0.4<br/>DST: 172.18.0.2<br/>Protocol: 4 (IPIP)"]
end
subgraph "内层 IP 头"
InnerIP["SRC: 10.24.197.x<br/>DST: 10.24.79.x<br/>Protocol: ICMP"]
end
subgraph "Payload"
Data["ICMP 数据"]
end
end
OuterIP --> InnerIP --> Data
| 字段 | 外层 IP | 内层 IP |
|---|---|---|
| Source IP | 172.18.0.4(Node 1) | 10.24.197.x(Pod A) |
| Dest IP | 172.18.0.2(Node 2) | 10.24.79.x(Pod B) |
| Protocol | 4(IPIP) | 1(ICMP)等 |
33.5.2 协议号¶
| 协议号 | 协议名 |
|---|---|
| 1 | ICMP |
| 4 | IPIP |
| 6 | TCP |
| 17 | UDP |
33.6 抓包验证¶
33.6.1 在 tunl0 上抓包¶
# 在 tunl0 上抓包
tcpdump -i tunl0 -nn -e
# 输出示例
IP 10.24.197.x > 10.24.79.x: ICMP echo request
[!NOTE] 在 tunl0 上抓包显示的是 Raw IP(裸 IP),没有 MAC 地址。 这是因为 tunl0 是三层设备,不处理二层信息。
33.6.2 在 eth0 上抓包¶
# 过滤 IPIP 协议
tcpdump -i eth0 -nn 'ip proto 4'
# 输出示例
IP 172.18.0.4 > 172.18.0.2: IP 10.24.197.x > 10.24.79.x: ICMP echo request
抓包结果说明:
| 层次 | 源地址 | 目的地址 |
|---|---|---|
| 外层 IP | 172.18.0.4(Node 1) | 172.18.0.2(Node 2) |
| 内层 IP | 10.24.197.x(Pod A) | 10.24.79.x(Pod B) |
33.6.3 Wireshark 分析¶
# 导出抓包文件
tcpdump -i eth0 -nn 'ip proto 4' -w ipip.pcap
在 Wireshark 中可以看到:
Ethernet II
├── IPv4 (外层): 172.18.0.4 -> 172.18.0.2, Protocol: IPIP (4)
│ └── IPv4 (内层): 10.24.197.x -> 10.24.79.x, Protocol: ICMP
│ └── ICMP: Echo request
33.7 IPIP vs VXLAN¶
graph TB
subgraph "IPIP 封装 (20 字节开销)"
IPIP_Outer["外层 IP 头<br/>20 bytes"]
IPIP_Inner["内层 IP 头"]
IPIP_Data["Payload"]
IPIP_Outer --> IPIP_Inner --> IPIP_Data
end
subgraph "VXLAN 封装 (50 字节开销)"
VXLAN_Outer["外层 IP 头<br/>20 bytes"]
VXLAN_UDP["UDP 头<br/>8 bytes"]
VXLAN_Header["VXLAN 头<br/>8 bytes"]
VXLAN_Inner["内层 MAC<br/>14 bytes"]
VXLAN_InnerIP["内层 IP 头"]
VXLAN_Data["Payload"]
VXLAN_Outer --> VXLAN_UDP --> VXLAN_Header --> VXLAN_Inner --> VXLAN_InnerIP --> VXLAN_Data
end
| 对比项 | IPIP | VXLAN |
|---|---|---|
| 额外开销 | 20 字节 | 50 字节 |
| 二层信息 | 无 | 有(内层 MAC) |
| VNI 隔离 | 无 | 有 |
| 协议类型 | IP 协议 4 | UDP 端口 4789 |
| 适用场景 | 简单隧道 | 多租户隔离 |
33.8 手工创建 IPIP 设备¶
33.8.1 创建 IPIP 隧道¶
# 在 Node 1 上创建
ip link add ipip0 type ipip local 172.18.0.4 remote 172.18.0.2
ip link set ipip0 up
ip addr add 1.1.1.1/24 dev ipip0
# 在 Node 2 上创建
ip link add ipip0 type ipip local 172.18.0.2 remote 172.18.0.4
ip link set ipip0 up
ip addr add 1.1.1.2/24 dev ipip0
33.8.2 验证¶
# 查看设备
ip -d link show ipip0
# 输出示例
ipip0@NONE: <NOARP,UP,LOWER_UP> mtu 1480
link/ipip 172.18.0.4 peer 172.18.0.2
# 测试连通性
ping 1.1.1.2
33.8.3 与 Calico tunl0 对比¶
| 对比项 | 手工创建 | Calico tunl0 |
|---|---|---|
| local/remote | 指定固定 IP | any(动态) |
| 路由 | 手动添加 | BIRD 自动注入 |
| 管理 | 手动 | Felix 自动 |
33.9 kind 环境 MAC 地址规律¶
[!TIP] 在 kind 创建的集群中,容器的 MAC 地址有规律:
- 格式:
02:42:ac:xx:xx:xx- 最后一位与 IP 地址最后一位相同
- 例如:IP
172.18.0.4对应 MAC02:42:ac:xx:xx:04
33.10 章节小结¶
mindmap
root((IPIP 跨节点通信))
路由表
目标网段匹配
下一跳为对端节点 IP
出接口为 tunl0
tunl0 设备
NOARP 三层设备
local/remote any
MTU 1480
IPIP 封装
外层 IP 头 20 字节
协议号 4
无二层信息
抓包验证
tunl0 显示 Raw IP
eth0 过滤 ip proto 4
两层 IP 地址
对比 VXLAN
开销更小
无 VNI 隔离
无内层 MAC
[!IMPORTANT] 核心要点总结:
- IPIP 封装原理:
- IP-in-IP,在原始 IP 包外再封装一层 IP 头
- 外层 IP:Node IP(local → remote)
- 内层 IP:Pod IP(src → dst)
协议号 4 表示 IPIP
tunl0 设备:
- Calico 自动创建的 IPIP 隧道设备
NOARP:三层设备,不处理 ARPlocal any remote any:动态选择 IPMTU 1480(1500 - 20)
路由决策:
- 目标 Pod 网段匹配对应路由
- 下一跳为目标节点 IP
出接口为 tunl0 触发 IPIP 封装
抓包技巧:
tcpdump -i tunl0:显示 Raw IPtcpdump -i eth0 'ip proto 4':过滤 IPIP 包Wireshark 可看到两层 IP
与 VXLAN 对比:
- IPIP 开销 20 字节 < VXLAN 50 字节
- IPIP 无二层信息、无 VNI
- IPIP 更简单,VXLAN 支持多租户
第三十四章 Calico-IPIP CrossSubnet 模式¶
本章详细讲解 Calico IPIP 的 CrossSubnet 模式,这是一种智能混合模式:同网段节点间走纯路由,跨网段节点间走 IPIP 封装,从而在性能和兼容性之间取得平衡。
34.1 背景与概述¶
34.1.1 什么是 CrossSubnet¶
graph TB
subgraph "子网 1: 10.1.5.0/24"
Node1["Node 1<br/>10.1.5.10"]
Node2["Node 2<br/>10.1.5.11"]
SW1["交换机 br-pro0"]
end
subgraph "子网 2: 10.1.8.0/24"
Node3["Node 3<br/>10.1.8.10"]
Node4["Node 4<br/>10.1.8.11"]
SW2["交换机 br-pro1"]
end
GW["网关 Gateway<br/>eth1: 10.1.5.1<br/>eth2: 10.1.8.1"]
Node1 --- SW1
Node2 --- SW1
Node3 --- SW2
Node4 --- SW2
SW1 --- GW
SW2 --- GW
Node1 -.->|"同网段: 纯路由"| Node2
Node1 ==>|"跨网段: IPIP 封装"| Node3
| 通信场景 | 模式 | 性能 |
|---|---|---|
| 同网段节点 | 纯路由转发 | 高(无封装开销) |
| 跨网段节点 | IPIP 封装 | 中(20 字节开销) |
34.1.2 三种 ipipMode 对比¶
| ipipMode | 同网段 | 跨网段 | 适用场景 |
|---|---|---|---|
| Never | 路由 | 路由 | 底层网络支持 Pod IP 路由 |
| Always | IPIP | IPIP | 所有场景都封装 |
| CrossSubnet | 路由 | IPIP | 混合场景,性能优化 |
34.2 CrossSubnet 工作原理¶
34.2.1 决策流程¶
flowchart TD
Start["Pod A 发送数据包"] --> Check["检查目标 Pod 所在节点"]
Check --> Same{"源节点与目标节点<br/>在同一子网?"}
Same -->|"是"| Route["纯路由转发<br/>无 Overlay"]
Same -->|"否"| IPIP["IPIP 封装<br/>通过 tunl0"]
Route --> Dest["目标 Pod"]
IPIP --> Dest
34.2.2 路由表差异¶
同网段路由条目:
# Node 1 (10.1.5.10) 到 Node 2 (10.1.5.11) 的 Pod 网段
10.24.x.x/26 via 10.1.5.11 dev net0
跨网段路由条目:
# Node 1 (10.1.5.10) 到 Node 3 (10.1.8.10) 的 Pod 网段
10.24.x.x/26 via 10.1.8.10 dev tunl0 onlink
| 关键差异 | 同网段 | 跨网段 |
|---|---|---|
| 出接口 | net0(物理网卡) |
tunl0(IPIP 隧道) |
| 封装 | 无 | IPIP |
| 下一跳可达性 | 直接可达 | onlink(不检查) |
34.3 配置 CrossSubnet 模式¶
34.3.1 IPPool 配置¶
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
cidr: 10.24.0.0/16
ipipMode: CrossSubnet # 关键配置
vxlanMode: Never
natOutgoing: true
nodeSelector: all()
| 参数 | 值 | 说明 |
|---|---|---|
ipipMode |
CrossSubnet |
跨网段时使用 IPIP |
vxlanMode |
Never |
不使用 VXLAN |
34.3.2 修改现有 IPPool¶
# 查看当前配置
calicoctl get ippool default-ipv4-ippool -o yaml
# 修改 ipipMode
calicoctl patch ippool default-ipv4-ippool -p '{"spec":{"ipipMode":"CrossSubnet"}}'
34.4 ContainerLab 跨网段拓扑¶
34.4.1 拓扑架构¶
graph TB
subgraph "Kind 集群"
subgraph "子网 10.1.5.0/24"
Control["control-plane<br/>10.1.5.10"]
Worker1["worker<br/>10.1.5.11"]
end
subgraph "子网 10.1.8.0/24"
Worker2["worker2<br/>10.1.8.10"]
Worker3["worker3<br/>10.1.8.11"]
end
end
subgraph "ContainerLab"
BR0["br-pro0<br/>Linux Bridge"]
BR1["br-pro1<br/>Linux Bridge"]
GW["Gateway (VyOS)<br/>eth1: 10.1.5.1<br/>eth2: 10.1.8.1"]
end
Control --- BR0
Worker1 --- BR0
Worker2 --- BR1
Worker3 --- BR1
BR0 --- GW
BR1 --- GW
34.4.2 Kind 集群配置¶
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-ip: 10.1.5.10
- role: worker
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
node-ip: 10.1.5.11
- role: worker
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
node-ip: 10.1.8.10
- role: worker
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
node-ip: 10.1.8.11
34.4.3 网关配置要点¶
网关需要配置 SNAT 以确保集群能访问外网:
# VyOS 网关配置
set interfaces ethernet eth1 address '10.1.5.1/24'
set interfaces ethernet eth2 address '10.1.8.1/24'
# SNAT 配置
set nat source rule 100 outbound-interface 'eth0'
set nat source rule 100 source address '10.1.0.0/16'
set nat source rule 100 translation address 'masquerade'
34.5 通信验证¶
34.5.1 同网段通信(纯路由)¶
# 在 Node 1 (10.1.5.10) 上的 Pod 访问 Node 2 (10.1.5.11) 上的 Pod
# 查看路由表
ip route | grep "10.1.5.11"
# 10.24.x.x/26 via 10.1.5.11 dev net0 <- 出接口是 net0
# 抓包验证
tcpdump -i net0 -nn icmp
抓包结果:
IP 10.24.x.x > 10.24.y.y: ICMP echo request
[!NOTE] 同网段通信时,数据包是普通的 IP 包,没有 IPIP 封装。 MAC 地址会逐跳变化,IP 地址保持不变。
34.5.2 跨网段通信(IPIP 封装)¶
# 在 Node 1 (10.1.5.10) 上的 Pod 访问 Node 3 (10.1.8.10) 上的 Pod
# 查看路由表
ip route | grep "10.1.8.10"
# 10.24.x.x/26 via 10.1.8.10 dev tunl0 onlink <- 出接口是 tunl0
# 抓包验证
tcpdump -i net0 -nn 'ip proto 4'
抓包结果:
IP 10.1.5.10 > 10.1.8.10: IP 10.24.x.x > 10.24.y.y: ICMP echo request
[!NOTE] 跨网段通信时,数据包有两层 IP:
- 外层 IP:10.1.5.10 → 10.1.8.10(Node IP)
- 内层 IP:10.24.x.x → 10.24.y.y(Pod IP)
34.6 MAC 地址变化分析¶
34.6.1 跨网段通信的 MAC 地址¶
sequenceDiagram
participant Pod as Pod A
participant Node1 as Node 1 (10.1.5.10)
participant GW as Gateway
participant Node3 as Node 3 (10.1.8.10)
participant PodB as Pod B
Pod->>Node1: IPIP 封装
Note over Node1: SRC MAC: Node1 net0<br/>DST MAC: 10.1.5.1 (GW)
Node1->>GW: 包发往网关
Note over GW: SRC MAC: GW eth1<br/>DST MAC: 10.1.8.10
GW->>Node3: 包发往 Node 3
Node3->>PodB: IPIP 解封装
关键点:
- Node 1 发包时:
- SRC MAC:Node 1 net0 的 MAC
-
DST MAC:网关 10.1.5.1 的 MAC(因为 10.1.8.10 不在同网段)
-
网关转发时:
- SRC MAC:网关 eth2 的 MAC
-
DST MAC:Node 3 net0 的 MAC
-
IP 地址不变:外层 IP 始终是 10.1.5.10 → 10.1.8.10
34.6.2 为什么 ARP 表没有对端 IP¶
# 在 Node 1 上查看 ARP 表
arp -n | grep 10.1.8.10
# 无结果!
原因:Node 1 (10.1.5.10) 和 Node 3 (10.1.8.10) 不在同一子网,无法直接 ARP 解析。
实际过程:
- Node 1 查路由,发现 10.1.8.10 的下一跳是网关 10.1.5.1
- Node 1 ARP 解析 10.1.5.1 的 MAC
- 数据包发给网关,由网关负责后续转发
34.7 Always vs CrossSubnet¶
graph LR
subgraph "Always 模式"
A1["Node 1"] -->|"IPIP"| A2["Node 2"]
A1 -->|"IPIP"| A3["Node 3"]
end
subgraph "CrossSubnet 模式"
C1["Node 1"] -->|"路由"| C2["Node 2 (同网段)"]
C1 -->|"IPIP"| C3["Node 3 (跨网段)"]
end
| 对比项 | Always | CrossSubnet |
|---|---|---|
| 同网段开销 | 20 字节 | 0 字节 |
| 跨网段开销 | 20 字节 | 20 字节 |
| 配置复杂度 | 低 | 中 |
| 适用场景 | 简单环境 | 大规模混合环境 |
| 性能 | 一般 | 同网段更优 |
34.8 章节小结¶
mindmap
root((IPIP CrossSubnet))
模式原理
同网段走路由
跨网段走 IPIP
智能判断子网
配置方式
ipipMode: CrossSubnet
IPPool 资源配置
路由表特征
同网段: dev net0
跨网段: dev tunl0 onlink
抓包验证
同网段: 普通 IP 包
跨网段: 两层 IP
过滤: ip proto 4
MAC 地址
跨网段先发给网关
逐跳 MAC 变化
IP 地址不变
[!IMPORTANT] 核心要点总结:
- CrossSubnet 模式:
- 同网段节点:纯路由转发,无封装开销
- 跨网段节点:IPIP 封装,20 字节开销
配置:
ipipMode: CrossSubnet路由表识别:
- 同网段:
dev net0(物理网卡)跨网段:
dev tunl0 onlink(隧道设备)抓包验证:
- 同网段:
tcpdump -i net0 icmp跨网段:
tcpdump -i net0 'ip proto 4'MAC 地址规律:
- 跨网段时,DST MAC 是网关的 MAC
- ARP 表中不会有对端节点的 MAC
每一跳 MAC 变化,IP 不变
适用场景:
- 大规模集群,节点分布在多个子网
- 同机房节点走高速路由
- 跨机房节点走 IPIP 穿透
第三十五章 Calico-IPIP 手工实践¶
本章通过手工创建 IPIP 隧道设备,深入理解 IPIP 封装的底层原理。同时介绍 SBR(Source-Based Routing)策略路由的概念,为理解复杂网络场景打下基础。
35.1 背景与目标¶
35.1.1 为什么要手工实践¶
手工创建 IPIP 隧道的目的:
- 加深理解:通过亲手操作,理解 Calico 自动创建
tunl0设备的底层机制 - 排障能力:掌握 IPIP 设备的配置方式,便于问题排查
- 扩展思维:理解如何将 Pod 流量引导至 Overlay 设备
35.1.2 实验拓扑¶
graph LR
subgraph "Node 1 (172.18.0.4)"
IPIP1["ipip0<br/>1.1.1.1/24"]
end
subgraph "Node 2 (172.18.0.2)"
IPIP2["ipip0<br/>1.1.2.1/24"]
end
IPIP1 <-->|"IPIP Tunnel<br/>Outer: 172.18.0.x"| IPIP2
| 节点 | 物理 IP | IPIP 接口地址 | Local | Remote |
|---|---|---|---|---|
| Node 1 | 172.18.0.4 | 1.1.1.1/24 | 172.18.0.4 | 172.18.0.2 |
| Node 2 | 172.18.0.2 | 1.1.2.1/24 | 172.18.0.2 | 172.18.0.4 |
35.2 手工创建 IPIP 隧道¶
35.2.1 Node 1 配置¶
# 1. 创建 IPIP 设备
ip link add ipip0 type ipip local 172.18.0.4 remote 172.18.0.2
# 2. 启用接口
ip link set ipip0 up
# 3. 配置隧道内部地址
ip addr add 1.1.1.1/24 dev ipip0
# 4. 添加到对端网段的路由
ip route add 1.1.2.0/24 dev ipip0
35.2.2 Node 2 配置¶
# 1. 创建 IPIP 设备(local/remote 互换)
ip link add ipip0 type ipip local 172.18.0.2 remote 172.18.0.4
# 2. 启用接口
ip link set ipip0 up
# 3. 配置隧道内部地址
ip addr add 1.1.2.1/24 dev ipip0
# 4. 添加到对端网段的路由
ip route add 1.1.1.0/24 dev ipip0
35.2.3 配置命令详解¶
| 参数 | 说明 | 示例 |
|---|---|---|
type ipip |
设备类型为 IPIP | - |
local |
本端物理 IP | 172.18.0.4 |
remote |
对端物理 IP | 172.18.0.2 |
| 隧道地址 | 隧道内部通信地址 | 1.1.1.1/24 |
| 路由 | 指向对端隧道网段 | 1.1.2.0/24 |
35.3 验证与抓包¶
35.3.1 连通性测试¶
# 在 Node 1 上 ping Node 2 的隧道地址
ping -I 1.1.1.1 1.1.2.1
35.3.2 抓包验证¶
# 在 Node 1 的物理网卡上抓包
tcpdump -i eth0 -nn 'ip proto 4'
抓包结果:
IP 172.18.0.4 > 172.18.0.2: IP 1.1.1.1 > 1.1.2.1: ICMP echo request
IP 172.18.0.2 > 172.18.0.4: IP 1.1.2.1 > 1.1.1.1: ICMP echo reply
graph LR
subgraph "IPIP 封装包结构"
Outer["外层 IP<br/>SRC: 172.18.0.4<br/>DST: 172.18.0.2<br/>Protocol: 4"]
Inner["内层 IP<br/>SRC: 1.1.1.1<br/>DST: 1.1.2.1"]
Payload["ICMP Data"]
end
Outer --> Inner --> Payload
35.3.3 查看设备信息¶
# 查看 IPIP 设备详情
ip -d link show ipip0
# 输出示例
# ipip0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 ...
# link/ipip 172.18.0.4 peer 172.18.0.2
| 属性 | 含义 |
|---|---|
link/ipip |
设备类型为 IPIP |
172.18.0.4 peer 172.18.0.2 |
Local 和 Remote IP |
mtu 1480 |
MTU = 1500 - 20 (IPIP Header) |
NOARP |
三层设备,无 ARP |
35.4 IPIP vs VXLAN 配置对比¶
graph TB
subgraph "IPIP 配置"
I1["ip link add ipip0 type ipip"]
I2["local/remote"]
I3["完成!"]
I1 --> I2 --> I3
end
subgraph "VXLAN 配置"
V1["ip link add vxlan0 type vxlan"]
V2["id (VNI)"]
V3["dstport 4789"]
V4["local/remote"]
V5["FDB 表"]
V6["完成!"]
V1 --> V2 --> V3 --> V4 --> V5 --> V6
end
| 对比项 | IPIP | VXLAN |
|---|---|---|
| 配置复杂度 | 简单 | 较复杂 |
| VNI | 无 | 需要 |
| 端口 | 无 | 4789/8472 |
| FDB 表 | 无 | 需要 |
| 封装开销 | 20 字节 | 50 字节 |
| 多租户 | 不支持 | 支持 |
35.5 生产环境扩展思考¶
35.5.1 Pod 流量如何到达 IPIP 设备¶
在实际生产环境中,Pod 的流量需要被引导到 IPIP 设备进行封装:
flowchart LR
Pod["Pod<br/>(Network NS)"]
VethPod["veth (Pod 端)"]
VethHost["veth (Host 端)"]
Route["路由表"]
IPIP["IPIP 设备"]
Physical["物理网卡"]
Pod --> VethPod
VethPod --> VethHost
VethHost --> Route
Route -->|"目标 Pod CIDR"| IPIP
IPIP -->|"封装"| Physical
关键步骤:
- Pod 到 Host:通过 veth pair + Proxy ARP
- Host 到 IPIP:通过路由表(Calico 自动注入)
- IPIP 封装:设备自动封装外层 IP
35.5.2 Calico 的自动化¶
Calico 自动完成的工作:
- 创建
tunl0设备(remote any local any) - 通过 BGP 学习其他节点的 Pod CIDR
- 注入路由:
10.24.x.x/26 via <Node IP> dev tunl0 onlink - 根据
ipipMode决定是否走隧道
35.6 SBR (Source-Based Routing) 策略路由¶
35.6.1 什么是 SBR¶
传统路由基于目的地址 (DST),而 SBR 基于源地址 (SRC):
graph TB
subgraph "传统路由 (DST-Based)"
D1["查目的 IP"] --> D2["查路由表"] --> D3["转发"]
end
subgraph "策略路由 (SRC-Based)"
S1["查源 IP"] --> S2["匹配 Rule"] --> S3["选择路由表"] --> S4["转发"]
end
35.6.2 SBR 配置示例¶
# 创建策略路由规则
ip rule add from 192.168.1.0/24 table 100
# 在表 100 中添加默认路由
ip route add default via 10.0.0.1 table 100
# 查看规则
ip rule show
输出示例:
0: from all lookup local
32765: from 192.168.1.0/24 lookup 100
32766: from all lookup main
32767: from all lookup default
35.6.3 SBR 应用场景¶
| 场景 | 说明 |
|---|---|
| 多网卡 (Multi-Homing) | 不同网卡的流量走不同出口 |
| Cilium CNI | 使用 SBR 实现复杂策略 |
| VPN 分流 | 特定源走 VPN 隧道 |
| 多 ISP | 根据源选择出口 |
35.6.4 多网卡场景的回程路由问题¶
sequenceDiagram
participant Client as 客户端
participant eth0 as eth0 (192.168.1.10)
participant eth1 as eth1 (10.0.0.10)
Note over Client,eth1: 请求从 eth0 进来
Client->>eth0: 请求 192.168.1.10
Note over eth0,eth1: 没有 SBR 时,可能从 eth1 回复
eth1--xClient: 回复可能走错接口!
Note over eth0,eth1: 有 SBR 时,确保从 eth0 回复
eth0->>Client: 回复走正确接口 ✓
问题:没有 SBR 时,回复包可能从错误的接口发出
解决:为每个接口配置 SBR,确保回程路径正确
35.7 CNI 网络实现套路总结¶
mindmap
root((CNI 网络套路))
设备创建
veth pair
bridge
ipip/vxlan
tun/tap
流量引导
路由表 DST
策略路由 SBR
iptables/nftables
eBPF
封装方式
IPIP
VXLAN
GRE
Geneve
WireGuard
路由协议
BGP
OSPF
静态路由
网关机制
Proxy ARP
Bridge
L3 Routing
[!IMPORTANT] 核心套路:
- 创建设备:赋予封装能力(IPIP/VXLAN/GRE)
- 引导流量:通过路由表将流量导向设备
- 封装转发:设备自动封装 Overlay 包
- 对端解封:对端设备解封装后路由到目标
35.8 章节小结¶
mindmap
root((IPIP 手工实践))
创建设备
ip link add type ipip
local/remote 参数
MTU 自动 1480
配置流程
创建设备
启用接口
配置地址
添加路由
抓包验证
ip proto 4
两层 IP 头
对比 VXLAN
IPIP 更简单
无 VNI/Port/FDB
SBR 策略路由
基于源地址
多网卡场景
回程路由问题
[!TIP] 实践要点总结:
- IPIP 设备创建:
ip link add ipip0 type ipip local <本端IP> remote <对端IP>
- 三步配置:
- 启用接口:
ip link set ipip0 up- 配置地址:
ip addr add x.x.x.x/24 dev ipip0添加路由:
ip route add <对端网段> dev ipip0抓包验证:
tcpdump -i eth0 'ip proto 4'生产扩展:
- Pod → veth → 路由表 → IPIP 设备
Calico 自动完成设备创建和路由注入
SBR 策略路由:
- 基于源地址选择路由表
- 解决多网卡回程路由问题
- Cilium 中有广泛应用
第三十六章 Calico-VxLAN 模式¶
本章介绍 Calico 的 VxLAN 封装模式,重点讲解 FDB(Forwarding Database)表机制以及与 IPIP 模式的区别。
36.1 背景与概述¶
36.1.1 VxLAN vs IPIP¶
| 对比项 | IPIP | VxLAN |
|---|---|---|
| 封装层级 | Layer 3 (IP-in-IP) | Layer 2 over Layer 3 |
| 协议号 | IP Protocol 4 | UDP 4789 |
| 封装开销 | 20 字节 | 50 字节 |
| BGP 依赖 | 需要(bird 组件) | 不需要 |
| FDB 表 | 不需要 | 需要 |
| 多租户 | 不支持 | 支持(VNI) |
| 防火墙友好 | 可能被阻挡 | UDP 更易穿透 |
36.1.2 何时选择 VxLAN¶
- 网络限制:底层网络不支持 IP Protocol 4
- 防火墙穿透:UDP 端口更易开放
- 无 BGP 需求:不想维护 BGP 组件
- 大二层网络:需要二层可达的场景
36.2 VxLAN 模式配置¶
36.2.1 IPPool 配置¶
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-pool
spec:
cidr: 10.244.0.0/16
ipipMode: Never # 禁用 IPIP
vxlanMode: Always # 启用 VxLAN
natOutgoing: true
[!IMPORTANT] 关键配置:
ipipMode和vxlanMode只能选一个- VxLAN 模式下需禁用 bird 相关组件
36.2.2 禁用 BGP/bird 组件¶
VxLAN 模式不依赖 BGP,需要修改 Calico 部署配置:
# calico-node DaemonSet 中
env:
- name: CALICO_NETWORKING_BACKEND
value: "vxlan" # 原来是 "bird"
# 移除或注释 bird 相关探针
livenessProbe:
exec:
command:
# - /bin/calico-node
# - -bird-live
readinessProbe:
exec:
command:
# - /bin/calico-node
# - -bird-ready
36.2.3 网卡自动检测(Auto-Detect)¶
多网卡环境需指定业务网卡:
env:
- name: IP_AUTODETECTION_METHOD
value: "interface=eth1" # 指定网卡
# 或使用 CIDR
# value: "cidr=10.1.0.0/16"
检测方法:
| 方法 | 示例 | 说明 |
|---|---|---|
first-found |
默认 | 自动选择默认路由网卡 |
interface= |
eth1 |
指定网卡名 |
cidr= |
10.1.0.0/16 |
匹配 CIDR |
can-reach= |
8.8.8.8 |
能到达的地址 |
36.3 MTU 配置¶
36.3.1 不同模式的 MTU¶
graph LR
subgraph "物理网卡 MTU 1500"
IPIP["IPIP<br/>MTU 1480<br/>(-20 字节)"]
VxLAN["VxLAN<br/>MTU 1450<br/>(-50 字节)"]
WG["WireGuard<br/>MTU 1440<br/>(-60 字节)"]
end
| 模式 | MTU 计算 | 结果 |
|---|---|---|
| IPIP | 1500 - 20 | 1480 |
| VxLAN | 1500 - 50 | 1450 |
| WireGuard + VxLAN | 1500 - 60 | 1440 |
| IPv6 + VxLAN | 1500 - 70 | 1430 |
36.3.2 巨型帧环境¶
数据中心常用 9000 MTU 巨型帧:
# 通过 calicoctl 修改 MTU
calicoctl patch felixconfiguration default \
--patch='{"spec": {"mtu": 8981}}'
# 验证
ip -d link show vxlan.calico
# mtu 8950 ...
36.4 VxLAN 设备与 FDB 表¶
36.4.1 vxlan.calico 设备¶
# 查看 VxLAN 设备
ip -d link show vxlan.calico
# 输出示例
# vxlan.calico: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 ...
# link/ether 66:c0:d0:7a:17:07 brd ff:ff:ff:ff:ff:ff
# vxlan id 4096 local 172.18.0.3 dev eth0 srcport 0 0 dstport 4789 ...
| 属性 | 含义 |
|---|---|
vxlan id 4096 |
VNI (VxLAN Network Identifier) |
local 172.18.0.3 |
本端 VTEP IP |
dstport 4789 |
VxLAN UDP 端口 |
mtu 1450 |
1500 - 50 |
36.4.2 FDB 表详解¶
FDB (Forwarding Database) 是 VxLAN 的核心机制,用于解决 外层目的 IP 的问题:
flowchart TB
subgraph "路由表"
Route["10.244.17.64/26 via 10.244.17.64<br/>dev vxlan.calico onlink"]
end
subgraph "ARP 表"
ARP["10.244.17.64 -> 66:c0:d0:7a:xx:xx"]
end
subgraph "FDB 表"
FDB["66:c0:d0:7a:xx:xx -> 172.18.0.4<br/>(outer dst IP)"]
end
Route --> ARP --> FDB
FDB -->|"封装外层 IP"| VxLAN["VxLAN 封装"]
查看 FDB 表:
# 方法 1: bridge fdb show
bridge fdb show dev vxlan.calico | grep -v permanent
# 方法 2: ip monitor 观察动态添加
ip monitor all
# 输出示例
# 66:c0:d0:7a:17:07 dev vxlan.calico dst 172.18.0.4 self permanent
36.4.3 FDB 表的核心含义¶
graph LR
MAC["MAC 地址<br/>66:c0:d0:7a:xx:xx"] -->|"belongs to"| Host["Host IP<br/>172.18.0.4"]
subgraph "VxLAN 封装"
Inner["内层 DST MAC"]
Outer["外层 DST IP"]
end
MAC --> Inner
Host --> Outer
[!IMPORTANT] FDB 表的作用:
- 回答问题:这个 MAC 地址属于哪个主机?
- 解决问题:VxLAN 封装时,外层 DST IP 用谁?
- 自动学习:类似 ARP,系统自动维护
两层理解:
- 深层理解:FDB 表示 MAC 地址与主机的映射,该 MAC 不一定是主机物理网卡的 MAC,只要在该主机的任意网卡上存在即可
- 简单理解:FDB 是自动维护的,可通过
ip monitor all观察添加过程
36.5 VxLAN 包封装分析¶
36.5.1 完整封装结构¶
graph LR
subgraph "外层 Ethernet"
OMac["Outer MAC<br/>SRC: Node1 eth0<br/>DST: Node2 eth0"]
end
subgraph "外层 IP"
OIP["Outer IP<br/>SRC: 172.18.0.3<br/>DST: 172.18.0.4"]
end
subgraph "UDP"
UDP["UDP<br/>SRC: random<br/>DST: 4789"]
end
subgraph "VxLAN Header"
VH["VNI: 4096"]
end
subgraph "内层 Ethernet"
IMac["Inner MAC<br/>SRC: vxlan.calico local<br/>DST: vxlan.calico remote"]
end
subgraph "内层 IP"
IIP["Inner IP<br/>SRC: Pod1 IP<br/>DST: Pod2 IP"]
end
OMac --> OIP --> UDP --> VH --> IMac --> IIP
36.5.2 内层 MAC 地址变化¶
sequenceDiagram
participant Pod as Pod (10.244.1.69)
participant Veth as veth (Host 端)
participant Route as 路由表
participant VxLAN as vxlan.calico
participant Eth as eth0
Pod->>Veth: SRC MAC: Pod MAC<br/>DST MAC: 网关 MAC (ee:ee:ee...)
Veth->>Route: 查路由表
Route->>VxLAN: 下一跳: 10.244.17.64<br/>dev vxlan.calico
Note over VxLAN: MAC 地址重写!
VxLAN->>Eth: Inner SRC MAC: 本地 vxlan.calico MAC<br/>Inner DST MAC: 远端 vxlan.calico MAC
[!WARNING] 关键点:内层 MAC 地址不是原始 Pod 的 MAC!
- Inner SRC MAC:本地
vxlan.calico接口的 MAC- Inner DST MAC:远端
vxlan.calico接口的 MAC(通过 ARP 表获取)
36.5.3 抓包验证¶
# 在 eth0 上抓 VxLAN 包
tcpdump -i eth0 -nn udp port 4789 -w vxlan.pcap
# 或直接查看
tcpdump -i eth0 -nn -e udp port 4789
# 输出示例
# 02:42:ac:12:00:03 > 02:42:ac:12:00:04, IP 172.18.0.3.random > 172.18.0.4.4789: VXLAN...
# 66:c0:d0:xx > 66:c0:d0:yy, IP 10.244.1.69 > 10.244.17.65: ICMP...
36.6 VxLAN vs IPIP 路由表对比¶
# IPIP 模式
10.244.17.64/26 via 172.18.0.4 dev tunl0 proto bird onlink
# VxLAN 模式
10.244.17.64/26 via 10.244.17.64 dev vxlan.calico onlink
| 对比项 | IPIP | VxLAN |
|---|---|---|
| 下一跳 | 物理节点 IP | Pod CIDR 网关 |
| 设备 | tunl0 | vxlan.calico |
| extern IP 来源 | 路由表直接给出 | FDB 表查询 |
| proto | bird (BGP) | 无 |
36.7 同节点通信¶
VxLAN 模式下,同节点 Pod 通信与 IPIP 模式完全相同:
flowchart LR
Pod1["Pod1"] --> Veth1["veth"]
Veth1 --> Route["路由表"]
Route -->|"目的 Pod 在本机"| Veth2["veth"]
Veth2 --> Pod2["Pod2"]
- 无 VxLAN 封装
- 纯三层路由 + Proxy ARP
36.8 章节小结¶
mindmap
root((Calico VxLAN 模式))
配置
vxlanMode Always
ipipMode Never
禁用 bird
FDB 表
MAC to Host IP
自动学习
bridge fdb show
封装
50 字节开销
UDP 4789
VNI 4096
内层 MAC
不是 Pod MAC
vxlan.calico MAC
MTU
1500 - 50 = 1450
巨型帧 9000
[!TIP] VxLAN 模式要点总结:
- 配置:
vxlanMode: Always,ipipMode: Never需禁用 bird 相关组件
FDB 表核心:
- 解决:外层 DST IP 用谁?
- 查询:
bridge fdb show dev vxlan.calico机制:MAC 地址 → 主机 IP
内层 MAC 变化:
- 不是原始 Pod MAC
- SRC: 本地
vxlan.calicoMACDST: 远端
vxlan.calicoMAC抓包:
tcpdump -i eth0 udp port 4789MTU:1500 - 50 = 1450
第三十七章 Calico-VxLAN-CrossSubnet 模式¶
本章介绍 Calico VxLAN 的 CrossSubnet 模式,这是生产环境中最推荐的混合部署方案——同网段走高性能路由,跨网段走 overlay 封装。
37.1 背景与概述¶
37.1.1 为什么需要 CrossSubnet¶
在大型数据中心中,节点通常分布在不同的网段:
graph TB
subgraph "机架 A (10.1.5.0/24)"
Node1["Node1<br/>10.1.5.10"]
Node2["Node2<br/>10.1.5.11"]
end
subgraph "机架 B (10.1.8.0/24)"
Node3["Node3<br/>10.1.8.10"]
Node4["Node4<br/>10.1.8.11"]
end
Router["路由器/三层交换机"]
Node1 & Node2 --> Router
Router --> Node3 & Node4
- 同机架:节点在同一二层,可直接路由
- 跨机架:节点在不同网段,需要三层转发
37.1.2 CrossSubnet 模式原理¶
flowchart LR
subgraph "同网段通信"
Pod1A["Pod1"] -->|"直接路由"| Pod2A["Pod2"]
Note1["无封装<br/>高性能"]
end
subgraph "跨网段通信"
Pod1B["Pod1"] -->|"VxLAN 封装"| Pod2B["Pod2"]
Note2["overlay<br/>兼容性好"]
end
| 场景 | 封装方式 | 性能 | 依赖 |
|---|---|---|---|
| 同网段 | 无(纯路由) | 高 | - |
| 跨网段 | VxLAN | 较低 | FDB 表 |
37.1.3 Always vs CrossSubnet¶
| 模式 | 同网段 | 跨网段 | 适用场景 |
|---|---|---|---|
Always |
VxLAN | VxLAN | 统一封装,简单 |
CrossSubnet |
路由 | VxLAN | 混合环境,性能优先 |
Never |
路由 | 路由 | 需要 BGP + 外部路由 |
37.2 配置详解¶
37.2.1 IPPool 配置¶
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-pool
spec:
cidr: 10.244.0.0/16
ipipMode: Never # 禁用 IPIP
vxlanMode: CrossSubnet # 跨网段 VxLAN
natOutgoing: true
[!IMPORTANT] 关键配置项:
vxlanMode: CrossSubnet— 只在跨网段时封装ipipMode: Never— 必须禁用 IPIP(二者互斥)- 仍需禁用 bird 相关组件
37.2.2 Calico 如何判断"同网段"¶
Calico 根据节点 IP 和网络掩码判断:
# 假设节点 IP
Node1: 10.1.5.10/24
Node2: 10.1.5.11/24 # 同网段
Node3: 10.1.8.10/24 # 不同网段
# Calico 判断逻辑
10.1.5.10/24 & 10.1.5.11/24 → 同网段 (10.1.5.0) → 路由
10.1.5.10/24 & 10.1.8.10/24 → 不同网段 → VxLAN
37.3 实验拓扑¶
37.3.1 ContainerLab 跨网段环境¶
graph TB
subgraph "Kind 集群"
Control["control-plane<br/>10.1.5.10"]
Worker1["worker1<br/>10.1.5.11"]
Worker2["worker2<br/>10.1.8.10"]
Worker3["worker3<br/>10.1.8.11"]
end
subgraph "ContainerLab"
BR1["bridge1<br/>10.1.5.0/24"]
BR2["bridge2<br/>10.1.8.0/24"]
Router["clab-gw0<br/>路由器"]
end
Control & Worker1 --> BR1
Worker2 & Worker3 --> BR2
BR1 --> Router
Router --> BR2
37.3.2 路由器配置¶
# ContainerLab 路由器配置
interface net0
ip address 10.1.5.1/24
interface net1
ip address 10.1.8.1/24
# SNAT for internet access
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
37.4 同网段通信验证¶
37.4.1 测试场景¶
# 从 control-plane (10.1.5.10) 上的 Pod
# ping worker1 (10.1.5.11) 上的 Pod
kubectl exec -it pod-on-control -- ping <pod-on-worker1-ip>
37.4.2 抓包分析¶
# 在 control-plane 节点的 net0 接口抓包
tcpdump -i net0 -nn icmp
# 输出示例
# 10.244.1.69 > 10.244.175.1: ICMP echo request
# 10.244.175.1 > 10.244.1.69: ICMP echo reply
特点:
- 无 VxLAN 封装
- 裸 ICMP 包
- MAC 地址逐跳变化(标准路由行为)
sequenceDiagram
participant Pod1 as Pod1 (10.244.1.69)
participant Node1 as Node1 (10.1.5.10)
participant Node2 as Node2 (10.1.5.11)
participant Pod2 as Pod2 (10.244.175.1)
Pod1->>Node1: SRC MAC: Pod1<br/>DST MAC: 网关(ee:ee...)
Node1->>Node2: SRC MAC: Node1 net0<br/>DST MAC: Node2 net0
Node2->>Pod2: DST MAC: Pod2 veth
37.5 跨网段通信验证¶
37.5.1 测试场景¶
# 从 control-plane (10.1.5.10) 上的 Pod
# ping worker2 (10.1.8.10) 上的 Pod
kubectl exec -it pod-on-control -- ping <pod-on-worker2-ip>
37.5.2 抓包分析¶
# 抓 VxLAN 包
tcpdump -i net0 -nn udp port 4789 -w vxlan-cross.pcap
Wireshark 分析:
Frame: 外层 Ethernet
├── SRC MAC: control-plane net0
├── DST MAC: router interface
├── Outer IP Header
│ ├── SRC: 10.1.5.10 (control-plane)
│ └── DST: 10.1.8.10 (worker2)
├── UDP Header
│ ├── SRC Port: random
│ └── DST Port: 4789
├── VxLAN Header
│ └── VNI: 4096
└── Inner Frame
├── Inner Ethernet
│ ├── SRC MAC: vxlan.calico (local)
│ └── DST MAC: vxlan.calico (remote)
└── Inner IP
├── SRC: 10.244.1.69 (Pod1)
└── DST: 10.244.73.x (Pod2)
37.6 与 IPIP CrossSubnet 对比¶
| 对比项 | IPIP CrossSubnet | VxLAN CrossSubnet |
|---|---|---|
| 同网段 | 路由 | 路由 |
| 跨网段 | IP-in-IP 封装 | VxLAN 封装 |
| 封装开销 | 20 字节 | 50 字节 |
| 协议 | IP Protocol 4 | UDP 4789 |
| BGP 依赖 | 需要 bird | 不需要 |
| FDB 表 | 不需要 | 需要 |
| 防火墙 | 可能受阻 | UDP 易穿透 |
37.7 Overlay 的安全性优势¶
graph LR
subgraph "路由模式"
Pod1R["Pod<br/>10.244.1.69"] -->|"IP 暴露"| Net1["外部网络"]
end
subgraph "Overlay 模式"
Pod1O["Pod<br/>10.244.1.69"] -->|"封装在节点 IP 内"| Net2["外部网络"]
Note["外部只看到<br/>节点 IP"]
end
Overlay 安全优势:
- Pod IP 隐藏:外部设备只看到节点 IP,不知道内部 Pod IP
- 网络隔离:中间网络设备无法直接访问 Pod
- 零改造成本:无需在外部网络配置 Pod 网段路由
37.8 故障排查¶
37.8.1 确认模式生效¶
# 查看 IPPool 配置
calicoctl get ippool -o yaml
# 确认 vxlanMode
spec:
vxlanMode: CrossSubnet
37.8.2 验证路由表¶
# 同网段节点 - 直接路由
ip route | grep <same-subnet-node-pod-cidr>
# 10.244.175.0/26 via 10.1.5.11 dev net0
# 跨网段节点 - VxLAN 路由
ip route | grep <cross-subnet-node-pod-cidr>
# 10.244.73.0/26 via 10.244.73.0 dev vxlan.calico onlink
37.8.3 常见问题¶
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 跨网段不通 | 路由器未配置 | 确保三层设备能转发 |
| FDB 表为空 | CNI 未正常启动 | 重启 calico-node |
| 同网段走 VxLAN | 掩码配置错误 | 检查节点 IP/掩码 |
37.9 章节小结¶
mindmap
root((VxLAN CrossSubnet))
原理
同网段 直接路由
跨网段 VxLAN
自动判断
配置
vxlanMode CrossSubnet
ipipMode Never
禁用 bird
验证
同网段 普通 ICMP
跨网段 UDP 4789
Wireshark 分析
优势
性能与兼容平衡
overlay 安全
零改造成本
对比
vs Always
vs IPIP CrossSubnet
[!TIP] VxLAN CrossSubnet 要点总结:
- 核心原理:
- 同网段走路由(高性能)
- 跨网段走 VxLAN(兼容性好)
Calico 根据节点 IP/掩码自动判断
配置:
vxlanMode: CrossSubnet验证方法:
- 同网段:
tcpdump -i net0 icmp(普通包)跨网段:
tcpdump -i net0 udp port 4789(VxLAN 包)适用场景:
- 多机架/多网段的生产环境
- 追求性能与兼容性平衡
不想改造外部网络
生产建议:节点数 <200 时推荐使用
第三十八章 Calico-BGP-Fullmesh 模式¶
本章介绍 Calico 的 BGP Fullmesh 模式,这是 Calico 最"原生"的网络方案——不使用任何 overlay 封装,完全依赖 BGP 协议进行路由通告。
38.1 背景与概述¶
38.1.1 BGP 基础回顾¶
graph LR
subgraph "AS 64512"
Node1["Node1"]
Node2["Node2"]
Node3["Node3"]
end
Node1 <-->|"iBGP"| Node2
Node2 <-->|"iBGP"| Node3
Node1 <-->|"iBGP"| Node3
BGP 核心概念:
| 概念 | 说明 |
|---|---|
| AS (自治系统) | 统一管理的网络域 |
| iBGP | 同一 AS 内的 BGP |
| eBGP | 不同 AS 间的 BGP |
| 水平分割 | iBGP 学到的路由不再通告给其他 iBGP peer |
| TCP 179 | BGP 使用 TCP 179 端口建立会话 |
38.1.2 为什么需要 Fullmesh¶
由于 BGP 水平分割规则:
- iBGP peer 学到的路由不会通告给其他 iBGP peer
- 要让所有节点学到完整路由,必须两两建立 BGP 邻居关系
- 这就是 Fullmesh(全网状连接)
graph TB
subgraph "3 节点 Fullmesh"
A["Node A"] <-->|"BGP"| B["Node B"]
B <-->|"BGP"| C["Node C"]
A <-->|"BGP"| C
end
subgraph "连接数计算"
Formula["N*(N-1)/2<br/>3 节点 = 3 条连接"]
end
38.1.3 Fullmesh 的适用场景¶
| 节点数 | 连接数 | 推荐方案 |
|---|---|---|
| 10 | 45 | Fullmesh ✅ |
| 50 | 1,225 | Fullmesh ✅ |
| 100 | 4,950 | Fullmesh ✅ |
| 200 | 19,900 | 边界 |
| 500 | 124,750 | Route Reflector ✅ |
[!IMPORTANT] 官方建议:
- 节点数 < 200:使用 Fullmesh
- 节点数 ≥ 200:使用 Route Reflector (RR)
38.2 Fullmesh vs Overlay¶
| 对比项 | BGP Fullmesh | IPIP/VxLAN |
|---|---|---|
| 封装 | 无 | 有 |
| 性能 | 最高 | 有开销 |
| MTU | 无损失 | 有损失 |
| 外部路由 | 需要配合 | 无需 |
| 复杂度 | 高 | 低 |
| 调试 | 需了解 BGP | 相对简单 |
38.3 配置详解¶
38.3.1 IPPool 配置¶
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-pool
spec:
cidr: 10.244.0.0/16
ipipMode: Never # 禁用 IPIP
vxlanMode: Never # 禁用 VxLAN
natOutgoing: true
[!IMPORTANT] 关键配置:
ipipMode: Never— 禁用 IPIP 封装vxlanMode: Never— 禁用 VxLAN 封装- 禁用 overlay 后,Calico 自动使用 BGP 路由
38.3.2 BGP 配置(默认)¶
Calico 默认启用 BGP Fullmesh:
apiVersion: crd.projectcalico.org/v1
kind: BGPConfiguration
metadata:
name: default
spec:
nodeToNodeMeshEnabled: true # 启用 Fullmesh
asNumber: 64512 # 默认 AS 号
38.4 路由分析¶
38.4.1 路由表特征¶
# 查看路由表
ip route show
# 输出示例
10.244.73.128/26 via 172.18.0.2 dev eth0 proto bird
10.244.83.128/26 via 172.18.0.3 dev eth0 proto bird
关键特征:
- proto bird:路由由 bird 进程(BGP daemon)维护
- 下一跳是 peer 节点 IP:直接路由到对端节点
- 无 tunl0/vxlan.calico:不使用隧道设备
38.4.2 与 Overlay 路由对比¶
| 模式 | 路由示例 | 设备 |
|---|---|---|
| BGP | via 172.18.0.2 dev eth0 proto bird |
eth0 |
| IPIP | via 172.18.0.2 dev tunl0 proto bird onlink |
tunl0 |
| VxLAN | via 10.244.73.0 dev vxlan.calico onlink |
vxlan.calico |
38.5 BGP 状态查看¶
38.5.1 calicoctl node status¶
# 查看 BGP peer 状态
calicoctl node status
# 输出示例
Calico process is running.
IPv4 BGP status
+----------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+----------------+-------------------+-------+----------+-------------+
| 172.18.0.2 | node-to-node mesh | up | 10:30:00 | Established |
| 172.18.0.3 | node-to-node mesh | up | 10:30:00 | Established |
+----------------+-------------------+-------+----------+-------------+
字段说明:
| 字段 | 说明 |
|---|---|
| PEER ADDRESS | BGP 邻居 IP |
| PEER TYPE | node-to-node mesh = Fullmesh |
| STATE | up = 正常 |
| INFO | Established = TCP 179 连接已建立 |
38.5.2 验证 TCP 179 连接¶
# 查看 BGP 端口连接
netstat -an | grep 179
# 输出
tcp 0 0 172.18.0.4:179 172.18.0.2:xxxxx ESTABLISHED
tcp 0 0 172.18.0.4:179 172.18.0.3:xxxxx ESTABLISHED
38.6 bird 命令详解¶
38.6.1 进入 bird 控制台¶
# 进入 calico-node Pod
kubectl exec -it calico-node-xxxxx -n kube-system -- /bin/sh
# 进入 bird 控制台
birdcl
# 或
birdc
38.6.2 常用 bird 命令¶
# 查看接口
birdc> show interfaces
# 查看路由表
birdc> show route
# 查看 BGP 协议路由
birdc> show route protocol Mesh_172_18_0_2
# 查看协议状态
birdc> show protocols
show route 输出示例:
10.244.73.128/26 via 172.18.0.2 on eth0 [Mesh_172_18_0_2 10:30:00] * (100/0) [i]
10.244.83.128/26 via 172.18.0.3 on eth0 [Mesh_172_18_0_3 10:30:00] * (100/0) [i]
38.7 AS Number 与配置文件¶
38.7.1 查看 AS Number¶
# 通过 calicoctl
calicoctl get nodes -o yaml | grep asNumber
# 或查看 BGPConfiguration
calicoctl get bgpconfig default -o yaml
38.7.2 bird 配置文件¶
# 配置文件位置
cat /etc/calico/confd/config/bird.cfg
# bird.cfg 示例
router id 172.18.0.4;
protocol bgp Mesh_172_18_0_2 {
local as 64512;
neighbor 172.18.0.2 as 64512;
# ...
}
protocol bgp Mesh_172_18_0_3 {
local as 64512;
neighbor 172.18.0.3 as 64512;
# ...
}
38.8 抓包验证¶
38.8.1 跨节点 Pod 通信¶
# 抓包
tcpdump -i eth0 -nn icmp
# 输出示例
172.18.0.4 > 172.18.0.2: ICMP echo request
# 注意:这是节点 IP,不是 Pod IP!
实际抓包:
# Pod 间 ping
10.244.140.132 > 10.244.88.128: ICMP echo request
特点:
- 无封装:直接看到 Pod IP
- MAC 地址变化:每跳变化
- 纯三层转发
38.9 Route Reflector 简介¶
当节点数超过 200 时,应使用 Route Reflector:
graph TB
subgraph "Route Reflector 模式"
RR1["RR1"]
RR2["RR2"]
RR1 <-->|"Mesh"| RR2
Client1["Node1"] --> RR1
Client2["Node2"] --> RR1
Client3["Node3"] --> RR2
Client4["Node4"] --> RR2
end
RR 优势:
- RR 之间 Fullmesh(少量连接)
- 普通节点只连接 RR(减少连接数)
- 多 RR 保证高可用
38.10 章节小结¶
mindmap
root((BGP Fullmesh))
原理
水平分割
两两建立 peer
N*(N-1)/2
配置
ipipMode Never
vxlanMode Never
nodeToNodeMesh true
验证
calicoctl node status
ip route proto bird
birdc show route
特点
无封装
最高性能
无 MTU 损失
限制
小于 200 节点
超过用 RR
[!TIP] BGP Fullmesh 要点总结:
- 核心原理:
- BGP 水平分割 → 需要 Fullmesh
- 连接数:N*(N-1)/2
官方建议:<200 节点
配置:禁用 IPIP/VxLAN,默认启用 BGP
关键命令:
calicoctl node status— 查看 BGP peerip route— 看到proto bird
birdc show route— BGP 路由详情抓包特点:无封装,直接看到 Pod IP
生产建议:
- 小集群:Fullmesh
- 大集群:Route Reflector
第三十九章 Calico-BGP-RR 模式¶
本章介绍 Calico 的 BGP Route Reflector (RR) 模式,这是大规模集群(>200 节点)的推荐 BGP 方案,采用 AS per Rack 拓扑设计。
39.1 背景与概述¶
39.1.1 为什么需要 Route Reflector¶
回顾 Fullmesh 的问题:
| 节点数 | Fullmesh 连接数 |
|---|---|
| 100 | 4,950 |
| 200 | 19,900 |
| 500 | 124,750 |
| 1000 | 499,500 |
Route Reflector 解决方案:
- RR 节点:接收所有路由,反射给所有客户端
- 客户端:只与 RR 建立连接
- 连接数:从 N*(N-1)/2 降低到 N
graph TB
subgraph "Route Reflector 模式"
RR["Route Reflector<br/>Leaf Switch"]
Client1["Node1<br/>RR Client"] --> RR
Client2["Node2<br/>RR Client"] --> RR
Client3["Node3<br/>RR Client"] --> RR
Client4["Node4<br/>RR Client"] --> RR
end
39.1.2 AS per Rack 拓扑¶
生产环境推荐的 Leaf-Spine 架构:
graph TB
subgraph "Spine Layer"
Spine0["Spine0<br/>AS 500"]
Spine1["Spine1<br/>AS 800"]
end
subgraph "Leaf Layer"
Leaf0["Leaf0<br/>AS 65005"]
Leaf1["Leaf1<br/>AS 65008"]
end
subgraph "Rack0 - 10.1.5.0/24"
Node1["Node1<br/>10.1.5.10"]
Node2["Node2<br/>10.1.5.11"]
end
subgraph "Rack1 - 10.1.8.0/24"
Node3["Node3<br/>10.1.8.10"]
Node4["Node4<br/>10.1.8.11"]
end
Spine0 <-->|"eBGP"| Leaf0
Spine0 <-->|"eBGP"| Leaf1
Spine1 <-->|"eBGP"| Leaf0
Spine1 <-->|"eBGP"| Leaf1
Node1 -->|"iBGP"| Leaf0
Node2 -->|"iBGP"| Leaf0
Node3 -->|"iBGP"| Leaf1
Node4 -->|"iBGP"| Leaf1
架构说明:
| 层级 | 说明 |
|---|---|
| Spine | 核心交换机,eBGP 互联 |
| Leaf | 接入交换机,作为 RR |
| Node | K8s 节点,RR Client |
| iBGP | 同 AS 内部(Node ↔ Leaf) |
| eBGP | 不同 AS 之间(Leaf ↔ Spine) |
39.2 配置详解¶
39.2.1 禁用 Node-to-Node Mesh¶
apiVersion: crd.projectcalico.org/v1
kind: BGPConfiguration
metadata:
name: default
spec:
nodeToNodeMeshEnabled: false # 关闭 Fullmesh!
asNumber: 65005
或通过命令:
calicoctl patch bgpconfig default -p \
'{"spec": {"nodeToNodeMeshEnabled": false}}'
39.2.2 配置节点 BGP 属性¶
apiVersion: crd.projectcalico.org/v1
kind: Node
metadata:
name: node1
labels:
rack: rack0 # 机架标签
spec:
bgp:
ipv4Address: 10.1.5.10/24
asNumber: 65005
39.2.3 创建 BGP Peer¶
apiVersion: crd.projectcalico.org/v1
kind: BGPPeer
metadata:
name: rack0-to-leaf0
spec:
peerIP: 10.1.5.1 # Leaf 交换机 IP
asNumber: 65005 # 同 AS = iBGP
nodeSelector: rack == 'rack0' # 只匹配 rack0 的节点
nodeSelector 机制:
# 查看节点标签
kubectl get nodes --show-labels | grep rack
# 输出
node1 rack=rack0
node2 rack=rack0
node3 rack=rack1
node4 rack=rack1
39.3 Leaf 交换机配置¶
39.3.1 VyOS 配置示例¶
# Leaf0 配置
set interfaces ethernet eth0 address 10.1.5.1/24
set interfaces ethernet eth1 address 10.1.10.2/24 # to Spine0
set interfaces ethernet eth2 address 10.1.12.2/24 # to Spine1
# BGP 配置
set protocols bgp 65005 router-id 10.1.5.1
# 宣告本地网段
set protocols bgp 65005 network 10.1.5.0/24
# 配置 RR Client (iBGP)
set protocols bgp 65005 neighbor 10.1.5.10 remote-as 65005
set protocols bgp 65005 neighbor 10.1.5.10 route-reflector-client
set protocols bgp 65005 neighbor 10.1.5.11 remote-as 65005
set protocols bgp 65005 neighbor 10.1.5.11 route-reflector-client
# 配置 eBGP (to Spine)
set protocols bgp 65005 neighbor 10.1.10.1 remote-as 500
set protocols bgp 65005 neighbor 10.1.12.1 remote-as 800
# SNAT for Internet
set nat source rule 10 outbound-interface eth0
set nat source rule 10 source address 10.1.5.0/24
set nat source rule 10 translation address masquerade
[!IMPORTANT] 关键配置:
route-reflector-client:指定节点为 RR 客户端- 同 AS(65005)= iBGP
- 不同 AS(500, 800)= eBGP
39.4 ECMP 等价多路径¶
39.4.1 什么是 ECMP¶
graph LR
subgraph "Rack0"
Pod1["Pod"]
end
subgraph "Spine"
Spine0["Spine0"]
Spine1["Spine1"]
end
subgraph "Rack1"
Pod2["Pod"]
end
Pod1 -->|"Path1"| Spine0 --> Pod2
Pod1 -->|"Path2"| Spine1 --> Pod2
ECMP (Equal Cost Multi-Path):
- 等价开销多路径
- 多条路径同时使用
- 负载分担 + 故障备份
39.4.2 VyOS ECMP 配置¶
# 启用 ECMP
set protocols bgp 65005 maximum-paths ebgp 2
set protocols bgp 65005 maximum-paths ibgp 2
39.4.3 验证 ECMP¶
# 查看路由表
show ip bgp
# 输出示例
Network Next Hop Metric Path
*> 10.1.8.0/24 10.1.10.1 0 500 65008 i
*= 10.1.12.1 0 800 65008 i
*>= 有效且最佳*== 有效且等价(ECMP)
39.5 验证步骤¶
39.5.1 calicoctl node status¶
calicoctl node status
# 输出
IPv4 BGP status
+----------------+---------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+----------------+---------------+-------+----------+-------------+
| 10.1.5.1 | node specific | up | 10:30:00 | Established |
+----------------+---------------+-------+----------+-------------+
关键变化:
| Fullmesh | Route Reflector |
|---|---|
node-to-node mesh |
node specific |
| 多个 peer(所有节点) | 单个 peer(仅 RR) |
39.5.2 验证 TCP 179¶
netstat -an | grep 179
# 输出
tcp 0 0 10.1.5.10:xxxxx 10.1.5.1:179 ESTABLISHED
39.5.3 查看 Leaf 上的 BGP 状态¶
# VyOS
show ip bgp summary
# 输出
Neighbor AS MsgRcvd MsgSent State/PfxRcd
10.1.5.10 65005 100 100 Established
10.1.5.11 65005 98 98 Established
10.1.10.1 500 200 200 Established
10.1.12.1 800 195 195 Established
39.6 Service IP Advertisement¶
39.6.1 宣告 ClusterIP¶
apiVersion: crd.projectcalico.org/v1
kind: BGPConfiguration
metadata:
name: default
spec:
nodeToNodeMeshEnabled: false
asNumber: 65005
serviceClusterIPs:
- cidr: 10.96.0.0/16 # ClusterIP 范围
或通过命令:
calicoctl patch bgpconfig default -p \
'{"spec": {"serviceClusterIPs": [{"cidr": "10.96.0.0/16"}]}}'
39.6.2 验证路由传播¶
# 在 Leaf 上查看
show ip route bgp
# 输出
B 10.96.0.0/16 [200/0] via 10.1.5.10, eth0, ...
# 在 Spine 上查看
show ip bgp 10.96.0.0/16
# 路由已传播
效果:外部设备可以直接通过 BGP 路由访问 ClusterIP!
39.7 故障排查¶
39.7.1 常见问题¶
| 问题 | 排查命令 | 解决方案 |
|---|---|---|
| BGP 未建立 | calicoctl node status |
检查 IP/AS 配置 |
| 路由缺失 | ip route |
检查 BGP 宣告 |
| ECMP 不生效 | show ip bgp |
检查 maximum-paths |
| 跨机架不通 | tcpdump |
检查 Spine 转发 |
39.7.2 调试命令¶
# 查看 bird 日志
kubectl logs -n kube-system calico-node-xxxxx -c bird
# 进入 bird 控制台
kubectl exec -it calico-node-xxxxx -n kube-system -- birdcl
# 查看 BGP 路由
birdc> show route protocol Mesh_10_1_5_1
39.8 二层隔离的安全优势¶
为什么使用不同网段(跨机架):
graph TB
subgraph "Rack0 - 10.1.5.0/24"
Infected["感染节点"]
Healthy1["正常节点"]
end
subgraph "三层路由器"
Router["Router<br/>隔离广播"]
end
subgraph "Rack1 - 10.1.8.0/24"
Healthy2["正常节点"]
Healthy3["正常节点"]
end
Infected -.->|"广播风暴"| Healthy1
Infected -.->|"被路由器阻断"| Router
Router -.->|"隔离保护"| Healthy2
优势:
- 广播域隔离
- 故障范围控制
- 安全性增强
39.9 章节小结¶
mindmap
root((BGP RR 模式))
原理
解决 Fullmesh 复杂度
Leaf 作为 RR
Node 作为 RR Client
拓扑
Leaf-Spine
AS per Rack
iBGP + eBGP
配置
nodeToNodeMesh false
BGPPeer nodeSelector
route-reflector-client
ECMP
等价多路径
负载分担
故障备份
宣告
Service ClusterIP
外部可访问
[!TIP] BGP RR 模式要点总结:
- 核心原理:
- Leaf 交换机作为 Route Reflector
- K8s 节点作为 RR Client
连接数从 N² 降低到 N
拓扑设计:
- Leaf-Spine 架构
- AS per Rack(每机架一个 AS)
iBGP(Node ↔ Leaf)+ eBGP(Leaf ↔ Spine)
关键配置:
nodeToNodeMeshEnabled: false— 禁用 FullmeshBGPPeer+nodeSelector— 按机架配置
route-reflector-client— Leaf 配置ECMP:等价多路径,实现负载分担和高可用
Service 宣告:ClusterIP 可通过 BGP 向外宣告
适用场景:大规模集群(>200 节点)
第四十章 Calico-eBPF-with-DSR 模式¶
本章介绍 Calico 的 eBPF 数据平面模式及 DSR(Direct Server Return)功能,这是一种高性能的替代方案,可以绑过 iptables 和 kube-proxy。
40.1 背景与概述¶
40.1.1 什么是 eBPF¶
graph LR
subgraph "传统方式"
App1["应用"] --> Kernel1["内核"]
Kernel1 --> |"修改需重编内核"| Hardware1["硬件"]
end
subgraph "eBPF 方式"
App2["应用"] --> eBPF["eBPF 程序"]
eBPF --> Kernel2["内核虚拟机"]
Kernel2 --> Hardware2["硬件"]
end
eBPF (extended Berkeley Packet Filter):
- 内核中安全运行的虚拟机
- 无需修改或重编内核
- 动态加载/卸载程序
- 广泛用于:网络、监控、安全、存储
40.1.2 Calico 与 eBPF 的结合¶
背景:
- 早期 Calico 不支持 eBPF
- 随着 Cilium 的崛起和 eBPF 技术火热
- Calico 3.x 版本引入 eBPF 数据平面
eBPF 数据平面优势:
| 对比项 | 传统 iptables | eBPF 数据平面 |
|---|---|---|
| Service 实现 | kube-proxy + iptables | eBPF map |
| 性能 | O(n) 规则匹配 | O(1) hash 查找 |
| conntrack | 内核 conntrack | eBPF conntrack |
| CPU 开销 | 较高 | 较低 |
| 功能 | 完整 | 部分限制 |
40.2 eBPF 数据平面原理¶
40.2.1 Bypass kube-proxy¶
graph TB
subgraph "传统模式"
Client1["Client"] --> KubeProxy["kube-proxy"]
KubeProxy --> |"iptables 规则"| Pod1["Pod"]
end
subgraph "eBPF 模式"
Client2["Client"] --> eBPFProg["eBPF 程序"]
eBPFProg --> |"eBPF map 查找"| Pod2["Pod"]
end
工作机制:
- Service ClusterIP → eBPF map
- 直接查找后端 Pod
- 绕过 iptables/IPVS
40.2.2 限制与适用场景¶
不推荐使用 eBPF 的场景:
- Service Mesh control plane(仍需 iptables)
- Packet-by-packet 处理
- 需要完整 iptables 功能
推荐使用 eBPF 的场景:
- Connect-time Load Balancing
- XDP 加速
- 高性能需求
平台兼容性:
| 支持 | 不支持 |
|---|---|
| OpenShift, AKS, EKS | GKE, RKE |
| Ubuntu 20.04+ (5.4+ 内核) | 旧内核 (<4.18) |
| kubeadm 安装 | IPv6, SCTP |
40.3 DSR 模式原理¶
40.3.1 什么是 DSR¶
DSR (Direct Server Return):
- 响应包不经过 Load Balancer
- 后端直接返回给客户端
- 减少 LB 负载,提升性能
graph LR
subgraph "传统模式 (Tunnel)"
C1["Client"] -->|"1.请求"| LB1["LB/Node"]
LB1 -->|"2.转发"| Backend1["Backend"]
Backend1 -->|"3.响应"| LB1
LB1 -->|"4.返回"| C1
end
graph LR
subgraph "DSR 模式"
C2["Client"] -->|"1.请求"| LB2["LB/Node"]
LB2 -->|"2.转发"| Backend2["Backend"]
Backend2 -->|"3.直接返回"| C2
end
40.3.2 Tunnel vs DSR 对比¶
TCP 三次握手流程对比:
| 步骤 | Tunnel 模式 | DSR 模式 |
|---|---|---|
| SYN | Client → LB → Backend | Client → LB → Backend |
| SYN-ACK | Backend → LB → Client | Backend → Client (直接) |
| ACK | Client → LB → Backend | Client → LB → Backend |
| 总包数 | 6 个 | 4 个 |
40.4 配置与实现¶
40.4.1 启用 eBPF 数据平面¶
步骤 1:创建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: kubernetes-services-endpoint
namespace: kube-system
data:
KUBERNETES_SERVICE_HOST: "<API_SERVER_IP>"
KUBERNETES_SERVICE_PORT: "6443"
步骤 2:禁用 kube-proxy
# Kind 集群:创建时设置
kubeProxyMode: none
# 现有集群:scale down
kubectl scale deployment kube-proxy -n kube-system --replicas=0
步骤 3:启用 eBPF
calicoctl patch felixconfiguration default \
--patch='{"spec": {"bpfEnabled": true}}'
40.4.2 启用 DSR 模式¶
calicoctl patch felixconfiguration default \
--patch='{"spec": {"bpfExternalServiceMode": "DSR"}}'
DSR 模式值:
| 模式 | 说明 |
|---|---|
Tunnel |
默认,VxLAN 封装双向 |
DSR |
响应直接返回 |
40.4.3 验证配置¶
# 检查 Felix 配置
calicoctl get felixconfiguration default -o yaml
# 检查 BPF 状态
kubectl exec -n kube-system calico-node-xxx -- \
calico-node -bpf conntrack dump
40.5 抓包分析¶
40.5.1 Tunnel 模式抓包¶
sequenceDiagram
participant C as Client (172.18.0.1)
participant LB as LB Node (172.18.0.2)
participant B as Backend (172.18.0.3)
C->>LB: SYN (端口 32000)
LB->>B: VxLAN封装 SYN
B->>LB: VxLAN封装 SYN-ACK
LB->>C: SYN-ACK
C->>LB: ACK
LB->>B: VxLAN封装 ACK
特点:
- 中间节点进行 VxLAN 封装
- 所有流量经过 LB 节点
- 共 6 个包完成握手
40.5.2 DSR 模式抓包¶
sequenceDiagram
participant C as Client (172.18.0.1)
participant LB as LB Node (172.18.0.2)
participant B as Backend (172.18.0.3)
C->>LB: SYN (端口 32000)
LB->>B: VxLAN封装 SYN
B-->>C: SYN-ACK (直接返回!)
C->>LB: ACK
LB->>B: VxLAN封装 ACK
特点:
- SYN-ACK 从 Backend 直接返回 Client
- 减少 LB 负载
- 共 4 个包完成握手
40.5.3 VxLAN 封装分析¶
# 外层 IP 头
Src: 172.18.0.2 (LB)
Dst: 172.18.0.3 (Backend)
# VxLAN 头
Port: 4789
VNI: xxx
# 内层 IP 头 (原始包)
Src: 172.18.0.1 (Client)
Dst: 172.18.0.2 (Service ClusterIP)
[!IMPORTANT] DSR 如何实现直接返回:
- VxLAN 内层包含原始 Client IP
- Backend 解封装后获取 Client 地址
- 直接构造响应包发送给 Client
- 响应包源 IP 为 Service IP(非 Backend IP)
40.6 验证步骤¶
40.6.1 检查 eBPF 启用状态¶
# 查看 Felix 配置
calicoctl get felixconfiguration default -o yaml | grep bpf
# 输出
bpfEnabled: true
bpfExternalServiceMode: DSR
40.6.2 验证无 kube-proxy¶
# 检查 iptables 规则(应该没有 Service 规则)
iptables -t nat -L KUBE-SERVICES
# 检查 conntrack(eBPF 模式下为空)
conntrack -L | grep <service-port>
40.6.3 使用 BPF 工具查看¶
# 进入 calico-node Pod
kubectl exec -it calico-node-xxx -n kube-system -- sh
# 使用 calico-node 内置 BPF 工具
calico-node -bpf conntrack dump
calico-node -bpf routes dump
calico-node -bpf arp dump
40.7 故障排查¶
40.7.1 常见问题¶
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Service 不通 | eBPF 未正确加载 | 检查 Felix 日志 |
| 性能没提升 | kube-proxy 仍运行 | 禁用 kube-proxy |
| 部分功能失效 | 平台不支持 | 检查兼容性列表 |
| conntrack 查不到 | 正常现象 | eBPF 有自己的 conntrack |
40.7.2 调试命令¶
# 查看 BPF 程序加载情况
tc -s qdisc show dev eth0
# 查看丢包统计
tc -s filter show dev eth0
# 查看 Felix 日志
kubectl logs -n kube-system calico-node-xxx -c felix | grep -i bpf
40.8 章节小结¶
mindmap
root((eBPF + DSR))
eBPF
内核虚拟机
无需重编内核
bypass iptables
性能提升
DSR
Direct Server Return
减少 LB 负载
响应直接返回
配置
bpfEnabled true
bpfExternalServiceMode DSR
禁用 kube-proxy
验证
calico-node bpf
conntrack 为空
抓包分析
[!TIP] eBPF + DSR 模式要点总结:
- eBPF 优势:
- 绕过 iptables/kube-proxy
- O(1) 查找性能
降低 CPU 开销
DSR 原理:
- 响应包直接从 Backend 返回 Client
- 减少 LB 节点负载
TCP 握手从 6 包减少到 4 包
关键配置:
bpfEnabled: truebpfExternalServiceMode: DSR禁用 kube-proxy
验证方法:
calico-node -bpf命令- 抓包分析 TCP 握手流程
conntrack 表为空(eBPF 管理)
注意限制:
- 内核版本要求 (>= 5.4)
- 部分平台不支持
- 功能不如 iptables 完整
第四十一章 Calico-VPP 模式¶
本章介绍 Calico 的 VPP(Vector Packet Processing)数据平面模式,这是一种高性能的用户态协议栈方案。
41.1 背景与概述¶
41.1.1 什么是 VPP¶
VPP (Vector Packet Processing):
- 矢量包处理框架
- 用户态协议栈
- 思科开源项目 (fd.io)
- 高性能网络处理
graph LR
subgraph "传统处理"
P1["Packet 1"] --> K1["内核协议栈"]
P2["Packet 2"] --> K1
P3["Packet 3"] --> K1
end
subgraph "VPP 矢量处理"
PV1["Packet Vector"] --> N1["Node 1"]
N1 --> N2["Node 2"]
N2 --> N3["Node 3"]
end
矢量处理优势:
| 特性 | 传统逐包处理 | VPP 矢量处理 |
|---|---|---|
| 处理方式 | 单包遍历所有层 | 批量处理同类包 |
| Cache 命中 | 低(频繁切换) | 高(连续访问) |
| 性能 | 较低 | 极高(百万 pps) |
| 扩展性 | 内核态修改 | Plugin 机制 |
41.1.2 VPP 与 DPDK 的关系¶
graph TB
subgraph "VPP 架构"
App["应用层<br/>Calico/Plugin"]
VPP["VPP 协议栈<br/>L2/L3/L4"]
Driver["数据平面驱动"]
NIC["网卡"]
end
App --> VPP
VPP --> Driver
Driver --> NIC
subgraph "数据平面选项"
D1["DPDK - 高性能"]
D2["AF_PACKET - 兼容性"]
D3["RDMA - 低延迟"]
D4["Virtio - 虚拟化"]
end
Driver -.-> D1
Driver -.-> D2
Driver -.-> D3
Driver -.-> D4
组合说明:
- VPP:提供用户态协议栈
- DPDK:提供快速数据通道
- SR-IOV + DPDK:生产环境最强组合
41.2 VPP 工作原理¶
41.2.1 Node Graph 处理模型¶
graph LR
Input["DPDK Input"] --> ARP["ARP Input"]
Input --> IP4["IP4 Input"]
IP4 --> IP4Lookup["IP4 Lookup"]
IP4Lookup --> IP4Rewrite["IP4 Rewrite"]
IP4Rewrite --> Interface["Interface Output"]
Interface --> TX["DPDK TX"]
每个 Node 代表一个处理单元:
dpdk-input:从网卡接收ip4-input:IPv4 处理ip4-lookup:路由查找interface-output:发送
41.2.2 传统模式 vs VPP 模式¶
传统模式(内核协议栈):
Pod → veth → Bridge/Route → 内核协议栈 → NIC → 交换机
VPP 模式(用户态协议栈):
Pod → tun/tap → VPP 协议栈 → DPDK/驱动 → NIC → 交换机
graph TB
subgraph "Host Kernel"
BGP["BIRD BGP"]
Kubelet["kubelet"]
Felix["Felix"]
TUN["tun 设备"]
end
subgraph "VPP 用户态"
VPPCore["VPP Core"]
Routing["Routing"]
LB["Load Balancing"]
Policy["Policy"]
DataPlane["Data Plane<br/>(DPDK/AF_PACKET)"]
end
subgraph "Pod"
PodApp["应用"]
PodTUN["tun 设备"]
end
PodApp --> PodTUN
PodTUN --> VPPCore
VPPCore --> DataPlane
DataPlane --> NIC["物理网卡"]
TUN <-.-> VPPCore
BGP <-.-> TUN
41.3 环境配置¶
41.3.1 前置要求¶
HugePages(大页内存):
# 查看当前配置
cat /proc/meminfo | grep Huge
# 配置 HugePages(2MB)
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# 或配置 1GB HugePages
echo 4 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
Kubernetes 中查看 HugePages:
kubectl describe node <node-name> | grep hugepages
# 输出
# hugepages-1Gi: 0
# hugepages-2Mi: 0
41.3.2 网卡驱动绑定¶
驱动类型对比:
| 驱动 | 性能 | HugePages | 适用场景 |
|---|---|---|---|
af_packet |
低 | 不需要 | 测试/兼容 |
uio_pci_generic |
中 | 需要 | 通用 |
igb_uio |
高 | 需要 | 已淘汰 |
vfio-pci |
高 | 需要 | 生产推荐 |
绑定网卡到 DPDK:
# 查看当前状态
dpdk-devbind.py --status
# 绑定到 vfio-pci
dpdk-devbind.py --bind=vfio-pci 0000:82:00.0
# 绑定后,网卡在内核中不可见
ip link show # 看不到该网卡
[!WARNING] 网卡绑定到 DPDK 后,将从内核中消失。需要 VPP 创建 tap 设备维持 SSH 连接。
41.4 安装与配置¶
41.4.1 Calico VPP 安装¶
步骤 1:准备空集群
# kubeadm init 后,不安装 CNI
kubeadm init --pod-network-cidr=192.168.0.0/16
步骤 2:安装 Calico Operator
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
步骤 3:安装 VPP 数据平面
# vpp-dataplane.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: calico-vpp-config
namespace: calico-vpp-dataplane
data:
CALICOVPP_INTERFACE: eth0
CALICOVPP_NATIVE_DRIVER: af_packet # 或 dpdk
SERVICE_PREFIX: 10.96.0.0/16
kubectl apply -f vpp-dataplane.yaml
41.4.2 VPP 启动配置¶
# /etc/vpp/startup.conf
unix {
nodaemon
full-coredump
}
cpu {
workers 1
main-core 0
}
dpdk {
dev 0000:82:00.0
}
plugins {
plugin dpdk_plugin.so { enable }
plugin ping_plugin.so { enable }
}
buffers {
buffers-per-numa 131072
}
关键配置说明:
| 配置项 | 说明 |
|---|---|
cpu.workers |
转发线程数 |
dpdk.dev |
网卡 PCI 地址 |
plugins |
启用的插件 |
buffers |
内存缓冲区 |
41.5 调试与验证¶
41.5.1 进入 VPP 控制台¶
# Calico VPP 环境
kubectl exec -it calico-vpp-node-xxx -n calico-vpp-dataplane -- vppctl
# 或直接执行命令
kubectl exec calico-vpp-node-xxx -n calico-vpp-dataplane -- vppctl show interface
41.5.2 常用 vppctl 命令¶
# 查看接口
vppctl show interface
# 查看接口统计
vppctl show interface addr
# 查看路由表
vppctl show ip fib
# 查看 ARP 表
vppctl show ip neighbor
# 查看隧道
vppctl show vxlan tunnel
vppctl show ipip tunnel
# 查看运行状态
vppctl show runtime
输出示例:
vpp# show interface
Name Idx State MTU RX packets TX packets
GigabitEthernet82/0/0 1 up 9000 12345678 87654321
tap0 2 up 1500 54321 12345
tun0 3 up 1500 11111 22222
41.6 Pod 通信原理¶
41.6.1 Pod 到 VPP 的连接¶
graph LR
subgraph "Pod"
App["应用进程"]
NS["Network Namespace"]
TUN["tun 设备"]
end
subgraph "VPP"
VPPTun["VPP tun"]
Route["路由处理"]
Output["接口输出"]
end
App --> NS
NS --> TUN
TUN <--> VPPTun
VPPTun --> Route
Route --> Output
tun vs tap 设备:
| 类型 | 层级 | 用途 |
|---|---|---|
| tun | L3(IP) | Pod 连接 VPP |
| tap | L2(Ethernet) | 需要 MAC 地址场景 |
41.6.2 跨节点通信¶
sequenceDiagram
participant Pod1 as Pod1 (Node1)
participant VPP1 as VPP (Node1)
participant NIC1 as NIC (Node1)
participant NIC2 as NIC (Node2)
participant VPP2 as VPP (Node2)
participant Pod2 as Pod2 (Node2)
Pod1->>VPP1: tun 设备
VPP1->>NIC1: DPDK 发送
NIC1->>NIC2: 物理网络(VxLAN/IPIP)
NIC2->>VPP2: DPDK 接收
VPP2->>Pod2: tun 设备
41.7 限制与注意事项¶
41.7.1 已知限制¶
| 功能 | 状态 |
|---|---|
| BGP | 必须启用 |
| Service Affinity | 不支持 |
| EndpointSlice | 不支持 |
| WireGuard | 部分限制 |
| IPv6 | 支持 |
41.7.2 生产建议¶
[!CAUTION] VPP 模式仍为实验性功能:
- 官方标记为 "Not Production Ready"
- 建议在测试环境充分验证
- 生产环境考虑使用 Multus + VPP
适用场景:
- 高性能网络需求(流媒体、AI/ML)
- SR-IOV 环境
- 专用硬件加速
41.8 章节小结¶
mindmap
root((Calico VPP))
VPP 概念
矢量包处理
用户态协议栈
fd.io 开源
数据平面
DPDK 高性能
AF_PACKET 兼容
RDMA/Virtio
配置
HugePages
网卡驱动绑定
startup.conf
架构
Pod via tun
VPP 协议栈
快速转发
验证
vppctl
show interface
show ip fib
[!TIP] VPP 模式要点总结:
- VPP 是什么:
- 矢量包处理框架
- 用户态高性能协议栈
通过 Plugin 扩展功能
与 DPDK 关系:
- VPP 提供协议栈
- DPDK 提供快速数据通道
生产推荐:SR-IOV + DPDK
关键配置:
- HugePages 内存
- 网卡驱动绑定(vfio-pci)
VPP startup.conf
Pod 连接方式:
- 通过 tun 设备连接到 VPP
VPP 替代内核协议栈转发
限制注意:
- 必须启用 BGP
- 实验性功能,非生产就绪
- 需要物理机或虚拟机(非容器化)
第四十二章 Calico-Service-DataPath¶
本章深入分析 Calico 环境下 Kubernetes Service 的数据路径,包括 ClusterIP、NodePort、LoadBalancer 三种类型的 NAT 转换过程。
42.1 背景与概述¶
42.1.1 Service 类型回顾¶
| 类型 | 访问范围 | 使用场景 |
|---|---|---|
| ClusterIP | 集群内部 | 默认类型,微服务通信 |
| NodePort | 集群外部 | 通过节点端口访问 |
| LoadBalancer | 外部 LB | 云环境,生产推荐 |
| Headless | 集群内部 | StatefulSet,直连 Pod |
42.1.2 NAT 转换基础¶
NAT 类型说明:
| NAT 类型 | 说明 | 场景 |
|---|---|---|
| SNAT | 修改源地址 | 内网访问外网 |
| DNAT | 修改目的地址 | 外网访问内网 |
| Reverse NAT | 反向还原 | 响应包还原地址 |
graph LR
subgraph "SNAT 场景"
A["内网 IP"] --> |修改源| B["公网 IP"]
end
subgraph "DNAT 场景"
C["Service IP"] --> |修改目的| D["Pod IP"]
end
42.2 ClusterIP 数据路径¶
42.2.1 工作原理¶
ClusterIP 是 Kubernetes 默认的 Service 类型,只能在集群内部访问。
sequenceDiagram
participant PodA as Pod A (Client)
participant VethA as veth pair
participant Host as Node (iptables)
participant PodB as Pod B (Backend)
Note over PodA,PodB: 第一阶段: 发送请求
PodA->>VethA: SRC: PodA IP<br/>DST: ClusterIP
VethA->>Host: 进入 root namespace
Note over Host: iptables 执行 DNAT<br/>ClusterIP → PodB IP
Host->>PodB: SRC: PodA IP<br/>DST: PodB IP
Note over PodA,PodB: 第二阶段: 返回响应
PodB->>Host: SRC: PodB IP<br/>DST: PodA IP
Note over Host: conntrack 执行 Reverse DNAT<br/>PodB IP → ClusterIP
Host->>VethA: SRC: ClusterIP<br/>DST: PodA IP
VethA->>PodA: 响应到达
42.2.2 DNAT 详解¶
请求阶段:
Pod A 发送请求:
原始包: SRC=10.244.1.10 (PodA), DST=10.96.0.100 (ClusterIP)
经过 iptables DNAT:
转换后: SRC=10.244.1.10 (PodA), DST=10.244.2.20 (PodB)
响应阶段:
Pod B 返回响应:
原始包: SRC=10.244.2.20 (PodB), DST=10.244.1.10 (PodA)
经过 conntrack Reverse DNAT:
转换后: SRC=10.96.0.100 (ClusterIP), DST=10.244.1.10 (PodA)
[!IMPORTANT] 为什么需要 Reverse DNAT? Pod A 发送的请求目的是 ClusterIP,如果响应的源地址是 PodB IP,Pod A 会因为地址不匹配而丢弃该包。
42.2.3 抓包验证¶
# 在 Client Pod 网卡抓包
kubectl exec client-pod -- tcpdump -i eth0 -nn
# 观察第一个 SYN 包
# SRC: 10.244.1.10 (PodA)
# DST: 10.96.0.100 (ClusterIP)
# 在 Host 网卡抓包
tcpdump -i eth0 -nn host 10.244.2.20
# 观察同一个 SYN 包 (DNAT 已完成)
# SRC: 10.244.1.10 (PodA)
# DST: 10.244.2.20 (PodB) ← 目的地址已变化
42.3 NodePort 数据路径¶
42.3.1 工作原理¶
NodePort 允许从集群外部通过节点 IP + 端口访问服务。
sequenceDiagram
participant Client as 外部客户端
participant Node1 as Node1 (入口)
participant Node2 as Node2
participant PodB as Pod B (Backend)
Note over Client,PodB: 第一阶段: 外部请求进入
Client->>Node1: SRC: ClientIP<br/>DST: Node1:30000
Note over Node1: SNAT + DNAT<br/>ClientIP→Node1IP<br/>Node1:30000→PodB:80
Node1->>Node2: SRC: Node1 IP<br/>DST: PodB IP
Node2->>PodB: 转发到 Pod
Note over Client,PodB: 第二阶段: 响应返回
PodB->>Node2: SRC: PodB IP<br/>DST: Node1 IP
Node2->>Node1: 路由回 Node1
Note over Node1: Reverse SNAT + DNAT<br/>还原所有地址
Node1->>Client: SRC: Node1:30000<br/>DST: ClientIP
42.3.2 为什么需要 SNAT?¶
graph TB
subgraph "问题场景 (不做 SNAT)"
C1["Client: 172.18.0.1"] --> N1["Node1: 172.18.0.2"]
N1 --> |"DST=PodB"| P1["PodB: 10.244.2.20"]
P1 --> |"SRC=PodB<br/>DST=Client"| C1
style P1 fill:#f66
Note1["❌ Client 收到 PodB 的响应<br/>但它期望的是 Node1 的响应"]
end
解决方案:SNAT
- 请求时:将源地址改为 Node1 IP
- 响应时:PodB 将响应发回 Node1
- Node1 再做 Reverse NAT 发回 Client
42.3.3 完整 NAT 过程¶
| 阶段 | 源地址 | 目的地址 | NAT 操作 |
|---|---|---|---|
| Client→Node1 | ClientIP | Node1:30000 | - |
| Node1→PodB | Node1 IP | PodB:80 | SNAT + DNAT |
| PodB→Node1 | PodB IP | Node1 IP | - |
| Node1→Client | Node1:30000 | ClientIP | Reverse NAT |
42.3.4 抓包验证¶
# 在 Node1 上抓包
tcpdump -i eth0 -nn port 30000 -w nodeport.pcap
# 从外部访问
curl 172.18.0.2:30000
# 分析 pcap 文件
# 可以看到 4 种不同的地址组合
42.4 LoadBalancer 数据路径¶
42.4.1 工作原理¶
LoadBalancer 在 NodePort 基础上增加了外部负载均衡器。
sequenceDiagram
participant Client as 外部客户端
participant LB as L4 LoadBalancer
participant Node1 as Node1
participant PodB as Pod B (Backend)
Client->>LB: SRC: ClientIP<br/>DST: LB IP
Note over LB: DNAT: 选择后端节点<br/>LB IP → Node1 IP
LB->>Node1: SRC: ClientIP<br/>DST: Node1:30000
Note over Node1: 等同于 NodePort 流程
Node1->>PodB: SNAT + DNAT
PodB->>Node1: 响应
Node1->>LB: Reverse NAT
LB->>Client: 返回响应
42.4.2 与 NodePort 的关系¶
LoadBalancer = 外部 L4 LB + NodePort
数据流:
Client → L4 LB (DNAT选择节点) → NodePort (SNAT+DNAT) → Pod
[!TIP] L4 LB 的作用:
- 隐藏后端节点 IP
- 负载均衡到多个节点
- 提供统一入口(外部 IP/VIP)
42.5 iptables 规则分析¶
42.5.1 Service 相关规则链¶
# 查看 Service 规则
iptables -t nat -L KUBE-SERVICES -n --line-numbers
# 查看特定 Service 的规则链
iptables -t nat -L KUBE-SVC-XXXXX -n
规则链结构:
graph TD
A["PREROUTING/OUTPUT"] --> B["KUBE-SERVICES"]
B --> C["KUBE-SVC-xxx<br/>(Service 入口)"]
C --> D["KUBE-SEP-xxx<br/>(Endpoint 1)"]
C --> E["KUBE-SEP-yyy<br/>(Endpoint 2)"]
D --> F["DNAT 到 Pod1"]
E --> G["DNAT 到 Pod2"]
42.5.2 iptables trace 调试¶
# 添加 trace 规则
iptables -t raw -A PREROUTING -p tcp --dport 80 -j TRACE
iptables -t raw -A OUTPUT -p tcp --dport 80 -j TRACE
# 查看 trace 日志
dmesg -T | grep TRACE
# 清除 trace 规则
iptables -t raw -D PREROUTING -p tcp --dport 80 -j TRACE
iptables -t raw -D OUTPUT -p tcp --dport 80 -j TRACE
42.6 conntrack 连接跟踪¶
42.6.1 查看连接表¶
# 查看所有连接
conntrack -L
# 过滤特定端口
conntrack -L -p tcp --dport 80
# 查看 NAT 连接
conntrack -L -n
42.6.2 conntrack 表项解读¶
tcp 6 117 TIME_WAIT
src=10.244.1.10 dst=10.96.0.100 sport=45678 dport=80
src=10.244.2.20 dst=10.244.1.10 sport=80 dport=45678
解读:
- 原始方向: PodA(10.244.1.10) → ClusterIP(10.96.0.100)
- 回复方向: PodB(10.244.2.20) → PodA(10.244.1.10)
- DNAT 映射: ClusterIP → PodB
42.7 实验环境搭建¶
42.7.1 测试 Pod 部署¶
# test-pods.yaml
apiVersion: v1
kind: Pod
metadata:
name: client-pod
spec:
nodeName: node1 # 固定到 node1
containers:
- name: client
image: curlimages/curl
command: ["sleep", "infinity"]
---
apiVersion: v1
kind: Pod
metadata:
name: backend-pod
labels:
app: backend
spec:
nodeName: node2 # 固定到 node2,跨节点测试
containers:
- name: backend
image: nginx
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
spec:
type: NodePort
selector:
app: backend
ports:
- port: 80
targetPort: 80
nodePort: 30000
42.7.2 验证步骤¶
# 1. ClusterIP 测试
kubectl exec client-pod -- curl backend-svc
# 2. NodePort 测试 (从集群外)
curl <node-ip>:30000
# 3. 抓包分析
# 在不同位置抓包对比
42.8 章节小结¶
mindmap
root((Service DataPath))
ClusterIP
仅 DNAT
集群内访问
Reverse DNAT 还原
NodePort
SNAT + DNAT
外部访问
跨节点需 SNAT
LoadBalancer
L4 LB + NodePort
负载均衡
隐藏后端
调试
tcpdump 抓包
iptables trace
conntrack 查看
[!TIP] Service DataPath 要点总结:
- ClusterIP:
- 仅做 DNAT(ClusterIP → PodIP)
conntrack 记录映射,响应时 Reverse DNAT
NodePort:
- SNAT + DNAT 双重转换
SNAT 确保响应能回到入口节点
LoadBalancer:
- L4 LB 选择后端节点
后续流程等同 NodePort
调试技巧:
tcpdump抓包:注意抓包位置影响看到的地址iptables trace:追踪规则命中
conntrack -L:查看 NAT 映射关系注意事项:
- 抓包时 iptables 已处理完成
- 生产环境谨慎修改 iptables 规则
- 使用 BGP Fullmesh 简化分析(无 overlay)
第四十三章 Calico 生产实践¶
本章介绍 Calico 在生产环境中的网络设计和最佳实践,包括数据中心网络拓扑、BGP 架构选型、AS 模式对比。
43.1 背景与概述¶
43.1.1 参考文档¶
[!IMPORTANT] 生产环境必读文档:
- Calico 官方文档:
Reference → Architecture → Calico IP Fabric- IETF RFC 7938:Use of BGP for Routing in Large-Scale Data Centers
建议:生产环境至少阅读 10 遍,理解每一句话的含义和技术依据。
43.1.2 数据中心网络演进¶
| 时代 | 架构 | 特点 |
|---|---|---|
| 传统 | 接入-汇聚-核心 | 生成树 + VRRP + BFD |
| 现代 | Spine-Leaf (Clos) | BGP 路由 + ECMP |
graph TB
subgraph "传统三层架构"
C1["核心层 Core"]
A1["汇聚层 Aggregation"]
A2["汇聚层 Aggregation"]
S1["接入层 Access"]
S2["接入层 Access"]
S3["接入层 Access"]
C1 --- A1
C1 --- A2
A1 --- S1
A1 --- S2
A2 --- S2
A2 --- S3
end
graph TB
subgraph "Spine-Leaf 架构"
SP1["Spine 1"]
SP2["Spine 2"]
L1["Leaf/ToR 1"]
L2["Leaf/ToR 2"]
L3["Leaf/ToR 3"]
SP1 --- L1
SP1 --- L2
SP1 --- L3
SP2 --- L1
SP2 --- L2
SP2 --- L3
end
43.2 BGP 网络拓扑模式¶
Calico 支持三种主要的 BGP 网络拓扑模式:
43.2.1 模式对比¶
| 模式 | AS 分配 | 复杂度 | 适用场景 |
|---|---|---|---|
| AS Per Rack | 每机架一个 AS | 中 | 大型数据中心 |
| AS Per Node | 每节点一个 AS | 高 | 特殊需求 |
| Downward Default | 默认路由下发 | 低 | 推荐生产 |
43.3 AS Per Rack 模式¶
43.3.1 架构原理¶
每个机架(Rack)作为一个独立的自治系统(AS)。
graph TB
subgraph "Spine 层"
SP["Spine Switch<br/>AS 65000"]
end
subgraph "Rack A - AS 65001"
ToR_A["ToR Switch<br/>Route Reflector"]
N1["Node 1"]
N2["Node 2"]
N3["Node 3"]
end
subgraph "Rack B - AS 65002"
ToR_B["ToR Switch<br/>Route Reflector"]
N4["Node 4"]
N5["Node 5"]
N6["Node 6"]
end
SP ---|eBGP| ToR_A
SP ---|eBGP| ToR_B
ToR_A ---|iBGP| N1
ToR_A ---|iBGP| N2
ToR_A ---|iBGP| N3
ToR_B ---|iBGP| N4
ToR_B ---|iBGP| N5
ToR_B ---|iBGP| N6
43.3.2 工作原理¶
- 机架内部(iBGP):
- ToR 交换机作为 Route Reflector
- 计算节点与 ToR 建立 iBGP
-
同机架节点共享同一 AS 号
-
机架之间(eBGP):
- ToR 与 Spine 建立 eBGP
- 不同机架使用不同 AS 号
- Spine 交换机汇聚全网路由
43.3.3 优势与劣势¶
| 方面 | 优势 | 劣势 |
|---|---|---|
| 扩展性 | 机架级隔离 | 需要规划 AS 号 |
| 故障域 | 故障隔离在机架 | ToR 压力较大 |
| 管理 | 结构清晰 | 配置复杂 |
43.4 AS Per Node 模式¶
43.4.1 架构原理¶
每个计算节点作为一个独立的自治系统。
graph TB
subgraph "Spine 层"
SP["Spine Switch"]
end
subgraph "Leaf 层"
ToR["ToR Switch"]
end
subgraph "Node 层"
N1["Node 1<br/>AS 65001"]
N2["Node 2<br/>AS 65002"]
N3["Node 3<br/>AS 65003"]
end
SP ---|eBGP| ToR
ToR ---|eBGP| N1
ToR ---|eBGP| N2
ToR ---|eBGP| N3
43.4.2 特点分析¶
优点:
- 每个节点完全独立
- 路由隔离彻底
缺点:
- 节点 BGP 压力大(Bird 进程)
- AS 号管理复杂
- 开源 BGP 实现不如商业设备稳定
[!WARNING] 不推荐在生产环境使用
计算节点使用 Bird 提供 BGP 能力,其稳定性和性能不如商业路由设备。每个节点最多 200 个 Pod,路由条目有限,没必要每节点独立 AS。
43.5 Downward Default 模式(推荐)¶
43.5.1 架构原理¶
下级设备只向上级通告默认路由,全网路由只在 Spine 维护。
graph TB
subgraph "Spine 层 - 全网路由"
SP["Spine Switch<br/>掌握所有路由"]
end
subgraph "Leaf 层 - 默认路由"
ToR1["ToR 1<br/>默认路由指向 Spine"]
ToR2["ToR 2<br/>默认路由指向 Spine"]
end
subgraph "Node 层 - 默认路由"
N1["Node 1"]
N2["Node 2"]
N3["Node 3"]
N4["Node 4"]
end
SP --> |"下发默认路由"| ToR1
SP --> |"下发默认路由"| ToR2
ToR1 --> |"下发默认路由"| N1
ToR1 --> |"下发默认路由"| N2
ToR2 --> |"下发默认路由"| N3
ToR2 --> |"下发默认路由"| N4
N1 -.-> |"通告 Pod 网段"| ToR1
N2 -.-> |"通告 Pod 网段"| ToR1
ToR1 -.-> |"汇聚上报"| SP
43.5.2 工作原理¶
路由通告方向(上行):
Node → ToR → Spine
每级只通告本级路由,汇聚后上报
默认路由方向(下行):
Spine → ToR → Node
每级下发默认路由,指向上级
43.5.3 优势分析¶
| 方面 | 说明 |
|---|---|
| 设备负载 | 专业设备做专业的事,Spine 处理复杂路由 |
| 节点压力 | 计算节点只维护少量路由,释放资源 |
| 配置简化 | 下级设备配置简单,默认路由即可 |
| 可扩展性 | Spine 交换机能力强,支持大规模路由表 |
[!TIP] 生产环境推荐使用 Downward Default 模式
这种模式让专业设备处理复杂的 BGP 路由,计算节点专注于运行工作负载,是最合理的分工。
43.6 Route Reflector 配置策略¶
43.6.1 选择 Route Reflector¶
graph LR
subgraph "方案1: ToR 作为 RR"
ToR1["ToR Switch<br/>性能好"]
end
subgraph "方案2: 专用 RR 节点"
RR1["RR Node 1"]
RR2["RR Node 2"]
end
subgraph "方案3: 控制节点作为 RR"
CP["Control Plane Node"]
end
选择建议:
| 方案 | 适用场景 | 说明 |
|---|---|---|
| ToR 交换机 | 大型数据中心 | 性能最好,需交换机支持 |
| 专用 RR 节点 | 中型集群 | 灵活,需额外资源 |
| 控制节点 | 小型集群 | 简单,复用现有资源 |
43.6.2 Route Reflector 数量¶
# 推荐配置: 至少 2 个 RR 实现高可用
# 使用 calicoctl 配置 RR
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
name: default
spec:
nodeToNodeMeshEnabled: false # 禁用 Full Mesh
asNumber: 65001
43.7 AS 号规划¶
43.7.1 私有 AS 号范围¶
| 类型 | 范围 | 说明 |
|---|---|---|
| 2 字节私有 | 64512-65534 | 常用,1023 个 |
| 4 字节私有 | 4200000000-4294967294 | 扩展,约 9500 万个 |
43.7.2 规划示例¶
数据中心 AS 规划示例:
Spine 层:
- Spine-1: AS 65000
- Spine-2: AS 65000 (同 AS,iBGP)
Leaf/ToR 层:
- Rack-A: AS 65001
- Rack-B: AS 65002
- Rack-C: AS 65003
...
- Rack-N: AS 6500N
注意: 每个 Rack 使用不同 AS 号
43.8 生产环境选型建议¶
43.8.1 决策流程¶
flowchart TD
A["开始选型"] --> B{"集群规模"}
B --> |"小型 <50 节点"| C["Full Mesh"]
B --> |"中型 50-200 节点"| D["AS Per Rack<br/>+ Route Reflector"]
B --> |"大型 >200 节点"| E["Downward Default<br/>+ Spine Leaf"]
C --> F["控制节点作为 RR"]
D --> G["ToR 作为 RR"]
E --> H["Spine 承担路由"]
43.8.2 模式选型建议¶
| 规模 | 推荐模式 | 理由 |
|---|---|---|
| 小型 (<50) | Full Mesh | 简单,无需额外配置 |
| 中型 (50-200) | AS Per Rack + RR | 平衡复杂度和扩展性 |
| 大型 (>200) | Downward Default | 专业设备处理路由 |
43.9 ContainerLab 实验¶
43.9.1 验证不同 BGP 架构¶
# 使用 ContainerLab 模拟 Spine-Leaf 架构
# 可以验证:
# 1. AS Per Rack 模式
# 2. AS Per Node 模式
# 3. Downward Default 模式
# 查看 BGP 邻居
calicoctl node status
# 查看路由表
ip route show
43.10 章节小结¶
mindmap
root((Calico 生产实践))
网络拓扑
传统三层
Spine-Leaf
Clos 架构
BGP 模式
AS Per Rack
AS Per Node
Downward Default
Route Reflector
ToR 交换机
专用 RR 节点
控制节点
选型建议
小型: Full Mesh
中型: AS Per Rack
大型: Downward Default
[!TIP] Calico 生产实践要点总结:
- 必读文档:
- Calico IP Fabric 设计文档
RFC 7938 数据中心 BGP 路由
架构选型:
- Downward Default:推荐生产使用
- AS Per Rack:大型数据中心
AS Per Node:不推荐
设计原则:
- 专业设备做专业的事
- Spine 处理复杂路由
计算节点专注工作负载
Route Reflector:
- 小型:控制节点兼任
- 中大型:ToR 或专用节点
至少 2 个实现高可用
验证测试:
- 使用 ContainerLab 模拟
- 理解每种模式的路由流向
- 生产上线前充分测试
第四十四章 Calico-IPAM 高级用法¶
本章介绍 Calico IPAM(IP Address Management)的高级配置,包括基于拓扑分配 IP、固定 IP、IP 池迁移、CNI 插件链等。
44.1 背景与概述¶
44.1.1 CNI 的两大核心组件¶
一个完备的 CNI 包含两大核心组件:
graph LR
subgraph "CNI 核心组件"
IPAM["IPAM<br/>IP 地址管理"]
Network["Network<br/>网络通路"]
end
IPAM --> Pod["Pod 获取 IP"]
Network --> Connectivity["Pod 互通"]
| 组件 | 职责 | 重要性 |
|---|---|---|
| IPAM | IP 地址分配和管理 | ⭐⭐⭐⭐⭐ |
| Network | 网络连通性 | ⭐⭐⭐⭐⭐ |
[!IMPORTANT] 两者并重:不要只关注网络通路,IPAM 在生产环境中同样重要!
44.1.2 Calico IPAM 类型¶
| 类型 | 说明 | 功能 |
|---|---|---|
| calico-ipam | Calico 原生 IPAM | 支持高级特性 |
| host-local | 主机本地 IPAM | 功能有限 |
44.2 基于拓扑分配 IP(Node Selector)¶
44.2.1 背景¶
在大型数据中心,需要根据物理拓扑(如机架/Rack)分配 IP 地址,便于管理和路由汇聚。
graph TB
subgraph "Rack 0 - 192.168.0.0/24"
Node1["Node 1"]
Node2["Node 2"]
Pod1["Pod: 192.168.0.x"]
Pod2["Pod: 192.168.0.y"]
end
subgraph "Rack 1 - 192.168.1.0/24"
Node3["Node 3"]
Node4["Node 4"]
Pod3["Pod: 192.168.1.x"]
Pod4["Pod: 192.168.1.y"]
end
Node1 --> Pod1
Node2 --> Pod2
Node3 --> Pod3
Node4 --> Pod4
44.2.2 原理¶
通过 IPPool 的 nodeSelector 字段,限制 IP 池只能在特定节点上使用。
默认配置:
# 默认 IPPool - 所有节点
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: default-pool
spec:
cidr: 10.244.0.0/16
nodeSelector: all() # 所有节点都使用此池
44.2.3 实现步骤¶
步骤 1:为节点打标签
# 标记机架
kubectl label node node1 rack=rack-0
kubectl label node node2 rack=rack-0
kubectl label node node3 rack=rack-1
kubectl label node node4 rack=rack-1
步骤 2:创建基于拓扑的 IP 池
# Rack 0 的 IP 池
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: rack-0-pool
spec:
cidr: 192.168.0.0/24
ipipMode: Always
natOutgoing: true
nodeSelector: rack == "rack-0"
---
# Rack 1 的 IP 池
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: rack-1-pool
spec:
cidr: 192.168.1.0/24
ipipMode: Always
natOutgoing: true
nodeSelector: rack == "rack-1"
步骤 3:验证
# 创建测试 Pod
kubectl create deployment test --image=nginx --replicas=4
# 查看 Pod IP 分配
kubectl get pods -o wide
# 预期结果:
# node1/node2 上的 Pod: 192.168.0.x
# node3/node4 上的 Pod: 192.168.1.x
44.2.4 关键点¶
| 要点 | 说明 |
|---|---|
| 标签一致性 | 节点标签必须与 nodeSelector 匹配 |
| 路由汇聚 | 同机架 IP 相同网段,便于路由聚合 |
| 管理清晰 | 根据 IP 可快速定位物理位置 |
| 故障隔离 | 网段隔离有助于故障排查 |
44.3 固定 IP 地址(Static IP)¶
44.3.1 背景¶
某些应用(如数据库)需要固定 IP,即使 Pod 重建也保持相同 IP。
44.3.2 原理¶
通过 Pod 注解指定固定 IP 地址。
sequenceDiagram
participant User as 用户
participant K8s as Kubernetes
participant IPAM as Calico IPAM
User->>K8s: 创建 Pod (带 IP 注解)
K8s->>IPAM: 请求分配 IP
IPAM->>IPAM: 检查注解中的 IP
IPAM->>IPAM: 分配指定 IP
IPAM->>K8s: 返回固定 IP
K8s->>User: Pod 启动完成
44.3.3 实现¶
创建带固定 IP 的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: static-ip-pod
annotations:
cni.projectcalico.org/ipAddrs: '["192.168.0.100"]'
spec:
containers:
- name: nginx
image: nginx
验证:
kubectl get pod static-ip-pod -o wide
# NAME READY STATUS IP NODE
# static-ip-pod 1/1 Running 192.168.0.100 node1
44.3.4 固定 IP 的局限性¶
[!WARNING] Pod 重建时的 IP 冲突问题
当 Pod 重建时,旧 Pod 的 IP 可能尚未释放,导致新 Pod 无法获取相同 IP。
解决方案:使用 IP 池(多个 IP)
# 为有状态应用分配 IP 范围
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: db-pool
spec:
cidr: 192.168.100.0/28 # 16 个 IP
nodeSelector: app == "database"
[!TIP] Spiderpool 项目
DaoCloud 开源的 Spiderpool 项目对 Calico 的静态 IP 功能进行了增强,支持:
- IP 池绑定到 Deployment
- Pod 重建时自动复用 IP
- 双栈 IP 地址管理
44.4 IP 池迁移(Migration)¶
44.4.1 背景¶
当需要更换 Pod 网段时,需要从旧 IP 池迁移到新 IP 池。
44.4.2 原理¶
flowchart LR
A["创建新 IP 池"] --> B["禁用旧 IP 池"]
B --> C["重启 Pod"]
C --> D["Pod 获取新 IP"]
44.4.3 实现步骤¶
步骤 1:创建新 IP 池
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: new-pool
spec:
cidr: 10.0.0.0/16
ipipMode: Always
natOutgoing: true
步骤 2:禁用旧 IP 池
# 查看当前 IP 池
calicoctl get ippool -o yaml
# 编辑旧池,添加 disabled: true
calicoctl patch ippool old-pool -p '{"spec": {"disabled": true}}'
# 验证
calicoctl get ippool
# NAME CIDR SELECTOR DISABLED
# old-pool 10.244.0.0/16 all() true
# new-pool 10.0.0.0/16 all() false
步骤 3:重启 Pod
# 方法1: 滚动更新
kubectl rollout restart deployment <name>
# 方法2: 删除 Pod
kubectl delete pod --all -n <namespace>
# 方法3: 使用 drain 节点
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
步骤 4:验证新 IP
kubectl get pods -o wide
# Pod IP 现在应该是 10.0.x.x
44.4.4 关键点¶
| 要点 | 说明 |
|---|---|
| 不可回滚 | 迁移后旧 IP 池已禁用 |
| 需重启 Pod | 需要 Pod 重建才能获取新 IP |
| 类似 DHCP | 类似 DHCP Server 更换后重新获取 IP |
44.5 指定 IP 池(Specific Pool)¶
44.5.1 背景¶
在同一机架内,不同应用需要从不同 IP 池分配地址。
44.5.2 原理¶
通过 Pod 注解指定从哪个 IP 池分配地址。
graph TD
subgraph "同一节点"
DB["DB Pod<br/>注解: db-pool"]
App["App Pod<br/>注解: app-pool"]
end
subgraph "IP 池"
DBPool["db-pool<br/>192.168.100.0/28"]
AppPool["app-pool<br/>192.168.200.0/24"]
end
DB --> DBPool
App --> AppPool
44.5.3 实现¶
创建多个 IP 池:
# 数据库专用池
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: db-pool
spec:
cidr: 192.168.100.0/28
ipipMode: Always
natOutgoing: true
---
# 应用专用池
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: app-pool
spec:
cidr: 192.168.200.0/24
ipipMode: Always
natOutgoing: true
使用注解指定 IP 池:
apiVersion: v1
kind: Pod
metadata:
name: db-pod
annotations:
cni.projectcalico.org/ipv4pools: '["db-pool"]'
spec:
containers:
- name: mysql
image: mysql:8.0
---
apiVersion: v1
kind: Pod
metadata:
name: app-pod
annotations:
cni.projectcalico.org/ipv4pools: '["app-pool"]'
spec:
containers:
- name: nginx
image: nginx
44.5.4 与 Node Selector 的区别¶
| 功能 | Node Selector | 指定 IP 池 |
|---|---|---|
| 粒度 | 节点级别 | Pod 级别 |
| 配置位置 | IPPool | Pod 注解 |
| 用途 | 机架隔离 | 应用隔离 |
| 灵活性 | 较低 | 较高 |
44.6 CNI 插件链¶
44.6.1 背景¶
Calico 支持 CNI 插件链,可以组合多个插件实现更多功能。
44.6.2 插件链架构¶
graph LR
Main["主插件<br/>Calico"]
PM["Port Mapping<br/>端口映射"]
BW["Bandwidth<br/>带宽限制"]
SBR["SBR<br/>源路由"]
Tuning["Tuning<br/>内核参数"]
Main --> PM
PM --> BW
BW --> SBR
SBR --> Tuning
44.6.3 常用辅助插件¶
| 插件 | 类型 | 功能 |
|---|---|---|
| portmap | 端口映射 | 支持 hostPort |
| bandwidth | 带宽限制 | QoS 流量控制 |
| sbr | 源路由 | Source Based Routing |
| tuning | 内核调优 | 设置 sysctl 参数 |
44.6.4 配置示例¶
查看 CNI 配置:
cat /etc/cni/net.d/10-calico.conflist
带插件链的配置:
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico",
"ipam": {
"type": "calico-ipam"
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
},
{
"type": "bandwidth",
"capabilities": {
"bandwidth": true
}
},
{
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "1024"
}
}
]
}
44.6.5 带宽限制示例¶
apiVersion: v1
kind: Pod
metadata:
name: bandwidth-limited-pod
annotations:
kubernetes.io/ingress-bandwidth: "10M"
kubernetes.io/egress-bandwidth: "10M"
spec:
containers:
- name: nginx
image: nginx
44.7 IPAM 配置参考¶
44.7.1 IPPool 完整字段¶
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: example-pool
spec:
# 必填字段
cidr: 192.168.0.0/24
# 可选字段
blockSize: 26 # 每节点分配的子网大小
ipipMode: Always # IPIP 模式: Always/CrossSubnet/Never
vxlanMode: Never # VxLAN 模式
natOutgoing: true # 出站 SNAT
disabled: false # 是否禁用
nodeSelector: all() # 节点选择器
allowedUses: # 允许的用途
- Workload
- Tunnel
44.7.2 常用注解¶
| 注解 | 用途 | 示例 |
|---|---|---|
cni.projectcalico.org/ipAddrs |
指定固定 IP | ["192.168.0.100"] |
cni.projectcalico.org/ipv4pools |
指定 IPv4 池 | ["my-pool"] |
cni.projectcalico.org/ipv6pools |
指定 IPv6 池 | ["my-ipv6-pool"] |
44.8 章节小结¶
mindmap
root((Calico IPAM 高级用法))
基于拓扑分配
Node Selector
机架隔离
路由汇聚
固定 IP
Pod 注解
静态 IP
Spiderpool 增强
IP 池迁移
禁用旧池
重启 Pod
获取新 IP
指定 IP 池
Pod 级别
应用隔离
更灵活
CNI 插件链
Port Mapping
Bandwidth
SBR
Tuning
[!TIP] Calico IPAM 高级用法要点总结:
- CNI 两大核心:
- IPAM:IP 地址管理
Network:网络通路
基于拓扑分配(Node Selector):
- 节点打标签 + IPPool nodeSelector
同机架使用同网段,便于管理
固定 IP:
- 使用
cni.projectcalico.org/ipAddrs注解注意 Pod 重建时的 IP 冲突问题
IP 池迁移:
- 创建新池 → 禁用旧池 → 重启 Pod
类似 DHCP Server 更换
指定 IP 池:
- 使用
cni.projectcalico.org/ipv4pools注解Pod 级别的精细控制
CNI 插件链:
- 多个插件组合使用
- 扩展功能:端口映射、带宽限制、内核调优
第四部分:Flannel-CNI¶
第四十五章 Flannel-TUN-TAP 精讲¶
本章深入讲解 Linux TUN/TAP 虚拟网络设备,这是理解 Flannel UDP 模式等网络方案的基础。
45.1 背景与概述¶
45.1.1 Flannel 简介¶
Flannel 是由 CoreOS 开源的轻量级 CNI 方案,适合快速部署 Kubernetes 网络。
资源获取:
| 资源 | 地址 |
|---|---|
| GitHub | https://github.com/flannel-io/flannel |
| Slack | Kubernetes Slack #flannel 频道 |
45.1.2 Flannel 与 Calico 的核心区别¶
| 特性 | Flannel | Calico |
|---|---|---|
| 同节点通信 | L2 交换(Bridge) | L3 路由 |
| Pod 网络 | 挂在交换机上 | 挂在路由器上 |
| Pod 掩码 | /24(同网段) | /32(独立主机路由) |
| 复杂度 | 简单 | 较复杂 |
graph LR
subgraph "Flannel 同节点通信"
Pod1F["Pod A<br/>10.244.1.2/24"]
Pod2F["Pod B<br/>10.244.1.3/24"]
Bridge["cni0 Bridge<br/>L2 交换"]
end
subgraph "Calico 同节点通信"
Pod1C["Pod A<br/>10.244.1.2/32"]
Pod2C["Pod B<br/>10.244.1.3/32"]
Router["L3 路由"]
end
Pod1F --> Bridge
Pod2F --> Bridge
Pod1C --> Router
Pod2C --> Router
45.2 TUN/TAP 设备原理¶
45.2.1 什么是 TUN/TAP 设备¶
TUN/TAP 是 Linux 内核提供的虚拟网络设备,用于在用户空间和内核空间之间传递网络数据。
graph TB
subgraph "用户空间 User Space"
App["应用程序<br/>如 flanneld"]
FD["/dev/net/tun<br/>字符设备"]
end
subgraph "内核空间 Kernel Space"
Stack["TCP/IP 协议栈"]
TUN["TUN/TAP 设备<br/>flannel0"]
NIC["物理网卡<br/>eth0"]
end
App <-->|"read/write"| FD
FD <-->|"数据传递"| TUN
TUN <-->|"L3/L2"| Stack
Stack <--> NIC
45.2.2 TUN 与 TAP 的区别¶
| 特性 | TUN 设备 | TAP 设备 |
|---|---|---|
| 工作层次 | L3 网络层 | L2 数据链路层 |
| 处理数据 | IP 包(裸 IP) | 以太网帧(含 MAC) |
| MAC 地址 | 无 | 有 |
| 典型应用 | VPN、Flannel UDP | 虚拟机网桥、OpenVPN TAP |
| 抓包格式 | raw IP packet | 完整以太网帧 |
graph LR
subgraph "TUN 设备 - L3"
TUN["flannel0<br/>TUN"]
IP["IP 包<br/>无 MAC"]
end
subgraph "TAP 设备 - L2"
TAP["tap0<br/>TAP"]
ETH["以太网帧<br/>有 MAC"]
end
TUN --> IP
TAP --> ETH
45.2.3 TUN/TAP 数据传输流程¶
sequenceDiagram
participant App as 应用程序 A
participant Stack as TCP/IP 协议栈
participant TUN as TUN 设备
participant DEV as /dev/net/tun
participant Proc as flanneld 进程
participant NIC as 物理网卡
App->>Stack: 1. 发送数据包
Stack->>TUN: 2. 路由到 TUN 设备
TUN->>DEV: 3. 写入字符设备
DEV->>Proc: 4. 用户空间读取
Proc->>Proc: 5. 封装处理
Proc->>Stack: 6. 重新发送
Stack->>NIC: 7. 物理网卡发出
数据流经过程说明:
- 应用发包:用户空间应用产生数据包
- 协议栈处理:经过 TCP/IP 协议栈
- 路由到 TUN:根据路由表转发到 TUN 设备
- 字符设备读取:数据写入
/dev/net/tun - 用户空间处理:flanneld 进程读取数据
- 封装转发:flanneld 封装后重新发送
- 物理发送:通过物理网卡发出
45.3 TUN/TAP 与内核模块的效率对比¶
45.3.1 效率差异¶
graph TB
subgraph "TUN/TAP 方式 - 效率较低"
U1["用户空间"] --> K1["内核空间"]
K1 --> U1
U1 --> K2["内核空间"]
K2 --> Out1["物理网卡"]
end
subgraph "内核模块方式 - 效率较高"
KM["内核模块<br/>VxLAN"]
KM --> Out2["物理网卡"]
end
| 方式 | 数据路径 | 上下文切换 | 效率 |
|---|---|---|---|
| TUN/TAP | 用户空间 ↔ 内核空间 多次 | 多次 | 较低 |
| 内核模块 | 全程内核空间 | 无 | 较高 |
45.3.2 为什么 TUN/TAP 效率低¶
flowchart LR
A["内核空间<br/>原始数据"] --> B["用户空间<br/>数据拷贝"]
B --> C["用户空间处理"]
C --> D["内核空间<br/>数据拷贝"]
D --> E["物理网卡"]
style B fill:#f9f,stroke:#333
style D fill:#f9f,stroke:#333
效率低的原因:
- 数据拷贝开销:内核 ↔ 用户空间多次拷贝
- 上下文切换:用户态/内核态切换消耗 CPU
- 中断处理:额外的中断开销
- 调度延迟:用户空间进程调度不确定
[!NOTE] 典型案例:OpenVPN 使用 TUN/TAP 设备,效率低于 WireGuard(内核模块实现)
45.4 Flannel 中的 TUN 设备实践¶
45.4.1 查看 TUN 设备¶
# 查看网络设备详情
ip -d link show flannel0
# 输出示例
# flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1472 qdisc pfifo_fast
# link/none
# tun type tun pi off vnet_hdr off persist off
关键信息解读:
| 字段 | 含义 |
|---|---|
link/none |
无 MAC 地址(TUN 设备特征) |
tun type tun |
TUN 类型设备 |
POINTOPOINT |
点对点连接 |
45.4.2 查看 TUN/TAP 设备类型¶
# 列出所有 TUN/TAP 设备
ip tuntap list
# 输出示例
# flannel0: tun
45.4.3 查看设备关联进程¶
# 查看 TUN 设备关联的进程
ip -d tuntap show
# 输出示例
# flannel0: tun persist
# Attached to processes: flanneld(1220)
这说明:
flannel0设备一端连接 TCP/IP 协议栈- 另一端连接用户空间的
flanneld进程
45.4.4 验证进程¶
# 查看 flanneld 进程
ps aux | grep flanneld
# 输出示例
# root 1220 0.0 0.5 1234567 12345 ? Sl 10:00 0:01 /opt/bin/flanneld
# 查看网络连接
netstat -anp | grep flanneld
45.5 Flannel UDP 模式配置¶
45.5.1 配置示例¶
# ConfigMap 配置
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "udp"
}
}
45.5.2 挂载 TUN 设备¶
在非特权模式下,需要显式挂载 /dev/net/tun:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds
spec:
template:
spec:
containers:
- name: kube-flannel
volumeMounts:
- name: dev-net-tun
mountPath: /dev/net/tun
volumes:
- name: dev-net-tun
hostPath:
path: /dev/net/tun
[!WARNING] 非特权模式注意事项
在非特权模式(
privileged: false)下:
- 必须显式挂载
/dev/net/tun- 需要添加
NET_ADMIN和NET_RAWcapabilities- 特权模式下无需额外配置
45.5.3 验证 UDP 模式¶
# 查看 flanneld 日志
kubectl logs -n kube-flannel -l app=flannel
# 应该看到
# Found network config - Backend type: udp
45.6 其他 TUN/TAP 应用场景¶
45.6.1 常见应用¶
| 应用 | 设备类型 | 用途 |
|---|---|---|
| OpenVPN | TUN/TAP | VPN 隧道 |
| Flannel UDP | TUN | 容器网络 |
| Calico VPP | TUN | 高性能转发 |
| 虚拟机网桥 | TAP | VM 网络 |
| WireGuard | 内核模块 | 高效 VPN |
45.6.2 TUN 设备在 Calico VPP 中的应用¶
graph LR
Pod["Pod"] --> TUN["TUN 设备"]
TUN -->|"点对点"| VPP["VPP<br/>用户空间"]
VPP --> DPDK["DPDK"]
DPDK --> NIC["物理网卡"]
45.7 章节小结¶
mindmap
root((TUN/TAP 精讲))
设备类型
TUN - L3 网络层
TAP - L2 数据链路层
工作原理
用户空间 ↔ 内核空间
字符设备 /dev/net/tun
一端协议栈 一端进程
效率对比
TUN/TAP 多次拷贝
内核模块 全程内核
应用场景
Flannel UDP
OpenVPN
Calico VPP
实践验证
ip tuntap list
ip -d tuntap show
[!TIP] TUN/TAP 设备要点总结:
- 设备类型:
- TUN:L3 网络层,处理 IP 包,无 MAC 地址
TAP:L2 数据链路层,处理以太网帧,有 MAC 地址
工作原理:
- 一端连接 TCP/IP 协议栈(内核空间)
- 一端连接用户空间进程(如 flanneld)
通过
/dev/net/tun字符设备通信效率对比:
- TUN/TAP:用户空间处理,效率较低
内核模块(VxLAN):全程内核处理,效率较高
Flannel 配置:
- UDP 模式使用 TUN 设备
- 非特权模式需挂载
/dev/net/tun需要
NET_ADMIN和NET_RAW权限验证命令:
ip tuntap list:列出 TUN/TAP 设备ip -d tuntap show:查看设备关联进程
第四十六章 Flannel-UDP 模式¶
本章深入讲解 Flannel 的 UDP 封装模式,包括同节点和跨节点的 Pod 通信原理及数据包封装机制。
46.1 背景与概述¶
46.1.1 Flannel 后端类型¶
Flannel 支持多种后端(Backend)类型:
| 后端类型 | 封装方式 | 性能 | 适用场景 |
|---|---|---|---|
| UDP | TUN + UDP 封装 | 较低 | 通用/测试 |
| VxLAN | 内核 VxLAN | 较高 | 生产推荐 |
| host-gw | 主机路由 | 最高 | 同子网 |
| IPIP | IP-in-IP | 中等 | 跨子网 |
46.1.2 UDP 模式特点¶
graph TB
subgraph "UDP 模式架构"
Pod["Pod"] --> TUN["flannel0<br/>TUN 设备"]
TUN --> FD["flanneld 进程<br/>用户空间"]
FD -->|"UDP 封装"| ETH["eth0<br/>物理网卡"]
end
UDP 模式核心特点:
- 使用 TUN 设备连接内核与用户空间
- flanneld 进程在用户空间处理封装
- 通过 UDP 协议封装原始 IP 包
- 监听端口:8285
46.2 同节点 Pod 通信¶
46.2.1 通信架构¶
同节点 Pod 通信通过 Linux Bridge 实现 L2 交换。
graph TB
subgraph "Worker 节点"
subgraph "Pod A"
PodA["10.244.2.2/24"]
EthA["eth0"]
end
subgraph "Pod B"
PodB["10.244.2.3/24"]
EthB["eth0"]
end
CNI0["cni0<br/>Linux Bridge"]
VethA["veth-podA"]
VethB["veth-podB"]
end
EthA --> VethA
EthB --> VethB
VethA --> CNI0
VethB --> CNI0
46.2.2 通信原理¶
路由表分析(Pod 内部):
# Pod 内查看路由
ip route
# 输出示例
10.244.2.0/24 dev eth0 scope link # 同网段走 L2
default via 10.244.2.1 dev eth0 # 跨网段走网关
关键点:
| 特征 | 说明 |
|---|---|
| 掩码 | /24(同网段) |
| 网关 | 0.0.0.0(无网关) |
| 通信层次 | L2 交换 |
| 设备 | cni0 Bridge |
46.2.3 Bridge MAC 地址学习¶
# 查看网卡所属 Bridge
ip -d link show veth-pod
# 输出示例
# veth-pod: ... master cni0 ...
# 查看 Bridge MAC 表
bridge fdb show dev cni0
# 输出示例
# 8e:b1:82:xx:xx:xx dev veth-podA master cni0
# 7a:c3:91:xx:xx:xx dev veth-podB master cni0
46.3 跨节点 Pod 通信¶
46.3.1 通信架构¶
sequenceDiagram
participant PodA as Pod A<br/>10.244.2.3
participant TUN as flannel0<br/>TUN
participant FD as flanneld
participant ETH as eth0
participant Net as 物理网络
participant ETH2 as eth0
participant FD2 as flanneld
participant TUN2 as flannel0
participant PodB as Pod B<br/>10.244.0.27
PodA->>TUN: 1. IP 包发送
TUN->>FD: 2. 读取数据
FD->>FD: 3. 查询目标节点
FD->>ETH: 4. UDP 封装发送
ETH->>Net: 5. 物理传输
Net->>ETH2: 6. 到达目标节点
ETH2->>FD2: 7. 接收 UDP
FD2->>TUN2: 8. 解封装写入
TUN2->>PodB: 9. 交付目标 Pod
46.3.2 路由表分析¶
# 宿主机路由表
ip route
# 输出示例
10.244.0.0/24 via 10.244.2.1 dev flannel0 # 目标网段走 flannel0
10.244.2.0/24 dev cni0 proto kernel # 本地网段走 cni0
路由匹配过程:
- 目标地址
10.244.0.27 - 匹配路由
10.244.0.0/24 via 10.244.2.1 dev flannel0 - 数据包发送到
flannel0TUN 设备
46.3.3 TUN 设备与 flanneld¶
# 查看 flannel0 设备
ip -d link show flannel0
# 输出示例
# flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP>
# link/none
# tun type tun pi off
# 查看关联进程
ip -d tuntap show
# 输出示例
# flannel0: tun persist
# Attached to processes: flanneld(1220)
46.3.4 flanneld 工作原理¶
flowchart TB
subgraph "flanneld 进程"
Read["读取 TUN 数据"]
Query["查询 API Server<br/>获取目标节点信息"]
Encap["UDP 封装"]
Send["发送到目标节点"]
end
TUN["flannel0"] --> Read
Read --> Query
Query --> Encap
Encap --> Send
Send --> ETH["eth0"]
flanneld 职责:
| 职责 | 说明 |
|---|---|
| 监听 TUN | 从 flannel0 读取原始 IP 包 |
| 查询路由 | 从 API Server/etcd 获取 Pod 所在节点 |
| UDP 封装 | 将原始包封装为 UDP 数据 |
| 发送数据 | 通过 8285 端口发送到目标节点 |
46.4 数据包封装分析¶
46.4.1 封装结构¶
graph LR
subgraph "原始包"
IP1["IP Header<br/>Src: 10.244.2.3<br/>Dst: 10.244.0.27"]
Data["Payload<br/>ICMP/TCP/..."]
end
subgraph "封装后"
IP2["外层 IP<br/>Src: 172.18.0.3<br/>Dst: 172.18.0.2"]
UDP["UDP Header<br/>Port: 8285"]
IP1_2["原始 IP"]
Data_2["原始 Payload"]
end
IP1 --> IP1_2
Data --> Data_2
46.4.2 封装字段详解¶
| 层次 | 字段 | 值 |
|---|---|---|
| 外层 IP | Src IP | 源节点物理 IP |
| Dst IP | 目标节点物理 IP | |
| UDP | Src Port | 随机高端口 |
| Dst Port | 8285(flanneld) | |
| 内层(原始) | Src IP | 源 Pod IP |
| Dst IP | 目标 Pod IP | |
| Payload | 原始数据 |
46.4.3 抓包验证¶
# 在 flannel0 上抓包(裸 IP)
tcpdump -i flannel0 -nn
# 输出示例(raw IP,无 MAC)
# IP 10.244.2.3 > 10.244.0.27: ICMP echo request
# 在 eth0 上抓包(UDP 封装)
tcpdump -i eth0 -nn port 8285
# 输出示例
# IP 172.18.0.3.xxxxx > 172.18.0.2.8285: UDP, length xxx
Wireshark 解码技巧:
# 将 8285 端口的 UDP 数据解码为 IPv4
Analyze -> Decode As -> UDP port 8285 -> IPv4
46.5 MTU 设置¶
46.5.1 为什么 MTU 是 1472¶
graph LR
ETH["eth0 MTU<br/>1500"] --> UDP["UDP Header<br/>8 bytes"]
UDP --> IP["IP Header<br/>20 bytes"]
IP --> Payload["Payload<br/>1472 bytes"]
MTU 计算:
| 组成 | 大小 |
|---|---|
| 物理网卡 MTU | 1500 bytes |
| - 外层 IP Header | 20 bytes |
| - UDP Header | 8 bytes |
| = flannel0 MTU | 1472 bytes |
[!WARNING] MTU 不匹配问题
如果内层 MTU 设置过大,会导致:
- 数据包分片
- 传输效率降低
- 可能出现通信失败
46.6 UDP 模式配置¶
46.6.1 ConfigMap 配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "udp"
}
}
46.6.2 DaemonSet 配置(非特权模式)¶
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds
spec:
template:
spec:
containers:
- name: kube-flannel
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
volumeMounts:
- name: dev-net-tun
mountPath: /dev/net/tun
volumes:
- name: dev-net-tun
hostPath:
path: /dev/net/tun
[!IMPORTANT] 非特权模式必须挂载 /dev/net/tun
否则 flanneld 无法创建 flannel0 TUN 设备,Pod 会 CrashLoopBackOff
46.7 UDP 模式 vs VxLAN 模式¶
46.7.1 效率对比¶
graph TB
subgraph "UDP 模式 - 效率较低"
U1["内核空间"] -->|"数据拷贝"| U2["用户空间<br/>flanneld"]
U2 -->|"数据拷贝"| U3["内核空间"]
U3 --> U4["eth0"]
end
subgraph "VxLAN 模式 - 效率较高"
V1["内核空间"] --> V2["VxLAN 模块<br/>内核"]
V2 --> V3["eth0"]
end
46.7.2 对比表¶
| 特性 | UDP 模式 | VxLAN 模式 |
|---|---|---|
| 封装位置 | 用户空间(flanneld) | 内核空间 |
| 数据拷贝 | 多次(内核↔用户) | 少(内核内) |
| 上下文切换 | 有 | 无 |
| 性能 | 较低 | 较高 |
| 调试难度 | 较易 | 较难 |
| 生产推荐 | ❌ | ✅ |
46.8 章节小结¶
mindmap
root((Flannel UDP 模式))
同节点通信
cni0 Bridge
L2 交换
veth pair
跨节点通信
flannel0 TUN
flanneld 用户空间
UDP 封装 Port 8285
数据封装
外层 IP 节点地址
UDP Header
内层 IP Pod 地址
配置要点
Backend Type udp
挂载 /dev/net/tun
MTU 1472
效率对比
低于 VxLAN
用户空间处理
[!TIP] Flannel UDP 模式要点总结:
- 同节点通信:
- 通过
cni0Linux Bridge 实现 L2 交换- Pod 使用 /24 掩码,在同一网段
无需封装,直接二层转发
跨节点通信:
- 数据包发送到
flannel0TUN 设备- flanneld 进程读取并 UDP 封装
通过 8285 端口发送到目标节点
封装机制:
- 外层:节点 IP + UDP 8285
- 内层:原始 Pod IP + 数据
MTU 设置为 1472(1500-20-8)
配置要点:
- Backend Type 设置为
udp- 非特权模式需挂载
/dev/net/tun需要
NET_ADMIN和NET_RAW权限生产建议:
- UDP 模式效率低于 VxLAN
- 生产环境推荐使用 VxLAN 或 host-gw
第四十七章 Flannel-VxLAN 模式¶
本章深入讲解 Flannel 的 VxLAN 封装模式,包括配置方法、内核封装机制、ARP/FDB 表工作原理,以及 DirectRouting 优化模式。
47.1 背景与概述¶
47.1.1 VxLAN 模式定位¶
Flannel VxLAN 模式是生产环境推荐的后端类型:
graph TB
subgraph "Flannel 后端选择"
UDP["UDP 模式<br/>❌ 不推荐"]
VxLAN["VxLAN 模式<br/>✅ 生产推荐"]
HostGW["host-gw 模式<br/>✅ 同子网最优"]
end
VxLAN -->|"跨子网"| Best["最佳选择"]
HostGW -->|"同子网"| Best
47.1.2 与 Calico VxLAN 的关系¶
Flannel 官方明确表示 VxLAN 模式设计目标是支持从 Calico 迁移:
| 特性 | Flannel VxLAN | Calico VxLAN |
|---|---|---|
| 封装机制 | 相同 | 相同 |
| FDB 表 | 相同 | 相同 |
| VNI | 1 | 4096 |
| 端口 | 8472 | 4789 |
| 网络策略 | 不支持 | 支持 |
[!TIP] 生产建议
- 如需网络策略功能,使用 Calico
- 如追求简单稳定,使用 Flannel
- 可使用 Flannel + Calico 组合:Flannel 负责网络,Calico 负责策略
47.2 VxLAN 配置¶
47.2.1 ConfigMap 配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
47.2.2 验证 VxLAN 设备¶
# 查看 flannel.1 VxLAN 设备
ip -d link show flannel.1
# 输出示例
# flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP>
# link/ether 7c:a2:9a:xx:xx:xx brd ff:ff:ff:ff:ff:ff
# vxlan id 1 local 172.18.0.2 dev eth0 srcport 0 0 dstport 8472 ...
# 查看 FDB 表
bridge fdb show dev flannel.1
47.3 VxLAN 通信原理¶
47.3.1 通信架构¶
sequenceDiagram
participant PodA as Pod A<br/>10.244.0.5
participant CNI as cni0
participant FL as flannel.1
participant Kernel as 内核 VxLAN
participant ETH as eth0
participant Net as 物理网络
participant ETH2 as eth0
participant Kernel2 as 内核 VxLAN
participant FL2 as flannel.1
participant CNI2 as cni0
participant PodB as Pod B<br/>10.244.2.2
PodA->>CNI: 1. 发送 IP 包
CNI->>FL: 2. 路由转发
FL->>Kernel: 3. VxLAN 封装
Kernel->>ETH: 4. UDP:8472
ETH->>Net: 5. 物理传输
Net->>ETH2: 6. 到达目标
ETH2->>Kernel2: 7. 接收 UDP
Kernel2->>FL2: 8. VxLAN 解封装
FL2->>CNI2: 9. 路由转发
CNI2->>PodB: 10. 交付 Pod
47.3.2 路由表分析¶
# 宿主机路由表
ip route
# 输出示例
10.244.0.0/24 dev cni0 proto kernel scope link # 本地网段
10.244.2.0/24 via 10.244.2.0 dev flannel.1 # 远端网段走 flannel.1
路由决策过程:
- Pod 发送数据包到
10.244.2.2 - 匹配路由
10.244.2.0/24 via 10.244.2.0 dev flannel.1 - 下一跳
10.244.2.0,出接口flannel.1
47.4 ARP 与 FDB 表机制¶
47.4.1 工作流程¶
flowchart TB
subgraph "封装过程"
Route["查询路由表<br/>确定下一跳"]
ARP["查询 ARP 表<br/>获取下一跳 MAC"]
FDB["查询 FDB 表<br/>获取目标节点 IP"]
Encap["VxLAN 封装<br/>发送到目标节点"]
end
Route --> ARP
ARP --> FDB
FDB --> Encap
47.4.2 ARP 表作用¶
# 查看 ARP 缓存
arp -n
# 或在 Pod 内查看
ip neigh
# 输出示例
10.244.2.0 dev flannel.1 lladdr 24:81:58:xx:xx:xx PERMANENT
ARP 表功能:
- 存储下一跳 IP 对应的 MAC 地址
- 用于封装内层以太网帧
47.4.3 FDB 表作用¶
# 查看 FDB 表
bridge fdb show dev flannel.1
# 输出示例
24:81:58:xx:xx:xx dev flannel.1 dst 172.18.0.4 self permanent
FDB 表功能:
| 作用 | 说明 |
|---|---|
| MAC → Node IP | 将目标 MAC 映射到节点 IP |
| 确定封装目标 | 外层 IP 的目标地址 |
| 由 flanneld 维护 | 通过 API Server 同步 |
47.5 内核空间封装优势¶
47.5.1 VxLAN vs UDP 模式¶
graph TB
subgraph "VxLAN 模式 - 内核空间"
V1["Pod 数据"] --> V2["内核路由"]
V2 --> V3["VxLAN 模块<br/>内核封装"]
V3 --> V4["eth0 发送"]
end
subgraph "UDP 模式 - 用户空间"
U1["Pod 数据"] --> U2["内核路由"]
U2 --> U3["TUN 设备"]
U3 -->|"上下文切换"| U4["flanneld<br/>用户空间封装"]
U4 -->|"上下文切换"| U5["eth0 发送"]
end
47.5.2 性能对比¶
| 指标 | VxLAN 模式 | UDP 模式 |
|---|---|---|
| 封装位置 | 内核空间 | 用户空间 |
| 上下文切换 | 无 | 有 |
| 数据拷贝 | 少 | 多 |
| CPU 开销 | 低 | 高 |
| 性能 | 高 | 低 |
| 端口 | 8472 | 8285 |
47.5.3 识别内核进程¶
# 查看 8472 端口
netstat -anp | grep 8472
# 输出示例
udp 0 0 0.0.0.0:8472 0.0.0.0:* -
# 注意: 进程名为 "-" 表示内核空间进程
# 用户空间进程会显示如: flanneld(1220)
47.6 抓包分析¶
47.6.1 抓包命令¶
# 在 eth0 上抓取 VxLAN 流量
tcpdump -i eth0 -nn port 8472 -w vxlan.pcap
# 测试跨节点通信
kubectl exec -it test-pod -- curl http://10.244.2.2:80
47.6.2 Wireshark 解码¶
# 将 8472 端口解码为 VxLAN
Analyze -> Decode As -> UDP port 8472 -> VxLAN
47.6.3 封装结构¶
graph LR
subgraph "VxLAN 封装"
L2_Out["外层 MAC"]
IP_Out["外层 IP<br/>节点 IP"]
UDP["UDP:8472"]
VXLAN["VxLAN Header<br/>VNI=1"]
L2_In["内层 MAC"]
IP_In["内层 IP<br/>Pod IP"]
Data["TCP/HTTP..."]
end
L2_Out --> IP_Out --> UDP --> VXLAN --> L2_In --> IP_In --> Data
47.7 DirectRouting 模式¶
47.7.1 概念说明¶
DirectRouting 是 VxLAN 模式的优化选项:
| 场景 | 通信方式 |
|---|---|
| 节点在同一二层 | 直接路由(无封装) |
| 节点跨二层 | VxLAN 封装 |
[!NOTE] 与 Calico CrossSubnet 相同
DirectRouting 功能等同于 Calico 的
ipipMode: CrossSubnet或vxlanMode: CrossSubnet
47.7.2 配置方法¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan",
"DirectRouting": true
}
}
47.7.3 路由表变化¶
# 启用 DirectRouting 后的路由表
ip route
# 同二层节点 - 直接路由
10.244.1.0/24 via 10.1.5.11 dev eth0 # 下一跳是节点 IP
# 跨二层节点 - VxLAN 封装
10.244.2.0/24 via 10.244.2.0 dev flannel.1 # 走 flannel.1
47.7.4 拓扑示例¶
graph TB
subgraph "二层域 A (10.1.5.0/24)"
N1["Node1<br/>10.1.5.10"]
N2["Node2<br/>10.1.5.11"]
end
subgraph "二层域 B (10.1.8.0/24)"
N3["Node3<br/>10.1.8.10"]
N4["Node4<br/>10.1.8.11"]
end
GW["网关/路由器"]
N1 <-->|"直接路由"| N2
N3 <-->|"直接路由"| N4
N1 <-->|"VxLAN"| GW
GW <-->|"VxLAN"| N3
47.7.5 抓包验证¶
# 同二层通信 - 抓 eth0
tcpdump -i eth0 host 10.244.1.2 -w direct.pcap
# 结果: 无 VxLAN 封装,直接 IP 路由
# 跨二层通信 - 抓 flannel.1 或 eth0
tcpdump -i eth0 port 8472 -w vxlan.pcap
# 结果: VxLAN 封装
47.8 VxLAN MTU 设置¶
47.8.1 MTU 计算¶
graph LR
ETH["eth0 MTU<br/>1500"] --> VXLAN["VxLAN Header<br/>50 bytes"]
VXLAN --> Payload["flannel.1 MTU<br/>1450 bytes"]
| 组成 | 大小 |
|---|---|
| 物理网卡 MTU | 1500 bytes |
| - 外层 IP Header | 20 bytes |
| - UDP Header | 8 bytes |
| - VxLAN Header | 8 bytes |
| - 外层 MAC | 14 bytes |
| = flannel.1 MTU | 1450 bytes |
47.9 章节小结¶
mindmap
root((Flannel VxLAN 模式))
配置
Backend Type vxlan
DirectRouting true
设备
flannel.1 VxLAN
端口 8472
VNI 1
机制
ARP 表
FDB 表
内核封装
DirectRouting
同二层走路由
跨二层走VxLAN
优势
内核空间处理
无上下文切换
生产推荐
[!TIP] Flannel VxLAN 模式要点总结:
- 配置方式:
Backend.Type: "vxlan"可选
DirectRouting: true优化同二层通信核心机制:
- ARP 表:下一跳 IP → MAC 地址
- FDB 表:MAC 地址 → 目标节点 IP
内核封装:UDP 8472 端口
与 UDP 模式对比:
- VxLAN 在内核空间封装,效率更高
- 无上下文切换,无多次数据拷贝
端口 8472(vs UDP 的 8285)
DirectRouting 模式:
- 同二层节点:直接路由,不封装
- 跨二层节点:VxLAN 封装
等同于 Calico CrossSubnet
生产建议:
- VxLAN 是 Flannel 生产推荐模式
- 如需网络策略,可结合 Calico 使用
第四十八章 Flannel-IPIP 模式¶
本章深入讲解 Flannel 的 IPIP 封装模式,包括配置方法、裸 IP 设备特性、NOARP 标志原理,以及 DirectRouting 优化模式。
48.1 背景与概述¶
48.1.1 IPIP 模式定位¶
IPIP(IP-in-IP)是一种轻量级的隧道封装方式:
graph TB
subgraph "封装方式对比"
VxLAN["VxLAN<br/>UDP + VxLAN Header<br/>50 bytes 开销"]
IPIP["IPIP<br/>IP Header<br/>20 bytes 开销"]
HostGW["host-gw<br/>无封装<br/>0 bytes 开销"]
end
VxLAN -->|"跨子网"| Use["适用场景"]
IPIP -->|"跨子网"| Use
HostGW -->|"同子网"| Use
48.1.2 与 Calico IPIP 的关系¶
| 特性 | Flannel IPIP | Calico IPIP |
|---|---|---|
| 封装机制 | IP-in-IP | IP-in-IP |
| 默认模式 | 需手动配置 | 默认开启 |
| BGP 路由 | 不支持 | 支持 |
| 网络策略 | 不支持 | 支持 |
[!NOTE] Flannel vs Calico IPIP
- Calico IPIP 模式结合了 BGP 路由宣告
- Flannel IPIP 仅做封装,路由由 flanneld 静态维护
48.2 IPIP 配置¶
48.2.1 ConfigMap 配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "ipip"
}
}
48.2.2 验证 IPIP 设备¶
# 查看 flannel.ipip 设备
ip -d link show flannel.ipip
# 输出示例
# flannel.ipip: <POINTOPOINT,NOARP,UP,LOWER_UP>
# link/ipip 172.18.0.3 brd 0.0.0.0
# ipip remote any local 172.18.0.3 ...
# 查看路由表
ip route | grep flannel.ipip
# 10.244.1.0/24 via 172.18.0.4 dev flannel.ipip
48.3 IPIP 通信原理¶
48.3.1 通信架构¶
sequenceDiagram
participant PodA as Pod A<br/>10.244.0.5
participant CNI as cni0
participant IPIP as flannel.ipip
participant Kernel as 内核 IPIP
participant ETH as eth0
participant Net as 物理网络
participant ETH2 as eth0
participant Kernel2 as 内核 IPIP
participant IPIP2 as flannel.ipip
participant CNI2 as cni0
participant PodB as Pod B<br/>10.244.1.2
PodA->>CNI: 1. 发送 IP 包
CNI->>IPIP: 2. 路由转发
IPIP->>Kernel: 3. IPIP 封装
Kernel->>ETH: 4. 外层 IP
ETH->>Net: 5. 物理传输
Net->>ETH2: 6. 到达目标
ETH2->>Kernel2: 7. 接收
Kernel2->>IPIP2: 8. IPIP 解封装
IPIP2->>CNI2: 9. 路由转发
CNI2->>PodB: 10. 交付 Pod
48.3.2 路由表分析¶
# 宿主机路由表
ip route
# 输出示例
10.244.0.0/24 dev cni0 proto kernel scope link # 本地网段
10.244.1.0/24 via 172.18.0.4 dev flannel.ipip # 远端走 IPIP
路由决策过程:
- Pod 发送数据包到
10.244.1.2 - 匹配路由
10.244.1.0/24 via 172.18.0.4 dev flannel.ipip - 下一跳
172.18.0.4(目标节点),出接口flannel.ipip
48.4 裸 IP 设备特性¶
48.4.1 Raw 设备说明¶
flannel.ipip 是一个 裸 IP(Raw IP) 设备:
graph LR
subgraph "普通以太网设备"
E1["MAC Header"]
E2["IP Header"]
E3["Payload"]
end
subgraph "Raw IP 设备"
R1["IP Header"]
R2["Payload"]
end
E1 --> E2 --> E3
R1 --> R2
特点:
| 特性 | 普通网卡 (eth0) | Raw 设备 (flannel.ipip) |
|---|---|---|
| MAC 层 | 有 | 无 |
| 抓包显示 | 有 MAC 地址 | 无 MAC 地址 |
| tcpdump 类型 | LINUX_SLL | Raw |
48.4.2 抓包验证¶
# 在 flannel.ipip 上抓包 - 无 MAC 层
tcpdump -i flannel.ipip -nn
# 输出示例 - 只有 IP 信息,无 MAC
# IP 10.244.0.5 > 10.244.1.2: ICMP echo request
# 在 eth0 上抓包 - 有完整 MAC 层
tcpdump -i eth0 -nn ip proto 4
# 输出示例 - IPIP 封装
# IP 172.18.0.3 > 172.18.0.4: IP 10.244.0.5 > 10.244.1.2 ...
48.5 NOARP 标志详解¶
48.5.1 标志含义¶
# 查看设备标志
ip link show flannel.ipip
# 输出示例
# flannel.ipip: <POINTOPOINT,NOARP,UP,LOWER_UP>
| 标志 | 含义 |
|---|---|
| POINTOPOINT | 点对点设备 |
| NOARP | 禁用 ARP 协议 |
| UP | 设备启用 |
| LOWER_UP | 底层链路可用 |
48.5.2 为什么禁用 ARP¶
flowchart TB
subgraph "ARP 功能"
ARP["ARP 协议<br/>解析 IP → MAC"]
GARP["GARP 消息<br/>地址冲突检测"]
end
subgraph "NOARP 原因"
R1["裸 IP 设备无 MAC 层"]
R2["避免地址冲突告警"]
R3["隧道封装不需要 MAC"]
end
ARP --> R1
GARP --> R2
禁用 ARP 的场景:
- 裸 IP 设备:flannel.ipip 没有 MAC 层,ARP 无意义
- 避免 GARP 冲突:多节点配置相同隧道网段时,避免 GARP 告警
- 隧道封装:IPIP/GRE 隧道直接使用 IP 封装,不需要 MAC 解析
[!TIP] NOARP 实际应用
在 FE/BE(前端/后端)负载均衡架构中,BE 节点可能需要配置与 VIP 相同的地址用于响应。 此时需要设置 NOARP 避免 GARP 冲突告警。
48.6 封装结构对比¶
48.6.1 IPIP vs VxLAN¶
graph LR
subgraph "IPIP 封装"
I1["外层 IP<br/>20 bytes"]
I2["内层 IP<br/>Pod 地址"]
I3["Payload"]
end
subgraph "VxLAN 封装"
V1["外层 MAC<br/>14 bytes"]
V2["外层 IP<br/>20 bytes"]
V3["UDP<br/>8 bytes"]
V4["VxLAN<br/>8 bytes"]
V5["内层 MAC<br/>14 bytes"]
V6["内层 IP"]
V7["Payload"]
end
I1 --> I2 --> I3
V1 --> V2 --> V3 --> V4 --> V5 --> V6 --> V7
48.6.2 开销对比¶
| 封装方式 | 额外开销 | MTU (1500基础) |
|---|---|---|
| IPIP | 20 bytes | 1480 |
| VxLAN | 50 bytes | 1450 |
| host-gw | 0 bytes | 1500 |
48.7 DirectRouting 模式¶
48.7.1 配置方法¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "ipip",
"DirectRouting": true
}
}
48.7.2 工作原理¶
graph TB
subgraph "节点 A (10.1.5.10)"
PodA["Pod A"]
end
subgraph "节点 B (10.1.5.11) - 同二层"
PodB["Pod B"]
end
subgraph "节点 C (10.1.8.10) - 跨二层"
PodC["Pod C"]
end
PodA -->|"直接路由<br/>无封装"| PodB
PodA -->|"IPIP 封装"| PodC
48.7.3 路由表变化¶
# 启用 DirectRouting 后的路由表
ip route
# 同二层节点 - 直接路由
10.244.1.0/24 via 10.1.5.11 dev eth0 # 下一跳是节点 IP,走 eth0
# 跨二层节点 - IPIP 封装
10.244.2.0/24 via 10.1.8.10 dev flannel.ipip # 走 flannel.ipip
48.7.4 抓包验证¶
# 同二层通信 - 抓 eth0,port 80
tcpdump -i eth0 port 80 -nn -w direct.pcap
# 结果: 普通 TCP 包,无 IPIP 封装
# 跨二层通信 - 抓 IPIP 协议
tcpdump -i eth0 -nn ip proto 4 -w ipip.pcap
# 结果: IPIP 封装包
48.8 抓包分析技巧¶
48.8.1 IPIP 协议过滤¶
# IPIP 协议号为 4
tcpdump -i eth0 -nn ip proto 4
# 或使用协议名
tcpdump -i eth0 -nn ipip
48.8.2 查看 HTTP 内容¶
# 使用 -X 或 -A 查看内容
tcpdump -i eth0 -nn -A port 80
# 输出示例
# GET / HTTP/1.1
# Host: 10.244.1.2
# ...
# HTTP/1.1 200 OK
# flannel-pod-name: xxx
48.9 章节小结¶
mindmap
root((Flannel IPIP 模式))
配置
Backend Type ipip
DirectRouting true
设备
flannel.ipip
Raw IP 裸设备
NOARP 标志
封装
20 bytes 开销
MTU 1480
轻量高效
DirectRouting
同二层走路由
跨二层走IPIP
与VxLAN对比
开销更小
无MAC层
内核封装
[!TIP] Flannel IPIP 模式要点总结:
- 配置方式:
Backend.Type: "ipip"可选
DirectRouting: true优化同二层通信设备特性:
flannel.ipip是 Raw IP 裸设备- 无 MAC 层,抓包只有 IP 信息
NOARP 标志禁用 ARP 协议
封装开销:
- 仅 20 bytes(外层 IP Header)
- 比 VxLAN 少 30 bytes
MTU = 1500 - 20 = 1480
DirectRouting 模式:
- 同二层节点:直接路由,走 eth0
- 跨二层节点:IPIP 封装,走 flannel.ipip
等同于 Calico CrossSubnet
抓包技巧:
- IPIP 协议:
tcpdump ip proto 4- flannel.ipip 上只能看到裸 IP,无 MAC
第四十九章 Flannel-HostGW 模式¶
本章深入讲解 Flannel 的 Host-Gateway 模式,包括配置方法、纯路由通信原理、MAC 地址变化规律,以及为什么只能在同二层网络中使用。
49.1 背景与概述¶
49.1.1 HostGW 模式定位¶
Host-Gateway 是 Flannel 中性能最高的后端类型:
graph TB
subgraph "Flannel 后端性能对比"
UDP["UDP 模式<br/>❌ 性能最低"]
VxLAN["VxLAN 模式<br/>⭐ 中等"]
IPIP["IPIP 模式<br/>⭐⭐ 较高"]
HostGW["host-gw 模式<br/>⭐⭐⭐ 最高"]
end
UDP -->|"封装开销大"| Low["低效"]
VxLAN -->|"50 bytes 开销"| Mid["中等"]
IPIP -->|"20 bytes 开销"| High["较高"]
HostGW -->|"无封装"| Best["最优"]
49.1.2 核心特点¶
| 特性 | 说明 |
|---|---|
| 封装方式 | 无封装 |
| MTU | 1500(无损耗) |
| 通信方式 | 纯三层路由 |
| 适用场景 | 同二层网络 |
| 性能 | 最高 |
49.2 HostGW 配置¶
49.2.1 ConfigMap 配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
49.2.2 路由表验证¶
# 查看宿主机路由表
ip route
# 输出示例
10.244.0.0/24 dev cni0 proto kernel scope link # 本地网段
10.244.1.0/24 via 172.18.0.4 dev eth0 # 远端走物理网卡
10.244.2.0/24 via 172.18.0.5 dev eth0 # 远端走物理网卡
关键观察:
- 远端网段的出接口是
eth0(物理网卡) - 下一跳是目标节点的物理 IP
- 没有
flannel.1、flannel.ipip等隧道设备
49.3 HostGW 通信原理¶
49.3.1 通信架构¶
sequenceDiagram
participant PodA as Pod A<br/>10.244.0.5
participant CNI as cni0
participant Host1 as 宿主机 1<br/>172.18.0.3
participant Net as 物理网络<br/>二层交换
participant Host2 as 宿主机 2<br/>172.18.0.4
participant CNI2 as cni0
participant PodB as Pod B<br/>10.244.1.2
PodA->>CNI: 1. 发送 IP 包
CNI->>Host1: 2. 查路由表
Host1->>Net: 3. 直接转发<br/>无封装
Net->>Host2: 4. 二层交换
Host2->>CNI2: 5. 查路由表
CNI2->>PodB: 6. 交付 Pod
49.3.2 路由核心原则¶
[!IMPORTANT] 纯路由转发的黄金法则
在没有 NAT 和 Overlay 封装的路由网络中:
- IP 地址不变:源 IP 和目标 IP 全程不变
- MAC 地址变化:每经过一跳,MAC 地址都会改变
49.3.3 数据包变化过程¶
graph TB
subgraph "第一跳:Pod → 宿主机"
P1["Src IP: 10.244.0.5"]
P2["Dst IP: 10.244.1.2"]
P3["Src MAC: Pod eth0"]
P4["Dst MAC: cni0 网关"]
end
subgraph "第二跳:宿主机 → 目标宿主机"
Q1["Src IP: 10.244.0.5"]
Q2["Dst IP: 10.244.1.2"]
Q3["Src MAC: eth0"]
Q4["Dst MAC: 目标节点 eth0"]
end
P1 --> Q1
P2 --> Q2
变化规律:
| 字段 | 变化情况 |
|---|---|
| 源 IP | ❌ 不变 |
| 目标 IP | ❌ 不变 |
| 源 MAC | ✅ 变为出接口 MAC |
| 目标 MAC | ✅ 变为下一跳 MAC |
49.4 为什么只能在同二层使用¶
49.4.1 同二层场景¶
graph TB
subgraph "同二层网络 - ✅ 可用 host-gw"
N1["Node1<br/>172.18.0.3"]
N2["Node2<br/>172.18.0.4"]
SW["二层交换机"]
end
N1 <-->|"直接可达"| SW
SW <-->|"直接可达"| N2
同二层时:
- 节点之间可以直接通过 MAC 地址转发
- 只需要配置静态路由,无需封装
- 每个节点都知道如何到达其他节点
49.4.2 跨二层场景¶
graph TB
subgraph "域 A"
N1["Node1<br/>172.18.0.3"]
end
subgraph "路由器"
R1["Router A"]
R2["Router B"]
end
subgraph "域 B"
N2["Node2<br/>10.1.8.10"]
end
N1 --> R1
R1 --> R2
R2 --> N2
跨二层时的问题:
| 问题 | 说明 |
|---|---|
| 路由器不知道 Pod 网段 | 中间路由器没有 10.244.x.0/24 的路由 |
| 无法转发 | 数据包到达路由器后会被丢弃或走默认路由 |
| 需要动态路由协议 | 必须使用 BGP 等协议宣告 Pod 网段 |
[!CAUTION] host-gw 限制
如果节点不在同一二层网络,host-gw 模式不能工作!
跨二层场景请使用:
- VxLAN 模式(推荐)
- IPIP 模式
- VxLAN/IPIP + DirectRouting 混合模式
49.5 抓包分析¶
49.5.1 抓包验证¶
# 在 eth0 上抓包
tcpdump -i eth0 -nn port 80
# 输出示例 - 无封装,直接是原始 IP 包
# 10.244.0.5.xxxxx > 10.244.1.2.80: Flags [S], ...
# 10.244.1.2.80 > 10.244.0.5.xxxxx: Flags [S.], ...
49.5.2 包结构对比¶
graph LR
subgraph "host-gw 模式"
H1["MAC Header"]
H2["IP Header<br/>Pod IP"]
H3["TCP/Payload"]
end
subgraph "VxLAN 模式"
V1["外层 MAC"]
V2["外层 IP"]
V3["UDP + VxLAN"]
V4["内层 MAC"]
V5["内层 IP"]
V6["TCP/Payload"]
end
H1 --> H2 --> H3
V1 --> V2 --> V3 --> V4 --> V5 --> V6
49.6 性能对比¶
49.6.1 各模式对比¶
| 模式 | 封装开销 | MTU | 性能 | 适用场景 |
|---|---|---|---|---|
| host-gw | 0 bytes | 1500 | ⭐⭐⭐ | 同二层 |
| IPIP | 20 bytes | 1480 | ⭐⭐ | 跨二层 |
| VxLAN | 50 bytes | 1450 | ⭐ | 跨二层 |
| UDP | 28 bytes | 1472 | ❌ | 不推荐 |
49.6.2 性能优势原因¶
flowchart LR
subgraph "host-gw 优势"
A["无封装"] --> B["无额外 Header"]
B --> C["MTU 无损耗"]
C --> D["CPU 开销最小"]
D --> E["延迟最低"]
end
49.7 与 Calico 对比¶
| 特性 | Flannel host-gw | Calico (无封装) |
|---|---|---|
| 路由方式 | 静态路由 | BGP 动态路由 |
| 跨子网支持 | ❌ 不支持 | ✅ 支持 |
| 网络策略 | ❌ 不支持 | ✅ 支持 |
| 复杂度 | 简单 | 较复杂 |
[!TIP] 选择建议
- 如果节点在同二层且不需要网络策略:Flannel host-gw
- 如果需要跨子网或网络策略:Calico
49.8 章节小结¶
mindmap
root((Flannel host-gw 模式))
配置
Backend Type host-gw
无封装
原理
纯路由转发
IP 不变 MAC 变
下一跳是节点 IP
限制
只能同二层
跨二层需封装
优势
MTU 1500
零封装开销
性能最高
对比
VxLAN 跨子网
IPIP 轻量封装
Calico BGP
[!TIP] Flannel host-gw 模式要点总结:
- 配置方式:
Backend.Type: "host-gw"路由表直接指向目标节点 IP
核心原理:
- 无封装:直接三层路由转发
- IP 不变 MAC 变:每跳 MAC 地址更新
出接口是物理网卡
eth0使用限制:
- 只能在同二层网络使用
- 跨二层需要封装(VxLAN/IPIP)
或使用 BGP 宣告路由(Calico)
性能优势:
- MTU 1500,无损耗
- 零封装开销
CPU 开销最小
适用场景:
- 所有节点在同一二层网络
- 追求最高网络性能
- 不需要跨子网通信
第五十章 Flannel-IPsec 模式¶
本章深入讲解 Flannel 的 IPsec 模式,包括预共享密钥(PSK)配置、ESP 封装原理、xfrm 状态与策略、Wireshark 解密技巧,以及与其他模式的对比。
50.1 背景与概述¶
50.1.1 IPsec 模式定位¶
IPsec(IP Security)是 Flannel 中提供加密传输的后端类型:
graph TB
subgraph "Flannel 后端功能对比"
UDP["UDP 模式<br/>🔓 不加密"]
VxLAN["VxLAN 模式<br/>🔓 不加密"]
IPIP["IPIP 模式<br/>🔓 不加密"]
HostGW["host-gw 模式<br/>🔓 不加密"]
IPsec["IPsec 模式<br/>🔐 加密"]
WireGuard["WireGuard 模式<br/>🔐 加密"]
end
IPsec -->|"ESP 封装+加密"| Secure["安全传输"]
WireGuard -->|"现代加密"| Secure
50.1.2 核心特点¶
| 特性 | 说明 |
|---|---|
| 加密协议 | ESP(Encapsulating Security Payload) |
| 密钥类型 | PSK(Pre-Shared Key) |
| 封装模式 | Tunnel Mode |
| 适用场景 | 跨不可信网络的安全通信 |
| 性能开销 | 加解密消耗 CPU |
50.2 IPsec 配置¶
50.2.1 生成 PSK¶
# 生成 96 位(12 字节)预共享密钥
dd if=/dev/urandom bs=12 count=1 2>/dev/null | base64
# 输出示例:iVzSdJHgXWNqpuJE
50.2.2 ConfigMap 配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "ipsec",
"PSK": "iVzSdJHgXWNqpuJE"
}
}
[!IMPORTANT] PSK 配置要点
- PSK 是必选参数,至少 96 位(12 字节 Base64)
- 所有节点必须使用相同的 PSK
- 生产环境建议使用更长的密钥
50.3 IPsec 工作原理¶
50.3.1 IPsec Tunnel 模式架构¶
sequenceDiagram
participant PodA as Pod A<br/>10.244.0.5
participant Kernel1 as Node1 内核<br/>ESP 封装
participant Network as 物理网络<br/>加密传输
participant Kernel2 as Node2 内核<br/>ESP 解封
participant PodB as Pod B<br/>10.244.1.2
PodA->>Kernel1: 1. 原始 IP 包
Kernel1->>Kernel1: 2. xfrm policy 匹配
Kernel1->>Kernel1: 3. ESP 加密+封装
Kernel1->>Network: 4. 加密数据包
Network->>Kernel2: 5. 传输
Kernel2->>Kernel2: 6. ESP 解密+解封
Kernel2->>PodB: 7. 原始 IP 包
50.3.2 ESP 包结构¶
graph LR
subgraph "IPsec ESP Tunnel 模式"
A["外层 MAC<br/>节点 MAC"]
B["外层 IP<br/>节点 IP"]
C["ESP Header<br/>SPI + 序列号"]
D["加密载荷<br/>内层 IP + 数据"]
E["ESP Trailer<br/>填充 + 认证"]
end
A --> B --> C --> D --> E
关键特点:
- 内层无 MAC:IPsec 是三层协议,封装时不包含内层 MAC
- 整体加密:内层 IP 包被完全加密
- 完整性校验:ESP 提供认证功能
50.4 xfrm 状态与策略¶
50.4.1 查看 xfrm 状态¶
# 查看 IPsec SA(Security Association)
ip xfrm state
# 输出示例
src 172.18.0.3 dst 172.18.0.4
proto esp spi 0x0000a1b2 reqid 1 mode tunnel
replay-window 0
auth-trunc hmac(sha256) 0x... 128
enc cbc(aes) 0x...
50.4.2 查看 xfrm 策略¶
# 查看 IPsec 策略
ip xfrm policy
# 输出示例
src 10.244.0.0/24 dst 10.244.1.0/24
dir out priority 100
tmpl src 172.18.0.3 dst 172.18.0.4
proto esp reqid 1 mode tunnel
50.4.3 xfrm 工作流程¶
flowchart TB
A["数据包到达内核"] --> B{"匹配 xfrm policy?"}
B -->|"是"| C["查找对应 xfrm state"]
B -->|"否"| D["普通路由转发"]
C --> E["获取加密算法和密钥"]
E --> F["ESP 封装+加密"]
F --> G["添加外层 IP"]
G --> H["发送到物理网络"]
[!TIP] IPsec 不依赖路由表
与 host-gw 不同,IPsec Tunnel 模式通过
xfrm policy决定转发,不需要在路由表中配置到远端 Pod 网段的路由。
50.5 抓包与解密¶
50.5.1 抓取 ESP 包¶
# 在 eth0 上抓取 ESP 协议包
tcpdump -i eth0 -nn esp -w ipsec.pcap
50.5.2 Wireshark 解密配置¶
flowchart LR
A["打开 Wireshark"] --> B["Edit → Preferences"]
B --> C["Protocols → ESP"]
C --> D["添加 SA"]
D --> E["填写 SPI/算法/密钥"]
E --> F["解密成功"]
解密步骤:
- 从
ip xfrm state获取 SPI、算法、密钥 - 在 Wireshark 中:
Edit → Preferences → Protocols → ESP - 添加 SA 条目:
- Source/Destination IP
- SPI 值
- 加密算法(如 AES-GCM-128)
- 密钥
50.6 与其他模式对比¶
50.6.1 封装方式对比¶
| 模式 | 封装 | 加密 | MTU | CPU 开销 |
|---|---|---|---|---|
| host-gw | 无 | ❌ | 1500 | 最低 |
| IPIP | IP-in-IP | ❌ | 1480 | 低 |
| VxLAN | UDP+VxLAN | ❌ | 1450 | 中 |
| IPsec | ESP+Tunnel | ✅ | ~1400 | 高 |
| WireGuard | UDP | ✅ | ~1420 | 中 |
50.6.2 IPsec vs WireGuard¶
graph TB
subgraph "IPsec"
I1["复杂配置"]
I2["成熟稳定"]
I3["多种算法"]
I4["较高 CPU"]
end
subgraph "WireGuard"
W1["简单配置"]
W2["现代设计"]
W3["固定算法"]
W4["较低 CPU"]
end
I1 -.->|"对比"| W1
I4 -.->|"对比"| W4
| 特性 | IPsec | WireGuard |
|---|---|---|
| 复杂度 | 高 | 低 |
| 密钥管理 | PSK/证书 | 公钥 |
| 性能 | 中等 | 较高 |
| 内核集成 | 传统 | Linux 5.6+ 原生 |
50.7 生产注意事项¶
[!CAUTION] IPsec 生产使用注意
- 性能开销:每个数据包都需要加解密,高流量场景下 CPU 消耗显著
- MTU 调整:ESP 封装会减少可用 MTU,注意调整应用配置
- 密钥安全:PSK 需要安全分发和存储
- 调试复杂:加密后的数据包难以直接分析
50.8 章节小结¶
mindmap
root((Flannel IPsec 模式))
配置
Type ipsec
PSK 预共享密钥
原理
Tunnel 模式
ESP 封装+加密
xfrm state/policy
特点
三层协议
内层无 MAC
不依赖路由表
解密
ip xfrm state
Wireshark ESP
对比
比 WireGuard 复杂
CPU 开销高
成熟稳定
[!TIP] Flannel IPsec 模式要点总结:
- 配置方式:
Backend.Type: "ipsec"PSK 是必选参数(至少 96 位)
核心原理:
- 使用 ESP Tunnel 模式封装和加密
- 通过 xfrm policy 决定转发(不依赖路由表)
三层协议,封装时不包含内层 MAC
xfrm 命令:
ip xfrm state:查看 SA(密钥、算法)
ip xfrm policy:查看转发策略抓包解密:
- 抓包:
tcpdump espWireshark 需配置 ESP SA 才能解密
适用场景:
- 跨不可信网络的安全通信
- 对数据传输有加密要求的场景
第五十一章 Flannel-WireGuard 模式¶
本章深入讲解 Flannel 的 WireGuard 模式,包括配置方法、公钥/私钥加密机制、flannel-wg 设备原理、peer 概念,以及与 IPsec 的对比。
51.1 背景与概述¶
51.1.1 WireGuard 模式定位¶
WireGuard 是 Flannel 中提供现代加密传输的后端类型:
graph TB
subgraph "加密后端对比"
IPsec["IPsec 模式<br/>🔐 传统加密<br/>复杂配置"]
WireGuard["WireGuard 模式<br/>🔐 现代加密<br/>简单高效"]
end
IPsec -->|"ESP + PSK"| Encrypt["加密传输"]
WireGuard -->|"ChaCha20 + 公钥"| Encrypt
51.1.2 核心特点¶
| 特性 | 说明 |
|---|---|
| 加密算法 | ChaCha20-Poly1305 |
| 密钥类型 | 公钥/私钥对 |
| 传输协议 | UDP |
| 默认端口 | 51820 |
| 内核集成 | Linux 5.6+ 原生支持 |
| 性能 | 优于 IPsec |
51.2 WireGuard 配置¶
51.2.1 ConfigMap 配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "wireguard"
}
}
[!TIP] 最简配置
WireGuard 后端只需要指定
Type: "wireguard",无需像 IPsec 那样手动配置 PSK。密钥对由 Flannel 自动生成和分发。
51.2.2 可选参数¶
| 参数 | 类型 | 说明 |
|---|---|---|
| PSK | string | 可选的预共享密钥 |
| ListenPort | int | 监听端口(默认 51820) |
| MTU | int | 自动协商 |
51.3 WireGuard 工作原理¶
51.3.1 设备与路由¶
# 查看 WireGuard 设备
ip link show type wireguard
# 输出示例
5: flannel-wg: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue
link/none
# 路由表
ip route
# 输出示例
10.244.0.0/16 dev flannel-wg
51.3.2 通信架构¶
sequenceDiagram
participant PodA as Pod A<br/>10.244.0.5
participant CNI as cni0
participant WG as flannel-wg<br/>WireGuard 设备
participant Network as 物理网络<br/>UDP 51820
participant WG2 as flannel-wg
participant CNI2 as cni0
participant PodB as Pod B<br/>10.244.1.2
PodA->>CNI: 1. 原始 IP 包
CNI->>WG: 2. 路由匹配
WG->>WG: 3. 加密+封装
WG->>Network: 4. UDP 51820
Network->>WG2: 5. 传输
WG2->>WG2: 6. 解密+解封
WG2->>CNI2: 7. 路由
CNI2->>PodB: 8. 交付
51.3.3 Peer 概念¶
graph TB
subgraph "Node1 172.18.0.3"
WG1["flannel-wg<br/>Private Key: xxx"]
Peer1A["Peer: Node2<br/>Public Key: yyy<br/>Endpoint: 172.18.0.4:51820<br/>AllowedIPs: 10.244.1.0/24"]
Peer1B["Peer: Node3<br/>Public Key: zzz<br/>Endpoint: 172.18.0.5:51820<br/>AllowedIPs: 10.244.2.0/24"]
end
WG1 --> Peer1A
WG1 --> Peer1B
Peer 配置说明:
| 字段 | 说明 |
|---|---|
| Public Key | 对端的公钥(用于加密) |
| Endpoint | 对端的 IP:Port |
| AllowedIPs | 允许通过此 Peer 的 Pod 网段 |
51.4 wg 命令详解¶
51.4.1 安装工具¶
# Debian/Ubuntu
apt-get install wireguard-tools
# CentOS/RHEL
yum install wireguard-tools
51.4.2 查看配置¶
# 查看 WireGuard 接口配置
wg show flannel-wg
# 输出示例
interface: flannel-wg
public key: ABC123...
private key: (hidden)
listening port: 51820
peer: XYZ789...
endpoint: 172.18.0.4:51820
allowed ips: 10.244.1.0/24
latest handshake: 5 seconds ago
transfer: 1.2 MiB received, 800 KiB sent
51.4.3 关键字段解读¶
flowchart LR
subgraph "wg show 输出"
A["listening port<br/>监听端口"]
B["public key<br/>本端公钥"]
C["peer<br/>对端公钥"]
D["endpoint<br/>对端地址"]
E["allowed ips<br/>允许的网段"]
F["handshake<br/>握手状态"]
end
A --> B --> C --> D --> E --> F
51.5 加密机制¶
51.5.1 公钥/私钥¶
graph TB
subgraph "Node1"
Priv1["私钥 (保密)"]
Pub1["公钥 (公开)"]
Priv1 -->|"生成"| Pub1
end
subgraph "Node2"
Priv2["私钥 (保密)"]
Pub2["公钥 (公开)"]
Priv2 -->|"生成"| Pub2
end
Pub1 -.->|"交换"| Node2
Pub2 -.->|"交换"| Node1
加密过程:
| 步骤 | 说明 |
|---|---|
| 1 | Node1 用 Node2 的公钥加密数据 |
| 2 | 数据通过网络传输 |
| 3 | Node2 用 自己的私钥解密数据 |
51.5.2 内核态处理¶
# 检查端口监听
ss -ulnp | grep 51820
# 输出示例
udp UNCONN 0 0 0.0.0.0:51820 0.0.0.0:*
users:(("kernel",pid=-))
[!IMPORTANT] 内核态进程
WireGuard 是 in-kernel 实现,端口由内核直接监听(pid 显示为
-或kernel),无用户态进程,性能更高。
51.6 与其他模式对比¶
51.6.1 加密模式对比¶
| 特性 | IPsec | WireGuard |
|---|---|---|
| 配置复杂度 | 高(需手动 PSK) | 低(自动密钥) |
| 加密协议 | ESP (AES-GCM) | ChaCha20-Poly1305 |
| 内核支持 | 传统模块 | 5.6+ 原生 |
| 性能 | 中等 | 较高 |
| 代码行数 | ~400,000 | ~4,000 |
51.6.2 Flannel 后端总结¶
graph TB
subgraph "无加密"
UDP["UDP<br/>❌ 废弃"]
VxLAN["VxLAN<br/>⭐ 推荐"]
IPIP["IPIP<br/>轻量"]
HostGW["host-gw<br/>最快"]
end
subgraph "有加密"
IPsec["IPsec<br/>传统"]
WireGuard["WireGuard<br/>⭐ 现代"]
end
| 模式 | 封装 | 加密 | 推荐场景 |
|---|---|---|---|
| VxLAN | ✅ | ❌ | 通用场景 |
| host-gw | ❌ | ❌ | 同二层高性能 |
| IPIP | ✅ | ❌ | 轻量跨子网 |
| IPsec | ✅ | ✅ | 传统安全需求 |
| WireGuard | ✅ | ✅ | 现代安全需求 |
51.7 章节小结¶
mindmap
root((Flannel WireGuard 模式))
配置
Type wireguard
自动密钥管理
端口 51820
原理
公钥/私钥
Peer 概念
UDP 封装
设备
flannel-wg
内核态
in-kernel
命令
wg show
wg showconf
优势
配置简单
性能高
代码精简
[!TIP] Flannel WireGuard 模式要点总结:
- 配置方式:
Backend.Type: "wireguard"密钥自动生成,无需手动配置 PSK
核心原理:
- 使用 公钥/私钥加密
- 通过 Peer 概念管理对端信息
UDP 51820 端口传输
关键设备:
flannel-wg:WireGuard 虚拟接口内核态运行,无用户态进程
核心命令:
wg show flannel-wg:查看配置
ip link show type wireguard:查看设备与 IPsec 对比:
- 配置更简单
- 性能更高
- Linux 5.6+ 原生支持
第五十二章 Multus 多网卡方案¶
本章深入讲解 Multus CNI 多网卡方案,包括核心架构、组件体系、NetworkAttachmentDefinition 配置、Pod 注解使用方式,以及典型应用场景。
52.1 背景与概述¶
52.1.1 为什么需要多网卡¶
传统 Pod 只有一个网卡(eth0),在以下场景存在局限:
graph TB
subgraph "单网卡限制"
A["所有流量混合"] --> B["控制面 + 数据面"]
B --> C["无法隔离"]
C --> D["性能瓶颈"]
end
多网卡需求场景:
| 场景 | 说明 |
|---|---|
| 转控分离 | 控制平面和数据平面使用不同网卡 |
| 高性能网络 | SR-IOV/DPDK 直通网卡 |
| 多租户隔离 | 不同业务使用不同网络 |
| 电信/金融 | NFV、流媒体处理 |
52.1.2 Multus 定位¶
graph TB
subgraph "Multus CNI 架构"
Pod["Pod"]
eth0["eth0<br/>默认 CNI"]
eth1["net1<br/>IPVLAN"]
eth2["net2<br/>MACVLAN"]
eth3["net3<br/>SR-IOV"]
Pod --> eth0
Pod --> eth1
Pod --> eth2
Pod --> eth3
end
Calico["Calico/Flannel"] --> eth0
Multus["Multus CNI"] --> eth1
Multus --> eth2
Multus --> eth3
[!IMPORTANT] Multus 核心功能
Multus CNI 是一个 Meta CNI,不直接提供网络功能,而是协调多个 CNI 插件为 Pod 添加多张网卡。
52.2 核心组件¶
52.2.1 组件架构¶
flowchart TB
subgraph "k8s-network-plumbing-wg 项目"
Multus["Multus CNI<br/>多网卡编排"]
SRIOV_DP["SR-IOV Device Plugin<br/>VF 资源管理"]
SRIOV_CNI["SR-IOV CNI<br/>网络通路搭建"]
WhereAbouts["WhereAbouts<br/>集群级 IPAM"]
end
Multus --> |"调用"| SRIOV_CNI
Multus --> |"调用"| MACVlan["macvlan/ipvlan"]
SRIOV_DP --> |"分配 VF"| Pod
WhereAbouts --> |"分配 IP"| Pod
52.2.2 组件职责¶
| 组件 | 职责 | 说明 |
|---|---|---|
| Multus CNI | 多网卡编排 | Meta CNI,协调多个 CNI 插件 |
| SR-IOV Device Plugin | VF 资源管理 | 发现和通告 VF/PF 资源 |
| SR-IOV CNI | 网络通路搭建 | 构建 SR-IOV 网络连接 |
| WhereAbouts | 集群级 IPAM | 跨节点 IP 地址管理 |
52.2.3 SR-IOV 概念¶
graph TB
subgraph "物理网卡"
PF["PF (Physical Function)<br/>物理功能<br/>真实网口"]
VF1["VF0"]
VF2["VF1"]
VF3["VF2"]
VFn["VF..."]
end
PF --> VF1
PF --> VF2
PF --> VF3
PF --> VFn
VF1 --> Pod1["Pod 1"]
VF2 --> Pod2["Pod 2"]
VF3 --> Pod3["Pod 3"]
| 术语 | 全称 | 说明 |
|---|---|---|
| PF | Physical Function | 物理网卡功能 |
| VF | Virtual Function | 虚拟化子网卡 |
52.3 NetworkAttachmentDefinition¶
52.3.1 NAD 概念¶
NetworkAttachmentDefinition(NAD)是 Multus 的核心 CRD:
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf
namespace: default
spec:
config: '{
"cniVersion": "0.3.1",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "whereabouts",
"range": "192.168.1.0/24"
}
}'
52.3.2 NAD 结构解析¶
flowchart LR
subgraph "NetworkAttachmentDefinition"
A["metadata.name<br/>网络名称"]
B["spec.config<br/>CNI 配置"]
end
subgraph "CNI 配置内容"
C["type<br/>CNI 类型"]
D["master<br/>父接口"]
E["mode<br/>模式"]
F["ipam<br/>IP 分配"]
end
B --> C
B --> D
B --> E
B --> F
52.4 Pod 多网卡配置¶
52.4.1 注解方式¶
apiVersion: v1
kind: Pod
metadata:
name: multi-net-pod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf
spec:
containers:
- name: app
image: nginx
52.4.2 多网卡配置¶
apiVersion: v1
kind: Pod
metadata:
name: multi-net-pod
annotations:
# 添加多张网卡
k8s.v1.cni.cncf.io/networks: |
[
{"name": "macvlan-conf"},
{"name": "ipvlan-conf", "interface": "net2"}
]
spec:
containers:
- name: app
image: nginx
52.4.3 Pod 网络结构¶
flowchart TB
subgraph "Pod multi-net-pod"
lo["lo<br/>127.0.0.1"]
eth0["eth0<br/>10.244.0.5<br/>默认 CNI"]
net1["net1<br/>192.168.1.10<br/>macvlan"]
net2["net2<br/>192.168.2.10<br/>ipvlan"]
end
Calico["默认 CNI<br/>(Calico/Flannel)"] --> eth0
NAD1["NAD: macvlan-conf"] --> net1
NAD2["NAD: ipvlan-conf"] --> net2
52.5 安装部署¶
52.5.1 安装 Multus¶
# 克隆项目
git clone https://github.com/k8snetworkplumbingwg/multus-cni.git
cd multus-cni
# 安装
kubectl apply -f deployments/multus-daemonset.yml
52.5.2 安装 WhereAbouts¶
# 安装 WhereAbouts IPAM
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/master/doc/crds/whereabouts.cni.cncf.io_ippools.yaml
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/master/doc/crds/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml
52.5.3 验证安装¶
# 检查 Multus DaemonSet
kubectl get pods -n kube-system | grep multus
# 检查 CNI 插件
ls /opt/cni/bin/ | grep -E "macvlan|ipvlan|multus"
52.6 使用场景¶
52.6.1 常用后端 CNI¶
| CNI 类型 | 特点 | 适用场景 |
|---|---|---|
| macvlan | 虚拟 MAC 地址 | 裸机环境 |
| ipvlan | 共享 MAC 地址 | 云环境/OpenStack |
| SR-IOV | 硬件直通 | 高性能/DPDK |
| bridge | 软件桥接 | 通用场景 |
52.6.2 macvlan vs ipvlan¶
graph TB
subgraph "macvlan"
M_Parent["父接口 eth0<br/>MAC: aa:bb:cc:dd:ee:ff"]
M_Sub1["子接口 1<br/>MAC: 11:22:33:44:55:66"]
M_Sub2["子接口 2<br/>MAC: aa:bb:cc:11:22:33"]
M_Parent --> M_Sub1
M_Parent --> M_Sub2
end
subgraph "ipvlan"
I_Parent["父接口 eth0<br/>MAC: aa:bb:cc:dd:ee:ff"]
I_Sub1["子接口 1<br/>MAC: 共享父接口"]
I_Sub2["子接口 2<br/>MAC: 共享父接口"]
I_Parent --> I_Sub1
I_Parent --> I_Sub2
end
| 对比项 | macvlan | ipvlan |
|---|---|---|
| MAC 地址 | 每个子接口独立 MAC | 共享父接口 MAC |
| 云环境 | ❌ 可能被拦截 | ✅ 推荐 |
| 裸机环境 | ✅ 推荐 | ✅ 可用 |
| 性能 | 高 | 更高 |
52.7 章节小结¶
mindmap
root((Multus 多网卡))
定位
Meta CNI
多网卡编排
英特尔开源
组件
Multus CNI
SR-IOV Device Plugin
SR-IOV CNI
WhereAbouts IPAM
配置
NetworkAttachmentDefinition
Pod 注解
多网卡列表
后端
macvlan
ipvlan
SR-IOV
bridge
场景
转控分离
高性能网络
电信/金融
[!TIP] Multus 多网卡方案要点总结:
- 核心概念:
- Multus 是 Meta CNI,协调多个 CNI 插件
为 Pod 添加除 eth0 外的额外网卡
核心组件:
- Multus CNI:多网卡编排
- SR-IOV Device Plugin:VF 资源管理
WhereAbouts:集群级 IPAM
配置方式:
- 创建 NetworkAttachmentDefinition CRD
Pod 通过 注解 引用 NAD
常用后端:
- macvlan:裸机环境
- ipvlan:云环境(共享 MAC)
SR-IOV:高性能直通
典型场景:
- 转控分离(控制面/数据面隔离)
- 电信 NFV、金融高频交易
- 流媒体处理
第五十三章 Multus IPVLAN L2 模式¶
本章深入讲解 IPVLAN L2 模式的工作原理、配置方法、适用场景,以及与 MACVLAN 的区别。
53.1 背景与概述¶
53.1.1 IPVLAN 简介¶
IPVLAN 是一种网卡复用技术,区别于 MACVLAN 的核心特点是:子接口与父接口共享同一 MAC 地址。
graph TB
subgraph "IPVLAN 特性"
Parent["父接口 eth0<br/>MAC: aa:bb:cc:dd:ee:ff"]
Sub1["子接口 ipvl0<br/>MAC: aa:bb:cc:dd:ee:ff<br/>IP: 172.18.0.200"]
Sub2["子接口 ipvl1<br/>MAC: aa:bb:cc:dd:ee:ff<br/>IP: 172.18.0.201"]
Parent --> Sub1
Parent --> Sub2
end
[!IMPORTANT] IPVLAN 核心特性
- 子接口与父接口 MAC 地址相同
- 不同子接口通过 IP 地址 区分
- 内核要求:Linux 4.18+
53.1.2 IPVLAN 命名由来¶
| 类型 | 区分方式 | MAC 地址 | IP 地址 |
|---|---|---|---|
| IPVLAN | IP 区分 | 相同 | 不同 |
| MACVLAN | MAC 区分 | 不同 | 不同 |
53.1.3 云环境适配¶
IPVLAN 在 公有云/OpenStack 环境中特别适用:
flowchart TB
subgraph "OpenStack 安全机制"
Port["虚拟机端口"]
Security["安全组<br/>allow_address_pair"]
Check["MAC 地址校验"]
end
Port --> Security
Security --> Check
subgraph "IPVLAN 优势"
Same["子接口 MAC = 父接口 MAC"]
Pass["无需修改 allow_address_pair"]
Work["正常通信"]
end
Same --> Pass --> Work
Check -.->|"不拦截"| Same
[!NOTE] 云环境兼容性
OpenStack 默认只允许与端口 MAC 匹配的流量通过。IPVLAN 由于 MAC 相同,无需额外配置即可正常工作。MACVLAN 则需要设置
allow_address_pair为0.0.0.0/0。
53.2 L2 模式原理¶
53.2.1 L2 模式工作方式¶
在 L2 模式下,父接口相当于一个 虚拟交换机:
graph TB
subgraph "节点 Node1"
eth0["父接口 eth0<br/>(虚拟交换机)"]
pod1["Pod1<br/>ipvl0<br/>172.18.0.200"]
pod2["Pod2<br/>ipvl1<br/>172.18.0.201"]
eth0 --> pod1
eth0 --> pod2
end
subgraph "节点 Node2"
eth0_2["父接口 eth0<br/>(虚拟交换机)"]
pod3["Pod3<br/>ipvl0<br/>172.18.0.202"]
eth0_2 --> pod3
end
eth0 <-->|"二层交换"| eth0_2
53.2.2 L2 vs L3 模式¶
| 对比项 | L2 模式 | L3 模式 |
|---|---|---|
| 工作层级 | 二层(交换) | 三层(路由) |
| 父接口角色 | 虚拟交换机 | 虚拟路由器 |
| 广播域 | 共享 | 隔离 |
| 适用场景 | 同子网通信 | 跨子网通信 |
53.3 配置实现¶
53.3.1 NetworkAttachmentDefinition¶
创建 IPVLAN L2 模式的 NAD:
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l2-whereabouts-conf
spec:
config: '{
"cniVersion": "0.3.1",
"name": "ipvlan-l2-whereabouts",
"type": "ipvlan",
"master": "eth0",
"mode": "l2",
"ipam": {
"type": "whereabouts",
"range": "172.18.0.200/24",
"range_start": "172.18.0.200",
"range_end": "172.18.0.205"
}
}'
53.3.2 NAD 配置解析¶
flowchart LR
subgraph "网络配置"
type["type: ipvlan<br/>网络类型"]
master["master: eth0<br/>父接口"]
mode["mode: l2<br/>L2 模式"]
end
subgraph "IPAM 配置"
ipam_type["type: whereabouts<br/>集群级 IPAM"]
range["range: 172.18.0.200/24<br/>地址范围"]
end
type --> master --> mode
ipam_type --> range
53.3.3 Pod 配置¶
apiVersion: v1
kind: Pod
metadata:
name: ipvlan-pod1
annotations:
k8s.v1.cni.cncf.io/networks: ipvlan-l2-whereabouts-conf@eth1
spec:
containers:
- name: app
image: busybox
command: ["sleep", "3600"]
53.3.4 注解语法¶
graph LR
A["k8s.v1.cni.cncf.io/networks"] --> B["NAD 名称"]
B --> C["@"]
C --> D["接口名称<br/>(可选)"]
E["示例"] --> F["ipvlan-l2-conf@eth1"]
| 语法 | 说明 |
|---|---|
nad-name |
使用默认接口名(net0, net1...) |
nad-name@eth1 |
指定接口名为 eth1 |
53.4 通信验证¶
53.4.1 验证 MAC 地址¶
# 查看节点 eth0 的 MAC
ip link show eth0
# 输出: link/ether 02:42:ac:12:00:02
# 进入 Pod 查看 eth1 的 MAC
kubectl exec ipvlan-pod1 -- ip link show eth1
# 输出: link/ether 02:42:ac:12:00:02 (与父接口相同)
53.4.2 同节点通信¶
# Pod1 ping Pod2 (同节点)
kubectl exec ipvlan-pod1 -- ping -I eth1 172.18.0.201
53.4.3 跨节点通信¶
# Pod1 ping Pod3 (跨节点)
kubectl exec ipvlan-pod1 -- ping -I eth1 172.18.0.202
[!WARNING] 多网卡环境下的 ping 命令
在多网卡环境中,务必使用
-I <interface>指定源接口,否则可能走错网络路径。
53.4.4 ARP 表验证¶
# 查看 ARP 表
kubectl exec ipvlan-pod1 -- arp -n
# 同节点 Pod:MAC 相同
# 172.18.0.201 02:42:ac:12:00:02
# 跨节点 Pod:MAC 为对端节点的父接口
# 172.18.0.202 02:42:ac:12:00:03
53.5 IPVLAN vs MACVLAN¶
53.5.1 对比总结¶
graph TB
subgraph "MACVLAN"
M_Parent["父接口<br/>MAC: aa:bb:cc:dd"]
M_Sub1["子接口<br/>MAC: 11:22:33:44"]
M_Sub2["子接口<br/>MAC: 55:66:77:88"]
M_Parent --> M_Sub1
M_Parent --> M_Sub2
end
subgraph "IPVLAN"
I_Parent["父接口<br/>MAC: aa:bb:cc:dd"]
I_Sub1["子接口<br/>MAC: aa:bb:cc:dd"]
I_Sub2["子接口<br/>MAC: aa:bb:cc:dd"]
I_Parent --> I_Sub1
I_Parent --> I_Sub2
end
| 对比项 | MACVLAN | IPVLAN |
|---|---|---|
| MAC 地址 | 子接口独立 | 子接口相同 |
| 云环境 | 可能被拦截 | ✅ 兼容 |
| 适用场景 | 裸机环境 | 云/虚拟化环境 |
| 内核要求 | 3.x+ | 4.18+ |
| 区分方式 | MAC | IP |
53.6 章节小结¶
mindmap
root((IPVLAN L2 模式))
核心特性
MAC 相同
IP 区分
内核 4.18+
适用场景
公有云
OpenStack
虚拟化环境
L2 模式
二层交换
父接口=交换机
同子网通信
配置
NAD 定义
type=ipvlan
mode=l2
master=父接口
验证
MAC 检查
ARP 表
指定源接口 ping
[!TIP] IPVLAN L2 模式要点总结:
- 核心特性:
- 子接口与父接口 MAC 地址相同
通过 IP 地址 区分不同子接口
适用场景:
- 云环境(OpenStack、公有云)
避免 MAC 地址被安全策略拦截
L2 模式:
- 父接口充当 虚拟交换机
子接口在同一广播域
配置要点:
type: ipvlanmode: l2
master: eth0(父接口)注意事项:
- 多网卡环境下 ping 需指定
-I <interface>- 内核版本要求 Linux 4.18+
第五十四章 Multus IPVLAN L3 模式¶
本章讲解 IPVLAN L3 模式的工作原理、配置方法、回程路由设置,以及与 L2 模式的区别。
54.1 背景与概述¶
54.1.1 L3 模式简介¶
在 L3 模式下,父接口充当 虚拟路由器,子接口可以配置 不同子网 的 IP 地址。
graph TB
subgraph "L3 模式架构"
Parent["父接口 eth0<br/>(虚拟路由器)"]
Sub1["子接口 ipvl0<br/>15.1.1.10/24"]
Sub2["子接口 ipvl1<br/>15.1.2.20/24"]
Parent --> Sub1
Parent --> Sub2
end
[!IMPORTANT] L3 模式核心特性
- 父接口充当 路由器(L2 模式是交换机)
- 子接口可配置 不同子网 的地址
- 需要添加 回程路由 实现跨子网通信
54.1.2 L2 vs L3 模式对比¶
| 对比项 | L2 模式 | L3 模式 |
|---|---|---|
| 父接口角色 | 虚拟交换机 | 虚拟路由器 |
| 子接口子网 | 必须相同 | 可以不同 |
| 通信方式 | 二层交换 | 三层路由 |
| 广播域 | 共享 | 隔离 |
| 路由配置 | 无需 | 需要回程路由 |
54.2 L3 模式原理¶
54.2.1 工作方式¶
flowchart TB
subgraph "节点 Node1"
eth0["父接口 eth0<br/>(路由器)"]
pod1["Pod1<br/>15.1.1.10/24"]
pod2["Pod2<br/>15.1.2.20/24"]
eth0 --> pod1
eth0 --> pod2
pod1 -.->|"回程路由"| pod2
end
54.2.2 路由查找过程¶
sequenceDiagram
participant Pod1 as Pod1<br/>15.1.1.10
participant Router as 父接口<br/>(路由器)
participant Pod2 as Pod2<br/>15.1.2.20
Pod1->>Pod1: 查路由表
Note over Pod1: 目的: 15.1.2.20
Pod1->>Router: 从 eth1 出接口发出
Router->>Router: 三层路由转发
Router->>Pod2: 送达目的 Pod
54.2.3 无网关路由¶
L3 模式的一个关键特性是 无网关路由(仅指定出接口):
# 传统路由(带网关)
ip route add 15.1.2.0/24 via 15.1.1.1 dev eth1
# 无网关路由(仅出接口)
ip route add 15.1.2.0/24 dev eth1
[!NOTE] 无网关路由原理
当出接口直连路由器时,只需指定出接口,无需指定下一跳网关。数据包从出接口发出后,直接到达路由器进行转发。
54.3 配置实现¶
54.3.1 NetworkAttachmentDefinition¶
创建 IPVLAN L3 模式的 NAD:
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l3-conf-1
spec:
config: '{
"cniVersion": "0.3.1",
"name": "ipvlan-l3-net1",
"type": "ipvlan",
"master": "eth0",
"mode": "l3",
"ipam": {
"type": "whereabouts",
"range": "15.1.1.0/24",
"range_start": "15.1.1.10",
"range_end": "15.1.1.20"
}
}'
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-l3-conf-2
spec:
config: '{
"cniVersion": "0.3.1",
"name": "ipvlan-l3-net2",
"type": "ipvlan",
"master": "eth0",
"mode": "l3",
"ipam": {
"type": "whereabouts",
"range": "15.1.2.0/24",
"range_start": "15.1.2.10",
"range_end": "15.1.2.20"
}
}'
54.3.2 配置要点¶
flowchart LR
subgraph "L3 模式配置"
mode["mode: l3<br/>三层模式"]
net1["NAD-1: 15.1.1.0/24"]
net2["NAD-2: 15.1.2.0/24"]
end
mode --> net1
mode --> net2
| 配置项 | 说明 |
|---|---|
mode: l3 |
启用 L3 路由模式 |
| 不同 NAD | 配置不同子网 |
| 同一 master | 共享父接口 |
54.3.3 Pod 配置¶
apiVersion: v1
kind: Pod
metadata:
name: ipvlan-l3-pod1
annotations:
k8s.v1.cni.cncf.io/networks: ipvlan-l3-conf-1@eth1
spec:
nodeName: node1 # 确保同节点
containers:
- name: app
image: busybox
command: ["sleep", "3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: ipvlan-l3-pod2
annotations:
k8s.v1.cni.cncf.io/networks: ipvlan-l3-conf-2@eth1
spec:
nodeName: node1 # 确保同节点
containers:
- name: app
image: busybox
command: ["sleep", "3600"]
54.4 回程路由配置¶
54.4.1 为什么需要回程路由¶
默认情况下,Pod 只知道自己所在子网的路由:
# Pod1 (15.1.1.10) 默认路由表
15.1.1.0/24 dev eth1 # 只知道本子网
# Pod2 (15.1.2.20) 默认路由表
15.1.2.0/24 dev eth1 # 只知道本子网
Pod1 无法直接访问 Pod2,因为没有去往 15.1.2.0/24 的路由。
54.4.2 添加回程路由¶
# 在 Pod1 中添加去往 Pod2 子网的路由
kubectl exec ipvlan-l3-pod1 -- ip route add 15.1.2.0/24 dev eth1
# 在 Pod2 中添加去往 Pod1 子网的路由
kubectl exec ipvlan-l3-pod2 -- ip route add 15.1.1.0/24 dev eth1
54.4.3 验证通信¶
# Pod1 ping Pod2(需指定源接口)
kubectl exec ipvlan-l3-pod1 -- ping -I eth1 15.1.2.20
[!WARNING] 关键注意事项
- 必须使用
-I eth1指定源接口- L3 模式需要 手动添加回程路由
- 同节点 才可通过回程路由互通
54.5 同节点 vs 跨节点¶
54.5.1 限制说明¶
graph TB
subgraph "同节点 ✅"
N1_eth0["Node1 eth0<br/>(共享路由器)"]
N1_Pod1["Pod1<br/>15.1.1.10"]
N1_Pod2["Pod2<br/>15.1.2.20"]
N1_eth0 --> N1_Pod1
N1_eth0 --> N1_Pod2
N1_Pod1 <-->|"回程路由"| N1_Pod2
end
subgraph "跨节点 ❌"
N2_eth0["Node2 eth0<br/>(独立路由器)"]
N2_Pod3["Pod3<br/>15.1.3.30"]
N2_eth0 --> N2_Pod3
end
N1_Pod1 -.->|"无法通信"| N2_Pod3
[!CAUTION] 跨节点限制
L3 模式下,不同节点的 Pod 无法直接通过回程路由通信,因为:
- 每个节点的父接口是独立的路由器
- 没有统一的路由平面
如需跨节点通信,需要额外的 Underlay 网络 或 BGP 路由宣告。
54.5.2 适用场景¶
| 场景 | 是否支持 |
|---|---|
| 同节点不同子网 Pod | ✅ 支持(需回程路由) |
| 跨节点不同子网 Pod | ❌ 不支持(需额外配置) |
| 同节点同子网 Pod | ✅ 支持(使用 L2 模式更佳) |
54.6 章节小结¶
mindmap
root((IPVLAN L3 模式))
核心特性
父接口=路由器
子接口可跨子网
需回程路由
配置要点
mode=l3
不同 NAD 不同子网
同一 master
回程路由
ip route add
仅出接口即可
无需指定网关
限制
仅同节点有效
跨节点不通
需额外 Underlay
[!TIP] IPVLAN L3 模式要点总结:
- 核心原理:
- 父接口充当 虚拟路由器
子接口可配置 不同子网 IP
回程路由:
- 必须手动添加
ip route add <对端子网> dev eth1无需指定网关,仅需出接口
限制:
- 仅 同节点 Pod 可通过回程路由通信
跨节点需要额外网络配置
与 L2 对比:
- L2:交换机,同子网
- L3:路由器,跨子网
第五十五章 Multus IPVLAN SBR 模式¶
本章讲解 IPVLAN 的 SBR(Source-Based Routing,源地址路由)模式,实现多网卡同时访问外网的场景。
55.1 背景与概述¶
55.1.1 什么是 SBR¶
SBR(Source-Based Routing) 是一种基于 源地址 进行路由决策的技术,也称为"原地路由"或"策略路由"。
graph LR
subgraph "传统路由"
Dst["基于目的地址"]
end
subgraph "SBR 路由"
Src["基于源地址"]
end
Dst --> D1["所有流量走默认网关"]
Src --> S1["不同源地址走不同网关"]
[!IMPORTANT] SBR 核心价值
- 允许 Pod 拥有 多张网卡同时访问外网
- 根据 源 IP 决定出口路由
- 适用于 多网络接入 场景
55.1.2 应用场景¶
| 场景 | 说明 |
|---|---|
| 多网卡上网 | 不同网卡走不同出口 |
| 网络隔离 | 内网/外网分离访问 |
| 流量分流 | 按源地址分配带宽 |
55.2 SBR 原理¶
55.2.1 核心组件¶
SBR 依赖 Linux 的 策略路由 机制,包含两个关键组件:
graph TB
subgraph "SBR 组件"
Rule["ip rule<br/>路由策略"]
Table["ip route table<br/>路由表"]
end
Rule --> Table
Table --> GW["网关出口"]
| 组件 | 命令 | 作用 |
|---|---|---|
| ip rule | ip rule add from <src> table <n> |
定义策略:源地址 → 路由表 |
| ip route table | ip route add default via <gw> table <n> |
定义路由表内容 |
55.2.2 工作流程¶
sequenceDiagram
participant Pod as Pod<br/>172.18.0.200
participant Rule as ip rule
participant Table as table 100
participant GW as 网关<br/>172.18.0.1
Pod->>Rule: 发包 src=172.18.0.200
Rule->>Rule: 匹配 from 172.18.0.0/24
Rule->>Table: 查询 table 100
Table->>Table: default via 172.18.0.1
Table->>GW: 发送到网关
GW->>Pod: SNAT 后访问外网
55.2.3 优先级机制¶
graph TB
subgraph "路由查找顺序"
Step1["1. 检查 ip rule"]
Step2["2. SBR 匹配则走对应 table"]
Step3["3. 否则走默认路由表 main"]
end
Step1 --> Step2
Step2 --> Step3
[!NOTE] SBR 优先级更高
当配置了 SBR 后,即使目的地址在 同一子网,也会优先走 SBR 指定的网关,而非直接二层通信。
55.3 配置实现¶
55.3.1 手动配置 SBR¶
在 Pod 内手动添加 SBR 规则:
# 1. 添加路由表(table 100)
ip route add default via 172.18.0.1 dev eth1 table 100
# 2. 添加路由策略(从 172.18.0.0/24 来的包走 table 100)
ip rule add from 172.18.0.0/24 table 100
验证配置:
# 查看路由策略
ip rule show
# 输出: from 172.18.0.0/24 lookup 100
# 查看路由表
ip route show table 100
# 输出: default via 172.18.0.1 dev eth1
55.3.2 NAD 自动配置 SBR¶
通过 NetworkAttachmentDefinition 自动配置 SBR:
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-sbr
spec:
config: '{
"cniVersion": "0.3.1",
"name": "ipvlan-sbr-net",
"type": "ipvlan",
"master": "eth0",
"mode": "l2",
"ipam": {
"type": "whereabouts",
"range": "172.18.0.0/24"
},
"plugins": [
{
"type": "sbr"
}
]
}'
55.3.3 配置对比¶
flowchart LR
subgraph "手动配置"
M1["ip route add"]
M2["ip rule add"]
end
subgraph "自动配置(NAD)"
A1["plugins: sbr"]
end
M1 --> Result["Pod 多网卡上外网"]
M2 --> Result
A1 --> Result
| 方式 | 优点 | 缺点 |
|---|---|---|
| 手动配置 | 灵活可控 | 需要 init 容器或手动执行 |
| NAD 自动 | 自动化,无需干预 | 需要 SBR CNI 插件支持 |
55.4 多网卡上外网验证¶
55.4.1 场景说明¶
graph TB
subgraph "Pod"
eth0["eth0<br/>默认网卡"]
eth1["eth1<br/>IPVLAN 网卡"]
end
subgraph "网关"
GW0["默认网关"]
GW1["IPVLAN 网关<br/>172.18.0.1"]
end
eth0 --> GW0
eth1 --> GW1
GW0 --> Internet["互联网"]
GW1 --> Internet
55.4.2 验证步骤¶
# 1. 从 eth0 访问外网(默认路由)
ping 114.114.114.114
# 2. 从 eth1 访问外网(SBR 路由)
ping -I 172.18.0.200 114.114.114.114
# 3. 抓包验证
tcpdump -i eth1 icmp
[!TIP] 指定源地址的重要性
使用
ping -I <IP地址>而非ping -I <接口名>,确保 SBR 规则正确匹配。
55.4.3 抓包分析¶
# 在 eth1 上抓包
tcpdump -i eth1 -n icmp
# 预期输出(走 SBR):
# 172.18.0.200 > 114.114.114.114: ICMP echo request
# 114.114.114.114 > 172.18.0.200: ICMP echo reply
55.5 SBR 与 ARP 缓存交互¶
55.5.1 首包行为¶
sequenceDiagram
participant Pod1 as Pod1<br/>172.18.0.200
participant SBR as SBR 规则
participant GW as 网关<br/>172.18.0.1
participant Pod2 as Pod2<br/>172.18.0.201
Note over Pod1: 无 ARP 缓存
Pod1->>SBR: 发包 dst=172.18.0.201
SBR->>GW: 优先走网关
GW->>Pod2: 转发
Pod2->>Pod1: 回包(携带真实 MAC)
Note over Pod1: 学习到 Pod2 MAC
55.5.2 后续包行为¶
当 ARP 缓存存在后,行为可能改变:
| 情况 | 首包 | 后续包 |
|---|---|---|
| 无 ARP 缓存 | 走 SBR → 网关 | 走 SBR → 网关 |
| 有 ARP 缓存 | 走 SBR → 网关 | 可能直接二层(ARP 优先级更高) |
[!WARNING] ARP 缓存影响
当 Pod 学习到对端的真实 MAC 地址后,后续包可能 绕过 SBR 直接走二层。这是因为:
- 本地 ARP 缓存优先级高于 SBR
- 已知 MAC 时无需查询路由策略
清除 ARP 缓存验证:
ip neigh del 172.18.0.201 dev eth1
55.5.3 双端 SBR 配置¶
当两端都配置 SBR 时:
sequenceDiagram
participant Pod1 as Pod1<br/>172.18.0.200
participant GW as 网关
participant Pod2 as Pod2<br/>172.18.0.201
Pod1->>GW: SBR → 网关
GW->>Pod2: 转发
Pod2->>GW: SBR → 网关
GW->>Pod1: 转发
两端都会经过网关,确保 SBR 策略生效。
55.6 章节小结¶
mindmap
root((IPVLAN SBR))
核心概念
Source-Based Routing
基于源地址路由
策略路由
关键命令
ip rule add
ip route table
配置方式
手动配置
NAD 自动配置
应用场景
多网卡上外网
网络隔离
流量分流
注意事项
SBR 优先级高
ARP 缓存影响
指定源 IP
[!TIP] IPVLAN SBR 模式要点总结:
- SBR 机制:
ip rule:定义源地址 → 路由表映射
ip route table:定义路由表内容配置方式:
- 手动:
ip route add+ip rule add自动:NAD 中
plugins: [{"type": "sbr"}]验证方法:
ping -I <源IP>指定源地址
tcpdump -i eth1抓包确认注意事项:
- SBR 优先级高于普通路由
- ARP 缓存可能影响后续包路径
- 双端配置 SBR 可确保策略生效
第五十六章 IPVLAN-SBR 深度解析¶
本章深入分析 SBR 的底层行为机制,包括首包处理、ICMP Redirect、单边与双边 SBR 差异,以及典型应用场景。
56.1 背景与问题¶
56.1.1 上章回顾¶
上一章介绍了 SBR 的基本配置和使用,但在实际抓包分析中发现了一些 意外行为:
- 首包发往网关,但后续包可能直接二层通信
- 存在 ICMP Redirect 消息
- 不同网络环境行为不同
56.1.2 本章目标¶
mindmap
root((SBR 深度解析))
首包行为
为什么走网关
MAC 地址封装
ICMP Redirect
什么是重定向
何时触发
单边 vs 双边
行为差异
抓包分析
环境差异
Linux Bridge
真实交换机
应用场景
多网卡多网关
管理/业务分离
56.2 SBR 首包行为分析¶
56.2.1 首包封装过程¶
sequenceDiagram
participant Pod1 as Pod1<br/>172.18.0.200
participant SBR as SBR 规则
participant GW as 网关<br/>172.18.0.1
participant Pod2 as Pod2<br/>172.18.0.201
Note over Pod1: 查路由:dst=172.18.0.201
Pod1->>SBR: 匹配 from 172.18.0.0/24
SBR->>Pod1: 走 table 100 → via 172.18.0.1
Pod1->>Pod1: 发 ARP 请求网关 MAC
Pod1->>GW: 封装: dst_mac=网关MAC
GW->>Pod2: 转发到 Pod2
56.2.2 为什么首包走网关¶
| 阶段 | 说明 |
|---|---|
| 无 ARP 缓存 | Pod1 不知道 Pod2 的 MAC |
| SBR 规则生效 | 指向 via 172.18.0.1 |
| ARP 请求网关 | 而非请求 Pod2 |
| 封装网关 MAC | dst_mac = 网关 MAC |
[!NOTE] 关键点
SBR 规则指定了下一跳网关,因此 Pod1 会 ARP 解析网关的 MAC,而非目的 Pod 的 MAC。
56.2.3 抓包验证¶
# Pod1 发送首包
tcpdump -i eth1 -en
# 首包内容:
# src_mac: 12:00:00:02 (Pod1)
# dst_mac: dc:cd:40:xx (网关)
# src_ip: 172.18.0.200
# dst_ip: 172.18.0.201
56.3 ICMP Redirect 机制¶
56.3.1 什么是 ICMP Redirect¶
sequenceDiagram
participant Pod1 as Pod1
participant GW as 网关
participant Pod2 as Pod2
Pod1->>GW: ICMP Echo Request
GW->>Pod2: 转发
GW-->>Pod1: ICMP Redirect
Note over GW: 告诉 Pod1:以后直接发给 Pod2
Pod2->>Pod1: ICMP Echo Reply
ICMP Redirect(重定向)是网关发送的一种通知消息:
"你发给我的包,目的地和你在同一网段,你应该直接发给它,不需要经过我。"
56.3.2 Redirect 消息格式¶
ICMP Type: 5 (Redirect)
Code: 1 (Redirect for Host)
Message: Redirect to 172.18.0.201
56.3.3 Redirect 触发条件¶
| 条件 | 说明 |
|---|---|
| 源和目的同子网 | 172.18.0.200 ↔ 172.18.0.201 |
| 包经过网关 | 网关发现"绕路" |
| 网关启用 Redirect | Linux Bridge 默认启用 |
[!WARNING] ICMP Redirect 对 SBR 的影响
收到 Redirect 后,Pod 可能 绕过 SBR 策略,直接二层通信。这可能不是期望的行为!
56.4 单边 vs 双边 SBR¶
56.4.1 单边 SBR(仅 Pod1 配置)¶
sequenceDiagram
participant Pod1 as Pod1<br/>有 SBR
participant GW as 网关
participant Pod2 as Pod2<br/>无 SBR
Pod1->>GW: ① 首包走网关
GW->>Pod2: 转发
GW-->>Pod1: ② ICMP Redirect
Pod2->>Pod1: ③ 回包(直接二层)
Note over Pod2: 发 ARP 请求 Pod1 MAC
Note over Pod1: 后续包直接二层
行为特点:
- 首包:Pod1 → 网关 → Pod2
- 回包:Pod2 直接发给 Pod1(无 SBR)
- 后续:可能绕过 SBR
56.4.2 双边 SBR(两端都配置)¶
sequenceDiagram
participant Pod1 as Pod1<br/>有 SBR
participant GW as 网关
participant Pod2 as Pod2<br/>有 SBR
Pod1->>GW: ① 首包走网关
GW->>Pod2: 转发
GW-->>Pod1: ② Redirect #1
Pod2->>GW: ③ 回包也走网关
GW->>Pod1: 转发
GW-->>Pod2: ④ Redirect #2
Note over Pod1,Pod2: 两次 Redirect 后恢复直接通信
行为特点:
- 首包:双方都走网关
- 两次 Redirect:各触发一次
- 后续:恢复直接通信
56.4.3 对比总结¶
| 场景 | 首包路径 | Redirect 次数 | 后续包路径 |
|---|---|---|---|
| 单边 SBR | Pod1→GW→Pod2 | 1 次 | 可能直接二层 |
| 双边 SBR | 双方都走网关 | 2 次 | 恢复直接通信 |
56.5 Linux Bridge vs 真实交换机¶
56.5.1 行为差异¶
graph TB
subgraph "Linux Bridge"
LB_GW["网关"]
LB_R["发送 ICMP Redirect"]
LB_D["后续包直接二层"]
LB_GW --> LB_R
LB_R --> LB_D
end
subgraph "真实交换机(H3C等)"
HW_GW["网关"]
HW_F["每包都转发"]
HW_N["无 Redirect"]
HW_GW --> HW_F
HW_F --> HW_N
end
56.5.2 差异对比¶
| 特性 | Linux Bridge | 真实交换机(H3C) |
|---|---|---|
| ICMP Redirect | ✅ 启用 | ❌ 通常不发 |
| SBR 首包 | 走网关 | 走网关 |
| SBR 后续包 | 可能绕过 | 始终走网关 |
| 行为一致性 | 可能变化 | 稳定可预期 |
[!CAUTION] 生产环境注意
Linux Bridge 环境下的 SBR 行为可能与真实交换机不同。测试时需要在 目标生产环境 中验证!
56.6 SBR 典型应用场景¶
56.6.1 多网卡多网关¶
graph TB
subgraph "Pod/虚拟机"
eth0["eth0<br/>管理网卡"]
eth1["eth1<br/>业务网卡"]
end
subgraph "网关"
GW0["管理网关<br/>10.0.0.1"]
GW1["业务网关<br/>192.168.0.1"]
end
eth0 --> GW0
eth1 --> GW1
GW0 --> OM["SSH/运维管理"]
GW1 --> BIZ["业务流量"]
56.6.2 问题:多网关冲突¶
场景:一台机器有两张网卡,只能配置一个默认路由。
# 默认路由只能有一个出接口
ip route add default via 192.168.0.1 dev eth1
# SSH 从 eth0 进来,回包却走 eth1
# 导致:非对称路由,连接失败!
56.6.3 SBR 解决方案¶
# 1. eth0(管理网)使用 SBR
ip route add default via 10.0.0.1 dev eth0 table 100
ip rule add from 10.0.0.0/24 table 100
# 2. eth1(业务网)使用默认路由
ip route add default via 192.168.0.1 dev eth1
flowchart LR
subgraph "SBR 路由策略"
SSH["SSH 请求<br/>from 10.0.0.x"]
BIZ["业务请求<br/>from 192.168.x"]
end
SSH --> T100["table 100"]
BIZ --> Main["default route"]
T100 --> GW0["eth0 网关"]
Main --> GW1["eth1 网关"]
56.6.4 典型使用场景¶
| 场景 | eth0 (管理网) | eth1 (业务网) |
|---|---|---|
| 运维管理 | SSH、监控 | - |
| 业务流量 | - | 应用数据 |
| 告警上报 | SBR 路由 | - |
| 外网访问 | - | 默认路由 |
56.7 章节小结¶
mindmap
root((SBR 深度解析))
首包行为
SBR 优先
ARP 解析网关
封装网关 MAC
ICMP Redirect
网关发送
通知直连
可能绕过 SBR
单边 vs 双边
单边=1 次 Redirect
双边=2 次 Redirect
后续恢复直连
环境差异
Linux Bridge 有 Redirect
真实交换机无 Redirect
应用场景
多网卡多网关
管理/业务分离
解决非对称路由
[!TIP] IPVLAN-SBR 深度解析要点总结:
- 首包行为:
- SBR 优先于普通路由
首包 ARP 解析网关 MAC
ICMP Redirect:
- 网关发现同子网绕路时发送
可能导致后续包绕过 SBR
单边 vs 双边:
- 单边 SBR:1 次 Redirect
双边 SBR:2 次 Redirect
环境差异:
- Linux Bridge:有 Redirect
真实交换机:通常无 Redirect
应用场景:
- 多网卡多网关
- SSH 管理 + 业务分离
- 解决非对称路由问题
第五十七章 MACVLAN-SBR 实践¶
本章介绍 MACVLAN 与 SBR 结合的实践配置,包括 MACVLAN 与 IPVLAN 的差异、NAD 配置方法、以及生产级多网卡环境的搭建。
57.1 背景与概述¶
57.1.1 MACVLAN vs IPVLAN 核心差异¶
graph TB
subgraph "MACVLAN"
MV_M["Master 接口"]
MV_S1["子接口1<br/>MAC: AA:BB:CC:01"]
MV_S2["子接口2<br/>MAC: AA:BB:CC:02"]
MV_M --> MV_S1
MV_M --> MV_S2
end
subgraph "IPVLAN"
IV_M["Master 接口<br/>MAC: AA:BB:CC:00"]
IV_S1["子接口1<br/>MAC: AA:BB:CC:00"]
IV_S2["子接口2<br/>MAC: AA:BB:CC:00"]
IV_M --> IV_S1
IV_M --> IV_S2
end
| 特性 | MACVLAN | IPVLAN |
|---|---|---|
| MAC 地址 | 各不相同 | 共享父接口 MAC |
| 内核支持 | 3.x 早期版本 | 4.x+ |
| 理解难度 | 简单(传统模式) | 需理解共享 MAC |
| 公有云兼容 | 可能受限(MAC 检查) | 更友好 |
| 典型场景 | 传统网卡复用 | 云原生环境 |
[!NOTE] 关键区别
- MACVLAN:每个子接口有 独立的 MAC 地址
- IPVLAN:所有子接口 共享父接口的 MAC 地址
57.1.2 MACVLAN 工作模式¶
| 模式 | 说明 | 使用场景 |
|---|---|---|
| bridge | 子接口间可直接通信(最常用) | 生产环境默认 |
| private | 子接口间完全隔离 | 安全隔离 |
| vepa | 流量必须经过外部交换机 | 硬件卸载 |
| passthrough | 直通模式 | SR-IOV |
57.2 基础 MACVLAN 配置¶
57.2.1 NAD 配置示例¶
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-basic
spec:
config: '{
"cniVersion": "0.3.1",
"name": "macvlan-net",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "whereabouts",
"range": "15.15.1.0/24"
}
}'
57.2.2 配置要点¶
flowchart LR
subgraph "NAD 配置"
type["type: macvlan"]
master["master: eth0"]
mode["mode: bridge"]
end
type --> |网卡复用类型| macvlan["MACVLAN"]
master --> |父接口| eth0["物理/虚拟网卡"]
mode --> |工作模式| bridge["Bridge 模式"]
| 参数 | 说明 |
|---|---|
| type | macvlan - 网卡复用类型 |
| master | 父接口名称(复用哪张网卡) |
| mode | 工作模式,通常用 bridge |
| ipam | IP 地址管理,通常用 whereabouts |
57.3 MACVLAN 基础验证¶
57.3.1 创建测试 Pod¶
apiVersion: v1
kind: Pod
metadata:
name: macvlan-pod1
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-basic
spec:
containers:
- name: test
image: nicolaka/netshoot
command: ["sleep", "infinity"]
57.3.2 验证网络¶
# 查看 Pod 网卡
kubectl exec macvlan-pod1 -- ip a
# 输出示例:
# eth1: <BROADCAST,MULTICAST,UP>
# link/ether 2a:b7:78:7c:xx:xx
# inet 15.15.1.20/24
# MACVLAN 的 MAC 地址与父接口不同!
57.3.3 同网段通信验证¶
sequenceDiagram
participant Pod1 as Pod1<br/>15.15.1.20
participant Pod2 as Pod2<br/>15.15.1.21
Pod1->>Pod1: ARP: 谁是 15.15.1.21?
Pod2->>Pod1: ARP Reply: 我是 2a:99:66:xx
Pod1->>Pod2: ICMP Echo Request
Pod2->>Pod1: ICMP Echo Reply
[!TIP] MACVLAN 同网段通信
MACVLAN 子接口之间的通信走 二层直连,因为 MAC 地址不同,可以正常 ARP 解析。
57.4 MACVLAN + SBR 高级配置¶
57.4.1 为什么需要 SBR¶
graph TB
subgraph "问题场景"
Pod["Pod<br/>eth0: 默认网卡<br/>eth1: MACVLAN"]
Default["默认路由 - eth0"]
External["外网 114.114.114.114"]
Pod --> Default
Default -.-> |"eth1 无法上外网"| External
end
问题:MACVLAN 接口默认没有默认路由,无法访问外网。
解决:使用 SBR 为 MACVLAN 接口添加专属路由表。
57.4.2 NAD + SBR 插件配置¶
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-sbr
spec:
config: '{
"cniVersion": "0.3.1",
"name": "macvlan-sbr-net",
"plugins": [
{
"type": "macvlan",
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "whereabouts",
"range": "15.15.1.0/24",
"gateway": "15.15.1.1",
"routes": [
{"dst": "0.0.0.0/0"}
]
}
},
{
"type": "sbr"
}
]
}'
57.4.3 plugins 链式配置¶
flowchart LR
subgraph "plugins 链式调用"
P1["Plugin 1<br/>macvlan"]
P2["Plugin 2<br/>sbr"]
end
P1 --> |创建网卡| P2
P2 --> |添加原地路由| Result["完成配置"]
| 插件 | 作用 |
|---|---|
| macvlan | 创建 MACVLAN 子接口 |
| sbr | 自动添加 ip rule + ip route table |
[!IMPORTANT] 自动化 SBR
通过
plugins数组配置sbr插件,Pod 启动时 自动 添加原地路由,无需手动配置!
57.5 Kind + ContainerLab 多网卡环境¶
57.5.1 架构设计¶
graph TB
subgraph "ContainerLab"
GW["Gateway<br/>VyOS 路由器"]
BR["Bridge"]
GW --> BR
end
subgraph "Kind K8s 集群"
M["Master<br/>eth0 + eth1"]
W1["Worker1<br/>eth0 + eth1"]
W2["Worker2<br/>eth0 + eth1"]
end
BR --> M
BR --> W1
BR --> W2
subgraph "网卡来源"
eth0_src["eth0 - Kind 网络"]
eth1_src["eth1 - ContainerLab"]
end
57.5.2 多网段设计¶
| 网卡 | 网段 | 网关 | 用途 |
|---|---|---|---|
| eth0 | 172.20.20.0/24 | Kind 默认 | K8s 集群通信 |
| eth1 | 15.15.1.0/24 | 15.15.1.1 | MACVLAN 网络1 |
| eth2 | 16.16.1.0/24 | 16.16.1.1 | MACVLAN 网络2 |
57.6 多网卡 Pod 配置¶
57.6.1 三网卡 Pod 示例¶
apiVersion: v1
kind: Pod
metadata:
name: multi-nic-pod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-sbr-net1, macvlan-sbr-net2
spec:
containers:
- name: test
image: nicolaka/netshoot
command: ["sleep", "infinity"]
57.6.2 Pod 网络结构¶
graph TB
subgraph "Pod: multi-nic-pod"
eth0["eth0<br/>K8s 默认网络<br/>10.244.x.x"]
eth1["eth1<br/>MACVLAN 网络1<br/>15.15.1.2"]
eth2["eth2<br/>MACVLAN 网络2<br/>16.16.1.2"]
end
eth0 --> K8s["K8s 集群通信"]
eth1 --> Net1["业务网络1"]
eth2 --> Net2["业务网络2"]
57.6.3 验证 SBR 自动配置¶
# 查看路由规则
kubectl exec multi-nic-pod -- ip rule
# 输出:
# 0: from all lookup local
# 100: from 15.15.1.0/24 lookup 100
# 101: from 16.16.1.0/24 lookup 101
# 32766: from all lookup main
# 32767: from all lookup default
# 查看路由表
kubectl exec multi-nic-pod -- ip route show table 100
# 输出:
# 15.15.1.0/24 dev eth1 scope link
# default via 15.15.1.1 dev eth1
57.7 多网卡通信验证¶
57.7.1 验证场景¶
flowchart TB
subgraph "验证项目"
T1["同网段 Pod 互通"]
T2["跨网段 Pod 互通"]
T3["每张网卡上外网"]
T4["网关可达性"]
end
57.7.2 验证命令¶
# 同网段 Pod 互通
ping -I 15.15.1.2 15.15.1.3
# 跨网段 Pod 互通(通过网关)
ping -I 15.15.1.2 16.16.1.3
# 每张网卡上外网
ping -I 15.15.1.2 114.114.114.114
ping -I 16.16.1.2 114.114.114.114
57.8 生产应用场景¶
57.8.1 网卡功能分离¶
graph LR
subgraph "Pod 多网卡"
eth0["eth0<br/>O&M 管理"]
eth1["eth1<br/>Data 业务"]
eth2["eth2<br/>Media 媒体"]
end
eth0 --> OM["SSH/监控/运维"]
eth1 --> Data["数据处理"]
eth2 --> Media["流媒体传输"]
57.8.2 典型行业应用¶
| 行业 | 网卡用途 | 说明 |
|---|---|---|
| 电信 | Control/Data 分离 | 控制面与数据面隔离 |
| 流媒体 | Media/Signal 分离 | 媒体流与信令分离 |
| 金融 | Trade/Admin 分离 | 交易网络与管理网络隔离 |
57.9 性能对比¶
| 方案 | 性能级别 | 说明 |
|---|---|---|
| MACVLAN Bridge | ⭐⭐⭐⭐ | 接近物理网卡性能 |
| IPVLAN L2 | ⭐⭐⭐⭐ | 与 MACVLAN 相当 |
| SR-IOV + DPDK | ⭐⭐⭐⭐⭐ | 最高性能(硬件虚拟化) |
| Overlay (VxLAN) | ⭐⭐ | 封装开销较大 |
57.10 章节小结¶
mindmap
root((MACVLAN-SBR 实践))
MACVLAN 特点
独立 MAC 地址
早期内核支持
Bridge 模式最常用
与 IPVLAN 对比
MAC 独立 vs 共享
理解难度
云环境兼容性
SBR 配置
plugins 链式调用
sbr 插件自动配置
无需手动添加路由
多网卡环境
Kind + ContainerLab
VyOS 网关
多网段设计
生产应用
O&M/Data/Media 分离
电信/流媒体/金融
[!TIP] MACVLAN-SBR 实践要点总结:
MACVLAN 特点:每个子接口有独立 MAC 地址,Bridge 模式最常用
与 IPVLAN 区别:MACVLAN MAC 独立,IPVLAN MAC 共享
SBR 自动配置:使用
plugins数组添加{"type": "sbr"}多网卡环境:Kind + ContainerLab 集成,多网卡可同时上外网
生产应用:网卡功能分离(管理/业务/媒体),电信、流媒体、金融行业常用
第五十八章 Multus-with-SRIOV-Kernel¶
本章介绍 SR-IOV Kernel 模式在 Kubernetes 中的应用,包括 SR-IOV 原理、硬件虚拟化技术、组件配置及与 Multus 的集成。
58.1 背景与概述¶
58.1.1 高性能网络需求¶
graph TB
subgraph "传统网络路径"
App["应用"]
Socket["Socket 层"]
TCPIP["TCP/IP 协议栈"]
Driver["网卡驱动"]
NIC["物理网卡"]
App --> Socket --> TCPIP --> Driver --> NIC
end
问题:当网卡带宽超过 10G 时,传统内核协议栈成为性能瓶颈。
解决方案:Kernel Bypass(内核旁路)技术。
58.1.2 SR-IOV 核心概念¶
| 术语 | 说明 |
|---|---|
| PF (Physical Function) | 物理网卡,完整的 PCIe 功能 |
| VF (Virtual Function) | 虚拟网卡,PF 划分出的轻量级功能 |
| SR-IOV | Single Root I/O Virtualization,硬件虚拟化标准 |
graph TB
subgraph "SR-IOV 架构"
PF["PF - 物理网卡<br/>10G/25G/40G"]
VF1["VF0"]
VF2["VF1"]
VF3["VF2"]
VFn["VF..."]
PF --> VF1
PF --> VF2
PF --> VF3
PF --> VFn
end
subgraph "Pod"
Pod1["Pod1<br/>使用 VF0"]
Pod2["Pod2<br/>使用 VF1"]
end
VF1 --> Pod1
VF2 --> Pod2
[!NOTE] SR-IOV 优势
- 硬件虚拟化:VF 直接由硬件提供,不经过 Host OS 协议栈
- 高性能:接近物理网卡性能
- 资源隔离:每个 VF 独立的带宽和资源
58.2 SR-IOV vs DPDK¶
58.2.1 Kernel Bypass 层次¶
graph TB
subgraph "裸机环境 - 双层 Bypass"
Pod["Pod"]
PodStack["Pod 协议栈"]
HostStack["Host OS 协议栈"]
HW["物理网卡"]
Pod --> |"DPDK/VPP Bypass"| PodStack
PodStack -.-> |"SR-IOV Bypass"| HostStack
HostStack --> HW
end
style PodStack stroke-dasharray: 5 5
style HostStack stroke-dasharray: 5 5
| 技术 | Bypass 层级 | 说明 |
|---|---|---|
| SR-IOV Kernel | Host OS 协议栈 | VF 直通到 Pod |
| DPDK/VPP | Pod 协议栈 | 用户态协议栈处理 |
| SR-IOV + DPDK | 双层 Bypass | 最高性能 |
58.2.2 适用场景¶
flowchart LR
subgraph "SR-IOV Kernel"
K1["通用高性能场景"]
K2["简单配置"]
K3["保留内核协议栈"]
end
subgraph "SR-IOV + DPDK"
D1["极致性能场景"]
D2["流媒体处理"]
D3["电信 NFV"]
end
58.3 BIOS/内核预设置¶
58.3.1 BIOS 设置¶
| 设置项 | 说明 |
|---|---|
| VT-d | Intel 虚拟化技术,必须开启 |
| SR-IOV | 网卡 SR-IOV 功能,必须开启 |
58.3.2 内核参数¶
# 编辑 GRUB 配置
vi /etc/default/grub
# 添加内核参数
GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt"
# 更新 GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg
# 重启生效
reboot
58.3.3 HugePages(大页内存)配置¶
# 编辑配置
vi /etc/sysctl.d/hugepages.conf
# 添加内容
vm.nr_hugepages = 16
vm.hugetlb_shm_group = 0
# 应用配置
sysctl -p /etc/sysctl.d/hugepages.conf
| 参数 | 说明 |
|---|---|
| nr_hugepages | 大页数量(1G 页 x 16 = 16G) |
| 用途 | 提高内存命中率,减少 TLB miss |
[!IMPORTANT] HugePages 注意事项
- HugePages 是从系统内存中划分的
- 例如:800G 内存,配置 300G HugePages,剩余 500G 可用
- 需要根据 Pod 数量和单 Pod 内存需求规划
58.4 VF 创建与管理¶
58.4.1 创建 VF¶
# 查看网卡
ip link show
# 创建 VF(例如:eth2 创建 8 个 VF)
echo 8 > /sys/class/net/eth2/device/sriov_numvfs
# 验证
ip -d link show eth2
# 输出会显示 vf 0, vf 1, ..., vf 7
58.4.2 VF 数量规划¶
| 网卡带宽 | VF 数量 | 单 VF 带宽 |
|---|---|---|
| 10G | 8 | ~1.25G |
| 25G | 8 | ~3G |
| 40G | 16 | ~2.5G |
58.5 SR-IOV Network Device Plugin¶
58.5.1 组件作用¶
graph TB
subgraph "SR-IOV 组件栈"
DP["SR-IOV Network Device Plugin<br/>VF 管理、资源上报"]
CNI["SR-IOV CNI<br/>网络通路搭建"]
Multus["Multus CNI<br/>多网卡管理"]
end
DP --> |"发现 VF"| K8s["K8s 资源"]
CNI --> |"注入 VF"| Pod["Pod"]
Multus --> |"调度网卡"| CNI
58.5.2 安装 Device Plugin¶
# 部署 SR-IOV Network Device Plugin
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/sriov-network-device-plugin/master/deployments/k8s-v1.16/sriovdp-daemonset.yaml
58.5.3 ConfigMap 配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: sriovdp-config
namespace: kube-system
data:
config.json: |
{
"resourceList": [
{
"resourceName": "sriov_netdevice",
"selectors": {
"vendors": ["8086"],
"devices": ["154c"],
"drivers": ["i40evf"],
"pfNames": ["eth2", "eth3"]
}
}
]
}
| 字段 | 说明 |
|---|---|
| resourceName | K8s 资源名称 |
| vendors | 网卡厂商 ID(如 8086 = Intel) |
| devices | 设备 ID |
| drivers | VF 驱动名称 |
| pfNames | PF 网卡名称 |
58.5.4 验证资源¶
# 查看节点资源
kubectl describe node <node-name> | grep -A 10 "Allocatable"
# 输出示例:
# intel.com/sriov_netdevice: 16
# hugepages-1Gi: 64Gi
58.6 SR-IOV CNI¶
58.6.1 安装 SR-IOV CNI¶
# 部署 SR-IOV CNI
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/sriov-cni/master/images/sriov-cni-daemonset.yaml
58.6.2 CNI 工作原理¶
sequenceDiagram
participant Kubelet
participant Multus
participant SRIOV_CNI as SR-IOV CNI
participant Pod
Kubelet->>Multus: 创建 Pod 请求
Multus->>Multus: 解析 NAD 注解
Multus->>SRIOV_CNI: 调用 SR-IOV CNI
SRIOV_CNI->>SRIOV_CNI: 分配 VF
SRIOV_CNI->>Pod: 注入 VF 网卡
SRIOV_CNI->>Multus: 返回结果
Multus->>Kubelet: 完成
58.7 NAD 配置¶
58.7.1 完整 NAD 示例¶
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: sriov-net
annotations:
k8s.v1.cni.cncf.io/resourceName: intel.com/sriov_netdevice
spec:
config: '{
"cniVersion": "0.3.1",
"name": "sriov-network",
"type": "sriov",
"spoofchk": "on",
"trust": "on",
"vlan": 100,
"ipam": {
"type": "whereabouts",
"range": "192.168.100.0/24",
"range_start": "192.168.100.10",
"range_end": "192.168.100.200"
}
}'
58.7.2 关键参数说明¶
| 参数 | 说明 |
|---|---|
| spoofchk | MAC 欺骗检查,on 开启 |
| trust | 信任模式,on 允许接收 GARP |
| vlan | VLAN ID,可选 |
| resourceName | 引用 Device Plugin 定义的资源 |
[!TIP] trust 参数
- 开启后 VF 可以接收 GARP(Gratuitous ARP)消息
- 避免 Pod 重启后因 MAC 地址变化导致的短暂不可达
58.8 Pod 配置¶
58.8.1 Pod 示例¶
apiVersion: v1
kind: Pod
metadata:
name: sriov-pod
annotations:
k8s.v1.cni.cncf.io/networks: sriov-net
spec:
containers:
- name: app
image: nicolaka/netshoot
command: ["sleep", "infinity"]
resources:
requests:
intel.com/sriov_netdevice: "1"
limits:
intel.com/sriov_netdevice: "1"
58.8.2 资源声明¶
flowchart LR
subgraph "资源声明"
A["resources.requests"]
B["intel.com/sriov_netdevice: 1"]
end
A --> B
B --> |"调度器检查"| Node["有 VF 的节点"]
Node --> |"分配 VF"| Pod["Pod"]
[!IMPORTANT] 必须声明资源
与 MACVLAN/IPVLAN 不同,SR-IOV 必须在 Pod 中声明资源请求,否则调度器无法分配 VF。
58.9 验证与调试¶
58.9.1 验证命令¶
# 查看 Pod 网卡
kubectl exec sriov-pod -- ip a
# 查看网卡驱动
kubectl exec sriov-pod -- ethtool -i net1
# driver: i40evf
# 查看 VF 分配
ip -d link show eth2 | grep vf
58.9.2 常见问题¶
| 问题 | 原因 | 解决 |
|---|---|---|
| VF 数量为 0 | 未创建 VF | echo N > sriov_numvfs |
| 资源不可见 | ConfigMap 配置错误 | 检查 pfNames、drivers |
| Pod 无法调度 | VF 不足 | 增加 VF 或减少 Pod |
58.10 章节小结¶
mindmap
root((SR-IOV Kernel))
原理
PF 到 VF 映射
硬件虚拟化
Bypass Host OS
预设置
BIOS VT-d
内核 IOMMU
HugePages
组件
Device Plugin
SR-IOV CNI
Multus
配置
ConfigMap 资源定义
NAD 网络配置
Pod 资源声明
关键参数
spoofchk
trust
vlan
[!TIP] SR-IOV Kernel 要点总结:
原理:PF 划分为多个 VF,VF 直通到 Pod,Bypass Host OS 协议栈
预设置:BIOS 开启 VT-d/SR-IOV,内核配置 IOMMU,配置 HugePages
组件:
- Device Plugin:管理 VF,上报资源
- SR-IOV CNI:搭建网络通路
Multus:多网卡管理
配置流程:
创建 VF → 部署 Device Plugin → 部署 SR-IOV CNI → 创建 NAD → 创建 Pod
关键参数:
spoofchk、trust、vlan、资源请求
第五十九章 Multus-with-SRIOV-DPDK-VPP¶
本章介绍 SR-IOV 结合 DPDK/VPP 的高性能网络方案,涵盖驱动配置、PMD 原理、CPU 绑核隔离等关键技术。
59.1 背景与概述¶
59.1.1 SR-IOV Kernel vs SR-IOV DPDK¶
graph TB
subgraph "SR-IOV Kernel"
K_VF["VF"]
K_Kernel["内核协议栈"]
K_Pod["Pod"]
K_VF --> K_Kernel --> K_Pod
end
subgraph "SR-IOV DPDK"
D_VF["VF"]
D_VPP["VPP 用户态协议栈"]
D_Pod["Pod"]
D_VF --> D_VPP --> D_Pod
end
| 对比项 | SR-IOV Kernel | SR-IOV DPDK |
|---|---|---|
| 协议栈 | 内核协议栈 | 用户态协议栈(VPP) |
| Bypass 层级 | Host OS | Host OS + Pod 内核 |
| 驱动 | 原生驱动(i40evf, sfc_efx) | vfio-pci, igb_uio |
| 性能 | 高 | 极高 |
| 复杂度 | 低 | 高 |
59.1.2 双层 Bypass 架构¶
graph TB
subgraph "裸机 DPDK 完整架构"
APP["应用"]
VPP["VPP 用户态协议栈<br/>(DPDK Bypass Pod 内核)"]
VF["VF(SR-IOV)<br/>(Bypass Host OS 内核)"]
PF["物理网卡 PF"]
APP --> VPP
VPP --> VF
VF --> PF
end
[!TIP] 双层 Bypass 理解
- 第一层:SR-IOV VF 直通,Bypass Host OS 内核协议栈
- 第二层:DPDK/VPP 用户态协议栈,Bypass Pod 内核协议栈
59.2 DPDK 驱动类型¶
59.2.1 驱动对比¶
| 驱动 | 说明 | 推荐 |
|---|---|---|
| vfio-pci | 现代推荐驱动,安全性好 | ✅ 生产推荐 |
| igb_uio | 早期驱动,需要更高权限 | ❌ 不推荐 |
| uio_generic | 通用驱动,性能较低 | ❌ 备选 |
59.2.2 驱动选择原因¶
flowchart TD
A["选择 DPDK 驱动"] --> B{"是否支持非特权模式?"}
B -->|"是"| C["vfio-pci ✅"]
B -->|"否"| D["igb_uio"]
C --> E["安全性好<br/>capabilities 要求少"]
D --> F["需要 privileged: true<br/>安全风险"]
[!IMPORTANT] 生产环境驱动选择
- 优先使用
vfio-pci,支持非特权模式运行- 避免使用
igb_uio,需要更高权限vfio-pci对 capabilities 要求更少,更安全
59.3 驱动绑定操作¶
59.3.1 加载驱动模块¶
# 加载 vfio-pci 驱动
modprobe vfio-pci
59.3.2 绑定 VF 到 DPDK 驱动¶
# 查看 VF 的 PCI 地址
lspci | grep -i ethernet
# 使用 dpdk-devbind 脚本绑定驱动
# 解绑原有驱动并绑定到 vfio-pci
for pci_addr in <VF_PCI_ADDR_LIST>; do
dpdk-devbind.py -u $pci_addr
dpdk-devbind.py -b vfio-pci $pci_addr
done
# 验证驱动绑定
dpdk-devbind.py --status
59.3.3 验证 VF 配置¶
# 查看 VF 状态
ip -d link show eth6
# 输出示例:
# eth6: ... link/ether ...
# vf 0 MAC ... spoof check on, trust on
# vf 1 MAC ... spoof check on, trust on
# ...
59.4 DPDK PMD 原理¶
59.4.1 中断模式 vs 轮询模式¶
sequenceDiagram
participant NIC as 网卡
participant Kernel as 内核
participant App as 应用
Note over NIC, App: 传统中断模式
NIC->>Kernel: 硬中断
Kernel->>Kernel: 软中断处理
Kernel->>App: 数据包
Note over NIC, App: DPDK PMD 轮询模式
loop 持续轮询
App->>NIC: 主动查询
NIC-->>App: 返回数据包
end
| 模式 | 中断模式 | PMD 轮询模式 |
|---|---|---|
| 触发方式 | 被动(中断触发) | 主动(持续轮询) |
| CPU 使用 | 按需 | 100% 占用 |
| 延迟 | 较高 | 极低 |
| 适用场景 | 通用场景 | 高性能转发 |
59.4.2 PMD 工作原理¶
graph LR
subgraph "PMD Poll Mode Driver"
CPU["专用 CPU<br/>100% 占用"]
Ring["Ring Buffer"]
VF["VF 网卡"]
CPU --> |"持续轮询"| Ring
VF --> Ring
Ring --> VF
end
[!TIP] PMD 核心特点
- CPU 持续轮询,不依赖中断
- CPU 显示 100% 使用率(正常现象)
- 实现纳秒级延迟
59.5 CPU 绑核与隔离¶
59.5.1 为什么需要 CPU 绑核¶
flowchart TD
A["PMD 需要 CPU 持续轮询"] --> B["问题:CPU 被其他进程抢占"]
B --> C["后果:数据包丢失/延迟"]
C --> D["解决:CPU 绑核隔离"]
问题:PMD 需要 CPU 24 小时持续轮询,如果 CPU 被其他进程抢占,会导致数据包丢失。
解决:CPU 绑核隔离,确保 PMD 专用 CPU 不被抢占。
59.5.2 禁用 irqbalance¶
# 停止 irqbalance 服务
systemctl stop irqbalance
systemctl disable irqbalance
[!IMPORTANT] irqbalance 说明
- irqbalance 会动态分配中断到不同 CPU
- 对于 PMD 专用场景,必须禁用
- 禁用后 CPU 使用更可控
59.5.3 配置 CPU 隔离¶
# 编辑 GRUB 配置
vi /etc/default/grub
# 添加 isolcpus 参数
GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt isolcpus=4-51,56-103"
# 更新 GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg
# 重启生效
reboot
59.5.4 CPU 分配策略¶
graph LR
subgraph "NUMA 0"
SYS0["CPU 0-3<br/>系统预留"]
APP0["CPU 4-51<br/>Pod 专用"]
end
subgraph "NUMA 1"
SYS1["CPU 52-55<br/>系统预留"]
APP1["CPU 56-103<br/>Pod 专用"]
end
| CPU 范围 | 用途 |
|---|---|
| 0-3, 52-55 | 系统预留(K8s、基础服务) |
| 4-51, 56-103 | Pod 专用(PMD、VPP) |
59.6 NAD 配置差异¶
59.6.1 DPDK NAD 特点¶
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: sriov-dpdk-net
annotations:
k8s.v1.cni.cncf.io/resourceName: intel.com/sriov_dpdk
spec:
config: '{
"cniVersion": "0.3.1",
"name": "sriov-dpdk-network",
"type": "sriov",
"vlan": 100
}'
[!WARNING] DPDK 模式 IPAM 注意事项
- DPDK 模式下,VPP 接管协议栈
- 传统 IPAM(如 whereabouts)无法直接使用
- IP 配置需通过 VPP 内部机制完成
59.6.2 ConfigMap 驱动配置¶
apiVersion: v1
kind: ConfigMap
metadata:
name: sriovdp-config
namespace: kube-system
data:
config.json: |
{
"resourceList": [
{
"resourceName": "sriov_dpdk",
"selectors": {
"vendors": ["8086"],
"drivers": ["vfio-pci"],
"pfNames": ["eth2", "eth3"]
}
},
{
"resourceName": "sriov_kernel",
"selectors": {
"vendors": ["8086"],
"drivers": ["i40evf"],
"pfNames": ["eth4", "eth5"]
}
}
]
}
59.7 Fast Path vs Slow Path¶
59.7.1 概念对比¶
graph TB
subgraph "Slow Path - 传统路径"
S_App["应用"]
S_Kernel["内核协议栈"]
S_Driver["网卡驱动"]
S_NIC["网卡"]
S_App --> S_Kernel --> S_Driver --> S_NIC
end
subgraph "Fast Path - DPDK 路径"
F_App["应用"]
F_VPP["VPP/DPDK"]
F_NIC["网卡 VF"]
F_App --> F_VPP --> F_NIC
end
| 路径 | 延迟 | 吞吐量 | 适用场景 |
|---|---|---|---|
| Slow Path | 高 | 一般 | 通用场景 |
| Fast Path | 极低 | 极高 | NFV、流媒体、金融 |
59.8 生产应用场景¶
59.8.1 典型应用领域¶
mindmap
root((SR-IOV DPDK 应用))
电信 NFV
虚拟路由器
虚拟防火墙
vBNG
流媒体处理
视频转码
CDN 边缘节点
直播推流
金融交易
高频交易
低延迟网关
行情分发
网络安全
DDoS 防护
流量清洗
深度包检测
59.9 章节小结¶
mindmap
root((SR-IOV DPDK VPP))
驱动选择
vfio-pci 推荐
igb_uio 不推荐
dpdk-devbind 绑定
PMD 原理
轮询模式
CPU 100%
纳秒延迟
CPU 隔离
禁用 irqbalance
isolcpus
NUMA 感知
配置差异
IPAM 不适用
VPP 配置 IP
驱动区分资源
性能优势
双层 Bypass
Fast Path
极致性能
[!TIP] SR-IOV DPDK VPP 要点总结:
驱动选择:优先
vfio-pci,安全且支持非特权模式PMD 原理:Poll Mode Driver 持续轮询,CPU 100% 占用但延迟极低
CPU 隔离:
- 禁用 irqbalance
- 配置 isolcpus 参数
NUMA 感知分配
配置差异:
- DPDK 模式下传统 IPAM 不适用
驱动类型区分 Kernel 和 DPDK 资源
性能优势:双层 Bypass + Fast Path = 极致性能
第六十章 K8s-CNI-IPAM 机制详解¶
本章系统介绍 Kubernetes CNI 中的 IPAM(IP Address Management)机制,涵盖各种 IPAM 类型、适用场景和选型建议。
60.1 背景与概述¶
60.1.1 CNI 的两大核心功能¶
graph LR
subgraph "CNI 核心功能"
NL["Network Links<br/>网络通路搭建"]
IPAM["IPAM<br/>IP 地址管理"]
end
CNI["CNI 插件"] --> NL
CNI --> IPAM
| 功能 | 说明 | 示例技术 |
|---|---|---|
| Network Links | 搭建网络通路 | VXLAN, IPIP, BGP, VLAN |
| IPAM | IP 地址分配与管理 | host-local, whereabouts, DHCP |
[!IMPORTANT] CNI = Network Links + IPAM
任何 CNI 方案都包含这两部分:
- Network Links:解决 Pod 之间如何通信
- IPAM:解决 Pod 如何获取 IP 地址
60.1.2 为什么需要 IPAM¶
flowchart TD
A["Pod 创建"] --> B["需要 IP 地址"]
B --> C{"如何分配?"}
C --> D["IPAM 机制"]
D --> E["分配唯一 IP"]
D --> F["避免 IP 冲突"]
D --> G["管理 IP 生命周期"]
60.2 IPAM 类型概述¶
60.2.1 四种标准 IPAM¶
graph TB
subgraph "CNI 标准 IPAM"
DHCP["DHCP<br/>动态主机配置协议"]
HL["host-local<br/>节点本地分配"]
ST["static<br/>静态 IP"]
WA["whereabouts<br/>集群级分配"]
end
| IPAM 类型 | 分配范围 | 适用场景 | 使用频率 |
|---|---|---|---|
| DHCP | 外部 DHCP 服务器 | 传统网络对接 | 少 |
| host-local | 节点本地 | Flannel 等通用场景 | 高 |
| static | 手动指定 | 多网卡固定 IP | 中 |
| whereabouts | 集群级别 | Multus 多网卡 | 中 |
60.3 host-local IPAM¶
60.3.1 工作原理¶
graph TB
subgraph "Node 1"
N1_Range["子网: 10.244.0.0/24"]
N1_Pod1["Pod1: 10.244.0.2"]
N1_Pod2["Pod2: 10.244.0.3"]
end
subgraph "Node 2"
N2_Range["子网: 10.244.1.0/24"]
N2_Pod1["Pod1: 10.244.1.2"]
N2_Pod2["Pod2: 10.244.1.3"]
end
subgraph "Node 3"
N3_Range["子网: 10.244.2.0/24"]
N3_Pod1["Pod1: 10.244.2.2"]
N3_Pod2["Pod2: 10.244.2.3"]
end
核心特点:
- 每个节点分配一个固定子网(如 /24)
- Pod IP 从节点子网中分配
- IP 与节点强关联
60.3.2 配置示例¶
{
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16",
"rangeStart": "10.244.0.10",
"rangeEnd": "10.244.0.250",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"dataDir": "/var/lib/cni/networks"
}
}
60.3.3 优缺点分析¶
| 优点 | 缺点 |
|---|---|
| 简单易用 | IP 与节点绑定 |
| 性能好(本地分配) | 节点故障 IP 不可迁移 |
| 无外部依赖 | 每节点 IP 数量有限 |
[!TIP] Flannel 默认使用 host-local
- 每节点分配 /24 子网(254 个可用 IP)
- 满足大多数场景(K8s 默认每节点最多 110 个 Pod)
60.4 static IPAM¶
60.4.1 适用场景¶
flowchart TD
A["多网卡场景"] --> B["Pod 需要固定 IP"]
B --> C["使用 static IPAM"]
D["重型容器/VM 风格"]
D --> E["多容器多进程"]
E --> F["固定 IP 便于管理"]
典型场景:
- 多网卡环境(Multus)
- VM 风格的 Pod
- 需要固定 IP 对接外部系统
60.4.2 配置示例¶
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: static-ip-net
spec:
config: '{
"cniVersion": "0.3.1",
"name": "static-network",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "static",
"addresses": [
{
"address": "192.168.1.100/24",
"gateway": "192.168.1.1"
}
],
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}'
60.4.3 为什么原生 K8s 不太需要 static IP¶
flowchart TD
A["Pod 重启 IP 变化"] --> B{"影响访问?"}
B -->|"否"| C["Service 抽象层"]
C --> D["Endpoints 自动更新"]
D --> E["iptables/IPVS 规则更新"]
E --> F["外部访问不受影响"]
[!NOTE] Service 机制解耦了 IP 变化问题
- Pod IP 变化 → Endpoints 自动更新
- Service ClusterIP/NodePort 保持不变
- 外部通过 Service 访问,无需关心 Pod IP
60.5 whereabouts IPAM¶
60.5.1 Cluster-Wide vs Host-Local¶
graph TB
subgraph "host-local"
HL_N1["Node1: 10.244.0.0/24"]
HL_N2["Node2: 10.244.1.0/24"]
HL_N3["Node3: 10.244.2.0/24"]
end
subgraph "whereabouts"
WA_Pool["集群 IP 池: 10.10.0.0/16"]
WA_P1["Pod1: 10.10.0.1(任意节点)"]
WA_P2["Pod2: 10.10.0.2(任意节点)"]
WA_P3["Pod3: 10.10.0.3(任意节点)"]
WA_Pool --> WA_P1
WA_Pool --> WA_P2
WA_Pool --> WA_P3
end
| 特性 | host-local | whereabouts |
|---|---|---|
| IP 分配范围 | 单节点 | 整个集群 |
| IP 迁移性 | 不支持 | 支持 |
| 实现复杂度 | 低 | 中 |
| 外部依赖 | 无 | 需要 CR 存储 |
60.5.2 配置示例¶
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: whereabouts-net
spec:
config: '{
"cniVersion": "0.3.1",
"name": "whereabouts-network",
"type": "macvlan",
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "whereabouts",
"range": "192.168.100.0/24",
"exclude": [
"192.168.100.0/32",
"192.168.100.1/32",
"192.168.100.255/32"
]
}
}'
60.6 Cilium IPAM¶
60.6.1 块分配机制¶
graph TB
subgraph "Cilium IPAM"
Pool["集群 Pod CIDR: 10.0.0.0/8"]
B1["Block 1: 10.0.0.0/26<br/>(64 个 IP)"]
B2["Block 2: 10.0.0.64/26<br/>(64 个 IP)"]
B3["Block 3: 10.0.0.128/26<br/>(64 个 IP)"]
Pool --> B1
Pool --> B2
Pool --> B3
B1 --> N1["Node 1"]
B2 --> N2["Node 2"]
B3 --> N3["Node 3"]
end
Cilium 特点:
- 使用 /26 块(64 个 IP)而非 /24
- 块可以动态扩展
- 支持更灵活的 IP 管理
60.6.2 与 host-local 对比¶
| 特性 | host-local (/24) | Cilium (/26 块) |
|---|---|---|
| 每节点 IP 数 | 254 | 可扩展(多块) |
| IP 利用率 | 可能浪费 | 更高效 |
| 灵活性 | 低 | 高 |
60.7 公有云 VPC IPAM¶
60.7.1 云厂商方案¶
graph LR
subgraph "公有云方案"
AWS["AWS VPC CNI"]
Ali["阿里云 Terway"]
GCP["GCP VPC-native"]
Tencent["腾讯云 TKE CNI"]
end
VPC["VPC 网络"] --> AWS
VPC --> Ali
VPC --> GCP
VPC --> Tencent
特点:
- 直接使用 VPC 子网 IP
- Pod IP 与 VPC 路由互通
- 无需 Overlay 封装
60.7.2 阿里云 Terway 示例¶
graph TB
subgraph "Terway 架构"
VPC["VPC 网络"]
ENI["弹性网卡 ENI"]
Pod["Pod"]
VPC --> ENI
ENI --> Pod
end
subgraph "组件"
Terway["Terway CNI<br/>Network Links"]
Cilium["Cilium<br/>Network Policy"]
end
[!TIP] Terway = VPC Network Links + Cilium Policy
- Network Links:基于 ENI 实现 VPC 互通
- Policy:使用 Cilium 实现网络策略
60.8 Spiderpool IPAM¶
60.8.1 新一代集群级 IPAM¶
flowchart TD
A["Spiderpool"] --> B["Cluster-Wide IPAM"]
B --> C["多 IP 池支持"]
C --> D["IP 固定/预留"]
D --> E["与 whereabouts 对比增强"]
Spiderpool 特性:
- 来自 DaoCloud 开源
- 解决 IP 固定问题
- 支持多 IP 池定义
- 与 whereabouts 功能对比增强
60.8.2 解决的问题¶
sequenceDiagram
participant Pod as Pod
participant IPAM as Spiderpool
participant Pool as IP Pool
Note over Pod, Pool: 问题场景:IP 未释放时重启
Pod->>IPAM: 请求 IP
IPAM->>Pool: 从多 IP 池分配
Pool-->>IPAM: 返回可用 IP
IPAM-->>Pod: 分配 IP
Note over Pod, Pool: 支持多 IP 池避免单点问题
60.9 IPAM 选型建议¶
60.9.1 决策流程¶
flowchart TD
A["选择 IPAM"] --> B{"场景类型?"}
B -->|"通用 K8s"| C["host-local"]
B -->|"多网卡 Multus"| D{"需要固定 IP?"}
B -->|"公有云"| E["云厂商 VPC IPAM"]
D -->|"是"| F["static / Spiderpool"]
D -->|"否"| G["whereabouts"]
C --> H["简单高效<br/>Flannel 默认"]
F --> I["多网卡固定 IP<br/>VM 风格 Pod"]
G --> J["集群级 IP 池<br/>跨节点分配"]
E --> K["VPC 互通<br/>无 Overlay"]
60.9.2 选型对照表¶
| 场景 | 推荐 IPAM | 原因 |
|---|---|---|
| 通用 K8s 集群 | host-local | 简单、稳定、性能好 |
| Multus 多网卡 | whereabouts | 集群级 IP 池 |
| 固定 IP 需求 | static / Spiderpool | 支持 IP 固定 |
| 公有云 | 云厂商 IPAM | VPC 原生互通 |
| NFV/电信 | whereabouts + static | 复杂网络需求 |
60.10 章节小结¶
mindmap
root((CNI IPAM))
IPAM 类型
DHCP(少用)
host-local(常用)
static(多网卡)
whereabouts(集群级)
host-local
节点子网分配
简单高效
Flannel 默认
whereabouts
Cluster-Wide
IP 可迁移
Multus 推荐
云厂商
VPC 互通
ENI 直通
Terway 等
新方案
Spiderpool
多 IP 池
IP 固定
[!TIP] CNI IPAM 要点总结:
CNI 双核心:Network Links(网络通路)+ IPAM(地址管理)
host-local:节点级分配,简单高效,Flannel 等通用 CNI 默认使用
static:固定 IP,适用于多网卡、VM 风格 Pod
whereabouts:集群级 IP 池,适用于 Multus 多网卡场景
云厂商:VPC 原生 IPAM,Pod 直接使用 VPC IP,无 Overlay
选型原则:根据场景选择,简单场景用 host-local,复杂多网卡用 whereabouts/Spiderpool