编程 eBPF + Cilium 深度拆解:当网络插件不再需要 iptables——从内核沙盒到 K8s CNI 的零损耗数据面全链路实战

2026-08-18 14:16:11 +0800 CST views 4

eBPF + Cilium 深度拆解:当网络插件不再需要 iptables——从内核沙盒到 K8s CNI 的零损耗数据面全链路实战

前言:从"修修补补"到"内核级革命"

2026年的云原生战场上,有一个技术方向正在悄悄改变 Kubernetes 网络的底层逻辑——它不需要修改你的应用代码,不需要引入复杂的 sidecar,甚至不需要重启服务,只需要加载一段字节码到 Linux 内核,就能实现网络策略、可观测性、服务网格,甚至直接接管 kube-proxy 的全部功能。

这就是 eBPF(extended Berkeley Packet Filter)+ Cilium

在深入之前,我们先来看看为什么这件事值得写一篇6000字以上的深度文章。传统 Kubernetes 网络方案面临三重困境:

困境一:iptables 的 O(n) 噩梦。 当你的集群有 10,000 个 Service 时,每条 iptables 规则的变更都需要遍历整条链表。kube-proxy 在重载规则时会造成短暂的网络中断,这在金融、游戏等低延迟场景中是致命的。

困境二:sidecar 代理的性能损耗。 Istio 等服务网格通过 sidecar 劫持所有流量来做可观测性和安全策略,但 sidecar 本身消耗 10-30% 的 CPU 和内存,对延迟敏感型业务是不可接受的。

困境三:可观测性的黑盒困境。 你知道 Pod 之间发了什么包吗?传统的方案是在应用层打日志,或者用 TCPDump 抓包——但这些都会干扰业务,而且数据量巨大。

Cilium 给出的答案是:用 eBPF 在内核层面直接解决这些问题。2026年的 Cilium 1.16 已经实现了生产级稳定,是时候认真了解这项技术了。


一、eBPF 是什么?它凭什么革了 iptables 的命?

1.1 从 BPF 到 eBPF:一次跨越 20 年的进化

BPF 最早出现在 1992 年的 BSD 系统里,最初是用来做网络包过滤的——它的设计哲学是"让内核告诉用户空间,哪些包是你们想要的"。最早的 BPF 是一个简单的过滤虚拟机,只有两条寄存器、七八条指令,性能极好但能力有限。

2014 年,Alexei Starovoitov 对 BPF 进行了革命性的重设计,创造了 eBPF(extended BPF)。这一次,寄存器从 2 个扩展到 10 个,指令集从 30 条扩展到 100 多条,加入了 Maps、尾调用、BTF(BPF Type Format)等基础设施。eBPF 不再只是一个包过滤器,而是一个通用的内核级可编程平台

// 第一个 eBPF 程序:统计系统调用次数
// 这是 Cilium 用来做系统可观测性的基础模式
#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <bpf/bpf_helpers.h>

// 定义一个哈希 Map,key 是 PID,value 是计数
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10240);
    __type(key, __u32);   // PID
    __type(value, __u64); // 计数
} syscall_count SEC(".maps");

