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 节点上,负责:
- 监听 Kubernetes 资源(NetworkPolicy、Service、Endpoints)的变化
- 将这些资源编译成 eBPF 程序的配置(policy maps、service maps)
- 通过
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 μs | 30-100 ns | 50-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 代表的是一种全新的基础设施思维方式:把需要内核参与的逻辑,直接以内核扩展的形式实现,而不是通过用户空间代理或控制平面协调。
从这篇文章的核心要点:
- eBPF 是内核可编程的革命——它让用户空间可以在内核的安全沙盒中执行自定义逻辑,性能接近原生内核代码
- Cilium 用 eBPF 重建了整个 K8s 网络层——kube-proxy、NetworkPolicy、服务网格、负载均衡,全部以内核程序实现
- 零 sidecar 的 L7 可观测性——Hubble 在内核层捕获 Flow 元数据,提供完整的网络可见性而不引入额外开销
- 从 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, 云原生网络, 服务网格