TLDR:分享一些使用RouterOS/Fortigate和Cilium CNI之间使用BGP宣告K8S集群IP的过程
一直对BGP协议抱有一种神秘感,觉得那是大型网络才需要的东西,于是一直没有去学习落地在Homelab。但实际操作下来其实没有那么复杂,在这分享一些有趣的试错过程,给同样对网络不精通但是热爱探索的伙伴一些参考。
起因
之前K8S用的是Flannel CNI + MetaLB Layer2模式通过ARP宣告Service的External IP地址来提供LoadBalancer IPAM和服务暴露。不过这样会产生一个问题,speaker响应的ARP请求似乎并不总能回复正确的节点ip,因此在配置Service的externalTrafficPolicy为Local时不确保能访问到服务,只有设置为Cluster,经过kube-proxy的转发后才能到达目标节点。看起来服务能够正常访问了,但是当应用通过X-Forwarded-For获取客户端IP地址的时候,会发现获取到的是k8s集群内部ip。并且,ingress nginx的源ip白名单策略也会因此失效。
于是这次用Cilium替代了之前的Flannel+MetaLB,尽管MetaLB也支持BGP模式,Cilium额外的功能还是吸引我决定使用Cilium。
配置
BGP的配置比想象中的简单,创建BGP实例、添加Peer、选择通告的路由,如果只是使用BGP基础的功能的话,仅此而已。
Cilium侧
根据官方的Example,依次创建CiliumBGPClusterConfig、CiliumBGPPeerConfig、CiliumBGPAdvertisement资源,之间的关系见官方文档)。如果需要对节点的bgp实例单独配置参数的话需要CiliumBGPNodeConfigOverride,需要启用MD5密码校验的话需要创建所引用的Secret。
BGP会话建立后可以用cilium cli查看peer情况和通告的路由。如果参数配置有误BGP会话不停重置或者无法建立可以查看cilium的pod日志查看原因。
Cilium可以选择通告PodCIDR、ClusterIP、ExternalIP和LoadBalancerIP,就像aws和oci的k8s服务一样,我也选择通告了PodCIDR到我的物理网络中方便测试。以及LoadBalancerIP,另外两项似乎暂时没有用武之地。
RouterOS侧
RouterOS支持的功能一如既往得全面,自然少不了BGP协议,就是winbox的界面逻辑一直很神奇。ROS这边只要在Routing/BGP里创建Connection即可。
一条Connection就是一个Peer。Template变得可有可无,因为修改完Template后Connection里的配置不会自动修改,反而不如直接修改Connection。
会话建立后可以看到对端的参数信息
路由表里会显示接受到的BGP路由
Fortigate侧
飞塔这边,BGP配置页面显示的功能有限,要修改详细的参数还是要用命令行。
配置方法大同小异,主要也是本端信息对端信息,会话建立后能够看到路由表里接受到的路由。
Loadbalancer IPAM
Cilium和MetaLB都可以像云平台的SLB一样作为K8S Loadbalancer provider提供外部IP地址管理的功能,首先需要创建CiliumLoadBalancerIPPool资源来提供IP池,Cilium支持创建多个池并且通过Service的Label来决定使用那个池。
接下来就正常创建Loadbalancer类型的Service即可。
一点观察
当externalTrafficPolicy为Local时,BGP会通告pod所在节点的IP地址,当参数为Cluster时BGP会通告所有节点ip,并且通过集群网络转发到达pod。但是要确保BGP Peer正确配置了Multipath,不然BGP Peer默认会选出一条最佳路径。
一点小问题
最初的测试拓补用运行ROS的交换机和Cilium进行BGP Peer,ROS上在vlan200接口上起了一个ip地址用来和Cilium Peer,网关所在的Fortigate再写了条静态路由将BGP通告的CIDR指给ROS。家里的客户端访问一切正常,而在外使用Wireguard连回家时发现和10.1.2.0/24下的主机不通。
检查后发现由于在ROS上起了一个vlan200的接口地址,系统会生成一条distance为0的直连路由,而Wireguard的隧道也建在ROS上,但是vlan200下主机的默认网关在FortiGate,导致Wireguard隧道的数据包到ROS后去往10.1.2.0/24的路由是错误的。
解决起来倒是不复杂,使用FortiGate建立BGP会话即可,ROS单纯做一个交换机,也不用再麻烦到FortiGate创建静态路由,事实上最终也是这么做的。
好奇心驱使我思考假如只能和交换机进行BGP Peer,且vlan200网关又不在交换机上的场景下,要如何配置。
Multihop
既然问题出在vlan200接口地址的直连路由,路由优先级又无法降低,第一想法是取消这个接口地址,Cilium和交换机的管理地址10.1.254.1建立BGP会话。修改Peer IP地址后发现会话在不停重置,Cilium日志里提示read-failed.
time="2025-04-21T03:13:40.604474556Z" level=info msg="Peer Up" Key=10.1.254.1 State=BGP_FSM_OPENCONFIRM Topic=Peer asn=65001 component=gobgp.BgpServerInstance subsys=bgp-control-plane
time="2025-04-21T03:13:50.38469061Z" level=info msg="Peer Down" Key=10.1.254.1 Reason=read-failed State=BGP_FSM_ESTABLISHED Topic=Peer asn=65001 component=gobgp.BgpServerInstance subsys=bgp-control-plane
这里涉及到Multihop参数的配置,除了Cilium侧要正确配置ebgpMultihop的跳数以外,ROS侧似乎只需要钩上Multihop无需具体配置Tx/Rx TTL,配置了反而会连接不上。
策略路由
使用ROS管理地址建立BGP会话之后,虽然能够收到路由了,但是客户端仍旧无法和Cilium通告的IP通讯。思考原因客户端通过网关的下一跳来到ROS之后,由于ROS没有vlan200下的地址,还是无法和k8s node进行通讯,仍旧需要创建一个vlan200的接口地址,问题又回到了原点。
看来只能想办法绕开直连路由把10.1.2.0/24指向网关才行。这里又引向了另一个神秘的未知领域,策略路由。
在这个场景下,策略路由可以根据流量的特征,在mangle表标记,应用到特定的路由表,绕开默认路由表里的接口直连路由。
首先需要创建一张新的路由表,然后写一条路由把默认路由指到网关。
然后在防火墙的mangle表里标记来自wireguad隧道的cidr列表,mark routing到新的路由表,这样就解决了网关不在ros上的问题。
后记
至此,Homelab的K8S网络也到了一个比较舒服可用的状态,像云一样使用本地K8S的体验又再进了一步。