// 这段程序会被 attach 到 tracepoint/sys_enter
SEC("tracepoint/syscalls/sys_enter_read")
int count_sys_read(struct trace_event_raw_sys_enter *ctx)
{
    __u32 pid = bpf_get_current_pid_tgid() >> 32;
    __u64 *count = bpf_map_lookup_elem(&syscall_count, &pid);
    
    if (count) {
        __sync_fetch_and_add(count, 1);
    } else {
        __u64 initial = 1;
        bpf_map_update_elem(&syscall_count, &pid, &initial, BPF_ANY);
    }
    
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

这段代码看起来简单,但它的意义是革命性的:用户空间程序可以在不修改内核、不加载内核模块的情况下,向 Linux 内核注入一段自定义逻辑。这段逻辑在内核的沙盒中运行(内核会先通过 Verifier 验证程序的安全性),既安全又高效。

1.2 eBPF 的内核执行模型

理解 eBPF 的执行模型是理解 Cilium 的基础。eBPF 程序不是随便运行的,它们需要挂载到内核的特定 hook 点

用户空间
    ↓ 加载 eBPF 字节码
eBPF Verifier(安全性检查)
    ↓ 验证通过
eBPF JIT 编译器(编译成机器码)
    ↓
内核 hook 点:
    ├── kprobe / kretprobe   → 内核函数入口/返回
    ├── tracepoint          → 内核静态追踪点
    ├── XDP (eXpress Data Path) → 网卡接收时(最早时机)
    ├── tc (traffic control) → 网卡发送/接收队列
    ├── sock_ops            → TCP 连接状态变化
    ├── sched_ext           → 调度器(Linux 6.11+)
    └── ... 还有 20+ 种 hook
    ↓ 执行结果
eBPF Maps(与用户空间共享数据)
    ↓ 读取 Maps
用户空间程序(收集结果/配置策略)

关键点是 eBPF Maps。这是内核和用户空间之间的"高速公路"——eBPF 程序在里面写入数据(包计数、连接跟踪、安全决策),用户空间程序从中读取并做进一步处理。Cilium 正是利用这套机制,实现了完全不需要 sidecar 的网络策略执行。

1.3 为什么 XDP 如此重要?

在所有 hook 中,XDP(eXpress Data Path) 是性能最恐怖的。它的 hook 时机在网卡驱动收到包之后、协议栈处理之前——这意味着你可以在包进入内核的第一时间就做出决策:放行、丢弃、或者重定向。

// XDP 程序示例:简单的 DDoS 防护(丢包)
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

// 连接跟踪 Map
struct {
    __uint(type, BPF_MAP_TYPE_LRU_HASH);
    __uint(max_entries, 100000);
    __type(key, __u32);    // 源 IP
    __type(value, __u64);  // 时间戳
} conn_tracking SEC(".maps");

// 限速阈值:每秒 1000 个包
#define RATE_LIMIT 1000

static __always_inline int is_rate_limited(__u32 src_ip) {
    __u64 *last_seen = bpf_map_lookup_elem(&conn_tracking, &src_ip);
    __u64 now = bpf_ktime_get_ns();
    
    if (last_seen) {
        // 每纳秒的速率,换算成每秒
        __u64 interval_ns = now - *last_seen;
        __u64 packets_per_sec = 1_000_000_000ULL / interval_ns;
        
        if (packets_per_sec > RATE_LIMIT) {
            return XDP_DROP;  // 超限,丢弃
        }
        // 更新时间戳
        bpf_map_update_elem(&conn_tracking, &src_ip, &now, BPF_ANY);
        return XDP_PASS;
    }
    
    // 首次见到该 IP,添加到跟踪表
    bpf_map_update_elem(&conn_tracking, &src_ip, &now, BPF_ANY);
    return XDP_PASS;
}

SEC("xdp")
int xdp_firewall(struct xdp_md *ctx)
{
    void *data     = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;
    
    // 解析 Ethernet 头
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;
    
    // 只处理 IPv4
    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return XDP_PASS;
    
    // 解析 IP 头
    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;
    
    __u32 src_ip = ip->saddr;
    return is_rate_limited(src_ip);
}

这个 XDP 程序在网卡层面以接近线速处理数据包,延迟只有几十纳秒,而 iptables 的处理延迟通常是几微秒。在 100Gbps 的网络环境下,这个差距是巨大的。


二、Cilium 架构:从网络插件到完整平台

2.1 为什么 Cilium 选择了 eBPF?

Cilium 的诞生背景值得单独讲一段。2016 年,Netflix 的工程师正在用 iptables 实现 Kubernetes 网络策略,他们发现随着集群规模增长,iptables 的性能问题越来越严重。同时,他们也在评估服务网格方案,但 sidecar 的开销让他们望而却步。

Cilium 的创始团队(来自 Red Hat 和 Google)做了一个关键判断:服务网格的核心需求——L7 可见性、安全策略、负载均衡——都可以在不引入 sidecar 的情况下,通过内核层面的 eBPF 程序来实现。这不是一个渐进式改进,而是一个范式转换。

2.2 Cilium 的核心组件

Cilium 的架构可以分为几个层次:

┌─────────────────────────────────────────────────────┐
│                  Kubernetes Layer                    │
│  (CiliumAgent runs as DaemonSet on each node)       │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐                │
│  │  cilium-agent │  │  cilium-cli   │                │
│  │  (DaemonSet)  │  │  (kubectl插件) │                │
│  └──────┬───────┘  └──────────────┘                │
│         │                                              │
│  ┌──────▼───────┐  ┌──────────────┐                │
│  │  eBPF Data Plane│  │ Hubble (可观测) │                │
│  │  (per-pod proxylets)│  │ (L7 流量可视化) │               │
│  └──────┬───────┘  └──────────────┘                │
└─────────┼─────────────────────────────────────────────┘
          │ 编译 eBPF 程序
          ▼
┌─────────────────────────────────────────────────────┐
│                   Linux Kernel                       │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │  eBPF Programs (loaded per namespace/pod)    │   │
│  │                                               │   │
│  │  [TC Ingress] ← NetworkPolicy enforcement    │   │
│  │  [TC Egress]  ← Egress Gateway, DNS proxy    │   │
│  │  [sockops]    ← TCP connection optimization   │   │
│  │  [sk_msg]     ← Socket redirects for L7 proxy │   │
│  │                                               │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │  eBPF Maps (shared state)                    │   │
│  │  ├── cilium_node_map (cluster nodes)         │   │
│  │  ├── cilium_policy_map (L3/L4/L7 rules)      │   │
│  │  ├── cilium_ct_* (connection tracking)        │   │
│  │  └── cilium_svc_v2 (service endpoints)       │   │
│  └─────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

Cilium Agent 是整个系统的控制平面,它运行在每个 Kubernetes 节点上,负责:

  1. 监听 Kubernetes 资源(NetworkPolicy、Service、Endpoints)的变化
  2. 将这些资源编译成 eBPF 程序的配置(policy maps、service maps)
  3. 通过 bpf() 系统调用将 eBPF 程序加载到内核

Cilium Operator 是集群级别的控制平面,负责:

  • IP 地址管理(IPAM)
  • KVStore 维护(cilium-managed bpf/map 同步)
  • 身份管理(为每个 Security Identity 生成 L3/L4 策略)

2.3 替代 kube-proxy:eBPF 的服务负载均衡

Cilium 最受欢迎的功能之一是用 eBPF 完全替代 kube-proxy。这不只是"更好",而是在架构上完全不同:

kube-proxy 的工作方式(iptables 模式):

Service A (10.96.0.1:80)
  └─→ Pod B (10.244.1.15:8080)
  └─→ Pod C (10.244.1.16:8080)
  └─→ Pod D (10.244.1.17:8080)

iptables 规则(假设 1000 个 Service):
  -A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.33 -j KUBE-SEP-XXXXX1
  -A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.50 -j KUBE-SEP-XXXXX2
  -A KUBE-SVC-XXXXX -j KUBE-SEP-XXXXX3

每次 Service 变更,iptables 需要重写整条规则链,涉及 O(n) 的遍历。

Cilium 的工作方式(eBPF 模式):

cilium_svc_v2 Map:
  key:   ServiceIP:Port → value: backend list (IP:Port:weight)
  
eBPF 程序(TC ingress hook):
  1. 根据 dest IP:Port 在 Map 中查找 Service
  2. 使用 Maglev 哈希或随机选择 backend
  3. 直接 NAT 并转发 —— 零额外开销
# 用户空间查看 Cilium 的 Service Map(Cilium CLI)
# 这让你看到 Cilium 实际上在 eBPF Map 里存了什么
import subprocess

# 获取所有 Service 的 eBPF 映射
result = subprocess.run(
    ["cilium", "service", "list"],
    capture_output=True, text=True
)
print(result.stdout)

# 示例输出:
# ID   Frontend              Type           Backend
# 1    10.96.0.1:80          ClusterIP      10.244.1.15:8080 (active)
#                                       ←    10.244.1.16:8080 (active)
#                                       ←    10.244.1.17:8080 (active)
# 2    10.96.0.1:443         ClusterIP      10.244.1.1:6443 (active)

Maglev 哈希是 Cilium 选择 backend 的算法,它保证每个 backend 在哈希环上有稳定的位置,避免了 iptables 随机模式导致的连接不均衡问题。Google 的研究显示,Maglev 只需要 O(1) 的查找时间,且重新均衡率极低。


三、NetworkPolicy 在 eBPF 层的执行

3.1 从 YAML 到 eBPF 字节码

让我们通过一个实际的 Kubernetes NetworkPolicy 来看看 Cilium 是如何将其转化为 eBPF 程序的。

# 一个典型的 CiliumNetworkPolicy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: web-frontend-policy
spec:
  endpointSelector:
    matchLabels:
      app: web-frontend
  ingress:
    # 允许来自 API Gateway 的流量
    - fromEndpoints:
        - matchLabels:
            app: api-gateway
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
          rules:
            http:
              - method: "GET"
                path: "/api/v1/.*"
    # 允许来自监控服务的健康检查
    - fromEndpoints:
        - matchLabels:
            k8s-app: prometheus
      toPorts:
        - ports:
            - port: "9090"
              protocol: TCP
  egress:
    # 只允许访问 API Gateway
    - toEndpoints:
        - matchLabels:
            app: api-gateway
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
    # 只允许访问 CoreDNS
    - toEndpoints:
        - matchLabels:
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP

Cilium Agent 收到这个 Policy 后,会做以下事情:

第一步:身份计算(Identity Resolution)

Cilium 使用 security identity 来标记每个端点组(由相同的 label 集合标识的 Pod 集合)。每个 identity 是一个 32 位的数字,在整个集群内唯一。

Identity Map(Cilium KVStore):
  security identity = hash(namespace + labels)
  
  "app=web-frontend"  → identity 0x00f001  → Pod: nginx-web-xxx
  "app=api-gateway"   → identity 0x00f002  → Pod: api-gw-xxx
  "k8s-app=prometheus"→ identity 0x00f003  → Pod: prometheus-xxx

第二步:策略编译

Cilium 将 YAML 策略编译成 eBPF 可以高效查找的格式。每个 namespace/pod 的 eBPF 程序都有一个关联的 policy map,里面存着允许访问的 identity 列表。

// Cilium eBPF 策略检查伪代码
SEC("tc_egress")
int tc_policy_check_egress(struct __sk_buff *skb)
{
    // 1. 获取源端点(发送方)的 identity
    __u32 src_identity = get_src_identity(skb);
    
    // 2. 获取目标端点(接收方)的 identity  
    __u32 dst_identity = get_dst_identity(skb);
    
    // 3. 在 policy map 中查找
    struct policy_entry *policy = bpf_map_lookup_elem(
        &cilium_policy_6,  // per-endpoint 的策略 map
        &dst_identity
    );
    
    if (!policy)
        return DROP;  // 没有策略,默认拒绝
    
    // 4. 检查 L4 端口(TCP/UDP 端口匹配)
    if (policy->l4_allow) {
        __u16 dport = get_dest_port(skb);
        if (port_in_allowed_set(dport, &policy->allowed_ports))
            return PASS;
    }
    
    // 5. 如果需要 L7 检查,重定向到 Envoy sidecar
    if (policy->l7_rules_exist) {
        // sockops/sk_msg 重定向到用户空间的 Envoy
        return REDIRECT_TO_L7_PROXY;
    }
    
    return DROP;
}

第三步:eBPF Map 加载

# 用 cilium CLI 查看一个 Pod 的 ingress 策略 map
# 这展示了 Cilium 实际上在 eBPF 层存了什么
$ cilium policy selectorcache dump | grep "web-frontend"

# 策略编译后的形式:
# identity 0x00f002 (api-gateway) → ALLOW TCP:8080 + HTTP:/api/v1/.*
# identity 0x00f003 (prometheus)   → ALLOW TCP:9090
# (implicit deny)                  → DENY ALL

3.2 零开销的 L3/L4 执行

关键在于,L3/L4 策略检查是在 TC(Traffic Control) ingress hook 中完成的,这个 hook 在内核协议栈的路径上,不需要任何用户空间参与。以一个千兆网卡的 Pod 为例:

  • 不使用 Cilium(iptables):每次入站包经过 iptables 需要遍历 ~5000 条规则,延迟 +5μs
  • 使用 Cilium(eBPF):eBPF Map 查找 O(1),延迟 +50ns

5000 个 Service 的集群,iptables 规则可能超过 100,000 条。Cilium 的 eBPF 方案始终是 O(1) 查找。


四、用 Go + cilium/ebpf 库写一个 eBPF 程序

光看不够,我们来动手写一个真实的 eBPF 程序。cilium/ebpf 是目前最流行的纯 Go eBPF 库,Cilium 自己也用它。让我们用它来实现一个连接速率限制器

4.1 环境准备

# 安装 cilium/ebpf 库
go get github.com/cilium/ebpf@latest
go get github.com/cilium/ebpf/cmd/bpf2go@latest

# 需要 Linux kernel >= 5.8(建议 >= 6.1 以获得完整功能)
uname -r
# 6.11.0-1-generic  # 看起来不错

4.2 eBPF 程序(ratelimit_kern.go)

//go:build ignore

package main

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/rlimit"
)

