Skip to content

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
    end

1.2 Overlay 技术对比 ​

技术封装方式性能特点
VXLANL2 over UDP较高24bit VNI 支持 16M 虚拟网络,主流 CNI 方案
Geneve灵活封装高可扩展元数据,OVN 使用
GREIP-in-IP高简单,但不支持多租户
IPIPIP-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 --> Payload

VXLAN 将 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
维度FlannelCalicoCilium
设计哲学极简,只解决连通BGP 路由,不求 OverlayeBPF 内核编程,取代 iptables
数据面VXLAN(默认)/ host-gwiptables / 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 通信:

后端原理性能要求适用
VXLANL2 帧封装到 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"]
    end

eBPF 绕过整个 iptables 规则链和部分内核网络栈,包在 veth 出口就被直接转发到目标 Pod——这对大规则量场景(数百个 NetworkPolicy)的性能提升是数量级的。

2.3 kube-proxy 模式 ​

kube-proxy 负责将 Service 的虚拟 IP 翻译为后端 Pod IP。它的实现模式直接决定了集群服务的转发性能:

模式实现性能延迟规则数量影响
userspace用户态代理转发❌ 最差高(多次上下文切换)—
iptablesiptables 规则链🟡 中等低❌ O(n),1000+ Service 明显变慢
IPVSLinux 内核 L4 负载均衡✅ 好低✅ O(1) 哈希查找
eBPFeBPF 程序替代 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"| DataPlane

3.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 代理"]
    end

Istio 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 -->|"容器级别"| App

4.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. 网络虚拟化方案对比 ​

维度FlannelCalicoCiliumIstio
层次L2/L3L3L3/L4/L7L4/L7
数据面VXLAN/IPIPiptables/BGP/eBPFeBPFEnvoy
性能🟡🟢🟢🟢🟡 (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: {}       # 只允许本命名空间内的 Pod
yaml
# 场景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.PodIP

7.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/核)

参考 ​

批注模式

💬 文章评论

暂无评论,来说点什么吧 👇

编程学习笔记