Kubernetes 通信链路详细分析

目录


1. Pod 与 Pod 通信

1.1 同节点 Pod 间通信

当两个 Pod 在同一个节点上运行时,它们的通信路径如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────────────┐
│ Node (Linux Host) │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Pod A │ │ Pod B │ │
│ │ netns A │ │ netns B │ │
│ │ 10.1.1.10 │ │ 10.1.1.11 │ │
│ │ eth0 │ │ eth0 │ │
│ │ │ │ │ │ │ │
│ └───┼────────┘ └────┼────────┘ │
│ │ veth pair A │ │
│ │◀─────────────────────────▶│ │
│ │ │ │
│ ┌───┴───────────────────────────┴───────────────────────────┐ │
│ │ CNI Bridge (cni0) │ │
│ │ │ │
│ │ MAC 地址表: │ │
│ │ ┌─────────────┬─────────────┬─────────────┐ │ │
│ │ │ Pod IP │ MAC │ veth │ │ │
│ │ ├─────────────┼─────────────┼─────────────┤ │ │
│ │ │ 10.1.1.10 │ aa:aa:aa:aa│ vethA │ │ │
│ │ │ 10.1.1.11 │ bb:bb:bb:bb│ vethB │ │ │
│ │ └─────────────┴─────────────┴─────────────┘ │ │
│ │ │ │
│ │ ARP 缓存: │ │
│ │ 10.1.1.11 → bb:bb:bb:bb (vethB) │ │
│ │ │ │
│ └─────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Root Netns │ │
│ │ eth0 (物理网卡)│ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

详细通信流程(10步)

步骤 1: Pod A 准备发送数据

1
2
3
4
5
6
7
Pod A 应用调用 socket API 发送数据到 10.1.1.11:80

数据包内容:
源 IP: 10.1.1.10 (Pod A eth0)
目标 IP: 10.1.1.11 (Pod B eth0)
源 MAC: aa:aa:aa:aa:aa:aa (Pod A eth0)
目标 MAC: ? (未知,需要 ARP 查询)

步骤 2: Pod A 发送 ARP 请求

1
2
3
4
5
6
7
Pod A 检查本地路由表:
10.1.1.0/24 via eth0 (同网段,直接发送)

Pod A 发送 ARP Request:
Who has 10.1.1.11? Tell 10.1.1.10

广播到: ff:ff:ff:ff:ff:ff

步骤 3: veth pair 传输 ARP

1
2
3
4
5
ARP 请求通过 veth pair A 传输到 Node 根网络命名空间

veth pair 是成对创建的:
- 一端在 Pod A 的 netns (名称通常是 eth0)
-另一端在 Node 根 netns (如 vethA)

步骤 4: CNI Bridge 接收并处理 ARP

1
2
3
4
5
6
7
8
9
cni0 网桥收到 ARP Request:

1. 学习源 MAC:
- 记录 10.1.1.10 -> aa:aa:aa:aa -> vethA

2. 广播 ARP 到所有端口 (除了收到数据的端口 vethA):
- vethB (Pod B 的 veth pair)
- 其他 veth 端口
- 可能有 tunnel 端口

步骤 5: Pod B 收到 ARP 请求并响应

1
2
3
4
5
6
Pod B 收到 ARP Request (因为 ARP 广播到 vethB):

Pod B 发送 ARP Reply:
10.1.1.11 is at bb:bb:bb:bb:bb:bb

这个 Reply 会被 CNI Bridge 收到

步骤 6: CNI Bridge 学习并转发 ARP Reply

1
2
3
4
5
6
7
cni0 处理 ARP Reply:

1. 学习 MAC 地址:
- 记录 10.1.1.11 -> bb:bb:bb:bb -> vethB

2. 查 MAC 表,知道 10.1.1.10 在 vethA:
- 直接转发到 vethA (不再广播)

步骤 7: Pod A 收到 ARP Reply 并缓存

1
2
3
4
5
6
7
Pod A 收到 ARP Reply:
10.1.1.11 is at bb:bb:bb:bb:bb:bb

Pod A 缓存 ARP:
10.1.1.11 -> bb:bb:bb:bb:bb:bb (缓存 20 分钟)

现在可以发送实际数据包了

步骤 8: Pod A 发送实际数据包

1
2
3
4
5
6
7
数据包:
源 IP: 10.1.1.10
目标 IP: 10.1.1.11
源 MAC: aa:aa:aa:aa:aa:aa
目标 MAC: bb:bb:bb:bb:bb:bb

通过 veth pair A 发送到 cni0

步骤 9: CNI Bridge 转发数据包

1
2
3
4
5
6
7
8
cni0 收到数据包:

1. 查 MAC 表:
- 目标 MAC bb:bb:bb:bb -> vethB

2. 从 vethB 端口转发 (直接发送,不再广播)

3. 通过 veth pair B 到达 Pod B 的 netns

步骤 10: Pod B 接收数据包

1
2
3
4
Pod B eth0 收到数据包:
目标 IP: 10.1.1.11 (匹配本机)

内核将数据交给 Pod B 内的应用进程

关键组件详解

组件 作用 关键操作
veth pair 虚拟网线,连接 Pod netns 和 Node netns 成对创建,一端在 Pod,一端在 Node
CNI Bridge 二层交换机,连接同节点所有 Pod MAC 地址学习,ARP 广播/转发
ARP 缓存 记录 IP-MAC 映射 动态学习,超时删除
路由表 判断目标是否同网段 同一网段走 bridge,不同网段走网关

源码位置

1
2
3
4
5
6
7
8
CNI 实现:
pkg/kubelet/network/cni/cni.go - CNI 插件主逻辑
pkg/kubelet/network/cni/cni_sysfs.go - 网络配置
pkg/kubelet/network/plugins.go - 网络插件接口

Kubelet 网络初始化:
pkg/kubelet/network/network.go - 网络初始化流程
pkg/kubelet/network_host.go - Host 网络接口

1.2 跨节点 Pod 间通信

当两个 Pod 在不同节点上运行时,通信需要经过物理网络。

1.2.1 整体架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
┌─────────────────────────────────────────────────────────────────────────────┐
│ 跨节点 Pod 通信架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Node A (192.168.1.10/24) Node B (192.168.1.11/24) │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ Pod A (10.1.1.10/24) │ │ Pod B (10.2.1.10/24) │ │
│ │ │ │ │ │ │ │
│ │ ▼ │ │ ▼ │ │
│ │ ┌──────┐ │ │ ┌──────┐ │ │
│ │ │ eth0 │ │ │ │ eth0 │ │ │
│ │ └──┬───┘ │ │ └──┬───┘ │ │
│ │ │ vethA │ │ │ vethB │ │
│ │ ▼ │ │ ▼ │ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │ cni0 │ │ │ │ cni0 │ │ │
│ │ └──┬─────┘ │ │ └──┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ │ flannel.1 │ │ │ flannel.1 │ │
│ │ │ (VTEP) │ │ │ (VTEP) │ │
│ │ │ 10.1.1.0/24 │ │ │ 10.2.1.0/24 │ │
│ │ │ VNI: 1 │ │ │ VNI: 1 │ │
│ │ └────────┬─────────┘ │ └────────┬─────────┘ │
│ │ │ UDP:8472 │ │ UDP:8472 │
│ └───────────────┼─────────────────┘ ├───────────────┘
│ │ │
│ ┌─────┴─────┐ ┌─────┴─────┐
│ │ eth0 │ │ eth0 │
│ │ 192.168.1.10│ │ 192.168.1.11│
│ └─────┬─────┘ └─────┬─────┘
│ │ │
│ ┌────────────────┼───────────────────────────────────┘
│ │ │ 物理网络 (交换机 L2)
│ ▼ ▼
│ ┌─────────────────────────────────────────────┐
│ │ 物理网络 (Layer 2 Switch) │
│ └─────────────────────────────────────────────┘
│ │
└─────────────────────────────────────────────────────────────────────────────┘

1.2.2 Flannel VXLAN 详细通信流程(15步)

步骤 1: Pod A 发送数据包(应用层)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Pod A 应用发送请求到 10.2.1.10:80

Socket 创建:
connect(fd, "10.2.1.10", 80)

数据包生成:
TCP Segment:
源端口: 40000
目标端口: 80
IP Header:
源 IP: 10.1.1.10
目标 IP: 10.2.1.10
Ethernet Header:
源 MAC: aa:aa:aa:aa:aa:aa
目标 MAC: ? (需要 ARP)

步骤 2: Pod A 本地路由决策

1
2
3
4
5
6
7
8
9
Pod A 路由表:
10.0.0.0/8 via eth0 (匹配 10.2.1.10)

目标 10.2.1.10 不在同网段 10.1.1.0/24
需要发送到网关 (但 Kubernetes 没有网关 IP)

实际行为:
在 CNI 层面,路由会指向 cni0 bridge
cni0 需要知道如何到达 10.2.1.0/24 网络

步骤 3: CNI Bridge 查询路由表

1
2
3
4
5
6
7
8
9
10
Node A 路由表:
10.1.1.0/24 dev cni0 (同网段,直接送达)
10.2.1.0/24 via 192.168.1.11 dev eth0 (跨节点,走物理网卡)

注意: 这是 Flannel 分配的路由!
flanneld 进程会配置这个路由

当查询 10.2.1.10 时:
匹配路由: 10.2.1.0/24 via 192.168.1.11 dev eth0
需要发送到 Node B (192.168.1.11)

步骤 4: ARP 查询 VTEP MAC

1
2
3
4
5
6
7
发送到 192.168.1.11 需要知道 Next Hop MAC

Node A 发送 ARP:
Who has 192.168.1.11? Tell 192.168.1.10

物理交换机广播,Node B 响应:
192.168.1.11 is at cc:cc:cc:cc:cc:cc

步骤 5: 创建 VXLAN 隧道端点 (VTEP)

1
2
3
4
5
6
7
8
9
10
flannel.1 是 VTEP (VXLAN Tunnel Endpoint):

属性:
本地 IP: 192.168.1.10
VNI: 1 (VXLAN Network Identifier)
端口: 8472 (Linux 内核 VXLAN 端口)

Node B 的 flannel.1:
本地 IP: 192.168.1.11
VNI: 1

步骤 6: 封装原始数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
flannel.1 将 Pod A 的数据包封装为 VXLAN:

┌────────────────────────────────────────────────────────────┐
│ 外层 Ethernet Header │
│ 源 MAC: aa:bb:cc:dd:ee:ff (Node A 物理网卡 MAC) │
│ 目标 MAC: 11:22:33:44:55:66 (物理网关/下一跳 MAC) │
├────────────────────────────────────────────────────────────┤
│ 外层 IP Header │
│ 源 IP: 192.168.1.10 (Node A 物理 IP) │
│ 目标 IP: 192.168.1.11 (Node B 物理 IP) │
├────────────────────────────────────────────────────────────┤
│ 外层 UDP Header │
│ 源端口: 随机 │
│ 目标端口: 8472 (VXLAN 端口) │
├────────────────────────────────────────────────────────────┤
│ VXLAN Header │
│ Flags: 0100 (I flag = 1, 表示有 VNI) │
│ VNI: 1 │
│ Reserved: 24 bits │
├────────────────────────────────────────────────────────────┤
│ 原始 Ethernet Header (被封装) │
│ 源 MAC: aa:aa:aa:aa:aa:aa │
│ 目标 MAC: bb:bb:bb:bb:bb:bb │
├────────────────────────────────────────────────────────────┤
│ 原始 IP Header │
│ 源 IP: 10.1.1.10 │
│ 目标 IP: 10.2.1.10 │
├────────────────────────────────────────────────────────────┤
│ 原始 TCP/应用数据 │
│ (Pod A 原本要发送的数据) │
└────────────────────────────────────────────────────────────┘

步骤 7: 通过物理网络发送

1
2
3
4
5
6
7
8
9
10
11
封装后的数据包:

外层:
源 MAC: Node A 物理 MAC
目标 MAC: Node B 物理 MAC
源 IP: 192.168.1.10
目标 IP: 192.168.1.11

通过物理网卡 eth0 发送

物理交换机根据目标 MAC 转发到 Node B

步骤 8: Node B 接收封装数据包

1
2
3
4
5
6
7
Node B eth0 接收数据包:
目标 IP: 192.168.1.11 (本机 IP)
目标 UDP 端口: 8472 (VXLAN)

Linux 内核协议栈处理:
- 识别 UDP 端口 8472
- 交给 VXLAN 模块处理

步骤 9: VXLAN 解封装

1
2
3
4
5
6
7
8
9
10
11
Node B 的 VXLAN 模块 (flannel.1):

1. 验证 VNI:
- 收到的 VNI: 1
- 本地 VNI: 1
- 匹配成功

2. 提取内部数据包:
- 原始 Ethernet Header
- 原始 IP Header (10.1.1.10 -> 10.2.1.10)
- 原始 TCP/应用数据

步骤 10: 发送到 Pod B

1
2
3
4
5
6
7
8
9
10
11
解封装后,数据包相当于在 Node B 的 cni0 网桥上:

原始数据包:
源 MAC: aa:aa:aa:aa:aa:aa
目标 MAC: bb:bb:bb:bb:bb:bb
源 IP: 10.1.1.10
目标 IP: 10.2.1.10

cni0 网桥:
- 查 MAC 表: 目标 MAC bb:bb:bb:bb:bb:bb -> vethB
- 通过 veth pair B 发送到 Pod B