// eBPF 程序定义(必须与内核代码中的结构匹配)
type ratelimitStats struct {
	PacketsAllowed uint64
	PacketsDropped  uint64
}

// Map 定义——ratelimit_kern.go 中会生成对应的常量
//
//go:generate bpf2go ratelimit ./ratelimit.bpf.c -- -I../headers
func main() {
	// 1. 移除内存限制(eBPF Map 可以使用大量内存)
	if err := rlimit.RemoveMemlock(); err != nil {
		log.Fatalf("移除内存限制失败: %v", err)
	}

	// 2. 编译 eBPF 程序
	// bpf2go 会生成 ratelimit_bpf.o
	objs := &ratelimitObjects{}
	if err := loadRatelimitObjects(objs, nil); err != nil {
		log.Fatalf("加载 eBPF 对象失败: %v", err)
	}
	defer objs.Close()

	// 3. 打开 XDP 设备
	ifaceName := "eth0"
	iface, err := net.InterfaceByName(ifaceName)
	if err != nil {
		log.Fatalf("打开网卡 %s 失败: %v", ifaceName, err)
	}

	// 4. 将 eBPF 程序 attach 到 XDP hook
	xdpLink, err := link.AttachXDP(link.XDPOptions{
		Program:   objs.XdpRatelimitFunc,
		Interface: iface.Index,
		Flags:     link.XDPGeneric, // 生产环境用 XDPDriver 或 XDPDirect
	})
	if err != nil {
		log.Fatalf("Attach XDP 失败: %v", err)
	}
	defer xdpLink.Close()

	fmt.Printf("XDP ratelimiter 已加载到 %s (XDP mode)\n", ifaceName)
	fmt.Printf("eBPF 程序 ID: %d\n", objs.XdpRatelimitFunc.FD())
	fmt.Println("\n按 Ctrl+C 退出")
	fmt.Println("开始统计中...\n")

	// 5. 持续读取统计数据
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			stats := ratelimitStats{}
			err := objs.Stats.Lookup(uint32(0), &stats)
			if err != nil {
				log.Printf("读取统计失败: %v", err)
				continue
			}
			fmt.Printf("[%s] 允许: %-10d | 丢弃: %-10d | 丢包率: %.2f%%\n",
				time.Now().Format("15:04:05"),
				stats.PacketsAllowed,
				stats.PacketsDropped,
				float64(stats.PacketsDropped)/float64(stats.PacketsAllowed+stats.PacketsDropped+1)*100,
			)
		}
	}
}

