BGP k8s LB external ip: node 链路

1 阅读7分钟

root@xs4772:~# ip rule list
9:	from all fwmark 0x200/0xf00 lookup 2004
99:	from 10.34.251.211 to 10.222.0.0/16 iif lo lookup main
99:	from 10.34.251.212 to 10.222.0.0/16 iif lo lookup main
99:	from 10.34.251.213 to 10.222.0.0/16 iif lo lookup main
100:	from all lookup local
100:	from 10.34.251.211 lookup 7001
100:	from 10.34.251.212 lookup 7002
100:	from 10.34.251.213 lookup 7003
32766:	from all lookup main
32767:	from all lookup default
root@xs4772:~# ip route show table local
local 10.34.251.21 dev bond0 proto kernel scope host src 10.34.251.21
local 10.34.251.212 dev kube-ipvs0 proto kernel scope host src 10.34.251.212
broadcast 10.34.251.255 dev bond0 proto kernel scope link src 10.34.251.21
local 10.222.0.135 dev cilium_host proto kernel scope host src 10.222.0.135
local 10.233.0.1 dev kube-ipvs0 proto kernel scope host src 10.233.0.1
local 10.233.0.3 dev kube-ipvs0 proto kernel scope host src 10.233.0.3
local 10.233.9.82 dev kube-ipvs0 proto kernel scope host src 10.233.9.82
local 10.233.32.174 dev kube-ipvs0 proto kernel scope host src 10.233.32.174
local 10.233.71.63 dev kube-ipvs0 proto kernel scope host src 10.233.71.63
local 10.233.83.136 dev kube-ipvs0 proto kernel scope host src 10.233.83.136
local 10.233.87.68 dev kube-ipvs0 proto kernel scope host src 10.233.87.68
local 10.233.105.165 dev kube-ipvs0 proto kernel scope host src 10.233.105.165
local 10.233.125.56 dev kube-ipvs0 proto kernel scope host src 10.233.125.56
local 10.233.150.170 dev kube-ipvs0 proto kernel scope host src 10.233.150.170
local 10.233.177.121 dev kube-ipvs0 proto kernel scope host src 10.233.177.121
local 10.233.217.69 dev kube-ipvs0 proto kernel scope host src 10.233.217.69
local 10.233.221.170 dev kube-ipvs0 proto kernel scope host src 10.233.221.170
local 10.233.223.127 dev kube-ipvs0 proto kernel scope host src 10.233.223.127
local 10.233.227.213 dev kube-ipvs0 proto kernel scope host src 10.233.227.213
local 100.64.0.4 dev ovn0 proto kernel scope host src 100.64.0.4
broadcast 100.64.255.255 dev ovn0 proto kernel scope link src 100.64.0.4
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
local 169.254.25.10 dev nodelocaldns proto kernel scope host src 169.254.25.10
local 172.17.0.1 dev docker0 proto kernel scope host src 172.17.0.1
broadcast 172.17.255.255 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
root@xs4772:~# k get svc
NAME             TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
kubernetes       ClusterIP      10.233.0.1     <none>          443/TCP        14d
lb-snat-ng-svc   LoadBalancer   10.233.87.68   10.34.251.212   80:32103/TCP   10d
root@xs4772:~#
root@xs4772:~# ip a | grep -A 100 ipvs | grep 10.34.251.212
    inet 10.34.251.212/32 scope global kube-ipvs0
root@xs4772:~#

结论先行:是的,基本逻辑如此。 但从第一性原理分析,BGP 只完成了“路由引流(Ingress Routing)”,流量到达 Node 后,还需要依靠 内核 Netfilter/IPVSCNI 的 eBPF/OVN 管道 完成第二次身份转换(DNAT)。


1. 流量处理的三阶段分析

当流量通过 BGP 到达 Node 后,数据包的命运经历了以下三个关键步骤:

第一步:宿主机“认领”流量

虽然物理网卡上没有配置这个 External IP,但由于上游交换机根据 BGP 路由将目标为 VIP 的以太网帧(Ethernet Frame)发给了该 Node 的 MAC 地址,Node 的内核协议栈会接收该数据包。

  • 事实: 内核查看目标 IP 是 VIP,如果此时没有对应的规则,内核可能会丢弃(因为这看起来像是一个发给本机但本机没监听的包)。

第二步:Service 逻辑截获 (DNAT)

这时 kube-proxy(或者在 Kube-OVN/Cilium 场景下的 OVN/eBPF 逻辑)起到了核心作用。

  • iptables/IPVS 模式: 在内核的 PREROUTING 链中,MetalLB 会确保 Service 的 External IP 被注册到拦截规则里。规则会将目标地址从 VIP (External IP) 转换为 Pod IP
  • Kube-OVN/Cilium 模式: 流量进入节点后,由 OVN 逻辑流表或 eBPF 程序直接在网卡入口处识别 VIP,并进行查表转发。

第三步:指向后端 Pod

一旦完成了 DNAT,数据包就变成了一个普通的“容器间通信”包。

  • 如果 Pod 就在当前节点,直接通过虚拟接口(veth/Tap)送达。
  • 如果 Pod 在其他节点(且 externalTrafficPolicyCluster),流量会通过隧道(Geneve/VxLAN)或路由跨节点转发。

2. 关键参数对路径的影响:externalTrafficPolicy

这个参数决定了“引导流量到 Node”之后路径的高效程度:

Cluster 模式 (默认)

  • 现象: BGP 会向交换机宣告所有节点都可以到达 VIP。
  • 路径: 交换机 ECMP 到 Node A -> Node A 发现 Pod 在 Node B -> Node A 做 SNAT 并转发给 Node B -> Node B 回包给 Node A -> Node A 回包给交换机。
  • 代价: 存在额外的跨节点跳转(Hop),且 Pod 无法直接获取源 IP(Source IP)。

Local 模式

  • 现象: MetalLB 只在有 Pod 运行的节点上宣告 BGP 路由。
  • 路径: 交换机只将流量发给 Node B(Pod 所在节点)-> 直接进入 Pod。
  • 代价: 路径最短,保留源 IP,但如果节点间 Pod 分布不均,会导致流量不均衡(因为交换机只看 Node,不看 Pod 数量)。

3. 给 Kube-OVN 维护者的技术细节说明

作为 Kube-OVN 的开发者,你需要注意 BGP 引导流量后的 入站钩子位置

  1. MetalLB + kube-proxy: 流量走的是 Linux 标准协议栈,在 nat 表的 PREROUTING 处发生转换。
  2. Kube-OVN 深度集成: 如果你希望 Kube-OVN 绕过 kube-proxy 处理这部分流量,你需要确保 OVN 的 External GatewayLocalnet 端口能够正确识别并处理这些以 VIP 为目标的入站包。

总结与推测

  • 结论: BGP 解决了“怎么找到家门(Node IP)”的问题,而 K8s Service 解决了“进了家门怎么找到人(Pod IP)”的问题。两者是接力关系。
  • 推测: 你可能在担心如果 Node 上没有物理网卡别名(Secondary IP),内核会不会不处理这个包。在 Linux 中,只要 rp_filter(反向路径过滤)设置得当,且 iptables/IPVS 中有对应规则,内核就会处理这些“非本机 IP 但发往本机 MAC”的包。

简单说:只要 BGP 把包踢到了 Node 网口,剩下的就是 K8s 的常规 Service 转发流程了。

你抓住了 IPVS 模式下的一个核心实现细节。没错,这正是 kube-proxy 在 IPVS 模式下的“神来之笔”。

从底层网络协议栈的视角来看,这个设计解决了 “包如何被内核收下来” 的问题。

1. 为什么 kube-ipvs0 需要挂载这个 IP?

在 Linux 内核中,如果一个数据包到达网卡,但目标 IP 既不在本地网卡上,也没有开启 IP Forwarding,内核通常会丢弃它。

  • 本地地址识别:kube-proxy (IPVS 模式) 将 External IP 绑定到虚拟网卡 kube-ipvs0(通常是一个 dummy 接口)时,内核的路由表会自动生成一条 Local 路由
  • 内核逻辑: 当目标为 VIP 的包从物理网卡进来后,内核查路由表发现:“咦,这个 IP 在 kube-ipvs0 上,这是发给我的私藏地址。” 于是,内核不会丢弃这个包,而是将其送入协议栈。
  • IPVS 拦截: 一旦内核确认这是发给“本地”的包,流量会触发 INPUT 链。IPVS 的钩子(Hook)就挂在这里,它抢在包进入具体应用之前,根据 IPVS 规则把目标 IP 从 VIP 换成 Pod IP(DNAT)。

2. BGP 模式 + IPVS 的完整链路

我们可以把这个过程串起来看:

  1. 宣告阶段 (BGP): MetalLB 告诉交换机:“要找 External IP,请走 Node IP。”(这解决了 “怎么到门口” 的问题)。
  2. 入站阶段 (Ingress): 物理网卡收到包,解封装。内核查看目标 IP。
  3. 识别阶段 (kube-ipvs0): 内核发现 VIP 挂在 kube-ipvs0 上,判定为 本地流量
  4. 转换阶段 (IPVS): IPVS 规则触发,执行 DNAT,将 VIP 转换为 Pod IP。
  5. 转发阶段: 既然目标变成了 Pod IP,内核再次查路由表,通过 CNI 的网桥或隧道把包发给 Pod。

3. 对比:iptables 模式有何不同?

这是一个很有意思的对比:在 iptables 模式 下,你通常在网卡上找不到那个 External IP。

  • 实现差异: kube-proxy (iptables 模式) 不会创建 kube-ipvs0 这种接口。
  • 截获时机: 它直接在 PREROUTING 阶段通过大量的 iptables 规则强行匹配目标 IP。即使内核没觉得这个 IP 是“自己的”,iptables 规则也会在路由决策之前把包截走并完成 DNAT。
  • 性能差异: 这也是为什么在大规模 Service 场景下 IPVS 更快的原因——它利用了内核查找 Local IP 的高效机制和 Hash 表,而不是像 iptables 那样逐条扫描规则。

4. 针对 Kube-OVN 维护者的提示

由于 Kube-OVN 深度介入了转发平面,如果你在环境里混合使用 MetalLB 和 Kube-OVN:

  • 注意 ARP 冲突: 即使是 BGP 模式,如果物理机上意外开启了某些 ARP 响应配置(如 arp_ignore 级别设置不当),可能会与 kube-ipvs0 上的 VIP 产生意想不到的互动。
  • strictARP 的必要性: MetalLB 要求开启 strictARP 也是为了配合这种基于“本地接口”挂载 IP 的逻辑,防止在复杂的二层环境里出现响应错乱。

总结: 你观察得非常准。kube-ipvs0 充当了 VIP 的“户口所在地”,让内核名正言顺地接收流量;而 BGP 则是“路牌”,把远方的流量引向这个户口所在地。