SDN 与网络虚拟化
#网络 · #SDN · #虚拟化 · #CNI · #ServiceMesh · #Overlay · #VXLAN
当物理网络无法满足云原生的动态性需求时,软件定义网络和网络虚拟化登场。从容器 CNI 到 Service Mesh,网络正在从"硬件配置"走向"软件编排"。
1. 从物理网络到 Overlay 网络
容器编排对网络提出了物理网络无法满足的需求:Pod IP 必须全局唯一且可路由,Pod 可能分布在不同子网甚至不同机房,且 Pod 启停频繁(秒级)——这不是手动配 VLAN 或静态路由能跟上的节奏。于是 Overlay 网络应运而生:在物理网络之上构建一层虚拟网络,让容器以为它们在同一个二层网络中。
1.1 为什么需要 Overlay
以前一个机房几百台物理机配静态 IP,现在 K8s 集群里 Pod 按秒级启停、IP 动态分配。物理网络的 VLAN(上限 4096)根本不够用,路由表也无法随着 Pod 漂移而秒级更新。Overlay 通过在现有 IP 网络上叠加一层"隧道",让每个 Pod 获得独立 IP 的同时,底层网络设备完全不用感知:
mermaid
flowchart LR
subgraph Physical["物理网络: 同子网直接通信"]
HA["Host A<br/>192.168.1.10"] ---|"直接"| HB["Host B<br/>192.168.1.20"]
end
subgraph Container["容器网络: 跨主机, 需要 Overlay"]
subgraph HA2["Host A"]
C1["Container 1<br/>10.244.1.2"]
end
subgraph HB2["Host B"]
C2["Container 2<br/>10.244.2.3"]
end
C1 -.-|"??? 跨主机需要 Overlay 隧道"| C2
end1.2 Overlay 技术对比
| 技术 | 封装方式 | 性能 | 特点 |
|---|---|---|---|
| VXLAN | L2 over UDP | 较高 | 24bit VNI 支持 16M 虚拟网络,主流 CNI 方案 |
| Geneve | 灵活封装 | 高 | 可扩展元数据,OVN 使用 |
| GRE | IP-in-IP | 高 | 简单,但不支持多租户 |
| IPIP | IP-in-IP | 最高 | 最简单,Calico 默认 |
1.3 VXLAN 封装格式
mermaid
flowchart LR
subgraph Outer["外层封装"]
Eth["Outer Ethernet Header"]
IP["Outer IP Header"]
UDP["Outer UDP Header"]
end
subgraph VXLAN_Header["VXLAN Header (8B)"]
Flags["Flags (8b)"]
VNI["VNI (24b): 虚拟网络标识符"]
end
subgraph Inner["内层原始帧"]
InnerEth["Inner Ethernet Header"]
InnerIP["Inner IP Header"]
Payload["Payload"]
end
Eth --> IP --> UDP --> VXLAN_Header --> InnerEth --> InnerIP --> PayloadVXLAN 将 L2 帧封装在 UDP 中,VTEP(VXLAN Tunnel End Point)负责封装和解封。
2. Kubernetes 网络模型
2.1 CNI(Container Network Interface)
CNI 是 K8s 网络插件的标准接口——它的设计哲学是"只管给 Pod 分配 IP 并保证通,别的不管"。kubelet 在创建 Pod 时调用 CNI 插件的 ADD 命令,删除时调用 DEL。这种极简主义让 CNI 插件生态百花齐放,但也意味着网络策略(NetworkPolicy)和服务代理(kube-proxy)必须由其他组件补充。
2.2 三大 CNI 插件深度对比
选择 CNI 不是在选"哪个更好",而是在做性能、复杂度、功能三者之间的权衡。下面从数据面实现、性能开销、适用规模、网络策略能力四个维度逐一对比:
mermaid
flowchart TB
subgraph Flannel["Flannel — 入门首选"]
F1["数据面: VXLAN / host-gw"]
F2["性能: 🟡 中等(VXLAN 封装开销)"]
F3["网络策略: ❌ 不支持"]
F4["规模: 中小集群 (< 500 节点)"]
end
subgraph Calico["Calico — 高性能标杆"]
C1["数据面: BGP + iptables / eBPF"]
C2["性能: 🟢 接近原生网络"]
C3["网络策略: ✅ 丰富的 L3/L4"]
C4["规模: 大集群 (500+ 节点)"]
end
subgraph Cilium["Cilium — 下一代"]
Ci1["数据面: eBPF"]
Ci2["性能: 🟢🟢 原生网络 + L7 加速"]
Ci3["网络策略: ✅ L3/L4/L7 (HTTP/gRPC/Kafka)"]
Ci4["规模: 大集群 + 可观测性 (Hubble)"]
end| 维度 | Flannel | Calico | Cilium |
|---|---|---|---|
| 设计哲学 | 极简,只解决连通 | BGP 路由,不求 Overlay | eBPF 内核编程,取代 iptables |
| 数据面 | VXLAN(默认)/ host-gw | iptables / eBPF(可选) | eBPF(核心) |
| 封装开销 | ~50 字节(VXLAN 头) | 0(BGP 直连)/ ~20 字节(IPIP) | 0(直连路由) |
| 跨子网 | ✅ VXLAN | ✅ IPIP / VXLAN 回退 | ✅ Native Routing 或 VXLAN |
| 网络策略 | ❌ | ✅(基于 iptables) | ✅✅(基于 eBPF,支持 L7) |
| 可观测性 | ❌ | 基础 flow log | ✅ Hubble(流量拓扑图) |
| 配置复杂度 | ⭐(5 分钟部署) | ⭐⭐(需 BGP 知识) | ⭐⭐⭐(需 eBPF 知识) |
| CPU 开销 | 低(内核 VXLAN) | 中(iptables 规则匹配 O(n)) | 低(eBPF O(1) 哈希查找) |
| 适用场景 | 学习、开发、小集群 | 生产通用、已有 BGP 设施 | 微服务、需要 L7 策略、大规模 |
为什么"iptables O(n) vs eBPF O(1)"在实际中很重要:
场景:K8s 集群有 1000 个 Service,每个 Service 3 个后端 Pod
iptables 模式:
kube-proxy 为每个 Service 生成多条 iptables 规则
每条新连接都要逐条匹配 → 3000+ 规则 → 连接建立延迟明显上升
eBPF 模式:
Cilium 将 Service→Pod 映射写入 eBPF map(哈希表)
每个新连接 O(1) 查找 → 无论 Service 数量多少,延迟恒定Flannel 后端模式详解
Flannel 的简单体现在"只做一件事"——为每个 Node 分配子网,通过不同后端实现跨 Node 通信:
| 后端 | 原理 | 性能 | 要求 | 适用 |
|---|---|---|---|---|
| VXLAN | L2 帧封装到 UDP,内核态处理 | 🟡 有封装开销 | L3 可达即可 | 通用,默认模式 |
| host-gw | 直接添加静态路由表项 | 🟢 原生性能 | Node 必须 L2 直连 | 同机房 |
| UDP | 用户态封装(早期方案) | ❌ 性能差 | L3 可达即可 | 已淘汰 |
Calico 核心机制
Calico 不使用 Overlay(默认):
- 每个 Node 上运行 BIRD(BGP 守护进程)
- 各 Node 通过 BGP 互相宣告自己分配的 Pod CIDR
- 物理网络路由器也参与 BGP → Pod IP 直接在底层网络可达
结果:没有隧道,没有封装,性能 ≈ 原生网络
当跨子网时回退到 IPIP 或 VXLAN:
Pod IP 被封装到 Node IP 包中 → 到目标 Node 后解封Cilium 的 eBPF 数据面
mermaid
flowchart TB
subgraph Traditional["传统路径 (iptables)"]
PodA["Pod A"] --> Veth["veth pair"]
Veth --> IPTables["iptables 规则链<br/>PREROUTING → FORWARD → POSTROUTING<br/>每个包都要逐条匹配"]
IPTables --> Stack["内核网络栈"]
Stack --> Veth2["veth pair"]
Veth2 --> PodB["Pod B"]
end
subgraph eBPF["eBPF 路径 (Cilium)"]
PodA2["Pod A"] --> TC_BPF["TC BPF 程序<br/>直接查 eBPF map<br/>O(1) 哈希查找"]
TC_BPF --> PodB2["Pod B"]
endeBPF 绕过整个 iptables 规则链和部分内核网络栈,包在 veth 出口就被直接转发到目标 Pod——这对大规则量场景(数百个 NetworkPolicy)的性能提升是数量级的。
2.3 kube-proxy 模式
kube-proxy 负责将 Service 的虚拟 IP 翻译为后端 Pod IP。它的实现模式直接决定了集群服务的转发性能:
| 模式 | 实现 | 性能 | 延迟 | 规则数量影响 |
|---|---|---|---|---|
| userspace | 用户态代理转发 | ❌ 最差 | 高(多次上下文切换) | — |
| iptables | iptables 规则链 | 🟡 中等 | 低 | ❌ O(n),1000+ Service 明显变慢 |
| IPVS | Linux 内核 L4 负载均衡 | ✅ 好 | 低 | ✅ O(1) 哈希查找 |
| eBPF | eBPF 程序替代 iptables | ✅✅ 最好 | 最低 | ✅ O(1) + 无规则链 |
3. Service Mesh
3.1 Sidecar 模式
mermaid
flowchart TB
subgraph Pod["Pod"]
App["App Container"]
Envoy["Envoy Container<br/>(Sidecar Proxy)"]
App -->|"localhost"| Envoy
Envoy -->|":15001<br/>拦截出入流量"| App
end
Envoy <-->|"xDS API<br/>动态配置下发"| Istiod["Control Plane<br/>Istiod"]Sidecar 接管应用的所有出入流量,实现:
- 流量管理(金丝雀发布、A/B 测试)
- 安全(mTLS 自动加密)
- 可观测性(Metrics, Tracing, Logging)
- 策略控制(限流、熔断、重试)
3.2 Istio 架构
mermaid
flowchart TB
subgraph DataPlane["数据面"]
E1[Envoy 1]
E2[Envoy 2]
E3[Envoy 3]
end
subgraph ControlPlane["控制面 (Istiod)"]
C1[Pilot: 服务发现 & 流量配置]
C2[Citadel: 证书管理 & mTLS]
C3[Galley: 配置验证]
end
ControlPlane -->|"xDS API"| DataPlane3.3 Sidecarless(Ambient Mesh)
mermaid
flowchart LR
subgraph Traditional["传统 Sidecar"]
T["App │ Envoy<br/>每个 Pod 注入一个"]
end
subgraph Ambient["Ambient Mesh"]
A["App<br/>无注入"]
A -->|"每节点"| Z["ztunnel<br/>L4 代理 (DaemonSet)"]
Z -->|"按需"| W["waypoint<br/>L7 代理"]
endIstio Ambient Mesh 将 Sidecar 拆分为节点级代理(ztunnel)和按需的 L7 waypoint proxy,减少资源开销。
4. eBPF 网络编程
4.1 eBPF 能做什么
mermaid
flowchart TB
subgraph HookPoints["eBPF 程序挂载点"]
XDP["XDP (网卡驱动层)<br/>最早介入, 可丢弃/转发<br/>DDoS防护、L4 LB"]
TC["TC (流量控制层)<br/>策略路由、QoS"]
Socket["Socket (套接字层)<br/>连接级控制、负载均衡"]
Cgroup["cgroup (cgroup级别)<br/>Pod/Socket 级别策略"]
end
XDP -->|"最高性能<br/>绕过内核协议栈"| App["应用程序"]
TC -->|"内核网络栈内"| App
Socket -->|"syscall 拦截"| App
Cgroup -->|"容器级别"| App4.2 XDP(eXpress Data Path)
c
// XDP 程序运行在网卡驱动中,在内核分配 sk_buff 之前
// 三种返回动作:
XDP_PASS // 交给内核协议栈(正常流程)
XDP_DROP // 丢弃包(DDoS 防护)
XDP_TX // 从同一网卡发回(L4 LB)
XDP_REDIRECT // 转发到其他网卡或用户态性能:XDP 比 iptables 快 10-100 倍,因为处理发生在协议栈最早期。
5. 网络虚拟化方案对比
| 维度 | Flannel | Calico | Cilium | Istio |
|---|---|---|---|---|
| 层次 | L2/L3 | L3 | L3/L4/L7 | L4/L7 |
| 数据面 | VXLAN/IPIP | iptables/BGP/eBPF | eBPF | Envoy |
| 性能 | 🟡 | 🟢 | 🟢🟢 | 🟡 (Sidecar开销) |
| 网络策略 | 基础 | 丰富 | 丰富 + L7 | 丰富 + L7 |
| 可观测性 | ❌ | 有限 | ✅ (Hubble) | ✅✅ (Kiali/Jaeger) |
| 复杂度 | 低 | 中 | 中高 | 高 |
6. NetworkPolicy — 网络安全策略实战
NetworkPolicy 是 K8s 内置的"防火墙",通过 YAML 定义 Pod 间的允许/拒绝规则。注意:只有支持 NetworkPolicy 的 CNI(Calico、Cilium)才能生效,Flannel 不支持。
6.1 典型场景
yaml
# 场景1: 只允许 frontend → backend 的流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels:
app: backend # 策略作用于带 app=backend 标签的 Pod
policyTypes:
- Ingress # 控制入站流量
ingress:
- from:
- podSelector:
matchLabels:
app: frontend # 只允许来自 app=frontend 的流量
ports:
- protocol: TCP
port: 8080 # 且仅限 8080 端口yaml
# 场景2: 拒绝所有外部访问(只允许同 namespace 内通信)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-external
spec:
podSelector: {} # 空选择器 = 作用于所有 Pod
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {} # 只允许本命名空间内的 Podyaml
# 场景3: Cilium L7 策略 — 只允许 GET /api/health
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-health-only
spec:
endpointSelector:
matchLabels:
app: api-server
ingress:
- fromEntities:
- world # 允许外部来源
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: GET # 仅允许 GET
path: "/api/health" # 仅允许 /api/health 路径6.2 NetworkPolicy 实现原理对比
Calico (iptables 模式):
NetworkPolicy → iptables 规则链
每条规则追加到 Pod 所在节点的 iptables FORWARD 链
问题:规则多时(100+ NetworkPolicy),iptables 规则数量暴增
Cilium (eBPF 模式):
NetworkPolicy → eBPF map
每条规则写入 eBPF map(哈希表),O(1) 查找
优势:策略再多也不影响数据面性能7. CNI 插件开发流程
CNI 插件本质就是一个可执行文件,kubelet 在创建/删除 Pod 时调用它。了解开发流程能帮你理解 CNI 的设计哲学。
7.1 插件接口
go
// CNI 插件的最小接口(简化版)
type CNI interface {
// ADD: 创建 Pod 网络 — 分配 IP、创建 veth、配路由
AddNetwork(ctx context.Context, conf *NetworkConfig) (*Result, error)
// DEL: 删除 Pod 网络 — 释放 IP、删除 veth、清理路由
DelNetwork(ctx context.Context, conf *NetworkConfig) error
// CHECK: 检查网络配置是否仍然正确(可选)
CheckNetwork(ctx context.Context, conf *NetworkConfig) error
// VERSION: 返回支持的 CNI 规范版本
GetVersion() string
}7.2 ADD 命令的调用流程
kubelet 调用: CNI_COMMAND=ADD CNI_CONTAINERID=<cid> CNI_NETNS=<ns> ./my-cni < config.json
插件执行:
1. 解析 config.json → 获取子网、IPAM 配置等
2. 创建 veth pair (host端: eth0, pod端在内)
3. 将 pod 端移入容器的 network namespace
4. 通过 IPAM 插件分配 IP
5. 配置 pod 端的 IP、路由、默认网关
6. 在 host 端添加路由规则
7. 返回结果 JSON: { "cniVersion": "1.0.0", "ips": [{"address": "10.244.1.5/24"}] }
kubelet 根据返回的结果更新 Pod status.PodIP7.3 IPAM 插件独立设计
CNI 规范刻意将"网络连接"和"IP 分配"分离:
- CNI 插件负责网络配置(veth、bridge、路由)
- IPAM 插件负责 IP 分配
示例 config.json:
{
"cniVersion": "1.0.0",
"name": "my-cni",
"type": "my-plugin", ← CNI 插件
"ipam": {
"type": "host-local", ← IPAM 插件
"subnet": "10.244.0.0/16",
"rangeStart": "10.244.1.1",
"rangeEnd": "10.244.1.254"
}
}
这种分离让 IP 管理策略可以独立演进
(host-local / DHCP / 自定义 IPAM)7.4 Calico BGP 模式配置示例
bash
# 安装 Calico 时启用 BGP 模式(而非默认的 IPIP)
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
# 修改配置为 BGP 直连
kubectl patch felixconfiguration default \
--type=merge -p '{"spec":{"ipipEnabled":false}}'
# 配置 BGP Peer(如果有外部路由器)
cat <<EOF | kubectl apply -f -
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: my-router
spec:
peerIP: 10.0.0.1
asNumber: 64512
EOF
# 验证 BGP 邻居状态
calicoctl node status
# 输出:
# +-----------+-------+-------+------+-------------+
# | PEER ADDR | STATE | SINCE | INFO |
# | 10.0.0.1 | up | 10:30 | Est. |
# +-----------+-------+-------+------+-------------+8. CNI 插件与 eBPF XDP 实战
CNI 插件核心骨架 (Go)
go
package main
import (
"encoding/json"
"os"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/vishvananda/netlink"
)
func cmdAdd(args *skel.CmdArgs) error {
conf := &types.NetConf{}
json.Unmarshal(args.StdinData, conf)
// 1. 在 host 端创建 veth pair
hostVeth := args.IfName // e.g. "eth0"
peerVeth := "tmp-" + args.ContainerID[:12]
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: hostVeth},
PeerName: peerVeth,
}
netlink.LinkAdd(veth)
// 2. 将 peer 端移入容器 network namespace
peerLink, _ := netlink.LinkByName(peerVeth)
nsFD, _ := os.Open(args.Netns)
netlink.LinkSetNsFd(peerLink, int(nsFD.Fd()))
// 3. 返回结果给 kubelet
result := &types.Result{CNIVersion: conf.CNIVersion}
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
hostVeth, _ := netlink.LinkByName(args.IfName)
netlink.LinkDel(hostVeth)
return nil
}
func main() {
skel.PluginMain(cmdAdd, nil, cmdDel,
types.All, "v1.0.0", "my-cni")
}eBPF XDP — 内核态包过滤
c
// xdp_drop.c — 最简单的 XDP 程序:丢弃所有来自黑名单 IP 的包
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10000);
__type(key, __u32); // IP 地址
__type(value, __u8); // 标记位
} blacklist SEC(".maps");
SEC("xdp")
int xdp_filter(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void*)(eth + 1) > data_end) return XDP_PASS;
struct iphdr *ip = (struct iphdr *)(eth + 1);
if ((void*)(ip + 1) > data_end) return XDP_PASS;
// 查黑名单
__u8 *flag = bpf_map_lookup_elem(&blacklist, &ip->saddr);
if (flag) {
return XDP_DROP; // 丢弃
}
return XDP_PASS; // 放行
}
char _license[] SEC("license") = "GPL";bash
# 编译 XDP 程序
clang -O2 -target bpf -c xdp_drop.c -o xdp_drop.o
# 加载到网卡
ip link set dev eth0 xdp obj xdp_drop.o sec xdp
# 查看状态
ip link show eth0 | grep xdp
# 卸载
ip link set dev eth0 xdp off
# 管理黑名单 (bpftool)
bpftool map update id 1 key 0x0A000001 value 1 # 添加 10.0.0.1
bpftool map dump id 1 # 查看text
XDP 的三种返回码对性能的影响:
XDP_DROP — 网卡驱动层直接丢弃, 不分配 sk_buff, 最快
XDP_TX — 同一网卡发回, 适合 L4 负载均衡
XDP_REDIRECT — 转发到其他网卡/CPU, 适合 DDoS 清洗
XDP_PASS — 交给内核协议栈, 性能无损失(但失去了 XDP 的加速)
性能: XDP_DROP 可达 20Mpps/核 (vs iptables 的 ~1Mpps/核)
登录后即可发表评论 👇