4.3 内核 eBPF 程序(ratelimit.bpf.c)

// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2026 Author

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <linux/icmp.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>

// 常量定义
#define RATE_LIMIT_PACKETS_PER_SEC 1000
#define NS_PER_SEC 1000000000UL

// eBPF Maps

// 连接跟踪:源 IP → 上次时间戳
struct {
    __uint(type, BPF_MAP_TYPE_LRU_HASH);
    __uint(max_entries, 65536);
    __type(key, __u32);   // 源 IP (网络序)
    __type(value, __u64); // 上次包到达时间(纳秒)
} conn_track SEC(".maps");

// 全局统计
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, __u64); // 0=allowed, 1=dropped
} stats SEC(".maps");

// 增加统计计数
static __always_inline void inc_stat(int idx) {
    __u32 key = idx;
    __u64 *val = bpf_map_lookup_elem(&stats, &key);
    if (val) {
        __sync_fetch_and_add(val, 1);
    }
}

// 速率限制检查
// 返回 1 = 允许, 0 = 拒绝
static __always_inline int check_ratelimit(__u32 src_ip) {
    __u64 now = bpf_ktime_get_ns();
    __u64 *last_time = bpf_map_lookup_elem(&conn_track, &src_ip);
    
    if (last_time) {
        __u64 elapsed_ns = now - *last_time;
        // 最少间隔:每包 1ms(对应每秒最多 1000 包)
        if (elapsed_ns < (NS_PER_SEC / RATE_LIMIT_PER_SEC)) {
            return 0; // 太频繁,拒绝
        }
    }
    
    // 更新或插入时间戳
    bpf_map_update_elem(&conn_track, &src_ip, &now, BPF_ANY);
    return 1;
}

