kubernetes – 大2层网络的坑与解决方法

公司是直接购买的云上K8S,厂商提供的大2层虚拟化网络,也因此踩到了一个大坑。

大2层原理

厂商的CNI给POD分配的IP网段与NODE网段相同,更大范围是与VPC网段相同,因此相当于二层互通。

当我们在VPC内请求POD IP时,会直接ARP请求解析POD的MAC地址,此时SDN会代答ARP请求,返回的MAC地址是POD所在NODE的MAC地址,这样流量2层送入到NODE,然后在NODE内3层路由送入到POD。

大2层坑点

坑点在于,随着POD的生生死死,CNI开始给新POD分配曾经释放过的IP地址,而持有该IP地址的新POD已经被调度到其他NODE了。

此时,如果我们再次ARP解析POD IP的MAC地址,SDN理应代答POD所在NODE的MAC地址,然而因为调用方的Linux的ARP缓存行为,仍旧会缓存着之前POD IP解析到的旧NODE MAC地址,因此流量被送到错误的NODE。

为什么调用方的ARP缓存不会更新呢?因为Linux对ARP缓存极度优化,调用方只要和对应IP之间有流量往来,则会认为该IP的MAC地址正确,不会进行ARP缓存淘汰。

总结一下,直接将POD IP解析到所在NODE MAC,会因为POD IP漂移NODE,导致调用方缓存到错误的NODE MAC。

改为3层方案

既然POD IP -> NODE MAC这个关系是易变的,容易导致缓存错误,那么如何绕开这种直接关系呢?

如果大家了解过calico bgp这个CNI的实现思路,就会知道3层其实是解决2层ARP问题的核心思路。

calico会在所有NODE上下发路由规则:

POD IP  网关至  NODE IP

也就是说,当我们从NODE-A上发起对POD IP调用的时候,会命中本NODE-A上的路由规则,决定将流量gateway给NODE-B,因为NODE之间2层互通,因此目标MAC地址被ARP解析到NODE-B,流量送到NODE-B,然后在NODE-B内路由进入POD。

说白了,现在POD IP和NODE MAC之间是间接关联关系:

POD IP -> NODE IP -> NODE MAC

因为NODE IP -> NODE MAC之间的关系是稳定的,因此就绕开了POD IP缓存到错误NODE MAC的问题,我们只要能及时更新POD IP via NODE IP的路由规则即可。

但问题也很明显,VPC内非K8S集群的调用方,它们就无法直接调用POD IP了,除非这些调用方也同步到上述路由规则,否则它们又要回到2层的坑上。

那么怎么办呢?

聪明的云厂商

既然如此,似乎问题也只能从SDN层面统一解决,否则VPC内非K8S集群的调用方还是无法享受到3层方案的优势。

云厂商最终给出了一个漂亮的答案:

在SDN层做调整,只要ARP请求的是POD IP,那么就返回一个SDN网关节点的MAC地址。

因此,VPC内所有发给POD IP的流量,都会先2层送给SDN网关,然后SDN网关会配置3层路由规则,也就是POD IP -> NODE IP的网关路由规则,经过SDN网关将流量FORWARD给POD所在NODE。

这样做的好处就是可以拦截到整个VPC内所有对POD IP的流量,让它们统一走SDN网关节点上3层网络,避免调用方直接2层ARP解析POD IP导致缓存错误NODE MAC。

同时,这也避免了修改CNI插件的工作量,因为CNI插件原本就是请求SDN分配POD IP,只需要SDN变更它自己的处理行为即可:

原先ARP查询POD IP,SDN会返回POD所在NODE的MAC。

现在ARP查询POD IP,SDN会返回一个网关节点的MAC,流量到网关后再走3层关联到NODE IP,进而关联到NODE MAC。

这样做的缺点就是流量绕的路更长,先去SDN网关过3层路由,才能最终送往NODE。

 

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~