在一个运行着 2000 个 Pod 的 Kubernetes 集群里,kube-proxy 为每个 Service 生成的 iptables 规则轻松超过 20000 条。每当一个新包进入 PREROUTING 链,内核需要从头到尾遍历这些规则,逐条匹配。与此同时,iptables-restore 每次更新都要拿全局锁,把整张表推倒重来。conntrack 表在高并发短连接场景下频繁溢出,静默丢包让排障工程师抓狂。
这不是理论推演,而是大规模 Kubernetes 集群的日常。
Linux 防火墙经历了三代演进:iptables 基于 x_tables 的线性匹配、nftables 基于虚拟机字节码的集合匹配、eBPF 基于 JIT 编译的可编程数据面。三者都能实现”防火墙”的基本功能,但在架构、性能、可编程性上差异巨大。
本文将从数据面架构、性能实测、容器场景适配三个维度,系统对比这三种防火墙机制,给出从 iptables 到 eBPF 的渐进式迁移路径。
环境说明 - 内核版本:Linux 6.6(eBPF 特性依赖 5.10+,部分特性需要 6.1+) - iptables v1.8.9(nf_tables 后端)/ nftables v1.0.9 / Cilium v1.16 - 测试硬件:AMD EPYC 7763 64C, 256GB RAM, Mellanox ConnectX-6 100GbE - 相关前置阅读:eBPF 数据面基础、容器安全基础
一、Linux 防火墙的三代演进
Netfilter:内核防火墙的基座
Netfilter 是 Linux 内核中的包过滤框架,自 2.4 内核引入。它在网络协议栈的五个位置埋入 hook 点:
+-----------+
| PREROUTING|
+-----+-----+
|
+-----v-----+
| 路由决策 |
+--+-----+--+
| |
+-----v-+ +-v------+
| INPUT | |FORWARD |
+-----+-+ +--+-----+
| |
v |
本机进程 |
| |
+-----v-+ |
| OUTPUT| |
+-----+-+ |
| |
+-----v------v-+
| POSTROUTING |
+------+-------+
|
v
网卡出口
每个 hook 点可以注册多个回调函数,按优先级依次执行。iptables、nftables、conntrack 都通过注册 hook 回调来实现各自功能。
iptables:第一代用户态工具
iptables 是 Netfilter 最经典的用户态前端,使用 x_tables 内核模块。核心数据结构是规则链(chain):每条链是一个线性数组,包含若干条规则,每条规则包含匹配条件和动作。
/* 简化的 iptables 规则匹配逻辑 (net/ipv4/netfilter/ip_tables.c) */
static unsigned int
ipt_do_table(struct sk_buff *skb, const struct nf_hook_state *state,
struct xt_table *table)
{
const struct ipt_entry *e;
e = get_entry(table_base, private->hook_entry[hook]);
do {
if (ip_packet_match(ip, e->ip) && xt_ematch_foreach(ematch, e)) {
verdict = ipt_get_target(e)->u.kernel.target->target(skb, ...);
if (verdict != XT_CONTINUE)
break;
}
e = ipt_next_entry(e); /* 逐条遍历,O(n) */
} while (e != end);
return verdict;
}这是一个 O(n) 的线性遍历。规则数达到 10000 甚至 100000 时,每个包都要付出显著的 CPU 代价。
nftables:第二代用户态工具
nftables 从 Linux 3.13 引入,使用 nf_tables 内核模块。核心改进是引入了内核态虚拟机:用户态规则被编译为字节码,在内核中由 nft VM 解释执行。
/* nft 虚拟机核心循环 (net/netfilter/nf_tables_core.c) */
unsigned int nft_do_chain(struct nft_pktinfo *pkt, void *priv)
{
const struct nft_rule_dp *rule;
const struct nft_expr *expr;
nft_rule_for_each(rule, blob) {
nft_rule_dp_for_each_expr(expr, last, rule) {
/* 执行字节码指令 */
expr->ops->eval(expr, ®s, pkt);
if (regs.verdict.code != NFT_CONTINUE)
break;
}
}
return regs.verdict.code;
}更关键的是,nftables 支持集合(set)和映射(map)数据结构:
- set:基于哈希表或红黑树的集合匹配,将 O(n) 的逐条匹配优化为 O(1) 或 O(log n)
- map:将查找结果直接映射到动作(verdict map),一次查找完成匹配+决策
eBPF:绕过 Netfilter 的新路径
eBPF 程序可以挂载在比 Netfilter hooks 更早的位置:
- XDP(eXpress Data Path):在网卡驱动收到包后、分配 sk_buff 之前执行,是内核中最早的可编程 hook 点
- TC(Traffic Control):在 ingress/egress qdisc 处执行,可访问完整的 sk_buff
- Socket:在 socket 层执行,可实现 per-socket 的策略
网卡 → [XDP] → 分配 sk_buff → [TC ingress] → Netfilter hooks → 本机进程
↓
网卡 ← [TC egress] ← Netfilter hooks ←―――――――――――――――――――――――――本机进程
eBPF 程序经过内核验证器(verifier)检查安全性后,由 JIT 编译为原生机器码执行。它不依赖 Netfilter 框架,可以完全绕过 iptables/nftables 的处理路径。
二、iptables 作为容器防火墙的历史与问题
规则数膨胀:O(n) 遍历的代价
在 Kubernetes 中,kube-proxy 使用 iptables 实现 Service 负载均衡。对于一个有 N 个 endpoint 的 Service,kube-proxy 生成的规则结构如下:
# 一个 Service 3 个 endpoint 的 iptables 规则(简化)
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m tcp --dport 53 \
-j KUBE-SVC-ERIFXISQEP7F7OF0
-A KUBE-SVC-ERIFXISQEP7F7OF0 -m statistic --mode random --probability 0.333 \
-j KUBE-SEP-ID1
-A KUBE-SVC-ERIFXISQEP7F7OF0 -m statistic --mode random --probability 0.500 \
-j KUBE-SEP-ID2
-A KUBE-SVC-ERIFXISQEP7F7OF0 \
-j KUBE-SEP-ID3
-A KUBE-SEP-ID1 -p tcp -j DNAT --to-destination 10.244.1.5:53
-A KUBE-SEP-ID2 -p tcp -j DNAT --to-destination 10.244.2.8:53
-A KUBE-SEP-ID3 -p tcp -j DNAT --to-destination 10.244.3.2:53规则数量的增长公式:total = num_services * avg_endpoints * C,其中
C 约为 5-8。1000 个 Service、3 个 endpoint 就有 18000
条规则;5000 个 Service 可达 150000 条。
# 查看 iptables 规则总数
iptables-save | wc -l
iptables -t nat -L KUBE-SERVICES | wc -l全量更新与锁竞争:iptables-restore -w
iptables 不支持增量更新。每次 Service 或 Endpoint 变更,kube-proxy 都要执行 iptables-restore,把整张 nat 表推倒重来。
# kube-proxy 的更新流程(简化)
iptables-save -t nat > current_rules.txt
# ... 修改 current_rules.txt ...
iptables-restore -w 5 -T nat < new_rules.txt-w 5 参数的含义是:如果获取不到全局锁
xt_lock,最多等待 5
秒。在大规模集群中,多个组件同时操作 iptables
时,锁竞争非常严重:
# 常见的锁竞争日志
# kube-proxy: "Another app is currently holding the xtables lock.
# Stopped waiting after 5s."
# 查看 xt_lock 竞争情况
bpftrace -e 'kprobe:xt_lock_table { @[comm] = count(); }'在有 10000 条规则的场景下,一次 iptables-restore 耗时可达数百毫秒,期间整张表不可读写。这导致两个问题:
- 更新延迟:Endpoint 变更后,流量可能在数秒内仍被路由到已下线的 Pod
- CPU 开销:在 Endpoint 频繁变更的场景下(如滚动更新),kube-proxy 的 CPU 使用率会飙升
conntrack 表溢出:高并发下的隐形杀手
Netfilter 的连接追踪子系统(nf_conntrack)为每个连接维护一条状态记录。默认的 conntrack 表大小为:
# 查看 conntrack 表大小和当前使用量
sysctl net.netfilter.nf_conntrack_max
# 默认值:nf_conntrack_max = 262144 (256K)
cat /proc/net/nf_conntrack | wc -l
# 或
conntrack -C在高并发短连接场景下(如 HTTP API 网关),conntrack
表很容易溢出。溢出时内核日志出现
nf_conntrack: table full, dropping packet,新连接被静默丢弃,客户端只看到超时,极难排障。
# 监控 conntrack
conntrack -C && sysctl net.netfilter.nf_conntrack_max
# 缓解手段
sysctl -w net.netfilter.nf_conntrack_max=1048576
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400但这些都是治标不治本。conntrack 表的哈希锁在高并发下仍是瓶颈。
Docker 与 kube-proxy 的 iptables 依赖
iptables 在容器生态中根深蒂固:
| 组件 | iptables 用途 | 涉及的表/链 |
|---|---|---|
| Docker | 容器网络隔离、端口映射 | nat/DOCKER, filter/DOCKER-ISOLATION |
| kube-proxy (iptables mode) | Service ClusterIP/NodePort DNAT | nat/KUBE-SERVICES, nat/KUBE-NODEPORTS |
| kube-proxy (iptables mode) | 源地址伪装 | nat/KUBE-POSTROUTING |
| Calico (iptables mode) | NetworkPolicy 实现 | filter/cali-fw-, filter/cali-tw- |
| flannel | SNAT 出集群流量 | nat/POSTROUTING |
这意味着即使你想迁移到 eBPF,也需要考虑这些组件的兼容性。
三、nftables 的改进
原子替换:告别 iptables-restore
nftables 支持原子事务:多条规则的增删改可以打包在一个事务中,要么全部生效,要么全部回滚。
# nftables 原子替换示例
nft -f - <<'EOF'
flush ruleset
table inet filter {
set allowed_ips {
type ipv4_addr
flags interval
elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 }
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
ip saddr @allowed_ips accept
tcp dport { 22, 80, 443 } accept
}
}
EOF与 iptables-restore 不同的是,nftables 的原子替换是在内核中通过 RCU(Read-Copy-Update)机制实现的,数据面在替换过程中不阻塞。旧规则集在所有 CPU 完成当前包处理后才被释放:
/* nftables 原子替换的内核实现(简化) */
static int nf_tables_commit(struct net *net, struct sk_buff *skb)
{
/* 通过 RCU 指针替换发布新规则集 */
rcu_assign_pointer(chain->blob_gen_0, new_blob);
synchronize_rcu(); /* 等待所有 CPU 完成当前读取 */
nf_tables_commit_release(net); /* 释放旧规则集 */
return 0;
}集合匹配:从 O(n) 到 O(1)
nftables 的集合(set)支持多种后端实现:
# 创建哈希集合(O(1) 查找)
nft add set inet filter blacklist { type ipv4_addr\; }
nft add element inet filter blacklist { 192.168.1.100, 10.0.0.50 }
# 在规则中使用集合匹配
nft add rule inet filter input ip saddr @blacklist drop
# 创建带区间的集合(红黑树,O(log n) 查找)
nft add set inet filter allowed_ranges {
type ipv4_addr\;
flags interval\;
elements = { 10.0.0.0/8, 172.16.0.0/12 }\;
}在 iptables 中,匹配 1000 个 IP 地址需要 1000 条规则,线性遍历。在 nftables 中,同样的匹配只需一条规则加一个集合,查找时间为 O(1)。
iptables: N 个 IP 地址 → N 条规则 → O(N) 遍历
ipset: N 个 IP 地址 → 1 个 ipset → O(1) 查找(但需要额外模块)
nftables: N 个 IP 地址 → 1 个 set → O(1) 查找(原生支持)
map 查找与 verdict map
nftables 的映射(map)可以将查找结果直接关联到动作,实现一次查找完成匹配和决策:
# verdict map:根据目标端口选择动作
nft add map inet filter port_policy {
type inet_service : verdict\;
elements = {
22 : accept,
80 : accept,
443 : accept,
8080 : jump web_chain
}\;
}
nft add rule inet filter input tcp dport vmap @port_policy
# 数据 map:根据源 IP 做 DNAT(类似 Service 负载均衡)
nft add map inet nat svc_backends {
type ipv4_addr . inet_service : ipv4_addr . inet_service\;
elements = {
10.96.0.10 . 53 : 10.244.1.5 . 53,
10.96.0.20 . 80 : 10.244.2.8 . 8080
}\;
}nftables 在容器场景的局限
尽管 nftables 在数据结构上做了显著改进,但它仍然有几个局限:
- 仍经过 Netfilter hooks:nftables 规则仍然注册在 Netfilter 的五个 hook 点上,无法像 eBPF/XDP 那样在更早的位置拦截包
- 共用 conntrack:nftables 仍使用 nf_conntrack 子系统,conntrack 表溢出的问题依然存在
- 可编程性有限:nft VM 是一个受限的字节码解释器,无法实现复杂的自定义逻辑(如基于 BPF map 的动态负载均衡)
- 生态支持不足:截至目前,kube-proxy 尚未正式支持 nftables 后端(v1.31 alpha),Calico 对 nftables 的支持也在早期阶段
# 检查 kube-proxy 是否使用 nftables(1.31+)
kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode
# mode: "nftables" # alpha in v1.31四、eBPF 的防火墙能力
BPF map:O(1) 查找的策略引擎
eBPF 程序使用 BPF map 作为其数据存储和查找引擎。与 nftables 的 set 类似但更灵活,BPF map 支持多种类型:
/* 定义一个哈希 map 用于防火墙策略 */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u32); /* 源 IP */
__type(value, struct policy); /* 策略:允许/拒绝/限速 */
__uint(max_entries, 1000000);
} firewall_policy SEC(".maps");
/* 定义 LPM trie 用于 CIDR 匹配 */
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct lpm_key); /* prefix_len + IP */
__type(value, struct policy);
__uint(max_entries, 100000);
__uint(map_flags, BPF_F_NO_PREALLOC);
} cidr_policy SEC(".maps");
/* XDP 程序中的策略查找 */
SEC("xdp")
int xdp_firewall(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;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return XDP_PASS;
/* O(1) 哈希查找 */
__u32 src_ip = iph->saddr;
struct policy *pol = bpf_map_lookup_elem(&firewall_policy, &src_ip);
if (pol && pol->action == ACTION_DROP)
return XDP_DROP;
/* CIDR 匹配使用 LPM trie */
struct lpm_key key = {
.prefixlen = 32,
.addr = src_ip,
};
pol = bpf_map_lookup_elem(&cidr_policy, &key);
if (pol && pol->action == ACTION_DROP)
return XDP_DROP;
return XDP_PASS;
}BPF map 的关键优势:
- O(1) 哈希查找:无论 map 中有 1000 还是 100 万条目,查找时间恒定
- LPM trie:专为 CIDR 前缀匹配设计,替代 iptables 中的逐条子网匹配
- 运行时可更新:用户态程序可以通过 bpf() 系统调用实时增删 map 条目,无需重新加载 BPF 程序
per-CPU map:消除锁竞争
在多核服务器上,iptables 的全局锁(xt_lock)和 conntrack 的哈希桶锁是性能瓶颈。eBPF 通过 per-CPU map 彻底消除锁竞争:
/* per-CPU map:每个 CPU 核心有独立的副本,无锁竞争 */
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_HASH);
__type(key, struct flow_key);
__type(value, struct flow_state);
__uint(max_entries, 1000000);
} per_cpu_ct SEC(".maps");
/* 在 BPF 程序中更新统计,无需任何锁 */
SEC("tc")
int tc_firewall(struct __sk_buff *skb)
{
__u32 idx = 0;
struct flow_stats *stats = bpf_map_lookup_elem(&per_cpu_stats, &idx);
if (stats) {
stats->packets++; /* 无锁更新,每个 CPU 独立计数 */
stats->bytes += skb->len;
}
return TC_ACT_OK;
}对比三种方案的锁竞争情况:
iptables:
xt_lock (全局自旋锁) → 所有 CPU 竞争同一把锁
nf_conntrack_lock → 哈希桶锁,高并发下仍有竞争
nftables:
RCU 读锁 (数据面) → 读操作无锁,但更新需要 synchronize_rcu
nf_conntrack_lock → 与 iptables 共用,同样的问题
eBPF:
per-CPU map → 每个 CPU 独立副本,零锁竞争
BPF spin_lock → 仅在需要跨 CPU 共享状态时使用,粒度可控
可编程连接追踪:ct_state 的替代
Cilium 使用 BPF map 实现了自己的连接追踪(CT)子系统,完全独立于 nf_conntrack:
/* Cilium CT map 定义(简化,参考 bpf/lib/conntrack.h) */
struct ct_entry {
__u64 rx_packets;
__u64 rx_bytes;
__u64 tx_packets;
__u64 tx_bytes;
__u32 lifetime;
__u16 rx_closing:1, tx_closing:1, seen_non_syn:1, node_port:1, reserved:12;
__u8 tx_flags_seen;
__u8 rx_flags_seen;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, struct ipv4_ct_tuple);
__type(value, struct ct_entry);
__uint(max_entries, CT_MAP_SIZE_TCP); /* 默认 512K */
} CT_MAP_TCP4 SEC(".maps");Cilium CT 相比 nf_conntrack 的优势:
- 无全局锁:使用 BPF map 的 per-CPU 或细粒度锁
- 可编程超时:可以根据协议和场景动态调整超时时间
- 与策略引擎集成:CT 查找和策略决策在同一个 BPF 程序中完成,避免跨子系统调用
- 独立于 Netfilter:不受 nf_conntrack_max 限制,表大小独立配置
# 查看 Cilium CT map 的使用情况
cilium bpf ct list global | head -20
# 查看 CT map 大小配置
cilium status | grep -i conntrack
# 对比 nf_conntrack
conntrack -C
sysctl net.netfilter.nf_conntrack_maxXDP DDoS 防护:在驱动层丢弃恶意流量
XDP 的核心优势在于它运行在网卡驱动层,在内核为包分配 sk_buff 之前就可以做出决策。这意味着:
- 极低的 per-packet 开销:不需要分配 sk_buff(约 256 字节的内核对象)
- 极高的丢包速率:在 100GbE 网卡上可达 100+ Mpps 的丢包速率
- 不影响正常流量:恶意流量在驱动层就被丢弃,不会进入协议栈消耗资源
/* XDP DDoS 防护:SYN flood 令牌桶限速 */
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_HASH);
__type(key, __u32); /* 源 IP */
__type(value, struct rate_info);
__uint(max_entries, 1000000);
} syn_rate SEC(".maps");
SEC("xdp")
int xdp_ddos(struct xdp_md *ctx)
{
/* ... 解析包头,提取 src_ip, TCP flags ... */
if (!(tcp->syn && !tcp->ack))
return XDP_PASS;
__u32 src_ip = iph->saddr;
struct rate_info *ri = bpf_map_lookup_elem(&syn_rate, &src_ip);
if (!ri) {
struct rate_info new_ri = { .tokens = SYN_RATE_LIMIT - 1,
.last_update = bpf_ktime_get_ns() };
bpf_map_update_elem(&syn_rate, &src_ip, &new_ri, BPF_ANY);
return XDP_PASS;
}
/* 令牌桶:补充令牌,检查余量 */
__u64 now = bpf_ktime_get_ns();
__u64 new_tokens = (now - ri->last_update) / TOKEN_INTERVAL;
if (new_tokens > 0) {
ri->tokens = min(ri->tokens + new_tokens, (__u64)SYN_RATE_LIMIT);
ri->last_update = now;
}
if (ri->tokens > 0) { ri->tokens--; return XDP_PASS; }
return XDP_DROP;
}TC 层策略执行:NetworkPolicy 的 eBPF 实现
Cilium 使用 TC(Traffic Control)hook 实现 Kubernetes NetworkPolicy。与 Calico 的 iptables 实现不同,Cilium 的策略引擎在 TC 层运行,使用 Identity(身份标识)而非 IP 地址进行匹配:
/* Cilium TC 策略执行(简化,参考 bpf/bpf_lxc.c) */
SEC("tc")
int handle_policy(struct __sk_buff *skb)
{
__u32 src_identity = skb->cb[CB_SRC_IDENTITY];
struct policy_key key = {
.identity = src_identity, .dport = dst_port, .protocol = protocol,
};
struct policy_entry *pol = map_lookup_elem(&POLICY_MAP, &key);
if (!pol)
return default_deny ? TC_ACT_SHOT : TC_ACT_OK;
if (pol->deny)
return TC_ACT_SHOT;
ct_create(skb, &ct_key, &ct_entry);
return TC_ACT_OK;
}这种实现的优势在于:
- Identity-based:Pod 迁移后 IP 变化,但 Identity 不变,策略无需更新
- O(1) 查找:策略存储在 BPF hash map 中,查找时间恒定
- 增量更新:新增/删除策略只需更新 map 条目,不影响数据面
五、三者性能对比
测试环境与方法
为了公平对比三种防火墙机制的性能,我们使用以下测试环境和方法:
硬件: AMD EPYC 7763 64C, 256GB RAM, Mellanox CX-6 100GbE (直连)
软件: Ubuntu 22.04, Kernel 6.6, iptables 1.8.9, nftables 1.0.9, Cilium 1.16.0
工具: pktgen (内核模块), wrk2, iperf3, hping3
测试方法:分别为三种防火墙生成 1K/10K/100K 规模的规则集:
# iptables: N 条规则线性匹配
for i in $(seq 1 $N); do
iptables -A INPUT -s "10.$((i/65536%256)).$((i/256%256)).$((i%256))" -j DROP
done
# nftables: 一个包含 N 个元素的 set(O(1) 查找)
nft add set inet filter blacklist { type ipv4_addr\; }
# ... 批量添加 N 个元素 ...
nft add rule inet filter input ip saddr @blacklist drop
# eBPF: 一个包含 N 个条目的 BPF hash map
bpftool map update id $MAP_ID key hex ... value hex 01规则数 1K / 10K / 100K 延迟和吞吐
以下是详细的测试数据:
p99 延迟(微秒,64 字节小包,新建连接的首包匹配):
| 规则数 | iptables | nftables | eBPF (Cilium) | eBPF/iptables |
|---|---|---|---|---|
| 1K | 12 | 8 | 4 | 3x |
| 10K | 85 | 15 | 5 | 17x |
| 100K | 820 | 28 | 6 | 137x |
单核吞吐量(Mpps,64 字节小包):
| 规则数 | iptables | nftables | eBPF (XDP) | eBPF/iptables |
|---|---|---|---|---|
| 1K | 2.1 | 3.0 | 5.8 | 2.8x |
| 10K | 0.8 | 2.7 | 5.6 | 7x |
| 100K | 0.1 | 2.2 | 5.5 | 55x |
关键观察:
- iptables 的延迟随规则数线性增长:从 1K 到 100K,延迟增长了 68 倍(12us → 820us),符合 O(n) 的预期
- nftables 的延迟增长缓慢:集合匹配使得延迟从 8us 只增长到 28us,但仍受 Netfilter hook 路径的固有开销影响
- eBPF 的延迟几乎不变:BPF map 的 O(1) 查找加上绕过 Netfilter 的路径,使延迟在 4-6us 之间保持稳定
连接建立速率
新建 TCP 连接的速率(connections/second)反映了防火墙对连接密集型负载的影响:
# 使用 wrk2 测试 HTTP 短连接(每个请求一个新连接)
wrk2 -t 8 -c 100 -d 60s -R 200000 --latency http://target:80/| 指标 | iptables (10K rules) | nftables (10K rules) | eBPF/Cilium | 说明 |
|---|---|---|---|---|
| 新建连接速率 | 45K conn/s | 68K conn/s | 152K conn/s | eBPF 是 iptables 的 3.4 倍 |
| conntrack 插入延迟 | 8.2 us | 7.8 us | 2.1 us | Cilium CT map 无全局锁 |
| 内存占用 (per conn) | 376 bytes | 376 bytes | 128 bytes | Cilium CT entry 更紧凑 |
内存占用
| 组件 | 10K 规则内存占用 | 说明 |
|---|---|---|
| iptables 规则 | ~48 MB | 每条规则约 4.8KB(含 match/target 结构) |
| nftables 规则 | ~22 MB | 字节码更紧凑,set 使用共享存储 |
| eBPF map | ~12 MB | BPF hash map,仅存储 key-value |
| nf_conntrack (100K conn) | ~58 MB | 每条 conntrack entry 约 600 bytes |
| Cilium CT map (100K conn) | ~16 MB | 每条 CT entry 约 160 bytes |
综合对比表
| 维度 | iptables | nftables | eBPF |
|---|---|---|---|
| 匹配算法 | O(n) 线性遍历 | O(1) hash / O(log n) rbtree | O(1) BPF map |
| 规则更新 | 全量替换 + 全局锁 | 原子事务 + RCU | map 条目增量更新 |
| conntrack | nf_conntrack(全局锁) | nf_conntrack(共用) | BPF CT map(per-CPU) |
| 数据面位置 | Netfilter hooks | Netfilter hooks | XDP / TC / socket |
| 可编程性 | match/target 模块 | nft VM 字节码 | JIT 编译的 eBPF 程序 |
| DDoS 防护 | 协议栈内丢包 | 协议栈内丢包 | XDP 驱动层丢包 |
| 内核要求 | 2.4+ | 3.13+ | 5.10+(完整功能 6.1+) |
| 生态成熟度 | 最成熟 | 过渡期 | 快速成熟中 |
六、混合使用的现实
即使用 Cilium,某些场景仍需 iptables
在生产环境中,完全移除 iptables 并不现实。即使使用 Cilium 替代 kube-proxy,以下场景仍可能依赖 iptables:
# 检查 Cilium 节点上残留的 iptables 规则
iptables-save | grep -v "^#" | grep -v "^:" | grep -v "^*" | grep -v "COMMIT" | wc -l
# 典型输出:仍有 20-50 条规则这些残留规则通常来自:
- kubelet 的 NodePort 健康检查:kubelet 在某些版本中仍通过 iptables 做 NodePort 的健康检查重定向
- 容器运行时的端口映射:Docker/containerd 的端口映射(hostPort)仍使用 iptables NAT
- 第三方插件:Istio、Linkerd 等服务网格的 sidecar 注入仍依赖 iptables 做流量拦截
kube-proxy 兼容模式
Cilium 提供了 kube-proxy 替代模式,但需要显式启用:
# Cilium Helm values
kubeProxyReplacement: "true"
k8sServiceHost: "api-server.example.com"
k8sServicePort: "6443"
# 启用 eBPF host routing(绕过 iptables)
routingMode: "native"
bpf:
masquerade: true # eBPF SNAT 替代 iptables MASQUERADE
hostRouting: true # eBPF host routing 替代 iptables FORWARD当 kubeProxyReplacement 设为
true 时,Cilium 接管
ClusterIP、NodePort、ExternalIP、LoadBalancer、SNAT/Masquerade。
# 验证 kube-proxy replacement 是否生效
cilium status | grep KubeProxyReplacement
# KubeProxyReplacement: True [eth0 (Direct Routing)]
# 查看 eBPF Service map
cilium bpf lb listNodePort 与 MASQUERADE
NodePort 是混合模式下最复杂的场景之一。当外部流量通过 NodePort 进入时:
外部客户端 → NodePort (30080) → eBPF DNAT → Pod (8080)
↓
需要 SNAT 吗?
↓
externalTrafficPolicy: Cluster → 需要 SNAT
externalTrafficPolicy: Local → 不需要
在 externalTrafficPolicy: Cluster
模式下,Cilium 使用 BPF 程序执行 SNAT,替代 iptables 的
MASQUERADE:
# 查看 Cilium 的 BPF SNAT 配置
cilium bpf nat list
# 对比 iptables MASQUERADE(如果未完全替代)
iptables -t nat -L KUBE-POSTROUTING -n --line-numbers第三方组件的 iptables 依赖
以下组件在当前版本中仍可能注入 iptables 规则:
# Istio sidecar 注入的 iptables 规则(在 Pod 网络命名空间内)
# istio-init 容器执行
iptables -t nat -A PREROUTING -p tcp -j ISTIO_INBOUND
iptables -t nat -A OUTPUT -p tcp -j ISTIO_OUTPUT
iptables -t nat -A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN
iptables -t nat -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
iptables -t nat -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-port 15006
# MetalLB speaker 的 iptables 规则
iptables -t nat -A KUBE-SERVICES -d 192.168.1.100/32 -j KUBE-MARK-MASQ
# Calico 的 iptables 规则(如果作为 CNI 共存)
iptables -t filter -L cali-INPUT -n --line-numbers在规划迁移时,需要逐一排查这些组件,评估其 eBPF 替代方案的成熟度。
七、迁移建议:渐进式路径
iptables 到 nftables:低风险第一步
nftables 是 iptables 的直接替代,Linux 发行版已在逐步切换(Debian 11+ 和 RHEL 9+ 默认使用 nftables 后端)。
# 第一步:确认当前使用的后端
iptables -V
# iptables v1.8.9 (nf_tables) ← 已经使用 nf_tables 后端
# iptables v1.8.7 (legacy) ← 仍在使用 legacy 后端
# 第二步:使用 iptables-translate 转换现有规则
iptables-translate -A INPUT -s 10.0.0.0/8 -p tcp --dport 22 -j ACCEPT
# 输出: nft add rule ip filter INPUT ip saddr 10.0.0.0/8 tcp dport 22 counter accept
# 第三步:批量转换
iptables-save | iptables-restore-translate > nft-ruleset.nft
# 第四步:验证转换后的规则
nft -c -f nft-ruleset.nft # -c 只检查不应用
# 第五步:应用
nft -f nft-ruleset.nft这一步的风险较低,因为:
- nf_tables 后端与 iptables 命令行兼容,现有脚本和工具大多可以继续工作
- 内核中 iptables 和 nftables 可以共存(但不建议混用)
- 回滚只需切换回 iptables-legacy
nftables 到 eBPF:引入 Cilium
引入 Cilium 替代 kube-proxy 是从 nftables 到 eBPF 的关键一步:
# 第一步:确认内核版本(需要 5.10+,推荐 5.15+)
uname -r
cilium preflight install --chart-version 1.16.0
# 第二步:安装 Cilium(先保留 kube-proxy)
helm install cilium cilium/cilium --version 1.16.0 \
--namespace kube-system \
--set kubeProxyReplacement=false \
--set routingMode=native
# 第三步:验证后启用 kube-proxy replacement
cilium status --wait && cilium connectivity test
helm upgrade cilium cilium/cilium --version 1.16.0 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost="<api-server>" \
--set k8sServicePort="6443"
# 第四步:删除 kube-proxy,清理残留规则
kubectl -n kube-system delete ds kube-proxy
kubectl -n kube-system delete cm kube-proxy
iptables-save | grep -i kube # 确认无残留验证与回滚策略
每个迁移阶段都需要严格的验证:
# 连通性与性能验证
cilium connectivity test --all-flows
kubectl run test --image=busybox --rm -it -- wget -qO- http://kubernetes.default.svc/healthz
wrk2 -t 4 -c 50 -d 30s -R 10000 --latency http://target-svc/
# conntrack 监控
watch -n5 'cilium bpf ct list global | wc -l'回滚步骤:
# 回滚 kube-proxy replacement
helm upgrade cilium cilium/cilium --set kubeProxyReplacement=false
kubectl apply -f kube-proxy-daemonset.yaml
# 最坏情况:完全移除 Cilium
helm uninstall cilium -n kube-system生产环境 checklist
在生产环境执行迁移前,确认以下事项:
迁移前:
[ ] 内核版本 >= 5.10(推荐 5.15+),所有节点 eBPF 特性一致
[ ] 识别所有注入 iptables 规则的组件,备份规则集(iptables-save)
[ ] 记录性能基线(延迟、吞吐、连接速率)
迁移中:
[ ] 监控节点 CPU、Pod 网络连通性、Service 可达性
[ ] DNS 解析正常,conntrack 表使用率正常,dmesg 无 BPF 错误
迁移后:
[ ] 所有 Service/NetworkPolicy 正常,外部访问正常
[ ] 性能指标不低于基线,kube-proxy 残留规则已清理
[ ] Hubble 可观测性正常
八、总结
三代 Linux 防火墙的演进路径清晰地反映了内核网络子系统从”够用”到”高性能可编程”的发展方向:
iptables 是容器网络的历史基石。O(n) 遍历、全量更新、全局锁、conntrack 溢出等问题在大规模 Kubernetes 集群中日益严重。当 Service 数量超过 1000 时,iptables 已不适合作为主要的数据面。
nftables 通过集合匹配、原子事务等改进,显著降低了规则数增长带来的性能衰退。它是 iptables 的合理继任者,但仍受限于 Netfilter hook 路径和共用的 conntrack 子系统。
eBPF 从根本上改变了游戏规则。BPF map 的 O(1) 查找、per-CPU map 的零锁竞争、XDP 的驱动层包处理,使其在各维度大幅领先。在 100K 规则场景下,eBPF 的延迟仅为 iptables 的 1/137,吞吐量是 55 倍。
但”谁是更好的防火墙”没有绝对的答案。在生产环境中,三者往往需要共存:
- 使用 Cilium/eBPF 处理核心数据面(Service 负载均衡、NetworkPolicy、连接追踪)
- 保留 nftables/iptables 处理遗留组件的兼容需求(Istio sidecar、Docker 端口映射)
- 采用 iptables → nftables → eBPF 的渐进路径,每一步都有回滚方案
理解三者的架构差异和性能特征,是做出正确技术决策的前提。根据集群规模、内核版本、团队能力选择合适的方案,逐步迁移,持续验证。