// XDP 程序入口
SEC("xdp")
int xdp_ratelimit(struct xdp_md *ctx) {
    void *data     = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;
    
    // 解析 Ethernet 头
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        goto pass;
    
    // 只处理 IPv4
    if (eth->h_proto != bpf_htons(ETH_P_IP))
        goto pass;
    
    // 解析 IP 头
    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        goto pass;
    
    // 获取源 IP(转为 host 序)
    __u32 src_ip = bpf_ntohl(ip->saddr);
    
    // 对 TCP/UDP 包进行速率限制
    // ICMP/其他协议直接放行
    if (ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP) {
        if (!check_ratelimit(src_ip)) {
            inc_stat(1); // 统计丢弃
            return XDP_DROP;
        }
    }
    
    inc_stat(0); // 统计允许
pass:
    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

4.4 在 Kubernetes 中部署

# 编译 eBPF 程序
go generate ./...

# 部署到 Kubernetes(使用 Cilium 的 eBPF 加载能力)
# 创建 Cilium EgressGateway 策略
apiVersion: cilium.io/v2
kind: CiliumEgressGatewayPolicy
metadata:
  name: web-egress
spec:
  egress:
    matchLabels:
      app: web-server
  destination:
    cidr: "10.0.0.0/8"  # 目标网络
  excludeCIDRs:         # 排除的 CIDR
    - "10.244.0.0/16"   # 集群内部
  sourceIP:
    ipv4: "192.168.1.100"  # 固定的出口 IP

4.5 性能基准测试

我们来测试一下这个 XDP 速率限制器的实际性能:

# 在测试 Pod 中运行(用 stress 制造流量)
kubectl run -it --rm load-gen \
    --image=busybox --restart=Never -- sh

# 使用 iperf3 测量吞吐
while true; do
    echo "Packet" | nc -u -q1 10.244.1.100 8080
done

# 在宿主机用 perf 观察 eBPF 程序的执行情况
perf stat -e cycles,instructions,bpf-output \
    kubectl exec -n kube-system ds/cilium-daemonset -- \
    cilium bpf metrics list

典型的 XDP 程序性能数据:

指标iptables 方案XDP eBPF 方案提升倍数
单包延迟5-15 μs30-100 ns50-500x
100G 线性转发不可行~98% 线速
CPU 占用(100k pps)~8% 单核~1.5% 单核5x
规则查找复杂度O(n)O(1)

五、用 Cilium Hubble 实现全链路可观测性

5.1 什么是 Hubble?

Cilium 自带了一个分布式可观测性系统 Hubble,它利用 eBPF 的能力,在内核层面捕获每个网络连接和 Flow 的元数据,而不需要任何应用修改。

关键在于,Hubble 捕获的只是元数据(五元组、连接状态、DNS 请求信息),而不是包内容——这意味着它的开销极低,同时提供了几乎完整的网络可见性。

# 启用 Hubble(通过 Helm 安装 Cilium 时)
helm install cilium cilium/cilium \
    --namespace kube-system \
    --set hubble.enabled=true \
    --set hubble.ui.enabled=true \
    --set hubble.metrics.enabled="{flows,drop,dns,port-distribution}"

5.2 用 Go 查询 Hubble API

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	
	v1 "github.com/cilium/hubble/api/v1"
	peer "github.com/cilium/hubble/pkg/peer"
	"github.com/cilium/hubble/pkg/session"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// 连接 Hubble gRPC API
	// 路径:/var/run/cilium/hubble.sock
	conn, err := grpc.NewClient(
		"passthrough:///unix:///var/run/cilium/hubble.sock",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		log.Fatalf("连接 Hubble 失败: %v", err)
	}
	defer conn.Close()

	client := v1.NewFlowServiceClient(conn)
	
	// 获取所有 Flow(网络连接)
	stream, err := client.GetFlows(ctx, &v1.GetFlowsRequest{})
	if err != nil {
		log.Fatalf("订阅 Flow 流失败: %v", err)
	}

	fmt.Println("Hubble Flow 监控(Ctrl+C 退出)")
	fmt.Println("时间戳 | 方向 | 源 IP:Port → 目标 IP:Port | 协议 | 事件")
	fmt.Println(strings.Repeat("-", 90))

	for {
		flow, err := stream.Recv()
		if err != nil {
			log.Printf("接收 Flow 失败: %v", err)
			break
		}

		// 提取 Flow 信息
		f := flow.Flow
		timestamp := f.GetTime().AsTime().Format("15:04:05.000")
		
		direction := "→"
		if f.GetIP().GetSource().Equals(f.GetSource().GetPodName()) {
			direction = "←"
		}

		verdict := "✓"
		if f.GetVerdict() == v1.Verdict_DROPPED {
			verdict = "✗ DROP"
		}

		protocol := "TCP"
		if f.GetIP().GetProtocol().String() == "ICMP" {
			protocol = "ICMP"
		}

		srcIP := f.GetIP().GetSource().String()
		dstIP := f.GetIP().GetDestination().String()
		srcPort := f.GetL4().GetTCP().GetSourcePort()
		dstPort := f.GetL4().GetTCP().GetDestinationPort()

		fmt.Printf("%s | %s | %s:%d → %s:%d | %s | %s\n",
			timestamp,
			direction,
			srcIP, srcPort,
			dstIP, dstPort,
			protocol,
			verdict,
		)
	}
}

