eBPF + Cilium + Hubble:云原生零侵入可观测性的完整实战——从内核级网络追踪到全链路流量可视化
当你的微服务出了问题,你真的能"看到"吗?
凌晨三点,告警群炸了。订单服务的 P99 延迟从 50ms 飙到 2s,但应用日志干干净净,没有任何异常。你查了 Pod 状态、CPU、内存——一切正常。你开始怀疑是网络问题,但传统的监控只能告诉你"网络延迟高",无法告诉你:
- 到底是哪个服务到哪个服务的调用慢了?
- 是 DNS 解析慢,还是 TCP 握手慢,还是 TLS 协商慢?
- 重传率是多少?哪个连接在丢包?
- 有没有连接被 iptables 规则默默 DROP 了?
传统的可观测性方案——无论是 Prometheus + Grafana 的指标监控,还是 Jaeger/Zipkin 的分布式追踪——都面临一个根本问题:你需要改代码。你得在业务代码里埋点、加 SDK、配置采样率,还要祈祷这些埋点本身不会成为性能瓶颈。
而 eBPF 的出现,彻底改变了这个局面。它让你不改一行业务代码,就能在 Linux 内核层面捕获所有网络流量、系统调用、函数执行细节。Cilium 把 eBPF 的能力封装成了云原生的网络和安全方案,Hubble 则在此基础上提供了网络流量的实时可视化。
这篇文章,我会从 eBPF 的底层原理讲起,带你理解 Cilium 如何用 eBPF 替换 kube-proxy,再到 Hubble 如何实现零侵入的全链路流量观测,最后给出完整的生产级部署方案和性能调优实践。
一、eBPF:Linux 内核的"可编程沙箱"
1.1 eBPF 是什么?从 BPF 说起
2014 年,Alexei Starovoitov 对经典的 Berkeley Packet Filter(BPF)进行了革命性改造,提出了 eBPF(extended BPF)。如果说原来的 BPF 是一个简单的包过滤器,那 eBPF 就是 Linux 内核的"JavaScript"——它让你安全地在内核态运行自定义代码,而不需要修改内核源码或加载内核模块。
eBPF 的核心设计哲学:
用户空间 内核空间
┌─────────┐ ┌──────────────────┐
│ 编写 BPF │ │ │
│ C 代码 │──→ LLVM ──→ BPF ──→│ 验证器(Verifier) │
│ │ 编译 字节码 │ ↓ │
└─────────┘ │ JIT 编译器 │
│ ↓ │
│ 原生机器码执行 │
│ ↓ │
│ 事件触发 → 运行 │
└──────────────────┘
关键安全机制:
- 验证器(Verifier):在加载时静态分析 BPF 程序,确保不会死循环、不会越界访问内存、不会崩溃内核
- JIT 编译:将验证通过的 BPF 字节码编译成本地机器码,接近原生性能
- Helper 函数:BPF 程序只能调用内核预定义的 helper 函数,不能随意调用内核函数
- Maps:BPF 程序通过 Maps 与用户空间通信,数据结构包括 Hash、Array、Ring Buffer 等
1.2 eBPF 程序类型与挂载点
eBPF 程序不是随便跑的,它必须挂载到内核的特定钩子上:
// 网络相关 - Cilium 核心使用的类型
BPF_PROG_TYPE_XDP // XDP 层,最早的数据包处理点(网卡驱动之后)
BPF_PROG_TYPE_TC // Traffic Control,流量控制层
BPF_PROG_TYPE_SOCKET_FILTER // 套接字过滤器
BPF_PROG_TYPE_SOCK_OPS // 套接字操作(追踪连接状态)
BPF_PROG_TYPE_SK_MSG // 套接字消息(数据发送拦截)
// 追踪相关
BPF_PROG_TYPE_KPROBE // 内核函数入口探针
BPF_PROG_TYPE_TRACEPOINT // 内核静态追踪点
BPF_PROG_TYPE_PERF_EVENT // 性能事件(CPU 采样等)
// 安全相关
BPF_PROG_TYPE_LSM // Linux Security Module 钩子
Cilium 主要使用 XDP、TC、SOCK_OPS 和 SK_MSG 这几种类型来实现网络功能。
1.3 动手写一个 eBPF 程序
让我们用 BTF CO-RE(Compile Once – Run Everywhere)方式写一个追踪 TCP 连接建立的 eBPF 程序:
// tcp_connect.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
struct tcp_event {
u32 pid;
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
char comm[16];
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} tcp_events SEC(".maps");
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(trace_tcp_connect, struct sock *sk)
{
struct tcp_event *e;
e = bpf_ringbuf_reserve(&tcp_events, sizeof(*e), 0);
if (!e)
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
e->saddr = sk->__sk_common.skc_rcv_saddr;
e->daddr = sk->__sk_common.skc_daddr;
e->sport = sk->__sk_common.skc_num;
e->dport = sk->__sk_common.skc_dport;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_ringbuf_submit(e, 0);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
用户空间程序:
# tcp_connect.py
from bcc import BPF
from time import strftime
bpf_code = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
struct tcp_event {
u32 pid;
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
char comm[16];
};
BPF_RINGBUF_OUTPUT(tcp_events, 1 << 24);
int trace_tcp_connect(struct pt_regs *ctx, struct sock *sk) {
struct tcp_event e = {};
e.pid = bpf_get_current_pid_tgid() >> 32;
e.saddr = sk->__sk_common.skc_rcv_saddr;
e.daddr = sk->__sk_common.skc_daddr;
e.sport = sk->__sk_common.skc_num;
e.dport = sk->__sk_common.skc_dport;
bpf_get_current_comm(&e.comm, sizeof(e.comm));
tcp_events.ringbuf_output(&e, sizeof(e), 0);
return 0;
}
"""
b = BPF(text=bpf_code)
b.attach_kprobe(event="tcp_v4_connect", fn_name="trace_tcp_connect")
def print_event(cpu, data, size):
event = b["tcp_events"].event(data)
print(f"{strftime('%H:%M:%S')} {event.comm.decode()} "
f"PID={event.pid} {event.saddr}:{event.sport} → "
f"{event.daddr}:{event.dport}")
b["tcp_events"].open_ring_buffer(print_event)
print("Tracing TCP connects... Ctrl+C to end")
while True:
b.ring_buffer_poll()
运行效果:
$ sudo python3 tcp_connect.py
Tracing TCP connects... Ctrl+C to end
03:42:11 curl PID=45231 10.0.1.5:52341 → 93.184.216.34:80
03:42:12 node PID=38901 10.0.1.5:52342 → 10.0.2.3:6379
03:42:13 python3 PID=38765 10.0.1.5:52343 → 10.0.3.7:5432
这就是零侵入追踪的力量——不需要改任何应用代码,你就能看到系统上所有 TCP 连接建立事件。
二、Cilium:用 eBPF 重写 Kubernetes 网络
2.1 kube-proxy 的问题
Kubernetes 默认使用 kube-proxy 实现 Service 的负载均衡,它基于 iptables 规则:
Client Pod → ClusterIP → iptables DNAT → Backend Pod
在大规模集群中,iptables 的问题非常严重:
| 问题 | 具体表现 |
|---|---|
| O(n) 规则匹配 | 每个数据包要线性遍历 iptables 规则链,10000 条规则意味着最坏情况匹配 10000 次 |
| 规则更新慢 | 新增/删除 Service 时,需要重建整个 iptables 规则链,在 5000+ Service 的集群中可能需要数秒 |
| 无法感知应用层 | iptables 只工作在 L3/L4,无法基于 HTTP path、method 做路由 |
| 可观测性差 | iptables 的日志几乎不可读,排查问题全靠猜 |
Cilium 用 eBPF 替换了 iptables,将所有网络策略和 Service 负载均衡逻辑编译成 eBPF 程序,挂载到 XDP 和 TC 钩子上:
Client Pod → XDP/TC eBPF → 直接查 Hash Map → DNAT → Backend Pod
O(1) 查找
2.2 Cilium 的 eBPF 数据路径
Cilium 的数据路径是其核心竞争力。让我们深入理解一个 Pod 发出请求时,数据包经过哪些 eBPF 程序:
出站路径(Pod → 外部):
┌─────────┐ ┌──────────────┐ ┌──────────────────┐ ┌──────────┐
│ App Pod │───→│ lxc eBPF(容器│───→│ TC eBPF(宿主机 │───→│ 物理网卡 │
│ │ │ 网卡出口) │ │ 出口) │ │ 或隧道 │
└─────────┘ └──────────────┘ └──────────────────┘ └──────────┘
│ │
├── SNAT ├── Service 负载均衡
├── 网络策略检查 ├── DNAT
├── CT 查找 ├── TTL 递减
└── 连接追踪 └── 直接转发
入站路径(外部 → Pod):
┌──────────┐ ┌──────────────────┐ ┌──────────────┐ ┌─────────┐
│ 物理网卡 │───→│ XDP eBPF(最早 │───→│ TC eBPF(宿主机│───→│ App Pod │
│ 或隧道 │ │ 处理点) │ │ 入口) │ │ │
└──────────┘ └──────────────────┘ └──────────────┘ └─────────┘
│ │
├── DDoS 防护 ├── DNAT 还原
├── 早期丢弃 ├── 网络策略检查
└── 包统计 └── 连接追踪
关键的 eBPF Map 数据结构:
// Cilium 核心的 BPF Map 定义(简化版)
// 连接追踪表 - 记录所有活跃连接的状态
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1000000);
__type(key, struct ct_key); // {src_ip, dst_ip, src_port, dst_port, proto}
__type(value, struct ct_entry); // {backend_ip, state, last_seen, ...}
} ct_map SEC(".maps");
// Service 负载均衡表
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 65536);
__type(key, struct lb_key); // {service_ip, service_port, proto}
__type(value, struct lb_value); // {backend_count, backend_array_index}
} lb_map SEC(".maps");
// Backend 列表
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 65536);
__type(key, __u32); // 索引
__type(value, struct lb_backend); // {backend_ip, backend_port}
} backend_map SEC(".maps");
2.3 Service 负载均衡的 eBPF 实现
让我们看 Cilium 是如何在 eBPF 中实现 Service 负载均衡的(简化版 TC 程序):
// cilium_lb_tc.c - TC 入口 eBPF 程序(核心逻辑简化)
SEC("tc")
int cilium_lb_tc(struct __sk_buff *skb)
{
struct iphdr *ip4 = parse_ip4(skb);
if (!ip4)
return TC_ACT_OK;
struct lb_key key = {
.address = ip4->daddr, // Service ClusterIP
.port = tcp_hdr->dest, // Service Port
.proto = ip4->protocol,
};
// O(1) 查找 Service
struct lb_value *svc = bpf_map_lookup_elem(&lb_map, &key);
if (!svc)
return TC_ACT_OK; // 不是 Service,放行
// 一致性哈希选择 Backend(基于 5 元组哈希,保证同一条流始终到同一后端)
__u32 hash = hash_5tuple(skb);
__u32 idx = hash % svc->count;
struct lb_backend *backend = bpf_map_lookup_elem(&backend_map, &idx);
if (!backend)
return TC_ACT_DROP;
// DNAT:修改目标 IP 和端口
ip4->daddr = backend->address;
tcp_hdr->dest = backend->port;
// 更新连接追踪表
struct ct_key ct_key = { ... };
struct ct_entry ct_entry = {
.backend_ip = backend->address,
.backend_port = backend->port,
.state = CT_ESTABLISHED,
};
bpf_map_update_elem(&ct_map, &ct_key, &ct_entry, BPF_ANY);
// 重新计算校验和
bpf_l4_csum_replace(skb, ...);
bpf_l3_csum_replace(skb, ...);
return TC_ACT_OK;
}
对比 iptables 实现:iptables 每次更新 Service 需要重建整个规则链,而 Cilium 只需要更新 BPF Map 中的一个条目——时间复杂度从 O(n) 降到了 O(1)。
2.4 XDP 加速:DDoS 防护的终极武器
XDP(eXpress Data Path)是 eBPF 最强大的网络处理点——它在网卡驱动收到数据包之后、内核协议栈之前执行:
// cilium_xdp.c - XDP 层早期丢弃
SEC("xdp")
int cilium_xdp(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 *ip4 = (void *)(eth + 1);
if ((void *)(ip4 + 1) > data_end)
return XDP_PASS;
// 检查是否在 IP 黑名单中(O(1) 查找)
__u32 *blocked = bpf_map_lookup_elem(&ip_blacklist, &ip4->saddr);
if (blocked)
return XDP_DROP; // 在最早可能的点丢弃,不消耗任何内核资源
// 速率限制
struct rate_key key = { .src_ip = ip4->saddr };
struct rate_value *rv = bpf_map_lookup_elem(&rate_limiter, &key);
if (rv && rv->count > MAX_PPS) {
return XDP_DROP;
}
return XDP_PASS;
}
XDP_DROP 的性能有多强?在生产环境中,单核每秒可以丢弃超过 2000 万个数据包,而 iptables DROP 只有约 200 万 PPS。差距 10 倍。
三、Hubble:让网络流量无所遁形
3.1 Hubble 的架构
Hubble 是 Cilium 的可观测性层,它利用 Cilium 在 eBPF 中已经收集的连接追踪信息,将网络流量事件导出到用户空间:
┌─────────────────────────────────────────────────┐
│ Kubernetes 集群 │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Node 1│ │Node 2│ │Node 3│ │
│ │ │ │ │ │ │ │
│ │ Cilium Cilium Cilium │
│ │ ↓eBPF ↓eBPF ↓eBPF │
│ │ Hubble Hubble Hubble ← 每个节点的 Agent │
│ │ Agent Agent Agent │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ │ │ │
│ └──────────┼──────────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ Hubble Relay │ ← 全局聚合层 │
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ Hubble UI │ ← Web 界面 │
│ │ / hubble CLI│ ← 命令行 │
│ └──────────────┘ │
└─────────────────────────────────────────────────┘
关键设计点:
- Hubble Agent 读取 Cilium eBPF 程序写入的事件 Map(Ring Buffer),不额外产生网络开销
- Hubble Relay 通过 gRPC 收集所有节点的流量事件
- 所有流量信息来自 eBPF 的连接追踪,不需要应用代码做任何修改
3.2 部署 Cilium + Hubble 完整方案
# 1. 添加 Cilium Helm 仓库
helm repo add cilium https://helm.cilium.io/
helm repo update
# 2. 获取当前集群的 API Server IP(用于 eBPF kube-proxy 替换)
API_SERVER_IP=$(kubectl get endpoints kubernetes -o jsonpath='{.subsets[0].addresses[0].ip}')
# 3. 安装 Cilium(替换 kube-proxy + 启用 Hubble)
helm install cilium cilium/cilium \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,icmp,http}" \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=6443 \
--set operator.replicas=2
# 4. 等待所有组件就绪
kubectl -n kube-system rollout status ds/cilium
kubectl -n kube-system rollout status deploy/cilium-operator
kubectl -n kube-system rollout status deploy/hubble-relay
# 5. 验证 kube-proxy 已被替换
cilium status --wait
cilium connectivity test
3.3 Hubble 流量观测实战
部署完成后,让我们看看 Hubble 能看到什么:
# 安装 Hubble CLI
HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/main/stable.txt)
curl -L -o /usr/local/bin/hubble \
"https://github.com/cilium/hubble/releases/download/${HUBBLE_VERSION}/hubble-linux-amd64"
chmod +x /usr/local/bin/hubble
# 设置端口转发
export HUBBLE_SERVER=localhost:4245
kubectl -n kube-system port-forward deploy/hubble-relay 4245:4245 &
# 查看所有命名空间的实时流量
hubble observe --since 1m
# 只看某个 Pod 的流量
hubble observe --pod-name order-service-7f9d8c6b4-x2k1p
# 只看被网络策略 DROP 的流量
hubble observe --verdict DROPPED
# 只看 DNS 查询
hubble observe --protocol dns
# 只看 HTTP 请求(需要启用 HTTP metrics)
hubble observe --protocol http
Hubble 的输出示例:
May 17 03:42:11.523 default/order-service:42341 -> default/payment-service:8080
tcp-established to-endpoint FORWARDED (TCP Flags: SYN)
May 17 03:42:11.524 default/payment-service:8080 <- default/order-service:42341
tcp-established to-endpoint FORWARDED (TCP Flags: SYN, ACK)
May 17 03:42:11.678 default/order-service:42341 -> default/payment-service:8080
http-request FORWARDED GET /api/v1/charge (200, 45ms)
May 17 03:42:12.001 default/unknown-ns:54321 -> default/order-service:8080
policy-denied DROPPED (TCP Flags: SYN) Reason: No matching policy
你看——连被网络策略 DROP 的请求都能看到,这在 iptables 时代是做不到的。
3.4 Hubble UI:可视化服务依赖图
Hubble UI 提供了一个强大的服务依赖图视图。当你打开 Hubble UI(通过 kubectl port-forward 访问),你会看到:
- 每个服务是一个节点,节点之间的边表示网络流量
- 边的粗细表示流量大小
- 红色边表示被 DROP 的流量
- 点击任何一条边,可以看到详细的流量统计(延迟分布、错误率、HTTP 方法分布等)
这对于理解微服务间的依赖关系、排查网络问题、验证网络策略的效果都非常有帮助。
四、零侵入 HTTP 可观测性:不用改代码的 APM
4.1 基于 eBPF 的 HTTP 追踪
Cilium 1.14+ 支持基于 eBPF 的 HTTP 层追踪,可以在不修改任何业务代码的情况下,捕获 HTTP 请求的方法、路径、状态码和延迟:
// Cilium 的 HTTP 追踪 eBPF 程序(简化版)
SEC("sock_ops")
int trace_http_sock_ops(struct bpf_sock_ops *skops)
{
switch (skops->op) {
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:
// 记录连接建立
bpf_sock_ops_cb_flags_set(skops, BPF_SOCK_OPS_RWND_CB_FLAG);
break;
case BPF_SOCK_OPS_RWND_CB_FLAG:
// 数据到达,检查是否为 HTTP
break;
}
return 0;
}
SEC("sk_msg")
int trace_http_sk_msg(struct sk_msg_md *msg)
{
// 读取数据 payload,匹配 HTTP/1.1 方法
char method[8] = {};
bpf_probe_read_kernel(method, sizeof(method), msg->data);
if (is_http_method(method)) {
// 解析 HTTP 请求行:GET /api/v1/orders HTTP/1.1
struct http_event e = {
.method = parse_method(method),
.path = parse_path(msg),
.src_ip = msg->remote_ip4,
.dst_ip = msg->local_ip4,
};
bpf_ringbuf_output(&http_events, &e, sizeof(e), 0);
}
return SK_PASS;
}
4.2 Hubble HTTP Metrics 导出 Prometheus
# cilium-helm-values-http-metrics.yaml
hubble:
enabled: true
metrics:
enabled:
- dns
- drop
- tcp
- flow
- icmp
- http
serviceMonitor:
enabled: true
dashboards:
enabled: true
annotations:
grafana_folder: "Cilium"
安装后,Hubble 会暴露以下 Prometheus 指标:
# HTTP 指标
hubble_http_requests_total{method="GET",path="/api/v1/orders",status="200",source="order-service",destination="payment-service"} 1523
hubble_http_request_duration_seconds_bucket{method="GET",path="/api/v1/orders",le="0.01"} 890
hubble_http_request_duration_seconds_bucket{method="GET",path="/api/v1/orders",le="0.05"} 1412
hubble_http_request_duration_seconds_bucket{method="GET",path="/api/v1/orders",le="0.1"} 1498
# DNS 指标
hubble_dns_queries_total{qname="payment-service.default.svc.cluster.local",qtype="A",source="order-service"} 3421
humble_dns_response_total{qname="payment-service.default.svc.cluster.local",rcode="NOERROR",ip="10.0.3.7"} 3421
# 丢包指标
hubble_drop_total{reason="POLICY_DENIED",source="unknown",destination="order-service"} 47
4.3 Grafana 仪表盘配置
{
"dashboard": {
"title": "Cilium Hubble - 服务网格可观测性",
"panels": [
{
"title": "HTTP P99 延迟(零侵入追踪)",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(hubble_http_request_duration_seconds_bucket[5m])) by (le, source, destination, path))"
}
]
},
{
"title": "被网络策略 DROP 的流量",
"type": "graph",
"targets": [
{
"expr": "sum(rate(hubble_drop_total{reason=\"POLICY_DENIED\"}[5m])) by (source, destination, reason)"
}
]
},
{
"title": "DNS 解析延迟",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(hubble_dns_response_duration_seconds_bucket[5m])) by (le, qname))"
}
]
}
]
}
}
五、网络策略实战:从"全通"到"零信任"
5.1 传统的网络安全 vs Cilium 网络策略
Kubernetes 原生的 NetworkPolicy 只支持 L3/L4 层的规则(基于 IP 和端口),而 CiliumNetworkPolicy 支持 L7 层的规则:
# 传统 NetworkPolicy:只能控制到端口级别
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-payment
namespace: default
spec:
podSelector:
matchLabels:
app: payment-service
ingress:
- from:
- podSelector:
matchLabels:
app: order-service
ports:
- port: 8080
protocol: TCP
# CiliumNetworkPolicy:可以控制到 HTTP 路径和方法级别
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-payment-api
namespace: default
spec:
endpointSelector:
matchLabels:
app: payment-service
ingress:
- fromEndpoints:
- matchLabels:
app: order-service
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: GET
path: "/api/v1/charge"
- method: POST
path: "/api/v1/refund"
这意味着:order-service 只能调用 payment-service 的 /api/v1/charge 和 /api/v1/refund 两个接口,其他任何请求都会被 eBPF 在内核态直接 DROP。
5.2 零信任网络策略渐进式落地
生产环境中,你不能一次性把所有网络策略都加上——那会直接把服务搞挂。正确的做法是先在"观察模式"下运行:
# Step 1: 观察模式 - 只记录,不拦截
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: observe-all-traffic
spec:
description: "观察模式 - 只记录不合规流量,不拦截"
endpointSelector: {}
ingress:
- fromEndpoints: []
# 空规则 = 不限制,但 Hubble 会记录所有流量
---
# Step 2: 基于观察结果生成策略
# 通过 Hubble 观察一段时间后,你能清楚知道每个服务实际需要访问哪些其他服务
# 用 hubble observe 导出流量,然后自动生成策略
自动生成策略的脚本:
# generate_policy.py - 根据 Hubble 流量日志自动生成 CiliumNetworkPolicy
import json
import subprocess
from collections import defaultdict
def get_hubble_flows(duration="1h"):
"""获取最近一段时间的 Hubble 流量"""
result = subprocess.run(
["hubble", "observe", "--since", duration, "-o", "json"],
capture_output=True, text=True
)
flows = [json.loads(line) for line in result.stdout.strip().split("\n") if line]
return flows
def generate_policies(flows):
"""根据流量生成最小权限策略"""
allowed = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
for flow in flows:
if flow.get("verdict") == "FORWARDED" and flow.get("eventType"):
src = flow["source"]["podName"].rsplit("-", 2)[0]
dst = flow["destination"]["podName"].rsplit("-", 2)[0]
port = flow["destination"]["port"]
proto = flow.get("Type", "tcp")
allowed[dst][src][port].add(proto)
policies = []
for dst, sources in allowed.items():
ingress_rules = []
for src, ports in sources.items():
port_rules = []
for port, protos in ports.items():
port_rules.append({"port": str(port), "protocol": "TCP"})
ingress_rules.append({
"fromEndpoints": [{"matchLabels": {"app": src}}],
"toPorts": [{"ports": port_rules}]
})
policy = {
"apiVersion": "cilium.io/v2",
"kind": "CiliumNetworkPolicy",
"metadata": {"name": f"zero-trust-{dst}", "namespace": "default"},
"spec": {
"endpointSelector": {"matchLabels": {"app": dst}},
"ingress": ingress_rules
}
}
policies.append(policy)
return policies
flows = get_hubble_flows("24h")
policies = generate_policies(flows)
for p in policies:
print(json.dumps(p, indent=2))
六、性能调优:生产级 Cilium 的关键配置
6.1 eBPF Map 大小调优
Cilium 的 eBPF Map 默认大小可能不适合大规模集群。关键参数:
# 查看当前 eBPF Map 使用情况
cilium bpf lb list
cilium bpf ct list global
# 查看容量和使用率
cilium bpf metrics
调优 Helm 参数:
# cilium-perf-values.yaml
cilium:
bpf:
ct:
tcpMax: 524288 # 默认 131072,大规模集群建议 524288+
udpMax: 131072 # 默认 8192
anyMax: 131072 # 默认 8192
nat:
max: 524288 # 默认 131072
lb:
max: 65536 # 默认 65536
neighbor:
max: 131072
autoDirectNodeRoutes: true
bandwidthManager:
enabled: true
bbr: true
cluster:
id: 1
name: default
enableLocalRedirectPolicy: true
routingMode: native
tunnelProtocol: vxlan
autoDetectNodeRoutes: true
6.2 连接追踪 GC 调优
连接追踪表是 Cilium 性能的关键瓶颈。如果 GC 不及时,表会填满,新连接会被丢弃:
cilium:
conntrackGCInterval: "30s"
conntrackGCMaxInterval: "300s"
bpf:
ct:
timeout:
tcpEstablished: "6h"
tcpClose: "10s"
tcpCloseWait: "30s"
udp: "60s"
icmp: "30s"
监控连接追踪表的健康状态:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cilium-ct-alerts
spec:
groups:
- name: cilium
rules:
- alert: CiliumConntrackTableNearFull
expr: cilium_ct_entries_global / cilium_ct_entries_global_max > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "Cilium 连接追踪表使用率超过 80%"
- alert: CiliumConntrackDropped
expr: rate(cilium_ct_dropped_total[5m]) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "Cilium 连接追踪表已满,新连接被丢弃"
6.3 CPU 和内存资源规划
| 集群规模 | Pod 数量 | Cilium Agent CPU | Cilium Agent Memory |
|---|---|---|---|
| 小型 | <500 | 100m | 256Mi |
| 中型 | 500-5000 | 200m | 512Mi |
| 大型 | 5000-20000 | 500m | 1Gi |
| 超大型 | >20000 | 1000m | 2Gi |
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 4Gi
operator:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
hubble:
relay:
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: "1"
memory: 2Gi
七、Cilium vs Istio:eBPF 替代 Sidecar 的未来
7.1 Sidecar 模式的痛点
Istio 的 Sidecar 模式(Envoy 代理)有一些无法回避的问题:
- 每个 Pod 多一个容器:额外消耗 CPU(每次请求经过两层代理)和内存(每个 Envoy 约 50-100MB)
- 延迟增加:请求从 Client → Envoy(出站)→ Envoy(入站)→ Server,多跳 2 次
- 运维复杂:Sidecar 注入、版本升级都需要重启 Pod
7.2 Cilium 的内核级 Service Mesh
Istio Sidecar 模式:
Client App → Client Envoy → Network → Server Envoy → Server App
(用户空间) (用户空间) (用户空间) (用户空间)
延迟增加:~2-5ms/跳
Cilium eBPF 模式:
Client App → eBPF(XDP/TC) → Network → eBPF(TC) → Server App
(用户空间) (内核空间) (内核空间) (用户空间)
延迟增加:~0.1ms
| 特性 | Istio Sidecar | Cilium eBPF |
|---|---|---|
| 代理位置 | 每个 Pod | 内核态 + Node 级 Envoy |
| 额外内存/Pod | ~100MB | ~0MB |
| 延迟开销 | 2-5ms | <0.1ms |
| L7 可观测性 | 需要代理 | eBPF 直接解析 |
| mTLS | Envoy 实现 | eBPF + WireGuard |
| 升级方式 | 重启 Pod | 热加载 BPF 程序 |
八、实战案例:从 iptables 迁移到 Cilium 的完整 Checklist
8.1 迁移前评估
# 1. 检查当前 kube-proxy 的 iptables 规则数量
iptables -L -n | wc -l
# 如果 > 10000 行,迁移收益最大
# 2. 检查内核版本(eBPF 需要 5.10+,推荐 5.15+)
uname -r
# 3. 检查内核 BTF 支持
ls /sys/kernel/btf/vmlinux
# 4. 基准测试:当前 Service 延迟
for i in $(seq 1 100); do
curl -o /dev/null -s -w '%{time_total}\n' http://my-service:8080/health
done | sort -n | awk '{a[NR]=$1} END{print "P50="a[int(NR*0.5)], "P95="a[int(NR*0.95)], "P99="a[int(NR*0.99)]}'
8.2 迁移步骤
# Step 1: 在混合模式下安装 Cilium
helm install cilium cilium/cilium \
--namespace kube-system \
--set kubeProxyReplacement=disabled \
--set hubble.enabled=true \
--set hubble.relay.enabled=true
# Step 2: 验证 Cilium 正常运行
cilium status
cilium connectivity test
# Step 3: 逐步切换到 strict 模式
cilium config set kube-proxy-replacement strict
# Step 4: 删除 kube-proxy DaemonSet
kubectl -n kube-system delete ds kube-proxy
# Step 5: 清理节点上的 iptables 规则(每个 Node)
iptables -F && iptables -X
iptables -t nat -F && iptables -t nat -X
iptables -t mangle -F && iptables -t mangle -X
# Step 6: 重启 Cilium
kubectl -n kube-system rollout restart ds/cilium
# Step 7: 验证所有 Service
kubectl get svc --all-namespaces
8.3 迁移后的性能对比
# 再次基准测试
for i in $(seq 1 1000); do
curl -o /dev/null -s -w '%{time_total}\n' http://my-service:8080/health
done | sort -n | awk '{a[NR]=$1} END{print "P50="a[int(NR*0.5)], "P95="a[int(NR*0.95)], "P99="a[int(NR*0.99)]}'
# 典型对比结果:
# 迁移前 (iptables): P50=1.2ms P95=3.5ms P99=8.2ms
# 迁移后 (Cilium): P50=0.6ms P95=1.8ms P99=4.1ms
# 提升:约50% 延迟降低
九、常见问题与排障指南
9.1 Pod 无法通信
cilium status
cilium endpoint list
cilium policy get <pod-name>
cilium bpf ct list global | grep <ip>
hubble observe --verdict DROPPED --last 10m
9.2 DNS 解析失败
cilium config | grep dns-proxy
hubble observe --protocol dns --verdict DROPPED
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot -- dig @<coredns-ip> kubernetes.default.svc.cluster.local
9.3 eBPF 程序加载失败
cilium version
cilium bpf config
cilium bpf lb list
cilium-dbg bpf compile --debug
十、总结与展望
eBPF + Cilium + Hubble 这套组合正在重新定义云原生的网络、安全和可观测性。回顾核心要点:
- eBPF 让你在内核态安全地运行自定义代码,验证器保证了安全性,JIT 编译保证了性能
- Cilium 用 eBPF 替换了 kube-proxy,Service 负载均衡从 O(n) 降到 O(1),XDP 实现 2000 万 PPS 级 DDoS 防护
- Hubble 提供零侵入可观测性,不改业务代码就能看到所有网络流量、DNS 查询、HTTP 请求
- CiliumNetworkPolicy 支持 L7 层规则,HTTP 级别的精细访问控制
- 渐进式零信任落地:先观察模式,再逐步收紧
展望未来,eBPF 在云原生领域还有更多想象空间:
- Cilium Service Mesh:用 eBPF 替代 Sidecar,消除性能开销
- eBPF 热升级:BPF 程序原子性替换,无需重启 Pod
- 跨集群可观测性:Hubble + ClusterMesh 多集群流量可视化
- AI 驱动的网络策略推荐:基于流量模式自动生成安全策略
如果你还在用 iptables + kube-proxy,是时候认真考虑迁移了。不为追新,而是当集群规模达到一定程度,iptables 的 O(n) 问题会变成凌晨被叫醒的主要原因。而 eBPF,是解决这个问题的正确方式。