问题的引出
事情是这样的项目上有个客户提到,我们容器平台提供的load balancer service是由metallb l2 mode去实现的,客户那边使用vip都来自于同一个ippool,后来发现vip老是飘到同一个节点上,导致这个节点的负载很高。之前我以为metallb的vip是随机飘到后端节点,直到这个问题出现,才去研究了下metallb的一致性和故障切换。
metallb l2 mode
metallb 的l2 mode实现原理很简单,由speaker发起arp声明vip对应的某个节点的mac。那么刚才的那个问题很明显就是vip所对应的mac,全固定到某个节点上了,所以流量全都经过这个节点,那么metallb是怎么去选vip对应的节点的呢?
metallb一致性实现memberlist
metallb的speaker部署在每个节点上,那么他们之间肯定会有一个协商,协商出当前memberlist集群中的节点成员,这个一致性的实现,就是通过memberlist这个库,github.com/hashicorp/m... ,这个库是参考一致性协议gossip去实现的。gossip和raft的目的一样,都是为了实现分布式数据的一致性,但是gossip比raft理解和实现起来简单很多(但我感觉问题也挺多的,gossip协议内容后面再说),总之先知道 metallb 调用了一个三方库 memberlist 去实现一致性。
memberlist 部分源码
看了下 metallb 调用 memberlist 部分的源码也比较简单,基本就是在 create 初始化 和 join 两步实现。
初始化 create
每个speaker在初始化的时候会 create memberlist,会创建一个NodeEvent的channel,那么一旦 memberlist 中有节点退出或者加入,就会通知到 memberlist

还有点注意的是mconfig,这里包含了 memberlist 协议的配置参数。

join 加入memberlist
调用了sl.ml.Join(joinIPs),其中joinIPs,就是所有 metallb speaker 的Pod IP

决定哪个speaker声明的源码
每当新的节点加入和退出,就会触发到 ServiceHandler 里面的 c.SetBalancer 重新去设定 vip 后端的节点。这个SetBalancer最终会调用到 ShouldAnnounce 这个函数上来,这个就是决定哪个speaker去声明arp,最关键是这一步,metallb speaker把memberlist中的成员重新排序,然后取第一个节点去声明arp.

但我想了下,不借助其他组件,确实没啥好办法让metallb的speaker共同协商出一个类似leader的角色来发arp声明,如果metallb 的一致性协议使用的是 raft 就不会有这种问题,raft 协议本身会有leader,选取leader作为声明arp的node即可。
这个问题workaround
我觉得最好的方法如果集群比较大lb service需求比较多的情况下,选型的时候别用metallb l2模式,本身该模式就不支持负载均衡只是个高可用。
如果已经部署了的话,可以申请多个ippool, 每个ippool绑定节点不一样,那vip也不会都飘到同一个节点上。