5.3 Hubble UI:交互式网络拓扑

安装 Hubble UI 后,你可以在 Kubernetes Dashboard 中看到一个交互式的网络拓扑图:

┌─────────────────────────────────────────────────────┐
│ Hubble UI - Cluster Flow Map                        │
│                                                     │
│     [🌐 Ingress] ──────→ [LoadBalancer]              │
│                             │                        │
│                             ▼                        │
│                    ┌───────────────┐                 │
│                    │  API Gateway  │                 │
│                    │  (10.244.1.5) │                 │
│                    └───────┬───────┘                 │
│              ┌─────────────┼─────────────┐          │
│              ▼             ▼             ▼          │
│        ┌─────────┐   ┌─────────┐   ┌─────────┐     │
│        │User Svc  │   │Order Svc │   │Pay Svc  │     │
│        │  (✓ OK) │   │ (✓ OK)  │   │(✗ DROP) │     │
│        └─────────┘   └─────────┘   └─────────┘     │
│                                          │          │
│                                          ▼          │
│                                   ┌─────────────┐   │
│                                   │ Database    │   │
│                                   │ ✗ DENIED   │   │
│                                   └─────────────┘   │
│                                                     │
│ 过滤器: [所有] [允许] [拒绝] [DNS] [HTTP 5xx]        │
└─────────────────────────────────────────────────────┘

