kevx's Writing

架构 / 云计算 / 技术管理 | kevx@outlook.com 未经授权请勿转载©

Contact Me

MACVLAN和内核ARP实现机制的潜在风险分析

macvlan是内核提供的一种常见的网络虚拟化机制,可以让普通的物理网卡“具有”多个mac地址,当然前提是网卡开启了混杂模式。macvlan本身支持多种运行模式,最常用的bridge即网桥模式,此时macvlan相当于一个普通的二层交换机。

首先通过ip link命令建立一个新的macvlan设备,系统中将可以看到一块虚拟网卡,且其具有独立的mac地址,对外可见并可响应ARP请求。

目前macvlan用得最多的场景则是k8s集群,将所有pod的虚拟网卡mac地址通过macvlan暴露到网关(物理交换机),使得这些pod处于同一个二层网络结构中,无需三层路由即可实现全部互通,这看上去似乎是一个不错的选择,但其实隐藏了巨大的风险。

macvlan是以linux驱动形式(即内核模块)存在于内核代码仓库中,主体代码位于下列路径:

./drivers/net/macvlan.c
./include/linux/if_macvlan.h

这里主要聚焦收发两个函数,也就是macvlan_handle_framemacvlan_queue_xmit,这里先不考虑多播报文的场景,毕竟这个在实际生产中用到的确实不多,下面的内容只考虑单播。

其中,接收时如果报文中的目标mac地址在macvlan模块的全局hash变量中且当前为bridge模式则将报文送入协议栈等候处理;如果目标mac未找到则采取丢弃或类似手段。这部分逻辑开销较小。

重点在与报文发送,也就是macvlan_queue_xmit函数,很显然,如果目标mac位于本机,例如同一台机器上另外一个Pod,则macvlan会将报文通过回环接口转发至相应的目标虚拟网卡。这里利用了内核的dev_forward_skb函数,相关文档请自行查询。

如果目标mac不在本机则会将报文转发到物理设备,也就是代码中的xmit_world标签部分。按照OSI七层模型,如果当前物理设备的ARP表中没有目标mac缓存的条目则必然会触发一次ARP广播(不要忘了,此时是一个大二层网络)。一般来说二层ARP广播速度是比较快的,而且内核会将结果缓存起来,最小数量128,达到512后开始进行逐出。

很可惜的是,内核的ARP表是一个全局变量,关键性代码如下:

https://github.com/torvalds/linux/blob/master/net/ipv4/arp.c
https://github.com/torvalds/linux/blob/master/net/core/neighbour.c

ARP表的声明位于arp.c文件的arp_tbl变量,这个变量本身并不区分namespace,无论物理机上运行了多少个Pod,它们都共用这一个表格,如果某个Pod下游连接的节点较多,比如业务网关这类应用,则大概率会触发ARP表的缓存上限。此时将会看到频繁的ARP逐出和重新广播。在四层和七层协议视角来看的话,整体网络性能抖动严重且部分请求耗时非常长。因为ARP广播耗时并不低,还有可能造成交换机负载过高,引发连锁反应。

缓解办法则是加大物理机的ARP表项最大数量限制。从架构层面来看macvlan并不是非常适合大规模的k8s集群场景,如果只有百个左右的Pod那还是值得一用。对于更多的Pod应重点考虑ipvlan等方案。

返回主页