Skip to content

第30课:k8主流网络方案(OVS、Flannel、Calico)及原理

容器间通信原理

我们知道 K8s 属于容器编排工具,了解K8s 网络其本质上需要了解容器间的网络互访原理。同宿主机上容器与容器通过 namespace 做网络隔离,使得每个容器由独立的网卡、回环设备以及监听的独立端口地址并不会相互影响。关于容器和容器之间的通信原理,我们就需要分两块来讲,一是同宿主机上,容器之间的通信过程,二就是不同宿主机上面,容器之间又是如何进行通信。

同宿主机上的容器通信原理

首先我们要来讲解的就是在同宿主机上的容器通信原理。 我们来看一下这样一张图:

这张图里面同一台宿主机上面包含的两个容器,分别是 Docker01 和 Docker02,这两个容器都有自己的网卡设备,名称是 eth0 ,对应到宿主机上面,就有一个对应给到容器的虚拟网卡设备,对应的名称就是 Veth,后面加序列号的名称。容器一个对外通讯网络接口,那么通过什么来将它们整个串联起来呢,就是通过在宿主机下面搭建一个网桥的服务,使得在一台宿主机上面所有的容器进行内部的关联和通信,网桥相当于在宿主机上面起到一个二层虚拟交换机的作用。

不同宿主机上的容器通信原理

以上就是同一台宿主机上面容器之间进行通信的原理,不同宿主机上面容器又是如何通信的? 对于不同主机上面的容器间通信主要有两种方式,第一种方式就是在三层网络上覆盖一层虚拟包头,如做对应的隧道,常见的有 VXLAN、GRE 等协议模式,都是利用了这样的原理。第二种方式就是通过路由的方式,修改路由表的信息,控制下一跳的 IP 地址来实现对应的功能。

首先我们来了解第一种方式的具体实现原理。要了解到主机之间的通信原理,我们可以先看一下这样的一张图:

如 A 和 B 的通信过程,点对点的通信通常必须包含两大部分的数据包,一个是 IP 包,我们要了解 A、B 两个地址对应的 IP 地址,同时也需要了解到 A 和 B 之间的 MAC 地址,这样无论是在二层还是三层的网络结构下面,都使得这样的数据范围能够正常可达和回复。那对容器的不同宿主机下的网络模式而言,就会有一定的区别了。

我们会看到这里我画了一个大圆圈,加了一个 C 和 D,它们分别是宿主机,中间两个红色的点点是对应的容器,容器的网段和宿主机的网站又有差异,它们是两个不同的网站,所以我们想要容器能够直接通信,必须通过宿主机来做对应地支持,那么通常采用加虚拟包头的方式,也就是在原有容器的内层包上封装一层虚拟包头。在图中我们会看到这一层的外层包头是 C 和 D 对应的 IP 地址包和 MAC 地址包,这样就使得容器之间原有的包头,能够借助于宿主机的通信过程,承载这样的一份数据包来进行通信,而它的实现原理是这样子的,从 C 端进行封包,也就是加上最外层的数据包,等数据包通过 C 正常传到 D 以后,再从 D 这一层对应地解外层的数据包,同样再给到这个容器,该容器就可以看到原地址的 IP 地址和原的 MAC 地址,这些信息都可以通过这样的一种方式进行承载。这是通过虚拟包头的方式来实现的。

另外一种方式就是通过路由来实现,而路由的实现方式,这里同样有一张图,我们来看一下。

在这张图里面我们会看到同样两台宿主机 C 和 D,以及容器 A 和 B,在张图中,我将对应的 IP 都加进去了,如果 A 需要发给 B 的话,就需要在 C 的宿主机上面添加对应的一条路由,发给 B 的 IP 地址段,以及它的掩码,如果是这样的目标地址和掩码,就可以交给 D,也就是这个 IP 地址的网关进行处理,也就是图示中的 192.168.0.2 了,我们会看到,这样的一种方式就使得 A 可以通过 C 直接路由的方式,把数据包给到 D,从而完成容器之间的通信。

这两种模式各有优劣,首先包头的方式,它的性能损耗会略大,因为需要封装包和解封,这本身就是对性能有影响的一个过程。但是路由这种方式,它的性能虽然好,但是需要我们去解决动态路由的一些问题,所以需要有整套的机制来完善,这样相对来说实现起来更加复杂,所以两种方式都有各自的一些优劣。而结合到 K8s 而言,它有对应的一整套 CNI 插件,在整个集群的海量容器和主机节点的模式上,能够让它们灵活自由地进行通信。