在这个拓扑中,你可以看到:

  • 每条连接的成功/失败状态
  • 被拒绝的连接(红色),以及拒绝原因(NetworkPolicy 规则、eBPF 丢弃等)
  • 连接的实时流量(bps)

六、Cilium 的生产环境最佳实践

6.1 eBPF 模式的选型

Cilium 支持多种 eBPF 加载模式,你需要根据硬件和内核版本选择:

模式内核要求性能适用场景
XDPGeneric>= 4.18中等开发测试、老网卡
XDPTun>= 4.18通用生产环境
XDPDriver>= 5.0最高高性能网络应用
TC (Sockmap)>= 5.3需要 L7 检查时
# 检查当前 Cilium 使用的模式
cilium status --verbose

# 显示:
# BPF compiler:   bcc (legacy)
# eBPF mode:       XDPDirect (native XDP, best performance)
# Kernel version:  6.11.0
# datapath type:   routed GRPC

6.2 内存配置

eBPF Map 需要占用宿主机内存,Cilium 默认的 Map 大小对大规模集群可能不够:

# 在 Helm values 中调整 Map 大小
helm upgrade cilium cilium/cilium \
    --namespace kube-system \
    --reuse-values \
    --set bpf.mapSizes.cilium_policy_6=131072 \
    --set bpf.mapSizes.cilium_policy_4=131072 \
    --set bpf.mapSizes.cilium_ct_6=131072 \
    --set bpf.mapSizes.cilium_ct_4=131072 \
    --set bpf.mapSizes.cilium_svc_v2=131072 \
    --set bpf.mapSizes.cilium_node_map=4096

# 推荐配置(100-500 节点集群)
# cilium_policy_*:  65536 (每个 namespace 的策略)
# cilium_ct_*:      131072 (连接跟踪表)
# cilium_svc_v2:    65536 (Service 端点)

6.3 从 kube-proxy 迁移到 Cilium

这是 Cilium 最受欢迎的用法。以下是零停机迁移的步骤:

# 1. 先在 "disable kube-proxy" 模式下安装 Cilium
# 这样 Cilium 会只做额外策略执行,kube-proxy 继续工作
helm install cilium cilium/cilium \
    --namespace kube-system \
    --set kubeProxyReplacement=partial \
    --set externalIPs.enabled=true \
    --set hostPort.enabled=true \
    --set nodePort.enabled=true

# 2. 等待 Cilium 完全就绪
kubectl wait --for=condition=ready pod -l k8s-app=cilium \
    -n kube-system --timeout=300s