步骤 11-15: Pod B 接收响应并返回 (反向流程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Pod B 处理请求后,准备响应:

响应数据包:
源 IP: 10.2.1.10 (Pod B)
目标 IP: 10.1.1.10 (Pod A)

通过 veth pair B -> cni0

cni0 查路由:
10.1.1.0/24 via 192.168.1.10 dev eth0

封装过程同上,只是源和目标反转:

Node B flannel.1 封装:
外层源 IP: 192.168.1.11
外层目标 IP: 192.168.1.10
内层: 10.2.1.10 -> 10.1.1.10

Node A 接收并解封装

1.2.3 VXLAN 原理详解

VXLAN (Virtual Extensible LAN) 是一种网络虚拟化技术,通过 UDP 封装在三层网络上创建二层虚拟网络。

VXLAN 核心概念
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────────────┐
│ VXLAN 核心概念 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ VNI (VXLAN Network Identifier): │
│ ──────────────────────────────── │
│ - 24 位标识符,支持 16M+ 虚拟网络 │
│ - 类似于 VLAN ID,但范围更大 │
│ - 每个 VNI 对应一个虚拟二层网络 │
│ │
│ VTEP (VXLAN Tunnel Endpoint): │
│ ───────────────────────────── │
│ - VXLAN 隧道端点,负责封装/解封装 │
│ - 通常是节点上的一个虚拟接口 (如 flannel.1) │
│ - 拥有独立的 MAC 和 IP 地址 │
│ │
│ UDP 端口 4789 (Linux 默认 8472): │
│ ────────────────────────────────────── │
│ - IANA 标准端口: 4789 │
│ - Linux 内核: 8472 (旧版本兼容) │
│ - 承载 VXLAN 数据包 │
│ │
│ 组播模式 vs Leader 模式: │
│ ────────────────────────── │
│ - 组播模式: 使用组播发现其他 VTEP,适合小规模 │
│ - Leader 模式: flanneld 充当 leader,分发ARP缓存,更适合大规模 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
VXLAN 数据包封装原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
┌─────────────────────────────────────────────────────────────────────────────┐
│ VXLAN 封装与解封装流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 原始数据包 (Pod 间通信): │
│ ───────────────────────── │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Ethernet: SRC_MAC(PodA) -> DST_MAC(PodB) │ │
│ │ IP: SRC_IP(10.1.1.10) -> DST_IP(10.2.1.10) │ │
│ │ TCP: SRC_PORT(40000) -> DST_PORT(80) │ │
│ │ Payload: Application Data │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ 封装 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ VXLAN Header: VNI=1, Flags=0100 │ │
│ │ UDP Header: SRC_PORT=随机, DST_PORT=8472 │ │
│ │ IP Header: SRC_IP=192.168.1.10 -> DST_IP=192.168.1.11 │ │
│ │ Ethernet: SRC_MAC=NodeA -> DST_MAC=下一跳MAC │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ 解封装 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Ethernet: SRC_MAC(PodA) -> DST_MAC(PodB) │ │
│ │ IP: SRC_IP(10.1.1.10) -> DST_IP(10.2.1.10) │ │
│ │ TCP: SRC_PORT(40000) -> DST_PORT(80) │ │
│ │ Payload: Application Data │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
VXLAN 封装/解封装设备 (VTEP) 工作机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌─────────────────────────────────────────────────────────────────────────────┐
│ VTEP 工作机制 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 本地 MAC 学习 (MAC Learning): │
│ ───────────────────────────────── │
│ 当 VTEP 收到发往本地 Pod 的数据包时: │
│ - 学习 "远程 Pod MAC -> 远程 VTEP IP" 映射 │
│ - 记录到 FDB (Forwarding Database) │
│ │
│ FDB 表示例: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ MAC Address │ VTEP IP │ Interface │ │
│ ├─────────────────────┼───────────────┼────────────│ │
│ │ aa:aa:aa:aa:aa:aa │ - │ vethA │ (本地) │
│ │ bb:bb:bb:bb:bb:bb │ 192.168.1.11│ tunnel0 │ (远程) │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 2. ARP 缓存 (ARP Proxy): │
│ ────────────────────── │
│ flanneld 维护 ARP 缓存,避免广播: │
│ - 本地 ARP: 正常广播学习 │
│ - 远程 ARP: 通过 flanneld 分发的缓存 │
│ │
│ 3. 封装过程: │
│ ──────────── │
│ VTEP 收到去往远程 Pod 的数据包: │
│ 1. 查找 FDB,确定目标 VTEP IP │
│ 2. 添加 VXLAN Header (VNI) │
│ 3. 添加 UDP/IP/Ethernet 外层头 │
│ 4. 发送到物理网络 │
│ │
│ 4. 解封装过程: │
│ ───────────── │
│ VTEP 收到 VXLAN 数据包: │
│ 1. 验证 VNI 匹配 │
│ 2. 移除外层头 │
│ 3. 根据内层 MAC 转发到本地 Pod │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
VXLAN 与 VLAN 对比
特性 VXLAN VLAN
网络标识 24 位 VNI (16M+ 网络) 12 位 VLAN ID (4K 网络)
二层范围 可跨三层网络 局限于单个交换机
封装方式 UDP 封装 无封装 (纯二层)
隧道 需要 VTEP 不需要
规模 支持 16M+ 虚拟网络 4K 虚拟网络
典型应用 云环境、多租户 数据中心网络隔离

1.2.4 Calico BGP 路由模式 (无封装)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌─────────────────────────────────────────────────────────────────────────────┐
│ Calico BGP 路由模式 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Node A 路由表 (由 Bird BGP 进程配置): │
│ ───────────────────────────────────── │
│ 10.1.1.0/24 dev cni0 (本地 Pod 网段) │
│ 10.2.1.0/24 via 192.168.1.11 dev eth0 (通过 BGP 学习到的) │
│ │
│ Node B 路由表 (由 Bird BGP 进程配置): │
│ ───────────────────────────────────── │
│ 10.2.1.0/24 dev cni0 (本地 Pod 网段) │
│ 10.1.1.0/24 via 192.168.1.10 dev eth0 (通过 BGP 学习到的) │
│ │
│ 数据包转发 (无封装,纯三层): │
│ ──────────────────────────────── │
│ Pod A -> 10.1.1.10 │
│ │ │
│ │ eth0 -> cni0 (因为目标 10.2.1.10 匹配 10.2.1.0/24) │
│ │ │
│ ▼ │
│ cni0 -> eth0 (查路由表 10.2.1.0/24 via 192.168.1.11) │
│ │ │
│ │ 直接从物理网卡发送,无任何封装 │
│ │ (目标 MAC 是 Node B 的 MAC, 目标 IP 是 Pod B 的 IP) │
│ ▼ │
│ 物理网络 (普通路由转发) │
│ │ │
│ ▼ │
│ Node B eth0 │
│ │ │
│ │ 目标 IP 是 10.2.1.10,匹配本地路由 10.2.1.0/24 dev cni0 │
│ ▼ │
│ cni0 -> veth pair -> Pod B │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

1.2.4 Calico 跨节点通信详细流程

Calico 采用纯三层路由方案,通过 BGP 协议在节点间分发路由信息。

Calico 架构组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
┌─────────────────────────────────────────────────────────────────────────────┐
│ Calico 架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Node A Node B │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Felix │◄──── BGP 协议 ────────▶│ Felix │ │
│ │ (路由/ACL) │ │ (路由/ACL) │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ ┌──────┴──────┐ ┌──────┴──────┐ │
│ │ Bird │◄──── BGP 会话 ─────────▶│ Bird │ │
│ │ (BGP 客户端)│ │ (BGP 客户端)│ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ ┌──────┴──────┐ ┌──────┴──────┐ │
│ │ calico-node │ │ calico-node │ │
│ │ DaemonSet │ │ DaemonSet │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Route Reflector (可选,大规模集群) │ │
│ │ │ │
│ │ Node A ◄─────── BGP RR ─────────▶ Node B │ │
│ │ Node C ◄─────── BGP RR ─────────▶ Node D │ │
│ │ │ │
│ │ 作用:替代全互联 BGP,减少 BGP 连接数 (N*(N-1)/2 → N) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Calico 详细通信流程(12步)

步骤 1: Pod A 发送数据包

1
2
3
4
5
6
7
Pod A (10.1.1.10) 发送请求到 Pod B (10.2.1.10):

数据包:
源 IP: 10.1.1.10
目标 IP: 10.2.1.10
源 MAC: aa:aa:aa:aa:aa:aa
目标 MAC: ? (需要 ARP)

步骤 2: Pod A 本地路由决策

1
2
3
4
5
Pod A 路由表:
10.0.0.0/8 via eth0 (K8s 默认)

目标 10.2.1.10 匹配 10.0.0.0/8
发送到 cni0 网桥

步骤 3: cni0 网桥转发

1
2
3
4
5
6
cni0 网桥:
- 查 MAC 表找目标 MAC
- 转发到 eth0

注意: cni0 在 Calico 中主要用于本地 Pod 通信
跨节点流量直接通过 eth0 发出

步骤 4: Node A 路由查找

1
2
3
4
5
Node A 路由表 (由 Felix/Bird 配置):
10.1.1.0/24 dev cni0 (本地 Pod 网段)
10.2.1.0/24 via 192.168.1.11 dev eth0 (BGP 学习到的)

目标 10.2.1.10 匹配 10.2.1.0/24 via 192.168.1.11

步骤 5: ARP 解析下一跳 MAC

1
2
3
4
5
6
7
需要发送到 192.168.1.11 (Node B)

Node A 发送 ARP:
Who has 192.168.1.11? Tell 192.168.1.10

物理交换机转发 ARP,Node B 响应:
192.168.1.11 is at bb:bb:bb:bb:bb:bb

步骤 6: 数据包发送

1
2
3
4
5
6
7
8
9
10
数据包:
源 MAC: Node A MAC
目标 MAC: Node B MAC (bb:bb:bb:bb:bb:bb)
源 IP: 10.1.1.10
目标 IP: 10.2.1.10

注意: 这里是关键区别!
- 目标 IP 保持为 Pod IP (10.2.1.10)
- 目标 MAC 是 Node B 的 MAC
- 没有额外的封装头

步骤 7: 物理网络转发

1
2
3
4
5
交换机根据目标 MAC (bb:bb:bb:bb:bb:bb) 转发到 Node B

整个转发过程:
交换机只看到 Node A MAC -> Node B MAC
完全基于 L2 转发

步骤 8: Node B 接收数据包

1
2
3
4
5
6
7
Node B eth0 接收:
目标 MAC: bb:bb:bb:bb:bb:bb (Node B MAC)
目标 IP: 10.2.1.10

内核协议栈处理:
- 识别目标 IP 是本机网段
- 发送到 cni0 网桥

步骤 9: cni0 网桥转发

1
2
3
4
5
6
Node B cni0 收到数据包:
源 MAC: aa:aa:aa:aa:aa:aa
目标 IP: 10.2.1.10

cni0 查 MAC 表:
10.2.1.10 -> veth pair B

步骤 10: 发送到 Pod B

1
2
3
4
5
通过 veth pair B 发送到 Pod B 的 netns

Pod B eth0 收到:
目标 IP: 10.2.1.10 (匹配本机)
交给应用进程

步骤 11: Pod B 响应

1
2
3
4
5
Pod B 发送响应到 10.1.1.10:
源 IP: 10.2.1.10
目标 IP: 10.1.1.10

流程同上,方向反转

步骤 12: Node B 返回路由

1
2
3
4
Node B 路由表:
10.1.1.0/24 via 192.168.1.10 dev eth0 (BGP 学习)

数据包返回 Node A,整个通信完成
IP-in-IP 模式 (可选封装)

Calico 支持两种模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────────────────────┐
│ Calico 模式对比 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Route (纯三层,默认) │
│ ────────────────────────── │
│ 数据包直接发送,无封装: │
│ 源 IP: Pod A IP (10.1.1.10) │
│ 目标 IP: Pod B IP (10.2.1.10) │
│ │
│ 要求: 集群网络可路由 (节点间三层可达) │
│ │
│ 2. IP-in-IP (封装模式) │
│ ─────────────────────── │
│ 外层再封装一层 IP: │
│ 外层: 192.168.1.10 -> 192.168.1.11 │
│ 内层: 10.1.1.10 -> 10.2.1.10 │
│ │
│ 适用场景: 网络不可路由 (如公有云 VPC) │
│ │
│ 配置: │
│ calicoctl get bgpconfiguration -o yaml │
│ # 设置 ipipMode: Always │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Calico 路由分发机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BGP 路由分发流程:

1. Felix 在每个节点上配置路由:
- 本地 Pod 网段路由
- 指向本地 Pod 的路径

2. Bird BGP 进程:
- 读取 Felix 配置的路由
- 与集群内其他 Bird 建立 BGP 会话
- 通告本地路由给对方
- 接收对方通告的路由并写入内核

3. 全互联 vs Route Reflector:
────────────────────────────
全互联 (小规模集群):
- 每 Node 与所有其他 Node 建立 BGP 会话
- N 个节点 = N*(N-1)/2 个 BGP 会话
- 适合 10-50 个节点的集群

Route Reflector (大规模集群):
- 部署 Route Reflector 节点
- 所有 Node 只与 RR 建立 BGP 会话
- N 个节点 = N 个 BGP 会话
- 适合 50+ 节点的集群
Calico 不同模式原理详解

Calico 支持三种数据平面模式,适用于不同的网络环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
┌─────────────────────────────────────────────────────────────────────────────┐
│ Calico 数据平面模式 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. IPIP Mode (封装模式) │
│ ────────────────────────── │
│ │
│ 原理: │
│ - 在原始 IP 包外再封装一层 IP 头 │
│ - 外层源/目标 IP 是节点物理 IP │
│ - 内层源/目标 IP 是 Pod IP │
│ │
│ 数据包格式: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 外层 IP: SRC=192.168.1.10 DST=192.168.1.11 │ │
│ │ 内层 IP: SRC=10.1.1.10 DST=10.2.1.10 │ │
│ │ TCP/应用数据 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 适用场景: │
│ - 节点间网络不可路由 (如跨 VPC、公有云) │
│ - 网络 ACL 限制严格的環境 │
│ │
│ 配置: │
│ ipipMode: Always | CrossSubnet | Never │
│ │
│ 2. VXLAN Mode (封装模式) │
│ ────────────────────────── │
│ │
│ 原理: │
│ - 使用 UDP 封装,与 Flannel 类似 │
│ - 支持多播 ARP 发现 │
│ - 更好的扩展性 │
│ │
│ 数据包格式: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ UDP: SRC=随机 DST=4789 │ │
│ │ VXLAN: VNI=1 │ │
│ │ 内层 IP: SRC=10.1.1.10 DST=10.2.1.10 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 适用场景: │
│ - 需要 VXLAN 特性的大规模部署 │
│ - 多租户环境需要 VNI 隔离 │
│ │
│ 3. Direct (纯三层,默认) │
│ ────────────────────────── │
│ │
│ 原理: │
│ - 纯三层路由,无任何封装 │
│ - Pod IP 直接暴露在物理网络 │
│ - 需要节点间三层可达 │
│ │
│ 数据包格式: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ IP: SRC=10.1.1.10 DST=10.2.1.10 │ │
│ │ TCP/应用数据 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 适用场景: │
│ - 节点间三层可路由 (如本地数据中心) │
│ - 对性能要求高 │
│ - 网络基础设施支持 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Calico Felix 组件工作原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────────────────────┐
│ Felix 工作流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Felix 是运行在每个节点上的 agent,负责: │
│ │
│ 1. 路由编程 (Route Programming): │
│ ───────────────────────────────── │
│ - 在内核路由表中安装 Pod 网段路由 │
│ - 指向本地 Pod 的本地路由 │
│ - 指向远程 Pod 的下一跳路由 │
│ │
│ 2. ACL 编程 (ACL Programming): │
│ ──────────────────────────── │
│ - 在内核中安装 iptables 规则 │
│ - 实现 NetworkPolicy │
│ - 过滤入口/出口流量 │
│ │
│ 3. 状态汇报 (State Reporting): │
│ ──────────────────────────── │
│ - 监控接口状态 │
│ - 汇报给 Typha (大规模时) │
│ │
│ 配置文件示例 (/etc/calico/felix.cfg): │
│ ─────────────────────────────────── │
│ [Global] │
│ BGPASN=64512 │
│ IPinIPEnabled=true │
│ IPinIPMode=CrossSubnet │
│ │
│ [Node] │
│ Hostname=node-a │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Calico Typha 组件 (大规模集群)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
┌─────────────────────────────────────────────────────────────────────────────┐
│ Typha 扩展机制 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 问题: │
│ - Felix 需要从 API Server 同步数据 │
│ - 大规模集群时,Felix 数量多,API Server 压力大 │
│ │
│ 解决方案: Typha (专用数据分发服务) │
│ ──────────────────────────────── │
│ │
│ ┌──────────────┐ │
│ │ Typha DaemonSet │ │
│ │ (2-3 个副本) │ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Felix │ │ Felix │ │ Felix │ │
│ │ Node A │ │ Node B │ │ Node C │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 优势: │
│ - Typha 缓存数据,减少 API Server 负载 │
│ - Felix 从 Typha 订阅增量更新 │
│ - 支持 1000+ 节点集群 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

1.2.5 CNI 插件对比

特性 Flannel (VXLAN) Calico (BGP) Cilium (eBPF) Weave
跨节点封装 VXLAN (UDP) 无封装 无封装/eBPF VXLAN/sleeve
封装位置 内核 VTEP 物理网络 eBPF 内核
性能 中等 (额外封装) 高 (无封装) 最高 (零拷贝) 中等
路由学习 flanneld 分发 BGP 协议 eBPF 探针 八卦协议
网络策略 支持 支持 支持 支持
IPAM flannel 分配 Calico IPAM Cilium IPAM Weave IPAM

源码位置

1
2
3
4
5
6
7
8
9
10
11
Flannel:
pkg/kubelet/network/cni/plugins/flake/flannel/ - flannel CNI 插件
vendor/github.com/flannel-io/flannel/ - flannel 主代码
pkg/util/encap/ - VXLAN 封装工具

Calico:
pkg/kubelet/network/plugins/plugins.go - CNI 插件接口
vendor/github.com/projectcalico/ - Calico 库

Cilium:
vendor/github.com/cilium/cilium/ - Cilium eBPF

2. Pod 到 Service 通信

2.1 ClusterIP 类型

ClusterIP 是 Kubernetes Service 的默认类型,通过 kube-proxy 实现负载均衡。

2.1.1 架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────────────────────────────────────────┐
│ ClusterIP Service 通信 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Pod A │ 连接 ClusterIP: 10.96.0.100 │
│ │ (client) │ │
│ │ │ 1. DNS 解析: my-svc -> 10.96.0.100 │
│ │ │ 2. 发送 SYN 到 10.96.0.100:80 │
│ │ ┌───────┐ │ │
│ │ │ app │ │ │
│ │ └───┬───┘ │ │
│ │ │ │ │
│ └──────┼──────┘ │
│ │ eth0 │
│ │ │
│ ┌──────┴─────────────────────────────────────────────────────────────┐ │
│ │ Node A 网络命名空间 │ │
│ │ │ │
│ │ ┌─────────┐ ┌──────────────────────────────────────────┐ │ │
│ │ │ eth0 │─────▶│ iptables/ipvs │ │ │
│ │ └─────────┘ │ │ │ │
│ │ │ PREROUTING │ │ │
│ │ │ └─ KUBE-SERVICES │ │ │
│ │ │ └─ KUBE-SVC-XXXXXXXX:80 │ │ │
│ │ │ ├─ KUBE-SEP-XXXXXXXX:80 │ │ │
│ │ │ ├─ KUBE-SEP-XXXXXXXX:80 │ │ │
│ │ │ └─ KUBE-SEP-XXXXXXXX:80 │ │ │
│ │ │ │ │ │
│ │ │ 每个 KUBE-SEP 进行 DNAT: │ │ │
│ │ │ 10.96.0.100:80 -> PodIP:PodPort │ │ │
│ │ │ │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ DNAT 后转发 │ │
│ └──────────────────────────────┼──────────────────────────────────────────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ 路由表 │ │
│ │ │ │
│ │ Pod 网段路由 │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

2.1.2 iptables 模式详细流程(12步)

步骤 1: Pod A 发起请求

1
2
3
4
5
6
7
8
9
10
11
12
Pod A 应用代码:
import requests
resp = requests.get("http://my-svc:80/api")

DNS 解析 (my-svc):
my-svc.default.svc.cluster.local -> 10.96.0.100

Pod A 发送 TCP SYN:
源 IP: 10.1.1.10
目标 IP: 10.96.0.100
源端口: 40000
目标端口: 80

步骤 2: Pod A 本地路由

1
2
3
4
5
Pod A 路由表:
10.96.0.0/12 via eth0 (Service 网段)

目标 10.96.0.100 匹配 10.96.0.0/12
发送到默认网关 (cni0)

步骤 3: Node A 接收数据包

1
2
3
4
5
6
7
8
cni0 网桥收到数据包:
源 MAC: Pod A veth MAC
目标 MAC: cni0 MAC (因为目标是 Service IP)

查路由:
10.96.0.100 匹配 KUBE-SERVICES 链

数据包进入 iptables nat 表 PREROUTING

步骤 4: iptables PREROUTING 处理

1
2
3
4
iptables 规则: 
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

PREROUTING 链跳转到 KUBE-SERVICES 链

步骤 5: KUBE-SERVICES 链匹配

1
2
3
4
5
6
7
8
9
KUBE-SERVICES 规则:
-A KUBE-SERVICES -d 10.96.0.100/32 -p tcp --dport 80 -m comment --comment \
"default/my-svc:80 cluster IP" -j KUBE-SVC-XXXXXXXX

匹配条件:
-d 10.96.0.100/32 ✓ (目标 ClusterIP)
-p tcp --dport 80 ✓

跳转到 KUBE-SVC-XXXXXXXX 链

步骤 6: KUBE-SVC-XXXXXXXX 负载均衡分发

1
2
3
4
5
6
7
8
9
10
KUBE-SVC-XXXXXXXX 链 (假设 3 个 Endpoints):

-A KUBE-SVC-XXXXXXXX -m statistic --mode random --probability 0.33333 \
-j KUBE-SEP-AAAAAAAA
-A KUBE-SVC-XXXXXXXX -m statistic --mode random --probability 0.50000 \
-j KUBE-SEP-BBBBBBBB
-A KUBE-SVC-XXXXXXXX -j KUBE-SEP-CCCCCCCC

假设随机选中 KUBE-SEP-AAAAAAAA
跳转到 KUBE-SEP-AAAAAAAA 链

步骤 7: KUBE-SEP-AAAAAAAA DNAT

1
2
3
4
5
6
7
8
9
10
11
12
KUBE-SEP-AAAAAAAA 规则:
-A KUBE-SEP-AAAAAAAA -m comment --comment "default/my-svc:80" \
-j DNAT --to-destination 10.1.1.20:80

# 注意: 实际 KUBE-SEP 规则通常不带 -s 源地址匹配
# DNAT 操作:
目标 IP: 10.96.0.100:80 → 10.1.1.20:80
(ClusterIP + Port 转换为 PodIP + Port)

iptables 记录 ConnTrack:
原始: 10.1.1.10:40000 -> 10.96.0.100:80
转换: 10.1.1.10:40000 -> 10.1.1.20:80

步骤 8: DNAT 后路由决策

1
2
3
4
5
6
iptables DNAT 后,数据包目标变为 10.1.1.20:80

查 Node A 路由表:
10.1.1.0/24 dev cni0 (本地网络)

数据包仍然是发往 10.1.1.20 (同节点)

步骤 9: cni0 网桥转发

1
2
3
4
cni0 查 MAC 表:
10.1.1.20 -> veth pair 到 Pod X

数据包通过 veth pair 发送到 Pod X

步骤 10: Pod X 接收请求

1
2
3
4
5
6
7
8
9
Pod X eth0 收到数据包:
目标 IP: 10.1.1.20 (Pod X 的 IP)
目标端口: 80

内核 TCP/IP 栈处理:
- 匹配本地端口 80
- 交给监听 80 端口的进程

Pod X 应用收到请求

步骤 11: Pod X 发送响应

1
2
3
4
5
6
7
8
9
Pod X 处理后发送响应:

TCP SYN-ACK:
源 IP: 10.1.1.20 (Pod X)
目标 IP: 10.1.1.10 (Pod A)
源端口: 80
目标端口: 40000

注意: 源 IP 是 Pod X,目标 IP 是 Pod A

步骤 12: 响应通过 ConnTrack 返回

1
2
3
4
5
6
7
8
9
10
11
Node A iptables 的 ConnTrack 处理:

1. 响应数据包进入 PREROUTING
2. ConnTrack 查表:
原始: 10.1.1.10:40000 -> 10.96.0.100:80
状态: RELATED,ESTABLISHED
3. 自动反向 DNAT:
源 IP: 10.1.1.20:80 -> 10.96.0.100:80
4. Pod A 收到响应:
源 IP: 10.96.0.100 (ClusterIP!)
目标 IP: 10.1.1.10

2.1.3 ipvs 模式详细流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌─────────────────────────────────────────────────────────────────────────────┐
│ ipvs 负载均衡模式 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ipvs 工作在 Linux 内核层 (netfilter之上),性能更高 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ipvsadm 规则 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ IP Virtual Server version 1.2.1 (size=4096) │ │
│ │ │ │
│ │ TCP 10.96.0.100:80 rr <- Service │ │
│ │ -> 10.1.1.20:80 Route (Endpoint 1) │ │
│ │ -> 10.1.1.21:80 Route (Endpoint 2) │ │
│ │ -> 10.1.1.22:80 Route (Endpoint 3) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ipvs 负载均衡算法: │
│ ─────────────────── │
│ rr (轮询) - 依次分发到每个 endpoint │
│ wrr (加权轮询) - 根据权重分发 │
│ lc (最小连接) - 选择连接数最少的 endpoint │
│ wlc (加权最小连接) - 结合权重和连接数 │
│ sh (源哈希) - 相同源 IP 总是分发到同一 endpoint │
│ dh (目标哈希) - 相同目标 IP 总是分发到同一 endpoint │
│ │
│ 通信流程: │
│ ──────── │
│ 1. Pod A 发送请求到 10.96.0.100:80 │
│ 2. 内核 ipvs 模块接收 (在 PREROUTING 之前) │
│ 3. ipvs 根据算法选择 endpoint (如 10.1.1.20:80) │
│ 4. ipvs 转发模式: │
│ - DR (Direct Routing): 直接修改 MAC,绕过 iptables │
│ - NAT (NAT 模式): 类似 iptables DNAT │
│ - tunnel (IPIP): 隧道模式 │
│ 5. 响应直接返回,不经过 ipvs (ConnTrack 处理) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

2.1.4 iptables vs ipvs 对比

特性 iptables ipvs
工作层级 netfilter (内核) IPVS (内核)
匹配方式 遍历规则链 哈希查找 O(1)
扩展性 规则多时性能下降 恒定高性能
算法支持 随机/概率 8种算法
健康检查 支持
连接复用 支持
典型规则数 1000+ 几十

2.1.5 kube-proxy iptables 规则详解

kube-proxy 在所有运行节点上都会创建 iptables 规则,规则结构相同。

kube-proxy 部署机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────────────┐
│ kube-proxy 部署架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ kube-proxy 以 DaemonSet 形式部署: │
│ ───────────────────────────────────── │
│ │
│ kubectl get daemonset -n kube-system kube-proxy │
│ NAMESPACE NAME DESIRED CURRENT READY │
│ kube-system kube-proxy 5 5 5 │
│ │
│ 每个节点运行一个 kube-proxy Pod: │
│ ────────────────────────────────────────── │
│ │
│ ┌─────────────────┐ │
│ │ kube-proxy Pod │ │
│ │ (每个节点一个) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node A │ │ Node B │ │ Node C │ │
│ │ iptables│ │ iptables│ │ iptables│ │
│ │ rules │ │ rules │ │ rules │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
所有节点规则结构相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────────────────────┐
│ 所有节点的 iptables 结构相同 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Node A Node B Node C │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ PREROUTING │ │ PREROUTING │ │ PREROUTING │ │
│ │ └─ KUBE-SVC │ │ └─ KUBE-SVC │ │ └─ KUBE-SVC │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
│ 规则内容: │
│ ───────── │
│ KUBE-SERVICES: ALL Services (集群中所有 Service) │
│ KUBE-SVC-XXXX: ALL Endpoints (该 Service 的所有 Pod) │
│ KUBE-SEP-YYYY: 指向具体 Pod IP:Port │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
iptables 规则链路详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
┌─────────────────────────────────────────────────────────────────────────────┐
│ iptables 规则链路 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ PREROUTING 链 (外部流量进入): │
│ ───────────────────────────────── │
│ -A PREROUTING -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS │
│ ↑ 进入本地 IP 的流量 (NodePort) │
│ │
│ -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
│ ↑ 其他服务流量 │
│ │
│ OUTPUT 链 (本地进程访问 Service): │
│ ───────────────────────────────── │
│ -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
│ ↑ Pod 内进程访问 Service IP │
│ │
│ KUBE-SERVICES 链 (匹配目标 Service): │
│ ───────────────────────────────────── │
│ -A KUBE-SERVICES -d 10.96.0.100/32 -p tcp --dport 80 \ │
│ -m comment --comment "default/my-svc:80 cluster IP" -j KUBE-SVC-XXXXX
│ ↑ 匹配 ClusterIP │
│ │
│ KUBE-SVC-XXXXX 链 (负载均衡到 Endpoints): │
│ ───────────────────────────────────────── │
│ -A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.33333 \ │
│ -j KUBE-SEP-AAAA │
│ -A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.50000 \ │
│ -j KUBE-SEP-BBBBBBBB │
│ -A KUBE-SVC-XXXXX -j KUBE-SEP-CCCCCCCC │
│ ↑ 概率负载均衡到各个 Endpoint │
│ │
│ KUBE-SEP-XXXX 链 (DNAT 到具体 Pod): │
│ ───────────────────────────────── │
│ -A KUBE-SEP-AAAA -m comment --comment "default/my-svc:80" \ │
│ -j DNAT --to-destination 10.1.1.20:80 │
│ ↑ DNAT 到具体 Pod IP:Port │
│ │
│ POSTROUTING 链 (Egress SNAT): │
│ ───────────────────────── │
│ -A POSTROUTING -m comment --comment "kubernetes service connections" \ │
│ -j KUBE-MARK-MASQ │
│ ↑ 标记需要 SNAT 的流量 │
│ │
│ -A POSTROUTING -s 10.1.1.0/24 ! -d 10.0.0.0/8 -j MASQUERADE │
│ ↑ Pod 访问外部 IP 时 SNAT │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Endpoints 跨节点规则示例
1
2
3
4
5
假设:
Service: my-svc (10.96.0.100) -> 3 个 Endpoints
- Pod X: 10.1.1.20 (在 Node A)
- Pod Y: 10.1.1.21 (在 Node B)
- Pod Z: 10.1.1.22 (在 Node B)

所有节点 (Node A, B, C) 的 KUBE-SVC-XXXX 规则都一样:

1
2
3
4
KUBE-SVC-XXXX:
-m statistic --mode random --probability 0.33333 -j KUBE-SEP-AAAA (10.1.1.20)
-m statistic --mode random --probability 0.50000 -j KUBE-SEP-BBBBBBBB (10.1.1.21)
-j KUBE-SEP-CCCCCCCC (10.1.1.22)

每个 KUBE-SEP 都指向实际的 Pod IP:

1
2
3
4
5
6
7
8
KUBE-SEP-AAAA:
-j DNAT --to-destination 10.1.1.20:80 <- Pod X 的实际 IP

KUBE-SEP-BBBBBBBB:
-j DNAT --to-destination 10.1.1.21:80 <- Pod Y 的实际 IP

KUBE-SEP-CCCCCCCC:
-j DNAT --to-destination 10.1.1.22:80 <- Pod Z 的实际 IP
跨节点流量转发示例
1
2
3
4
5
6
7
8
9
Client 在 Node C 访问 my-svc:80
─────────────────────────────────────

1. Node C 收到请求,进入 iptables PREROUTING
2. 匹配 KUBE-SERVICES -> KUBE-SVC-XXXX
3. KUBE-SVC-XXXX 随机选中 KUBE-SEP-BBBBBBBB
4. DNAT 到 10.1.1.21:80 (Pod Y,在 Node B)
5. 查路由: 10.1.1.21 不在本地,通过物理网络转发到 Node B
6. Node B 的 cni0 收到,转发给 Pod Y
ConnTrack 作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────────────────┐
│ ConnTrack 连接跟踪 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ConnTrack 记录活跃连接,实现反向转换: │
│ ───────────────────────────────────────── │
│ │
│ 请求流程: │
│ 原始: 10.1.1.10:40000 -> 10.96.0.100:80 │
│ DNAT后: 10.1.1.10:40000 -> 10.1.1.20:80 │
│ ConnTrack 记录这条转换关系 │
│ │
│ 响应流程 (ConnTrack 自动反向): │
│ 收到: 10.1.1.20:80 -> 10.1.1.10:40000 │
│ 查表: 原始连接 10.1.1.10:40000 -> 10.96.0.100:80 │
│ 反向: 自动将源 IP 从 10.1.1.20 转换回 10.96.0.100 │
│ 结果: 10.96.0.100:80 -> 10.1.1.10:40000 │
│ Client 看到的是 Service IP │
│ │
│ 查看 ConnTrack 表: │
│ conntrack -L | grep 10.96.0.100 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
iptables 规则查看命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 查看 nat 表规则
iptables -t nat -L -n -v

# 查看 KUBE-SERVICES 链
iptables -t nat -L KUBE-SERVICES -n -v

# 查看某个 Service 的规则
iptables -t nat -L KUBE-SVC-XXXXXXXX -n -v

# 查看 KUBE-SEP 链
iptables -t nat -L KUBE-SEP-XXXXXXXX -n -v

# 统计规则数量
iptables -t nat -L KUBE-SERVICES -n | wc -l

# 查看 POSTROUTING 规则
iptables -t nat -L POSTROUTING -n -v

# 使用 ipvsadm 查看 ipvs 规则 (如果使用 ipvs 模式)
ipvsadm -L -n
ipvsadm -L -n --stats
iptables vs ipvs 规则对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────────────────┐
│ iptables vs ipvs 规则对比 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ iptables 模式: │
│ ───────────────── │
│ - 规则数量: 1000+ (每个 Service + Endpoint) │
│ - 匹配方式: 遍历规则链 O(n) │
│ - 更新方式: 全量刷新 │
│ - 适用场景: 小规模集群 (< 100 Services) │
│ │
│ ipvs 模式: │
│ ──────────── │
│ - 规则数量: 几十 (每个 Service) │
│ - 匹配方式: 哈希查找 O(1) │
│ - 更新方式:增量更新 │
│ - 适用场景: 大规模集群 │
│ │
│ 切换模式: │
│ ───────── │
│ kube-proxy --proxy-mode=ipvs │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

2.1.6 iptables 五链四表完整流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
┌─────────────────────────────────────────────────────────────────────────────┐
│ iptables 数据包处理流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Linux 内核协议栈 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ PREROUTING 链 (路由决策前) │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ raw │ │ mangle │ │ nat (DNAT)│ │ filter │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ - NOTRACK │ │ - MARK │ │ - KUBE- │ │ (不常用) │ │ │
│ │ │ │ │ - TOS │ │ SERVICES│ │ │ │ │
│ │ │ │ │ - TTL │ │ - KUBE- │ │ │ │ │
│ │ │ │ │ - WL │ │ NODEPORTS│ │ │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────┐ │ │
│ │ │ 路由决策 (Routing Decision) │ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────────┴───────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────────────┐ ┌────────────────┐ │ │
│ │ │ 本地进程 │ │ 转发 │ │ │
│ │ │ (Local) │ │ (Forward) │ │ │
│ │ └───────┬────────┘ └───────┬────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ INPUT 链 │ │ FORWARD 链 │ │ │
│ │ │ │ │ │ │ │
│ │ │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │ │ │
│ │ │ │mangle│ │filter│ │ │ │mangle│ │filter│ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │-MARK │ │ACCEPT│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │DROP │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │REJECT│ │ │ │ │ │ │ │ │ │
│ │ │ └─────┘ └─────┘ │ │ └─────┘ └─────┘ │ │ │
│ │ └────────────────────┘ └────────────────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ 本地进程 (Application) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ 响应数据包 │ │
│ │ │ 生成 │ │
│ │ ▼ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ OUTPUT 链 (本地生成) │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ raw │ │ mangle │ │ nat (SNAT)│ │ filter │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ - MARK │ │ - KUBE- │ │ - OUTPUT │ │ │
│ │ │ │ │ │ │ SERVICES│ │ policy │ │ │
│ │ │ │ │ │ │ - MASQUERADE│ │ │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ POSTROUTING 链 (出站前) │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ │ │
│ │ │ mangle │ │ nat (SNAT)│ │ │
│ │ │ │ │ │ │ │
│ │ │ - CLASSIFY│ │ - KUBE- │ │ │
│ │ │ │ │ MARK-MASQ│ │ │
│ │ │ │ │ - MASQUERADE│ │ │
│ │ │ │ │ │ │ │
│ │ └───────────┘ └───────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ 物理网卡/虚拟网卡 │ │
│ │ (eth0, cni0, veth) │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
四表详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
┌─────────────────────────────────────────────────────────────────────────────┐
│ iptables 四表 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. raw 表 (原始表) │ │
│ │ ───────────────────── │ │
│ │ │ │
│ │ 优先级: HIGHEST (最先处理) │ │
│ │ │ │
│ │ 用途: │ │
│ │ - 决定是否对数据包进行连接跟踪 (NOTRACK) │ │
│ │ - 绕过 ConnTrack │ │
│ │ │ │
│ │ 常用规则: │ │
│ │ -A PREROUTING -j NOTRACK │ │
│ │ -A OUTPUT -j NOTRACK │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 2. mangle 表 (标记表) │ │
│ │ ───────────────────── │ │
│ │ │ │
│ │ 优先级: HIGH │ │
│ │ │ │
│ │ 用途: │ │
│ │ - 修改数据包字段 (TOS, TTL, MARK) │ │
│ │ - 流量分类 │ │
│ │ - QoS │ │
│ │ │ │
│ │ 常用规则: │ │
│ │ -A PREROUTING -j MARK --set-mark 0x1 │ │
│ │ -A POSTROUTING -j CLASSIFY --set-class 1:0 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 3. nat 表 (网络地址转换表) │ │
│ │ ───────────────────── │ │
│ │ │ │
│ │ 优先级: NORMAL │ │
│ │ │ │
│ │ 用途: │ │
│ │ - DNAT (目标地址转换) - PREROUTING │ │
│ │ - SNAT (源地址转换) - POSTROUTING │ │
│ │ - MASQUERADE (动态源地址转换) │ │
│ │ │ │
│ │ 常用规则: │ │
│ │ -A PREROUTING -j DNAT --to-destination 10.1.1.20:80 │ │
│ │ -A POSTROUTING -j MASQUERADE │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 4. filter 表 (过滤表) │ │
│ │ ───────────────────── │ │
│ │ │ │
│ │ 优先级: LOW (最后处理) │ │
│ │ │ │
│ │ 用途: │ │
│ │ - 包过滤 (ACCEPT, DROP, REJECT) │ │
│ │ - 防火墙规则 │ │
│ │ │ │
│ │ 常用规则: │ │
│ │ -A INPUT -j ACCEPT │ │
│ │ -A FORWARD -j DROP │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
五链详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
┌─────────────────────────────────────────────────────────────────────────────┐
│ iptables 五链 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 五链概述 │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌──────────┬────────────────────────────────────────────────┐ │ │
│ │ │ 链 │ 位置和用途 │ │ │
│ │ ├──────────┼────────────────────────────────────────────────┤ │ │
│ │ │ PREROUTING│ 路由前 - 网络接口接收数据包后 │ │ │
│ │ │ │ 用于 DNAT (外部访问本地服务) │ │ │
│ │ ├──────────┼────────────────────────────────────────────────┤ │ │
│ │ │ INPUT │ 路由后 - 发往本地进程的数据包 │ │ │
│ │ │ │ 用于允许/禁止进入本地应用 │ │ │
│ │ ├──────────┼────────────────────────────────────────────────┤ │ │
│ │ │ FORWARD │ 路由后 - 需要转发的数据包 │ │ │
│ │ │ │ 用于网络转发 (K8s Pod 通信) │ │ │
│ │ ├──────────┼────────────────────────────────────────────────┤ │ │
│ │ │ OUTPUT │ 本地生成 - 进程发出的数据包 │ │ │
│ │ │ │ 用于本地进程访问外部 (Egress SNAT) │ │ │
│ │ ├──────────┼────────────────────────────────────────────────┤ │ │
│ │ │ POSTROUTING│ 路由后 - 数据包发出前 │ │ │
│ │ │ │ 用于 SNAT, MASQUERADE │ │ │
│ │ └──────────┴────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ K8s 中的链应用 │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ PREROUTING: │ │
│ │ ─────────── │ │
│ │ - KUBE-NODEPORTS: NodePort 外部访问 │ │
│ │ - KUBE-SERVICES: ClusterIP 访问 │ │
│ │ │ │
│ │ OUTPUT: │ │
│ │ ─────── │ │
│ │ - KUBE-SERVICES: 本地 Pod 访问 Service │ │
│ │ │ │
│ │ FORWARD: │ │
│ │ ─────── │ │
│ │ - KUBE-FORWARD: Pod 流量转发 │ │
│ │ │ │
│ │ POSTROUTING: │ │
│ │ ─────────── │ │
│ │ - KUBE-MARK-MASQ: Egress 流量标记 │ │
│ │ - MASQUERADE: Pod 访问外部 IP │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
K8s iptables 完整流程示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
┌─────────────────────────────────────────────────────────────────────────────┐
│ K8s 外部访问 NodePort 完整流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 外部 Client 访问 http://NodeIP:30080/api │
│ │
│ 1. 数据包到达 Node eth0 │
│ └─ 目标 IP: NodeIP, 目标端口: 30080 │
│ │
│ 2. PREROUTING 链 │
│ └─ raw/PREROUTING: NOTRACK (可选) │
│ └─ mangle/PREROUTING: (可选 MARK) │
│ └─ nat/PREROUTING: │
│ └─ KUBE-NODEPORTS 链 │
│ └─ 匹配 --dport 30080 │
│ └─ -j KUBE-SVC-XXXX │
│ │
│ 3. KUBE-SVC-XXXX 链 │
│ └─ 负载均衡规则 │
│ └─ -j KUBE-SEP-AAAA (随机选中) │
│ │
│ 4. KUBE-SEP-AAAA 链 │
│ └─ DNAT --to-destination PodIP:TargetPort │
│ │
│ 5. 路由决策 │
│ └─ 目标 IP 变为 PodIP │
│ └─ 查路由表: 决定发往本地还是转发 │
│ │
│ 6. FORWARD 链 (如果需要转发) │
│ └─ KUBE-FORWARD 链 │
│ └─ -j ACCEPT │
│ │
│ 7. POSTROUTING 链 │
│ └─ 如果 Pod 在其他节点: MASQUERADE (SNAT) │
│ │
│ 8. 数据包从 eth0 发出到物理网络 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 NodePort 类型

NodePort 通过宿主机端口暴露服务,允许外部访问。

2.2.1 详细架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
┌─────────────────────────────────────────────────────────────────────────────┐
│ NodePort 通信详细流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 外部 Client │
│ │ │
│ │ http://192.168.1.10:30080 │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Node A │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ eth0 (物理网卡) │ │ │
│ │ │ 192.168.1.10/24 │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ 进入 PREROUTING │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ iptables nat 表 │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │
│ │ │ │ KUBE-NODEPORTS ││ │ │
│ │ │ │ -A KUBE-NODEPORTS -p tcp --dport 30080 -j KUBE-SVC-XXXX││ │ │
│ │ │ └─────────────────────────────────────────────────────────┘│ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │
│ │ │ │ KUBE-SVC-XXXX (负载均衡到 endpoints) ││ │ │
│ │ │ │ -A KUBE-SVC-XXXX -j KUBE-SEP-AAAA ││ │ │
│ │ │ │ ... ││ │ │
│ │ │ └─────────────────────────────────────────────────────────┘│ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │
│ │ │ │ KUBE-SEP-AAAA (DNAT 到 Pod X) ││ │ │
│ │ │ │ -A KUBE-SEP-AAAA -j DNAT --to 10.1.1.20:80 ││ │ │
│ │ │ └─────────────────────────────────────────────────────────┘│ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ └──────────────────────────────┼─────────────────────────────────────┘ │
│ │ │
│ │ DNAT 后 │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ cni0 网桥 │ │
│ │ │ │
│ │ 路由: 10.1.1.0/24 dev cni0 │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Pod X │ │ Pod Y │ │ Pod Z │ │
│ │ 10.1.1.20 │ │ 10.1.1.21 │ │ 10.1.1.22 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

2.2.2 NodePort 通信流程(10步)

步骤 1: 外部 Client 发起请求

1
2
3
4
5
6
7
外部 Client:
curl http://192.168.1.10:30080/api

TCP SYN:
源 IP: 客户端公网 IP (如 203.0.113.50)
目标 IP: Node A 公网 IP (192.168.1.10)
目标端口: 30080 (NodePort)

步骤 2: Node A 物理网卡接收

1
2
3
4
5
6
7
8
eth0 (192.168.1.10) 接收:
目标 MAC: Node A 物理 MAC
目标 IP: 192.168.1.10
目标端口: 30080

Linux 内核协议栈处理:
- 识别是发往本机 IP + 端口 30080
- 交由 iptables 处理

步骤 3: iptables PREROUTING

1
2
3
4
5
6
PREROUTING 链:
-A PREROUTING -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS

匹配 addrtype --dst-type LOCAL:
- dst 是本机 IP (192.168.1.10)
- 跳转到 KUBE-NODEPORTS 链

步骤 4: KUBE-NODEPORTS 匹配

1
2
3
4
5
6
7
8
KUBE-NODEPORTS 规则:
-A KUBE-NODEPORTS -p tcp --dport 30080 -m comment --comment \
"default/my-svc:80 nodeport" -j KUBE-SVC-XXXX

匹配:
-p tcp --dport 30080 ✓

跳转到 KUBE-SVC-XXXX 链

步骤 5: KUBE-SVC-XXXX 负载均衡

1
2
3
4
5
6
7
8
KUBE-SVC-XXXX (假设 3 个 endpoints):
-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.33 \
-j KUBE-SEP-AAAA
-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.50 \
-j KUBE-SEP-BBBB
-A KUBE-SVC-XXXX -j KUBE-SEP-CCCC

假设命中 KUBE-SEP-AAAA

步骤 6: DNAT 到 Pod IP

1
2
3
4
5
6
7
8
9
10
KUBE-SEP-AAAA 规则:
-A KUBE-SEP-AAAA -j DNAT --to-destination 10.1.1.20:80

DNAT:
目标 IP: 192.168.1.10:30080 -> 10.1.1.20:80
(NodeIP:NodePort -> PodIP:TargetPort)

ConnTrack 记录:
原始: 203.0.113.50:xxxxx -> 192.168.1.10:30080
转换: 203.0.113.50:xxxxx -> 10.1.1.20:80

步骤 7: DNAT 后路由

1
2
3
4
5
6
7
8
数据包 DNAT 后:
源 IP: 203.0.113.50 (Client)
目标 IP: 10.1.1.20 (Pod X)

查路由表:
10.1.1.0/24 dev cni0

发送到 cni0 网桥

步骤 8: cni0 转发到 Pod X

1
2
3
4
cni0 查 MAC 表:
10.1.1.20 -> veth pair X

通过 veth pair 发送到 Pod X

步骤 9: Pod X 处理请求

1
2
3
4
5
Pod X eth0 收到:
目标 IP: 10.1.1.20
目标端口: 80

应用收到 HTTP 请求,处理并响应

步骤 10: 响应返回

1
2
3
4
5
6
7
8
Pod X 发送响应:
源 IP: 10.1.1.20
目标 IP: 203.0.113.50

Node A ConnTrack 反向转换:
源 IP: 10.1.1.20:80 -> 192.168.1.10:30080

Client 收到响应,源 IP 看起来像是 NodeIP

2.3 LoadBalancer 类型

LoadBalancer 配合云服务商提供外部负载均衡。

2.3.1 云平台 LoadBalancer 通信流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
┌─────────────────────────────────────────────────────────────────────────────┐
│ LoadBalancer 通信流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 创建 LoadBalancer Service │
│ ──────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ kube-apiserver │ │
│ │ │ │
│ │ 创建 Service type=LoadBalancer │ │
│ │ 调用 Cloud Controller Manager │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Cloud Controller Manager │ │
│ │ │ │
│ │ 云平台 API 调用: │ │
│ │ - 创建负载均衡器 (ELB/ALB/NLB) │ │
│ │ - 配置监听器 (port 80 -> NodePort 30080) │ │
│ │ - 配置目标组 (NodeIP:NodePort) │ │
│ │ - 配置健康检查 │ │
│ │ │ │
│ │ 更新 Service 状态: │ │
│ │ status.loadBalancer.ingress[0].ip = 203.0.113.50 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 外部 Client 访问 │
│ ───────────────── │
│ │
│ Client │
│ │ │
│ │ https://203.0.113.50:443 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 云平台负载均衡器 (如 AWS ELB) │ │
│ │ │ │
│ │ 监听器: 443 -> NodePort:30080 │ │
│ │ 健康检查: 30080 -> /healthz │ │
│ │ │ │
│ │ 后端: Node A:30080, Node B:30080, Node C:30080 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 选择健康的后端节点 (如 Node B) │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Node B (192.168.1.11) │ │
│ │ │ │
│ │ eth0: 192.168.1.11:30080 ──┐ │ │
│ │ │ iptables DNAT │ │
│ │ ▼ │ │
│ │ kube-proxy -> Pod X │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

2.3.2 详细通信流程(12步)

步骤 1: 创建 LoadBalancer Service

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 443 # ELB 监听端口
targetPort: 8443 # Pod 容器端口
protocol: TCP

步骤 2: Cloud Controller Manager 创建 LB

1
2
3
4
5
6
7
8
9
10
CCM 调用云平台 API:
AWS: CreateLoadBalancer
GCP: forwardingRules.insert
Azure: LoadBalancer.CreateOrUpdate

创建的 LB 配置:
- 外部 IP: 203.0.113.50
- 前端端口: 443
- 后端端口: NodePort (随机分配,如 30080)
- 健康检查: HTTP /healthz on NodePort

步骤 3: CCM 配置节点为 LB 后端

1
2
3
4
5
6
7
云平台自动将所有 Node 加入目标组:
TargetGroup:
- Node A:30080 (健康)
- Node B:30080 (健康)
- Node C:30080 (不健康,移除)

云平台定期执行健康检查

步骤 4: 外部 Client 发起请求

1
2
3
4
5
6
7
8
9
Client:
curl https://203.0.113.50/api

DNS:
myapp.example.com -> 203.0.113.50

TCP SYN 到 LB:
目标 IP: 203.0.113.50
目标端口: 443

步骤 5: LB 接收并分配后端

1
2
3
4
5
6
7
LB 负载均衡算法选择后端:
假设选择 Node A (192.168.1.10:30080)

LB 转发 TCP:
源 IP: Client IP
目标 IP: 192.168.1.10
目标端口: 30080

步骤 6: Node A 接收请求

1
2
3
4
5
Node A eth0 收到:
目标 IP: 192.168.1.10 (Node A)
目标端口: 30080

iptables PREROUTING -> KUBE-NODEPORTS

步骤 7-12: 同 NodePort 流程

1
2
3
4
5
6
步骤 7: KUBE-NODEPORTS 匹配端口 30080
步骤 8: KUBE-SVC-XXXX 负载均衡
步骤 9: DNAT 到 Pod IP:Port
步骤 10: cni0 转发到 Pod
步骤 11: Pod 处理请求
步骤 12: 响应返回 (经过 ConnTrack)

2.4 Headless Service

Headless Service 不分配 ClusterIP,DNS 直接解析到 Pod IP。

2.4.1 详细架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
┌─────────────────────────────────────────────────────────────────────────────┐
│ Headless Service DNS 解析 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Pod A │ │
│ │ (client) │ │
│ └──────┬──────┘ │
│ │ │
│ │ 1. DNS 查询 │
│ │ my-headless.default.svc.cluster.local │
│ │ │
│ ┌──────┴──────────────────────────────────────────────────────────────┐ │
│ │ Pod A 的 /etc/resolv.conf │ │
│ │ │ │
│ │ nameserver 169.254.20.10 <- nodelocaldns │ │
│ │ search default.svc.cluster.local svc.cluster.local cluster.local │ │
│ │ options ndots:5 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 2. UDP DNS 查询到 10.96.0.10:53 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ CoreDNS / kube-dns │ │
│ │ │ │
│ │ 配置文件 (/etc/coredns/Corefile): │ │
│ │ ────────────────────────────── │ │
│ │ cluster.local { │ │
│ │ kubernetes cluster.local in-addr.arpa ip6.arpa { │ │
│ │ pods insecure │ │
│ │ service kube-dns │ │
│ │ prometheus metrics │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ Service 变更时,Endpoints Controller 更新 DNS 记录 │ │
│ │ │ │
│ │ Headless Service DNS 记录 (无 ClusterIP): │ │
│ │ ──────────────────────────────────── │ │
│ │ my-headless.default.svc.cluster.local. 300 IN A 10.1.1.20 │ │
│ │ my-headless.default.svc.cluster.local. 300 IN A 10.1.1.21 │ │
│ │ my-headless.default.svc.cluster.local. 300 IN A 10.1.1.22 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 3. DNS 响应 (多个 A 记录) │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Pod X │ │ Pod Y │ │ Pod Z │ │
│ │ 10.1.1.20 │ │ 10.1.1.21 │ │ 10.1.1.22 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

2.4.2 DNS 记录格式对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────────────────────┐
│ 普通 Service vs Headless Service DNS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 普通 ClusterIP Service: │
│ ─────────────────────── │
│ my-app.default.svc.cluster.local. 300 IN A 10.96.0.100 │
│ │
│ 返回: 单个 ClusterIP │
│ │
│ Headless Service: │
│ ───────────────── │
│ my-headless.default.svc.cluster.local. 300 IN A 10.1.1.20 │
│ my-headless.default.svc.cluster.local. 300 IN A 10.1.1.21 │
│ my-headless.default.svc.cluster.local. 300 IN A 10.1.1.22 │
│ │
│ 返回: 所有 Pod IP (无 ClusterIP) │
│ │
│ Pod 级别 DNS (需启用): │
│ ────────────────── │
│ 10-1-1-20.default.pod.cluster.local. 300 IN A 10.1.1.20 │
│ (格式: pod-ip-with-dashes.namespace.pod.cluster.local) │
│ │
│ SRV 记录 (用于发现端口): │
│ ──────────────────── │
│ _http._tcp.my-app.default.svc.cluster.local. 300 IN SRV │
│ 0 100 80 my-app.default.svc.cluster.local. │
│ │
│ Headless SRV 记录: │
│ _http._tcp.my-headless.default.svc.cluster.local. 300 IN SRV │
│ 0 100 8080 10-1-1-20.default.pod.cluster.local. │
│ 0 100 8080 10-1-1-21.default.pod.cluster.local. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

3. 外部到 Service 通信

3.1 Ingress 通信

Ingress 通过 HTTP/HTTPS 路由到后端 Service。

3.1.1 详细架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
┌─────────────────────────────────────────────────────────────────────────────┐
│ Ingress 通信详细流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 外部 User │
│ │ │
│ │ https://app.example.com/api/v1/users │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 域名 DNS 解析 │ │
│ │ │ │
│ │ app.example.com -> 203.0.113.100 (Ingress Controller External IP) │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Ingress Controller (nginx-ingress) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Ingress 规则 │ │ │
│ │ │ │ │ │
│ │ │ apiVersion: networking.k8s.io/v1 │ │ │
│ │ │ kind: Ingress │ │ │
│ │ │ metadata: │ │ │
│ │ │ name: my-app-ingress │ │ │
│ │ │ spec: │ │ │
│ │ │ rules: │ │ │
│ │ │ - host: app.example.com │ │ │
│ │ │ http: │ │ │
│ │ │ paths: │ │ │
│ │ │ - path: /api/v1 │ │ │
│ │ │ pathType: Prefix │ │ │
│ │ │ backend: │ │ │
│ │ │ service: │ │ │
│ │ │ name: my-api-svc │ │ │
│ │ │ port: │ │ │
│ │ │ number: 8080 │ │ │
│ │ │ - path: /web │ │ │
│ │ │ backend: │ │ │
│ │ │ service: │ │ │
│ │ │ name: my-web-svc │ │ │
│ │ │ port: │ │ │
│ │ │ number: 80 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Nginx 配置生成: │ │
│ │ ────────────── │ │
│ │ server { │ │
│ │ server_name app.example.com; │ │
│ │ │ │
│ │ location /api/v1/ { │ │
│ │ proxy_pass http://my-api-svc:8080; │ │
│ │ } │ │
│ │ │ │
│ │ location /web { │ │
│ │ proxy_pass http://my-web-svc:80; │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ proxy_pass 到 Service │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ kube-proxy (iptables/ipvs) │ │
│ │ │ │
│ │ Service: my-api-svc │ │
│ │ ClusterIP: 10.96.0.100 │ │
│ │ Endpoints: 10.1.1.20:8080, 10.1.1.21:8080 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 负载均衡到 Pod │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Pod X │ │ Pod Y │ │
│ │ 10.1.1.20 │ │ 10.1.1.21 │ │
│ │ :8080 │ │ :8080 │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

3.1.2 Ingress TLS 终止流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
┌─────────────────────────────────────────────────────────────────────────────┐
│ Ingress TLS 终止流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Client 发起 HTTPS 请求 │
│ ─────────────────────── │
│ │
│ Client: │
│ TLS ClientHello │
│ SNI: app.example.com │
│ (TLS 加密的应用数据) │
│ │
│ 2. Ingress Controller 接收 (TLS Pass-through 或 终止) │
│ ────────────────────────────────────────────────────────── │
│ │
│ 模式 A: TLS 终止 (Termination) │
│ ────────────────────────────── │
│ Ingress Controller: │
│ - 使用配置的证书解密 HTTPS │
│ - 提取 HTTP 请求 (明文) │
│ - proxy_pass 到后端 Service (HTTP) │
│ │
│ 模式 B: TLS Pass-through │
│ ──────────────────────────── │
│ Ingress Controller: │
│ - 不解密,直接转发加密数据 │
│ - 代理到后端 Pod (HTTPS) │
│ - 后端 Pod 需要配置 TLS │
│ │
│ TLS 终止配置: │
│ ───────────── │
│ apiVersion: networking.k8s.io/v1 │
│ kind: Ingress │
│ metadata: │
│ name: my-tls-ingress │
│ spec: │
│ tls: │
│ - hosts: │
│ - app.example.com │
│ secretName: my-app-tls │
│ rules: │
│ - host: app.example.com │
│ ... │
│ │
│ Secret 存储: │
│ ────────── │
│ kubectl create secret tls my-app-tls \ │
│ --cert=path/to/cert.pem \ │
│ --key=path/to/key.pem │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

4. DNS 解析流程

4.1 完整 DNS 解析架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
┌─────────────────────────────────────────────────────────────────────────────┐
│ DNS 解析完整流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Pod 内部 │ │
│ │ │ │
│ │ 应用发起 DNS 查询: │ │
│ │ ───────────────── │ │
│ │ import socket │ │
│ │ socket.getaddrinfo("my-svc.default.svc.cluster.local", "80") │ │
│ │ │ │
│ │ /etc/resolv.conf (Pod 内): │ │
│ │ ───────────────────────────── │ │
│ │ nameserver 169.254.20.10 <- nodelocaldns (推荐) │ │
│ │ nameserver 10.96.0.10 <- CoreDNS (无 nodelocaldns 时) │ │
│ │ search default.svc.cluster.local svc.cluster.local cluster.local │ │
│ │ options ndots:5 timeout:2 attempts:2 │ │
│ │ │ │
│ │ ndots:5 含义: │ │
│ │ - 查询名称少于 5 个点,先搜索 search 列表 │ │
│ │ - 如 "my-svc" 会补全为 "my-svc.default.svc.cluster.local" │ │
│ │ - 减少集群内部 DNS 查询 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ nodelocaldns (缓存层) │ │
│ │ │ │
│ │ 架构: │ │
│ │ - DaemonSet 运行在每个节点 │ │
│ │ - 监听 169.254.20.10:53 (在节点网络命名空间) │ │
│ │ - 使用 skfilter 绑定到该 IP,不占用端口 │ │
│ │ │ │
│ │ 缓存策略: │ │
│ │ - A 记录: 缓存 TTL 或默认 30 秒 │ │
│ │ - NXDOMAIN (不存在): 缓存 5 秒 │ │
│ │ │ │
│ │ 优势: │ │
│ │ - 减少到 CoreDNS 的 DNS 查询 │ │
│ │ - 降低 DNS 查询延迟 (本机 vs 网络) │ │
│ │ │ │
│ │ Pod 端点: │ │
│ │ /etc/resolv.conf 指向 169.254.20.10:53 │ │
│ │ ( kubelet 通过 --cluster-dns 配置) │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 查询 my-svc.default.svc.cluster.local │ │ │
│ │ │ │ │ │
│ │ │ nodelocaldns 缓存检查: │ │ │
│ │ │ - 命中: 直接返回 │ │ │
│ │ │ - 未命中: 转发到上游 CoreDNS (10.96.0.10) │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ CoreDNS (集群 DNS) │ │
│ │ │ │
│ │ Deployment: kube-dns │ │
│ │ replicas: 2+ │ │
│ │ Service: kube-dns │ │
│ │ ClusterIP: 10.96.0.10 │ │
│ │ │ │
│ │ Corefile 配置: │ │
│ │ ────────────── │ │
│ │ .:53 { │ │
│ │ kubernetes cluster.local in-addr.arpa ip6.arpa { │ │
│ │ pods insecure │ │
│ │ fallthrough in-addr.arpa ip6.arpa │ │
│ │ } │ │
│ │ │ │
│ │ cache 30 │ │
│ │ forward . 8.8.8.8 │ │
│ │ } │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────┐ │ │
│ │ │ DNS 记录类型 │ │ │
│ │ │ │ │ │
│ │ │ Service (ClusterIP): │ │ │
│ │ │ my-svc.default.svc.cluster.local. 300 A 10.96.0.100 │ │ │
│ │ │ │ │ │
│ │ │ Service (Headless): │ │ │
│ │ │ my-headless.default.svc.cluster.local. 300 A 10.1.1.20 │ │ │
│ │ │ my-headless.default.svc.cluster.local. 300 A 10.1.1.21 │ │ │
│ │ │ │ │ │
│ │ │ Service (ExternalName): │ │ │
│ │ │ my-ext.default.svc.cluster.local. 300 CNAME external.com. │ │ │
│ │ │ │ │ │
│ │ │ Pod: │ │ │
│ │ │ 10-1-1-20.default.pod.cluster.local. 300 A 10.1.1.20 │ │ │
│ │ │ (需要启用 pod.insecure 特性开关) │ │ │
│ │ │ │ │ │
│ │ │ SRV: │ │ │
│ │ │ _http._tcp.my-svc.default.svc.cluster.local. 30 SRV │ │ │
│ │ │ 0 100 80 my-svc.default.svc.cluster.local. │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 外部域名查询: │ │
│ │ - CoreDNS 收到外部域名查询 │ │
│ │ - forward . 8.8.8.8 转发到上游 DNS │ │
│ │ - 返回结果给 Pod │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 DNS 查询详细流程(8步)

步骤 1: 应用发起 DNS 查询

1
2
3
4
5
// Go 代码示例
import "net"

addrs, err := net.LookupHost("my-svc.default.svc.cluster.local")
// 实际调用 glibc / musl 的 getaddrinfo()

步骤 2: 读取 /etc/resolv.conf

1
2
3
4
/etc/resolv.conf:
nameserver 169.254.20.10 <- nodelocaldns
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

步骤 3: nodelocaldns 查询

1
2
3
4
5
Pod 内查询 169.254.20.10:53

nodelocaldns 检查缓存:
- 缓存命中: 直接返回
- 未命中: 转发到 CoreDNS (10.96.0.10)

步骤 4: CoreDNS 处理查询

1
2
3
4
5
6
7
8
9
10
11
CoreDNS 收到查询:
QNAME: my-svc.default.svc.cluster.local
QTYPE: A
QCLASS: IN

CoreDNS 插件链处理:
1. kubernetes 插件:
- 检查是否集群内部域名 (*.cluster.local)
- 匹配则通过 Kubernetes API 查找
- CoreDNS 维护本地缓存 (从 API Server 同步)
- 返回 Service ClusterIP

步骤 5: 查询 Kubernetes API

1
2
3
4
5
6
7
CoreDNS 通过 list/watch 从 kube-apiserver 获取:
1. Service "my-svc" 的 ClusterIP
2. 对应的 Endpoints/EndpointSlice 列表

CoreDNS 缓存这些数据,无需每次查询 API Server

结果: 10.96.0.100

步骤 6: CoreDNS 返回响应

1
2
3
4
5
6
DNS Response:
Transaction ID: 0x1234
Flags: QR=1 (响应), AA=1 (权威), RD=1
Answers:
my-svc.default.svc.cluster.local. 300 IN A 10.96.0.100
TTL: 300 秒

步骤 7: nodelocaldns 缓存并返回

1
2
3
nodelocaldns:
- 缓存 A 记录 (TTL 300 秒)
- 返回给 Pod

步骤 8: 应用收到 IP

1
2
addrs = ["10.96.0.100"]
// 应用使用这个 IP 建立连接

5. 网络流量架构图

5.1 完整流量架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubernetes 网络架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ 集群外部网络 │ │
│ │ │ │
│ │ Internet │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ Cloud LB / │ │ │
│ │ │ Ingress │ │ │
│ │ │ Controller │ │ │
│ │ └──────┬──────┘ │ │
│ │ │ │ │
│ └─────────┼──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────┼──────────────────────────────────────────────────────────────┐ │
│ │ │ Node A (192.168.1.10) │ │
│ │ │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ │ Pod A │ │ Pod B │ │ Ingress│ │ │
│ │ │ │ :80 │ │ :8080 │ │ Pod │ │ │
│ │ │ └───┬─────┘ └────┬────┘ └───┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ │ └────────────┬┴────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────▼────────────────┐ │ │
│ │ │ │ CNI Bridge (cni0) │ │ │
│ │ │ │ ┌────────────────────────────────┐│ │ │
│ │ │ │ │ kube-proxy ││ │ │
│ │ │ │ │ (iptables / ipvs) ││ │ │
│ │ │ │ └────────────────────────────────┘│ │ │
│ │ │ └────────────────┬────────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────▼────────────────┐ │ │
│ │ │ │ eth0 (物理网卡) │ │ │
│ │ │ └────────────────────────────────────┘ │ │
│ │ │ │ │
│ └─────────┼───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 物理网络 (Layer 2 / Layer 3) │
│ │ │
│ ┌─────────┼───────────────────────────────────────────────────────────────┐ │
│ │ │ Node B (192.168.1.11) │ │
│ │ │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ │ Pod C │ │ Pod D │ │ Pod E │ │ │
│ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ └───────────┼┴────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────▼────────────────┐ │ │
│ │ │ │ CNI Bridge (cni0) │ │ │
│ │ │ └────────────────┬────────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────▼────────────────┐ │ │
│ │ │ │ eth0 (物理网卡) │ │ │
│ │ │ └────────────────────────────────────┘ │ │
│ │ │ │ │
│ └─────────┼───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────┼───────────────────────────────────────────────────────────────┐ │
│ │ ▼ Kubernetes 控制平面 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ kube-apiserver│ │ kube-controller│ │ kube-scheduler│ │ etcd │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │ │ │ │
│ │ └───────────────┴────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────────────────────▼──────────────────────────┐ │ │
│ │ │ CoreDNS / kube-dns │ │ │
│ │ │ (通常以 Pod 形式运行) │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

5.2 数据包封装对比

通信场景 封装层数 封装类型 说明
同节点 Pod 间 1 层 无封装 直接通过 veth + bridge
Flannel 跨节点 Pod 4 层 VXLAN UDP → IP → Ethernet
Calico 跨节点 Pod 1 层 无封装 纯三层路由
Weave 跨节点 Pod 2-4 层 VXLAN/UDP 取决于模式
Cilium 跨节点 Pod 1 层 无封装 eBPF 加速

6. 关键源码位置

6.1 网络相关代码

组件 源码路径 说明
Kubelet 网络 pkg/kubelet/network/ 网络插件管理
CNI 插件 pkg/kubelet/network/cni/ CNI 实现
Kube-proxy pkg/kube-proxy/ Service 负载均衡
DNS cluster/addons/dns/ DNS 部署配置
NetworkPolicy pkg/kubelet/network/plugins.go 网络策略

6.2 相关接口

1
2
3
4
5
6
7
8
9
10
11
12
13
// CNI 插件接口
type NetworkPlugin interface {
Init(host Host, hairpinMode bool, mtu int) error
SetUpPod(namespace string, name string, podInfraUID types.UID) error
TearDownPod(namespace string, name string, podInfraUID types.UID) error
GetPodNetworkStatus(namespace string, name string, podInfraUID types.UID) (*PodNetworkStatus, error)
}

// kube-proxy 负载均衡接口
type LoadBalancer interface {
NewLoadBalancer(service *v1.Service) (LoadBalancer, error)
ExecHealthCheck(node string) error
}

7. 常见通信问题排查

7.1 Pod 间通信问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1. 检查 Pod 网络状态
kubectl get pod -n <namespace> -o wide

# 2. 进入 Pod 调试网络
kubectl exec -it <pod-name> -- /bin/sh

# 3. 在 Pod 内测试连通性
kubectl exec -it <pod-name> -- ping <target-pod-ip>
kubectl exec -it <pod-name> -- curl -k https://<service-name>

# 4. 查看 Kubelet 日志
journalctl -u kubelet -n 100

# 5. 检查 CNI 插件状态
cat /etc/cni/net.d/
ip link show type bridge

# 6. 检查节点路由表
ip route show
ip route show table all

# 7. 检查 ARP 表
ip neigh show

# 8. 检查 VXLAN 状态 (Flannel)
ip -d link show flannel.1
bridge fdb show dev flannel.1

7.2 Service 通信问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1. 检查 Service 状态
kubectl get svc -o wide
kubectl describe svc <service-name>

# 2. 检查 Endpoints
kubectl get endpoints <service-name>

# 3. 检查 kube-proxy 状态
kubectl get pod -n kube-system -l k8s-app=kube-proxy
kubectl logs <kube-proxy-pod> -n kube-system

# 4. 检查 iptables 规则
iptables -t nat -L -n | grep <service-name>
iptables -t nat -L -n -v | head -50

# 5. 检查 ipvs 规则 (如果使用 ipvs 模式)
ipvsadm -L -n
ipvsadm -L -n --stats

# 6. 检查 ConnTrack 连接表
conntrack -L | grep <pod-ip>
conntrack -L | grep <service-ip>

# 7. 测试 DNS 解析
kubectl exec -it <pod-name> -- nslookup <service-name>
kubectl exec -it <pod-name> -- dig <service-name>

8. 总结

通信链路对比表

源 → 目标 通信方式 负载均衡位置 DNS 解析
Pod → Pod (同节点) 直接 veth + bridge
Pod → Pod (跨节点) 路由/VXLAN
Pod → ClusterIP kube-proxy iptables/ipvs
Pod → NodePort kube-proxy iptables/ipvs
Pod → Headless 直接 Pod IP
External → NodePort kube-proxy iptables
External → LoadBalancer 云平台 云平台

关键网络组件职责

组件 职责
CNI Pod 网络配置,IP 分配,跨节点网络
kube-proxy Service 到 Pod 的负载均衡
CoreDNS 服务发现和 DNS 解析
NetworkPolicy Pod 级别访问控制
Cloud Controller 云平台网络集成

9. 补充通信场景

9.1 Pod 内部容器间通信

同一个 Pod 内多个容器通过 localhost 直接通信,它们共享同一个网络命名空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────────────┐
│ Pod 内部容器间通信 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Pod │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Main │ │ Sidecar │ │ Init │ │ │
│ │ │ Container │ │ Container │ │ Container │ │ │
│ │ │ :80 │ │ :9090 │ │ :8080 │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └─────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌───────────┴───────────┐ │ │
│ │ │ │ Network Namespace │ │ │
│ │ │ │ (共享 eth0, lo) │ │ │
│ │ │ └───────────┬───────────┘ │ │
│ │ │ │ │ │
│ └─────────┼─────────────────┼──────────────────────────────────┘ │
│ │ │ │
│ │ localhost:9090│ │
│ │◀─────────────────▶│ │
│ │ │ │
└────────────┴─────────────────┴────────────────────────────────────────┘

Pod 内容器通信详细流程(5步)

步骤 1: Pod 创建时的网络命名空间设置

1
2
3
4
5
6
7
8
9
10
11
12
Kubelet 创建 Pod 流程:
1. 创建 pause 容器 (暂停进程)
- 创建网络命名空间 netns
- 创建 veth pair: eth0 <-> vethxxx
- 配置 netns 内的 eth0 IP (从 CNI 获取)
- pause 容器持有这个 netns

2. 主容器加入 pause 容器的网络命名空间
- 使用 --net=container:pause
- 或 --network=container:pause (Docker 语法)

结果: 所有容器共享同一个网络命名空间

步骤 2: 容器网络命名空间视图

1
2
3
4
5
6
7
8
# 在 Pod 内查看网络
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
inet 127.0.0.1/8 scope host lo
2: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP>
inet 10.1.1.20/24

所有容器看到相同的 eth0 和 lo

步骤 3: Main 容器连接 Sidecar

1
2
3
4
5
6
7
8
9
// Main 容器内
conn, err := net.Dial("tcp", "localhost:9090")
// 或
conn, err := net.Dial("tcp", "127.0.0.1:9090")
// 或
conn, err := net.Dial("tcp", ":9090")

// 实际连接到 Pod IP:9090
// 但因为是 localhost,流量不经过网卡

步骤 4: localhost 连接的特殊性

1
2
3
4
5
6
7
8
9
数据包流向:
应用 -> 127.0.0.1:9090
-> 主机内核协议栈
-> 本地端口 9090 监听进程

不经过:
- eth0 网卡
- veth pair
- 物理网络

步骤 5: Init 容器特点

1
2
3
4
5
6
7
8
9
10
11
Init 容器特点:
- 在主容器之前启动
- 完成初始化后退出
- 不属于 running 状态
- 仍然共享网络命名空间

示例: 等待数据库就绪
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db:5432; do sleep 1; done']

9.2 Pod 到 API Server 通信

Pod 通过 ServiceAccount 自动挂载的 Token 访问 API Server。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌─────────────────────────────────────────────────────────────────────┐
│ Pod 到 API Server 通信 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Pod │ │
│ │ (client) │ │
│ └──────┬──────┘ │
│ │ │
│ │ 1. 读取 /var/run/secrets/kubernetes.io/serviceaccount/ │
│ │ - token (JWT) │
│ │ - ca.crt (API Server 证书) │
│ │ - namespace (Pod 所在命名空间) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ API Server (kubernetes Service) │ │
│ │ │ │
│ │ Endpoint: 10.96.0.1 (ClusterIP) │ │
│ │ │ │
│ │ 认证方式: │ │
│ │ - Token (ServiceAccount JWT) │ │
│ │ - TLS 证书 (双向 TLS) │ │
│ │ │ │
│ │ 授权方式: │ │
│ │ - RBAC (Role/ClusterRole + RoleBinding/ClusterRoleBinding) │ │
│ │ - ABAC (Attribute-based) │ │
│ │ - Webhook │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

Pod 到 API Server 详细流程(8步)

步骤 1: ServiceAccount Token 自动挂载

1
2
3
4
5
6
7
8
Kubelet 创建 Pod 时:

1. 为 Pod 分配 ServiceAccount (默认 default)
2. 在 Pod 内挂载 ServiceAccount secrets:
- /var/run/secrets/kubernetes.io/serviceaccount/
├── token (JWT)
├── ca.crt (API Server CA)
└── namespace (文件内容为命名空间名)

步骤 2: Token 内容解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# token 文件是 Opaque token (不是标准 JWT)
cat /var/run/secrets/kubernetes.io/serviceaccount/token

# 内容是一串签名后的数据,格式如下:
BearerToken=
aHR0cHM6Ly9rdWJlcm5ldGVzLmNsdXN0ZXIubG9jYWw6MTIzNDU2Nzg5YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=

# Token 内部 Base64 编码的内容包含:
{
"iss": "kubernetes/serviceaccount",
"kubernetes.io/serviceaccount/namespace": "default",
"kubernetes.io/serviceaccount/secret-name": "default-token-xxxxx",
"kubernetes.io/serviceaccount/service-account-name": "default",
"sub": "system:serviceaccount:default:default"
}

# 注意: K8s ServiceAccount token 不是 RFC 7519 标准 JWT
# API Server 使用 --service-account-issuer 和 --service-account-signing-key 生成的
# 才能被验证为 JWT 格式,Pod 挂载的通常是 Opaque token

步骤 3: Client 配置 InClusterConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Go 代码
import "k8s.io/client-go/rest"

config, err := rest.InClusterConfig()
if err != nil {
panic(err)
}

/*
InClusterConfig 读取:
- KUBERNETES_SERVICE_HOST: API Server ClusterIP
- KUBERNETES_SERVICE_PORT: API Server Port
- SERVICEACCOUNT_TOKEN 文件
- SERVICEACCOUNT_CA_FILE: /var/run/secrets/.../ca.crt
*/

clientset, err := kubernetes.NewForConfig(config)

步骤 4: TLS 握手

1
2
3
4
1. Client 使用 CA.crt 验证 API Server 证书
2. Client 发送请求,带上 JWT Token
3. API Server 验证 Token 签名
4. API Server 提取 Token 中的 ServiceAccount 信息

步骤 5: API Server 认证

1
2
3
4
5
6
7
8
9
API Server 认证插件处理:

1. Authentication Token Review:
- 验证 JWT signature (使用 ServiceAccount 公钥)
- 检查 Token payload

2. 提取身份信息:
- User: system:serviceaccount:default:default
- Groups: ["system:serviceaccounts", "system:serviceaccounts:default"]

步骤 6: API Server 授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Authorizer 检查 (RBAC):

1. 查看 User 的 RoleBindings:
kind: RoleBinding
metadata:
name: default-binding
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: Role
name: pod-reader

2. 检查请求权限:
- 需要的 verb: get, list, watch
- 需要的 resource: pods
- 命名空间是否匹配

步骤 7: Admission Control

1
2
3
4
5
6
7
8
Admission Controllers:
- AlwaysPullImages
- DefaultStorageClass
- NodeRestriction
- ...

Mutating 阶段: 可能修改请求对象
Validating 阶段: 决定是否允许请求

步骤 8: 请求处理

1
2
3
4
5
6
7
请求通过认证、授权、Admission 后:
- 写入 etcd
- 返回响应给 Client

示例:
GET /api/v1/namespaces/default/pods
Response: Pod list JSON

9.3 ExternalName Service 通信

ExternalName Service 返回 CNAME 记录,将服务名映射到外部域名。

详细通信流程(5步)

步骤 1: 创建 ExternalName Service

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Service
metadata:
name: my-database
namespace: default
spec:
type: ExternalName
externalName: database.example.com

步骤 2: CoreDNS 生成 CNAME 记录

1
2
3
4
5
DNS 查询 my-database.default.svc.cluster.local

CoreDNS 响应:
my-database.default.svc.cluster.local. 300 IN CNAME database.example.com.
database.example.com. 300 IN A 203.0.113.10

步骤 3: Pod 解析外部域名

1
2
3
4
5
6
7
// 应用代码
conn, err := net.Dial("tcp", "my-database:5432")

// 解析结果:
// 1. DNS 查询返回 CNAME
// 2. 客户端自动跟随 CNAME
// 3. 最终连接到 203.0.113.10:5432

步骤 4: 直接网络连接

1
2
3
4
5
6
Pod 直接连接到 externalName:
- 不经过 kube-proxy
- 不经过 Service 负载均衡
- DNS 返回什么就连接什么

连接: Pod IP:任意端口 -> database.example.com IP:5432

步骤 5: ExternalName 流量特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌────────────────────────────────────────────────────────────────┐
│ ExternalName 流量特点 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 无 kube-proxy 参与: │
│ - 没有 iptables/ipvs 规则 │
│ - 没有 DNAT 转换 │
│ - 没有负载均衡 │
│ │
│ 直接 DNS 解析: │
│ - CNAME 展平 (客户端跟随 CNAME) │
│ - 直接连接外部 IP │
│ │
│ 适用场景: │
│ - 外部数据库服务 │
│ - 外部 API 端点 │
│ - 遗留系统集成 │
│ │
└────────────────────────────────────────────────────────────────┘

9.4 Pod 到外部网络 (Egress) 通信

Pod 访问集群外部网络时,需要进行 SNAT (Source Network Address Translation)。

详细通信流程(10步)

步骤 1: Pod 发送请求到外部 IP

1
2
3
4
5
6
Pod A 应用:
socket.connect("8.8.8.8", 53)

数据包:
源 IP: 10.1.1.10 (Pod IP)
目标 IP: 8.8.8.8

步骤 2: Pod 本地路由

1
2
3
4
5
6
Pod A 路由表:
10.0.0.0/8 via eth0 (K8s 默认路由)
# 或者精确匹配

目标 8.8.8.8 不在 10.0.0.0/8 范围
发送到网关 (cni0)

步骤 3: Node 路由决策

1
2
3
4
5
6
7
Node A 路由表:
10.1.1.0/24 dev cni0 (Pod 网段)
192.168.1.0/24 dev eth0 (Node 网段)
default via 192.168.1.1 (物理网关)

目标 8.8.8.8 匹配 default
发送到物理网关 192.168.1.1

步骤 4: SNAT 转换

1
2
3
4
5
6
7
8
9
10
11
iptables POSTROUTING:

-A POSTROUTING -s 10.1.1.0/24 ! -d 10.0.0.0/8 -j MASQUERADE

MASQUERADE 操作:
源 IP: 10.1.1.10 -> 192.168.1.10 (Node IP)
源端口: 重新分配

ConnTrack 记录:
原始: 10.1.1.10:40000 -> 8.8.8.8:53
转换: 192.168.1.10:50000 -> 8.8.8.8:53

步骤 5: 通过物理网络发送

1
2
3
4
5
6
7
数据包:
源 IP: 192.168.1.10 (Node A IP)
源 MAC: Node A MAC
目标 IP: 8.8.8.8
目标 MAC: 192.168.1.1 (网关 MAC)

物理交换机发送到网关

步骤 6: 外部服务器接收

1
2
3
4
5
6
外部 DNS 服务器 8.8.8.8 收到:
源 IP: 192.168.1.10 (看起来是 Node A)
目标 IP: 8.8.8.8
目标端口: 53

响应发送回 192.168.1.10

步骤 7: Node 接收响应

1
2
3
4
5
6
7
Node A 收到响应:
源 IP: 8.8.8.8
目标 IP: 192.168.1.10

ConnTrack 查表:
查找 192.168.1.10:50000 -> 8.8.8.8:53
找到对应记录,状态 ESTABLISHED

步骤 8: DNAT 反向转换

1
2
3
4
5
6
7
8
ConnTrack 自动反向 SNAT:

原始响应: 8.8.8.8 -> 192.168.1.10:50000
转换后: 8.8.8.8 -> 10.1.1.10:40000 (Pod IP)

Pod A 看到:
源 IP: 8.8.8.8
目标 IP: 10.1.1.10 (自己!)

步骤 9: ConnTrack 作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ConnTrack 表的作用:

1. 记录活跃连接:
conntrack -L | grep 10.1.1.10

2. 自动反向转换:
- 不需要手动配置 DNAT 规则
- 内核自动处理

3. 连接状态跟踪:
- NEW: 新连接
- ESTABLISHED: 已建立
- RELATED: 相关连接
- INVALID: 无效

步骤 10: Egress 控制 (NetworkPolicy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
默认情况下 Pod 可以访问任何外部 IP

限制 Egress:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-rule
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Egress
egress:
- to:
- podSelector: {}
# 只允许同命名空间 Pod 访问
- to:
- namespaceSelector: {}
# 只允许同命名空间 Service 访问

9.5 Service Mesh (Istio) Sidecar 通信

在 Service Mesh 架构中(如 Istio),每个 Pod 都有一个 Sidecar Proxy (Envoy) 拦截所有流量。

详细通信流程(15步)

步骤 1: Pod 部署时的 Sidecar 注入

1
2
3
4
5
# Istio 自动注入 Sidecar
kubectl label namespace default istio-injection=enabled

# 或手动注入
istioctl kube-inject -f deployment.yaml

步骤 2: Init 容器配置 iptables

1
2
3
4
5
6
7
8
9
10
11
12
13
Init 容器 (istio-init) 设置 iptables:

iptables -t nat -N ISTIO_INBOUND
iptables -t nat -A ISTIO_INBOUND -p tcp --dport 80 -j ISTIO_IN_REDIRECT
iptables -t nat -A ISTIO_IN_REDIRECT -j RETURN

iptables -t nat -A OUTPUT -p tcp -j ISTIO_OUTPUT
iptables -t nat -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
iptables -t nat -A ISTIO_OUTPUT -j ISTIO_REDIRECT

结果:
- 所有出站流量 redirect 到 Envoy (15001)
- 所有入站流量 redirect 到 Envoy (15006)

步骤 3: Envoy Sidecar 启动

1
2
3
4
5
6
7
8
9
10
11
Envoy 配置文件:
listeners:
- address: 0.0.0.0:15006
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager

- address: 0.0.0.0:15001
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager

步骤 4: xDS 配置获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Envoy 通过 xDS 协议从 Istiod 获取配置:

1. CDS (Cluster Discovery Service):
- 集群列表
- 每个集群的 endpoints

2. LDS (Listener Discovery Service):
- 监听器配置
- 过滤器链

3. EDS (Endpoint Discovery Service):
- 具体的后端 IP:Port

4. RDS (Route Discovery Service):
- 路由规则

Istiod:
- 发现服务
- 下发配置
- mTLS 证书分发

步骤 5: Pod A 应用发送请求

1
2
// 应用代码
resp, err := http.Get("http://service-b:8080/api")

步骤 6: iptables 重定向

1
2
3
4
5
6
7
8
9
10
11
12
应用发送请求到 service-b:8080

数据包原始:
目标 IP: service-b 的 ClusterIP 或 Pod IP
目标端口: 8080

Istio iptables 拦截:
所有 TCP 流量 redirect 到 15001

数据包变为:
目标 IP: 127.0.0.1
目标端口: 15001 (Envoy)

步骤 7: Envoy Outbound 接收

1
2
3
4
5
6
7
Envoy 15001 端口接收:
- 从 iptables redirect 来的流量
- Envoy 作为代理

HTTP Connection Manager:
- 解码 HTTP 请求
- 提取 Host/Path

步骤 8: Envoy 路由匹配

1
2
3
4
5
6
7
8
9
10
11
12
Envoy 配置 (来自 RDS):

routes:
- match:
prefix: /api
route:
cluster: outbound|8080||service-b.namespace.svc.cluster.local

cluster 定义 (来自 CDS/EDS):
service-b.namespace.svc.cluster.local:
- 10.1.1.20:8080 (Pod X)
- 10.1.1.21:8080 (Pod Y)

步骤 9: 负载均衡选择 endpoint

1
2
3
4
5
6
7
负载均衡算法 (可配置):
- ROUND_ROBIN: 轮询
- LEAST_REQUEST: 最少连接
- RANDOM: 随机
- consistent_hashing: 一致性哈希

假设选择: 10.1.1.20:8080 (Pod X)

步骤 10: mTLS 加密 (可选)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如果启用 mTLS:

1. Envoy 使用客户端证书:
- 证书来自 Istiod (通过 SDS)
- 包含 ServiceAccount 身份

2. TLS 握手:
Client Hello
Server Certificate (Pod X Envoy)
Key Exchange
Finished

3. 加密通信:
- Envoy <-> Envoy 加密
- 应用层不感知

步骤 11: 转发到 Pod X Envoy

1
2
3
4
5
6
7
请求从 Pod A Envoy 发出:
源: Pod A IP (10.1.1.11) <- Envoy 代理
目标: Pod X Envoy (15006 端口)

Pod X Envoy 接收 (Inbound):
- 验证客户端证书 (mTLS)
- 确认调用方身份

步骤 12: Pod X Envoy 认证

1
2
3
4
Pod X Envoy inbound 配置:
- 验证客户端证书
- 检查 peer 身份 (Pod A 的 ServiceAccount)
- 应用 Authorization 策略

步骤 13: Pod X Envoy 转发到应用

1
2
3
4
5
6
通过 lo 接口转发到应用:
localhost:15006 -> localhost:8080 (应用)

Envoy 配置:
- 15006 是 inbound listener
- 转发给本地应用端口

步骤 14: Pod X 应用处理

1
2
3
4
5
6
7
应用收到请求:
- 来源是 localhost:15006
- 应用不需要感知 Service Mesh

应用处理并响应:
- 响应到 localhost:15006
- Envoy 收到响应

步骤 15: 响应返回

1
2
3
4
5
6
7
响应流程 (反向):

Pod X Envoy -> Pod A Envoy -> Pod A 应用

Pod A Envoy 收到 Pod X 响应后:
- 通过 outbound 发回
- Pod A iptables -> 应用

Istio 流量管理能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌─────────────────────────────────────────────────────────────────────────────┐
│ Istio 流量管理能力 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 金丝雀发布: │
│ ────────────── │
│ VirtualService: │
│ - 50% 流量到 v1 │
│ - 50% 流量到 v2 │
│ │
│ 2. A/B 测试: │
│ ────────── │
│ - 根据 header 分组 │
│ - header["X-User-ID"] 路由 │
│ │
│ 3. 流量镜像: │
│ ─────────── │
│ - 100% 到 v1 │
│ - 100% 镜像到 v2 (不返回给用户) │
│ │
│ 4. 超时重试: │
│ ────────── │
│ timeout: 5s │
│ retries: │
│ attempts: 3 │
│ perTryTimeout: 2s │
│ │
│ 5. 断路器: │
│ ─────── │
│ circuitBreakers: │
│ maxConnections: 100 │
│ maxPendingRequests: 50 │
│ │
│ 6. 限流: │
│ ──── │
│ rateLimit: │
│ requestsPerUnit: 100 │
│ unit: second │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

9.6 Node 本地通信

节点上的系统组件(kubelet、container runtime)之间的通信。

详细组件交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
┌─────────────────────────────────────────────────────────────────────────────┐
│ Node 本地系统组件通信 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Kubelet │ │
│ │ │ │
│ │ 端口: 10250 ( kubelet API, metrics, exec) │ │
│ │ 认证: 需要 Node 证书 │ │
│ │ │ │
│ │ 主要职责: │ │
│ │ - Pod 生命周期管理 │ │
│ │ - 容器运行时交互 │ │
│ │ - 网络插件调用 │ │
│ │ - 资源监控 │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────┐ │ │
│ │ │ CRI 接口调用 │ │ │
│ │ │ │ │ │
│ │ │ RunPodSandbox() │ │ │
│ │ │ CreateContainer() │ │ │
│ │ │ StartContainer() │ │ │
│ │ │ StopContainer() │ │ │
│ │ │ RemoveContainer() │ │ │
│ │ │ ContainerStats() │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────┐ │ │
│ │ │ CNI 接口调用 │ │ │
│ │ │ │ │ │
│ │ │ ADD /etc/cni/net.d/10-flannel.conflist │ │ │
│ │ │ DEL │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ └──────────────────────────────┼──────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────┼──────────────────────────────────────┐ │
│ │ Containerd │ │
│ │ │ │
│ │ Socket: /run/containerd/containerd.sock │ │
│ │ Protocol: gRPC │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────┐ │ │
│ │ │ Containerd shim (runc-v2) │ │ │
│ │ │ │ │ │
│ │ │ containerd-shim-runc-v2 \ │ │ │
│ │ │ -address /run/containerd/containerd.sock \ │ │ │
│ │ │ -containerd-binary /usr/bin/containerd \ │ │ │
│ │ │ -namespace k8s.io │ │ │
│ │ │ │ │ │
│ │ │ 作用: │ │ │
│ │ │ - 充当 containerd 和 runc 的中介 │ │ │
│ │ │ - 允许容器不依赖 containerd 进程 │ │ │
│ │ │ - 处理容器重启 │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ └──────────────────────────────┼──────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────┼──────────────────────────────────────┐ │
│ │ runc │ │
│ │ │ │
│ │ 职责: │ │
│ │ - 创建容器进程 │ │
│ │ - 配置 namespace │ │
│ │ - 配置 cgroups │ │
│ │ - 配置 seccomp │ │
│ │ │ │
│ │ 创建容器流程: │ │
│ │ 1. runc create --pid-file /run/.../pid rootfs/ │ │
│ │ 2. 创建 container 进程 (pause 容器) │ │
│ │ 3. 配置网络 namespace (从 CNI 获取) │ │
│ │ 4. 设置 cgroups │ │
│ │ 5. runc start │ │
│ │ 6. 容器内执行ENTRYPOINT/CMD │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

9.7 完整通信场景汇总

通信场景 通信方式 是否经过 kube-proxy 是否经过 CNI DNS 解析
Pod → Pod (同节点) veth + bridge
Pod → Pod (跨节点) 路由/VXLAN
Pod → ClusterIP iptables/ipvs
Pod → NodePort iptables/ipvs
Pod → ExternalName CNAME 是 (CNAME)
Pod → External IP SNAT/NAT
Pod 内容器间 localhost 共享
Pod → API Server HTTPS + Token
Istio Sidecar → Sidecar mTLS Envoy 拦截 iptables redirect
External → NodePort iptables
External → LoadBalancer 云平台 LB