这个插件里面规范了网络通信的一些接口和规范,主要的功能包含创建 pod 时的一些网络接口地址,提供 pod 间的相互通信功能,删除 pod 时释放分配的资源 ,这些在 K8s 里面的 CNI 插件里都可以提供对应的功能。常见的 CNI 插件,包含了 Flannel、Canal 等网络模式。

K8s 网络通信方案

接下来我们来具体介绍在 K8s 里,对应的网络插件是如何来帮助整个容器进行容器间的通信,以及对应关联的。我们首先来介绍的就是 Flannel 这样一个网络插件,它包含了几种对应的模式,首先给你介绍的就是 UDP 这样一种网络模式。这里有两张图,分别是物理机 1 和物理机 2,那么用 Flannel 这种模式,在组下面都会启用一个 Flanneld 的进程,它用于不同宿主机之间对应的通信,可以通过 Flanneld 查询 K8s 的 etcd 子网与宿主机的对应关系。

另外,在每台数据层都会有一个 flannel1 的网卡名称,在往外发数据包的时候都要通过 flannel1 的网卡进行发送。我们看一下具体的发送流程:可以看到,整体的发送模式是基于隧道模式来实现的,也就是说在容器原有 IP 的基础上封装了一层主机之间的 IP,我们来对应看一下,容器 A 要发给另外一台物理机上面的容器 B,首先它会通过自己的虚拟网卡发到桥接的网卡地址上,这里原有的数据包包含了原地址和目的地址,原地址当然是容器 A 的主机地址,那么目的地址是容器 B 的主机地址,它要发送出去的时候,先会通过 Flanneld 这样的一个进程,这个进程是在用户态的,它起到的作用就是把原数据包进行封装,封装对应目标物理机的地址和目标物理机 Flanneld 进程的监听端口地址,还有物理机的源地址等信息来进行封装,封装完成以后再交给 flannel1 网卡通过 eth0 发送出去。

这种模式很大的一个特点就是需要通过 Flanneld 进程来进行 UDP 数据包的封装,它的性能损耗比较大,因为它需要通过用户态和内核态进行数据拷贝,我们知道数据包在用户态进行了一次处理,再回给了内核态,这样的性能其实是相对低下的,为了解决这样的问题, flannel 还有另外一种模式,就是 VXLAN 模式:

相较于 UDP 模式而言,它们的共同点是都是基于数据包的这种报文封装,只不过它封装的不是 UDP 的报文,而是封装成 VXLAN 这种模式的报文,报文有一个非常大的特点,就是对于数据包的封装和解封装,VXLAN 完全可以交给 Linux 的内核来实现,而不用跑用户态,这样它的性能是相对比较高的,那么这里还会有一些细小的区别,这里就不给你详细讲解了。

我们重点来看一下 VXLAN 的特性。那么无论是 VXLAN 也好,flannel 这种网络模式而言也好,它们都是基于数据包的封装而实现的。但是如果想要讲到最高的性能模式,还是需要考虑使用路由模式。那么 K8s 下面的 CALICO 组件和模式就是来实现的这样一套路由的机制。

我们讲到,容器宿主机间联接的路由模式最大的问题,就是动态路由,为了解决动态表的问题,在 CALICO 这个组件下面,有两个非常重要的组件,一个是 BGP speak,它用于路由广播,另外一个组件是 Felix ,它是做路由配置的,有了这两个组件,配合 K8s 就实现了动态路由更新配置的实现机制。由于它不会做数据包的封装解封,所以它的性能一定是更优了,但是这样的一种模式受制于二层网络的条件,为了解决二层网络的限制问题,就需要通过结合 CALICO 的另外一种模式,就是 IPIP 的模式:

这种模式既做路由的协议,同时也通过更底层的封装解封的协议,来打通受制于二层网络的瓶颈问题,那么通过 tun0 这样的网卡设备,来把不同主机通过隧道直接打通,这就是 CALICO IPIP 对应的实现机制。

本课时我们从容器的网络通信讲起,讲到了 K8s 对于容器编排所支持的 CNI 的几套对应插件的实现机制。