# 3. 逐个节点启用 eBPF 模式(每个节点单独迁移)
# 用 kubectl node-labels 标记要迁移的节点
kubectl label node <node-name> cilium.io/agent-sandbox=sidecar

# 4. 确认 eBPF 接管了 kube-proxy 的功能
cilium service list
# 应该看到和 kube-proxy 一样的 Service 列表

# 5. 全部节点迁移完成后,删除 kube-proxy
# 注意:这一步不可逆,确保 Cilium 100% 就绪
kubectl delete daemonset kube-proxy -n kube-system

# 6. 确认没有遗留的 iptables 规则
iptables -L | grep KUBE
# 应该返回空(或者只有少量非 Cilium 管理的规则)

6.4 故障排查清单

# 1. eBPF 程序是否正常加载?
cilium bpf lb list
cilium bpf policy get <pod-namespace>/<pod-name>

# 2. 查看 Hubble 的实时流量(最有用!)
cilium hubble observe --from-label app=web-frontend --to-label app=api

# 3. 检查 eBPF Map 内容
cilium bpf map list
# 显示所有 Map 的大小和使用率

# 4. 调试 NetworkPolicy 不生效
cilium policy trace 
    --src-label app:client 
    --dst-label app:server 
    --ingress
    --dport 8080

# 5. 性能问题诊断
cilium bpf metrics
# 显示每个 eBPF hook 的执行统计(latency、drop count)

七、展望:eBPF 的下一个前沿

2026 年,eBPF 生态系统有几个值得关注的发展方向:

1. sockmap + sk_msg 的 L7 服务网格替代

Cilium 的 L7 代理(用于 HTTP/gRPC 策略执行)目前通过 Envoy sidecar 实现。但 sockmap + sk_msg eBPF 程序的组合,可以让 eBPF 直接拦截和转发 L7 流量,完全消除 sidecar。Netflix 和 Meta 正在这个方向做生产探索。

2. sched_ext:调度器级 eBPF

Linux 6.11 引入的 sched_ext 是 eBPF 在调度器领域的突破——你可以用 eBPF 完全重写进程的 CPU 调度逻辑。这意味着可以实现自定义的调度策略(如延迟敏感优先、公平调度等),而不需要修改内核。

3. eBPF 驱动的安全运行时

基于 eBPF 的运行时安全工具(如 Falco 的 eBPF 后端)可以在内核层检测恶意行为(提权、容器逃逸、敏感文件访问),性能比传统的 Falco 引擎快 10 倍,且零误报率更低。


总结

eBPF + Cilium 代表的是一种全新的基础设施思维方式:把需要内核参与的逻辑,直接以内核扩展的形式实现,而不是通过用户空间代理或控制平面协调。

从这篇文章的核心要点:

  1. eBPF 是内核可编程的革命——它让用户空间可以在内核的安全沙盒中执行自定义逻辑,性能接近原生内核代码
  2. Cilium 用 eBPF 重建了整个 K8s 网络层——kube-proxy、NetworkPolicy、服务网格、负载均衡,全部以内核程序实现
  3. 零 sidecar 的 L7 可观测性——Hubble 在内核层捕获 Flow 元数据,提供完整的网络可见性而不引入额外开销
  4. 从 iptables 到 eBPF 是不可逆的趋势——2026 年的 Cilium 1.16 已经是生产级稳定方案,迁移成本很低

如果你还在用 kube-proxy + iptables 管理大规模 Kubernetes 集群,是时候认真评估 Cilium 了。性能差距不是 20%,而是一到两个数量级——在追求极致稳定性的生产环境中,这个差距值得认真对待。


Tags: eBPF, Cilium, Kubernetes, 网络插件, 云原生, CNI, 服务网格, XDP, BPF, 网络性能优化

Keywords: eBPF, Cilium, Kubernetes CNI, kube-proxy, NetworkPolicy, Hubble, XDP, TC, L7 proxy, 云原生网络, 服务网格

推荐文章

FcDesigner:低代码表单设计平台
2024-11-19 03:50:18 +0800 CST
filecmp,一个Python中非常有用的库
2024-11-19 03:23:11 +0800 CST
如何在 Vue 3 中使用 Vuex 4?
2024-11-17 04:57:52 +0800 CST
10个几乎无人使用的罕见HTML标签
2024-11-18 21:44:46 +0800 CST
程序员茄子在线接单