0x00 起因

昨天,我在研究容器网络的时候,同事突然问了我一个问题: Kubernetes 上的容器能支持跨主机二层通信?这个问题一下把我问住了,我感觉自己这几天的容器网络都白学了,我竟然无言以对。。但这个问题也激起了我强烈的好奇心,我怎么也要弄个明白。

0x01 思考

在被问到这个问题后,我开始进行了思考。首先无论是 IaaS 平台的网络或者是 PaaS 平台的网络架构跨宿主机通信的方式主要也就以下几种方式:

  • Host-gw
  • UDP
  • VXLAN
  • GRE
  • IPSec
    其实从根本上看,我认为只有 Overlay 网络和 Host-gw 这两种。Overlay 在我看来就是和隧道协议的原理是差不多的,就是在承载网络协议上面再定义新的协议。而 Host-gw 则是通过配置路由了,使宿主机成为一个路由器,像是把网络架构从三维压缩到了二维,从而让容器和宿主机展开在同一平面上。

但由于 Host-gw 通过宿主机进行了路由转发, 自然两个宿主机上把原始容器的 mac 地址也修改了,无疑是无法进行二层通信的。而 UDP 封装的是三层的数据包,所以也是无法进行链路层通信的。那么只剩下 VXLAN 了。

VXLAN 相比 UDP 和其他的一些隧道协议的好处如数据包内核态封装解封装,24位 VXLANID,支持组播和二层通信等等。所以我把希望全部寄托在 VXLAN 上了。加上之前有搭建过 OpenStack 并且平时也经常使用云主机,同一 VPC 内的云主机可以二层通信这我是知道的。所以我认为在 Kubernetes 上使用 VXLAN 也是可以二层通信的。

然而,事实并没有这么简单。首先让我们看一下 IaaS 平台上 VXLAN 是如何工作的。

0x02 VXLAN in OpenStack

协议图

在 IaaS 平台上,VXLAN 的协议格式如下图所示:

clipboard.png
可以看到跨宿主机通信的报文就是 外层数据帧 + VXLAN 头 + 内层数据帧。
那么问题来了,两台虚拟机通信,一台是是 A,一台是 B,他们通信存在如下问题:

  1. A 往 B 发数据时,A 是怎么知道 B 的 mac 地址的呢?
  2. 谁能知道 A 和 B 的宿主机的 IP 地址?
  3. 谁来封装和解封装 A 和 B 的数据包?
    答案是 VTEP,所有宿主机上的 VTEP 都在一个组播组内,他们之间通过组播通信,所以上面的这几件事都是 VTEP 负责干。那么自然 VTEP 知道各台虚拟机在哪台宿主机上了,不知道就在组播组内问一下,然后自己学习记住 mac 地址。如下图所示:

clipboard.png

在 VTEP 的帮助下,各个虚拟机就能达到了跨宿主机通信的目的。

0x03 VXLAN in kubernetes

Kubernetes 上的网络管理需要通过第三方的插件,如 Flannel,weave,calico 等。这里我们使用 Flannel VXLAN。在Kubernetes 上使用 VXLAN 同样也要新建一个 VTEP 设备,如 flannel.1。

本以为,在 IaaS 平台上,使用 VXLAN 协议可以跨主机二层通信,那么同样在 Kubernetes 上 使用 VXLAN ,容器也可以实现二层通信啦?然而事实证明我还是太年轻了。

在容器里面用 tcpdump 抓包发现,内层报文的源 mac 地址和目的 mac 地址并不是容器的的 mac 地址,而是源宿主机和目标宿主机 flannel.1 的 mac 地址,如下图所示:

clipboard.png

这就让人很困惑了,这又是为啥呢。原来 VTEP 是个链路层的端点设备,在 OpenStack 等 IaaS 平台上多是用 OpenvSwitch 来实现 VTEP 的功能,OpenvSwitch 的功能非常强大,他可以自动的学习各个宿主机上的虚拟机的 mac 地址,并且通过 openflow 协议的流表做数据包的操作。

而在 Kubernetes 上就不一样了, VTEP 他并不是通过组播的方式来学习容器的 Mac 地址。而是每台 Kubernetes node 启动时,flannel 进程会把他的子网, flannel.1 网卡 mac 地址写入到 Etcd 里面,并下发到每台 node 上,从而每台主机上都会建立发往目标网段的路由,并由 flannel.1 发出。如下图所示:

clipboard.png

然而此时 flannel.1 只知道发送目标 flannel.1 的 mac 地址,并不知道对方的宿主机 ip,那么怎么封装外层帧呢?这个时候 flannel 设备会查到 FBD,根据 mac 地址找到对方的宿主机地址 ip。同时,VTEP 还把 VNI 设置为 1,这个也是 VTEP 识别是不是归自己处理的数据帧的一个重要的标识,并最终组装成如下所示的包发往对方:

clipboard.png

那么也就可以解释为什么内部数据帧的 mac 地址为什么是 VTEP 设备的 mac 地址了。

0x04 结论

至此,我们可以得出结论了。Kubernetes 无法保证二层网络互通,因为 flannel.1 设备并没有学习到容器的 mac 地址,所以如果一个容器 A 发往容器 B ,那么容器 A 并不知道容器 B 的 mac 地址。
那么如果想要实现容器跨主机二层通信,那么可能需要像 OpenvSwtich 一样,所有的 VTEP 设备加入到一个组播组中,并且要具有学习 mac 的能力。

0x05 总结

VXLAN 的一些功能 flannel 都没有用到,比如flannel 上 VXLAN 的VNI 都是设置为 1,无法对网络做一个隔离。总的来说在 flannel 使用 VXLAN更多的只是作为 UDP 模式的一种替代,只是把封装、解封装放在内核态了,并且从三层降为二层。当然,一般情况下容器网络三层互通也够用了。