在当今的 Kubernetes 生产实践中,Cilium(eBPF CNI) 与 Istio(Envoy Service Mesh) 的强强联合已成为高性能云原生架构的标配。然而,这种双重数据面架构也引入了极高的复杂度。
当一个外部客户端通过 NodePort 访问集群内的服务时,流量在内核态与用户态之间频繁穿梭。本文将深度剖析:NodePort 流量是如何在主机的 eBPF 层面完成初次拦截与路由,又是如何安全、高效地递交给 Pod 内的 Envoy Sidecar 的?
流量鸟瞰图:从物理网卡到应用进程
在展开细节之前,我们先梳理一条清晰的流量链路(假设 Pod 部署在当前接收到 NodePort 流量的节点上):
- 主机物理网卡接收 $\rightarrow$ Cilium
tc/XDPeBPF 程序拦截。 - eBPF 查表 DNAT $\rightarrow$ 转换
NodeIP:NodePort为PodIP:TargetPort。 - 内核跨命名空间路由 $\rightarrow$ 通过
veth pair或ipvlan将报文送入 Pod 网络命名空间。 - Pod 命名空间劫持 $\rightarrow$ 传统模式(
iptables)或加速模式(Ciliumsockops)将流量重定向至 Envoy 的15006监听端口。 - Envoy 转发 $\rightarrow$ Envoy 经过 L7 规则处理后,将流量投递给本地的应用容器。
第一阶段:主机网卡处的 eBPF 拦截与 DNAT
传统的 Kubernetes 节点使用 kube-proxy 的 iptables 或 IPVS 规则来处理 NodePort。但在 Cilium 接管的集群中,kube-proxy 通常会被完全替换(Kube-Proxy Free 模式)。
[ 外部流量 ] -> [ 主机网卡 eth0 ]
│
(tc ingress BPF) ──> 查找 cilium_lb4_services
│
[ eBPF 核心态 DNAT ] (修改 DstIP=PodIP, DstPort=AppPort)
│
(bpf_redirect_peer)
│
▼
[ Pod veth (lxcxx) ]
1. eBPF 挂载点(Hook Point)
当 NodePort 报文到达物理网卡(如 eth0)时,挂载在 tc(Traffic Control)ingress 阶段(或 XDP,如果开启了 XDP 加速)的 Cilium eBPF 程序(通常是 from-netdev)会率先捕获该报文。
2. BPF Map 检索与 DNAT
eBPF 程序会提取报文的五元组信息,并检索 Cilium 维护的 BPF 映射表:
cilium_lb4_services:存储 Service 的前端配置(例如NodeIP:NodePort)。cilium_lb4_backends:存储后端的 Endpoint 列表(即真实的PodIP:TargetPort)。
一旦匹配成功,eBPF 直接在内核态修改 IP 首部和 TCP/UDP 首部,进行 DNAT 转换:
- 目标 IP 变更为:
PodIP - 目标端口 变更为:
ContainerPort(即容器声明的监听端口,并非 Envoy 的 15006) - 同时计算并更新 IP 和 TCP 校验和。
3. 直接重定向(Direct Routing)
传统网络栈需要经过繁琐的路由表查询。Cilium 此时会利用 bpf_redirect 或 bpf_redirect_peer 辅助函数,避开主机的协议栈,将报文直接推送到连接该 Pod 的虚拟网卡对(lxcxxxx)的主机端。
第二阶段:进入 Pod 命名空间
当报文跨越 veth pair 边界,从主机的 lxcxxxx 进入到 Pod 内部的 eth0(Pod 内部网卡)时,报文的特征为:
- 源 IP / 端口:外部客户端 IP / 客户端随机端口(若未开启 SNAT)
- 目的 IP / 端口:
PodIP:TargetPort(例如10.244.1.45:8080)
此时,流量正式移交给 Pod 的网络命名空间。根据集群的配置不同,接下来的“劫持”动作分两种路径进行。
第三阶段:Pod 内的流量劫持与 Envoy 接管
这是 Cilium 与 Istio 交接的核心地带。
路径 A:标准 Istio 模式(基于 iptables 劫持)
即使主机侧使用了 eBPF 替代了 kube-proxy,在 Pod 内部,Istio 默认依然使用 istio-init 容器注入的 iptables(或 nftables)规则。
[ Pod eth0 ]
│
▼
[ TCP/IP 协议栈 ]
│
(PREROUTING 链) ──> 命中 ISTIO_INBOUND 规则
│
▼
[ REDIRECT / TPROXY ] ──> 强制将 DstPort 改为 15006
│
▼
[ Envoy (15006) ] ──> (L7 策略处理) ──> 发送给本地 App 进程 (8080)
- 协议栈上送:内核协议栈开始在 Pod 命名空间内解析此 IP 报文。
- 命中 Netfilter 规则:
- 报文触发
PREROUTING链。 - Istio 预设的
ISTIO_INBOUND规则链拦截目的端口为8080(TargetPort)的 TCPSYN包。 - 通过
REDIRECT或TPROXY动作,将该报文的目标端口强行重定向到本地的15006端口(Envoy 的 Inbound 监听器)。
- 报文触发
- Envoy 接收连接:
- Envoy 的
virtualInbound监听器在15006端口接收到连接。 - 通过 Linux
getsockopt(SOL_IP, SO_ORIGINAL_DST, ...)系统调用,Envoy 能够读取到被iptables修改前的原始目标地址——即PodIP:8080。 - Envoy 匹配对应的 Filter Chain,执行 mTLS 认证、L7 路由、限流等策略。
- Envoy 的
路径 B:Cilium Sockops 加速模式(免 iptables 劫持)
如果集群开启了 Cilium Socket Layer Enforcement(即 sockops 优化),那么整个劫持过程将发生质的飞跃:丢弃 iptables,在 Socket 层直接握手。
[ Pod eth0 ]
│
▼
[ TCP 握手 (SYN) ] ──> 触发 eBPF sockops (cgroup)
│
┌──────────────────┴──────────────────┐
▼ ▼
识别目标为 PodIP:8080 直接修改 Socket 关联关系
│ │
└─────────────────────────────────────┘
│
▼
[ Envoy (15006) Socket ]
- Cgroup 级 eBPF 挂载:
Cilium 将 eBPF 程序(如bpf_sockmap)挂载到 Pod 所在的 cgroup。这意味着该 Pod 内所有进程的 Socket 创建、连接建立等系统调用都会被 BPF 拦截。 - Socket 映射表(
sockmap):
当外部流量到达,内核准备为新连接建立struct sock时,eBPF 程序触发。 - 四层直接重定向:
- eBPF 判断此入站连接的目标是本 Pod 的服务端口(
8080)。 - eBPF 直接修改内核 Socket 的对端关联,将原本指向
App:8080的连接,在套接字层直接重定向(Redirect)到正在监听15006的 Envoy Socket。 - 优势:此过程完全绕过了 TCP/IP 协议栈中复杂的路由、Netfilter 过滤和多次内存拷贝。数据包在内核中通过
sk_msg直接从网卡接收缓冲区复制到 Envoy 的接收缓冲区。
- eBPF 判断此入站连接的目标是本 Pod 的服务端口(
第四阶段:Envoy 到真实应用(Local Loopback)
当 Envoy 处理完 L7 逻辑后,需要将流量发送给真正的应用进程(如容器内的 Go/Java 进程,监听 127.0.0.1:8080)。
[ Envoy (Client Sock) ] ──(bpf_msg_redirect)──> [ App (Server Sock) ]
▲
│
(绕过本地环回 TCP/IP 栈)
- 传统路径: Envoy 发起一个到
127.0.0.1:8080的新 TCP 连接,数据包通过本地环回网卡(lo)进行一次完整的 TCP/IP 封装和解封装。 - Cilium Sockops 优化路径:
- Cilium 的
sockops已经将 Envoy 的客户端套接字与应用程序的监听套接字记录在了同一张 BPF Map 中。 - 当 Envoy 向 App 发送数据(调用
sendmsg)时,eBPF 程序bpf_msg_redirect拦截此调用,直接将数据放入 App 套接字的接收队列中。 - 彻底免除了
lo接口的 CPU 消耗和网络延迟。
- Cilium 的
深度比对:两种交接模式的性能与可观测性损耗
| 维度 | 路径 A:标准 Istio(iptables) | 路径 B:Cilium eBPF Sockops 加速 |
|---|---|---|
| 内核态/用户态上下文切换 | 高(多次经历 TCP/IP 协议栈及 Netfilter) | 极低(Socket 层直接映射,短路协议栈) |
| CPU 消耗 | 较高(在大流量下,iptables 规则链匹配尤为明显) | 极低 |
| 排障工具表现 | tcpdump -i any 能清晰看到重定向过程 |
tcpdump 可能看不到部分被 eBPF bypass 的本地流量 |
| 诊断手段 | iptables-save, netstat |
cilium monitor, bpftool map dump |
| 配置复杂度 | 默认支持,开箱即用 | 依赖特定内核版本(建议 $\ge 5.4$),需调整 Cilium 配置 |
避坑指南与排障建议
在混合部署 Cilium 与 Istio 的环境中,定位 NodePort 链路问题时,请遵循以下路径:
1. 确认主机层 DNAT 是否成功
在 Node 节点上执行,查看 Cilium 的负载均衡 BPF Map:
cilium bpf lb list
检查是否存在你所访问的 NodeIP:NodePort 映射到 PodIP:TargetPort 的条目。如果这里缺失,说明 Cilium 未正确感知 Service 或 Endpoint。
2. 区分流量是被 Cilium 丢弃还是被 Envoy 拒绝
使用 cilium monitor 工具捕获丢包:
cilium monitor --type drop
如果未发现 Drop 记录,但外部访问返回 503 或连接重置,通常说明流量已安全到达 Pod,问题出在 Envoy 的路由配置(VirtualService)或 mTLS 证书校验失败上。
3. 注意 MTU 引起的“诡异”丢包
Cilium 结合 Istio 时,如果开启了 VxLAN 隧道模式,额外的头部开销会导致有效 MTU 减小。若 NodePort 传输大包时发生连接卡死,尝试调整:
# Cilium ConfigMap
mtu: "1450" # 针对 VxLAN 环境,默认 1500 可能会导致 IP 分片或丢包